first commit
This commit is contained in:
commit
e2bc788886
116
README.md
Normal file
116
README.md
Normal file
@ -0,0 +1,116 @@
|
||||
# set
|
||||
|
||||
A small, generic Go set implementation for ordered types (numbers, strings, etc.).
|
||||
|
||||
This package provides a lightweight `Set[T]` built on Go maps, with common
|
||||
set operations such as union, intersection, difference, and helpers to convert
|
||||
to slices. It uses Go generics and requires ordered element types.
|
||||
|
||||
## Features
|
||||
|
||||
- Generic `Set[T]` where `T` is any `constraints.Ordered` type (e.g. `int`,
|
||||
`string`, `float64`).
|
||||
- Common set operations: `Union`, `Intersection`, `Difference`.
|
||||
- Convenience methods: `Add`, `Remove`, `Contains`, `Size`, `ToSlice`,
|
||||
`ToSortedSlice`, and `Equal`.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Go 1.20+ (uses `maps` package and generics introduced in earlier versions; `maps.Equal` requires Go 1.20+).
|
||||
|
||||
## Module & Dependencies
|
||||
|
||||
- **Module path:** `code.wmdillon.com/wmdillon/settings`
|
||||
- **Go toolchain:** `go 1.25.3`
|
||||
- **Direct dependencies:**
|
||||
- `golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39`
|
||||
|
||||
To fetch dependencies for this module run:
|
||||
|
||||
```bash
|
||||
go mod download
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
Add the package to your module by importing it. Replace the import path with
|
||||
your module path if the package is local to your repository.
|
||||
|
||||
```go
|
||||
import "your/module/path/set"
|
||||
```
|
||||
|
||||
## Quick Example
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"your/module/path/set"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create new sets of ints
|
||||
s1 := set.New[int](1, 2, 3, 4)
|
||||
s2 := set.New[int](3, 4, 5)
|
||||
|
||||
// Union
|
||||
u := s1.Union(s2)
|
||||
fmt.Println("Union:", u.ToSortedSlice()) // [1 2 3 4 5]
|
||||
|
||||
// Intersection
|
||||
i := s1.Intersection(s2)
|
||||
fmt.Println("Intersection:", i.ToSortedSlice()) // [3 4]
|
||||
|
||||
// Difference
|
||||
d := s1.Difference(s2)
|
||||
fmt.Println("Difference:", d.ToSortedSlice()) // [1 2]
|
||||
|
||||
// Add / Remove / Contains
|
||||
s := set.New[int]()
|
||||
s.Add(10)
|
||||
s.Add(20)
|
||||
fmt.Println("Contains 20?", s.Contains(20))
|
||||
s.Remove(20)
|
||||
fmt.Println("Size:", s.Size())
|
||||
}
|
||||
```
|
||||
|
||||
## API Overview
|
||||
|
||||
- `type Set[T constraints.Ordered] map[T]struct{}`
|
||||
- `func New[T constraints.Ordered](elements ...T) Set[T]`
|
||||
- `func (s Set[T]) Add(element T)`
|
||||
- `func (s Set[T]) Remove(element T)`
|
||||
- `func (s Set[T]) Contains(element T) bool`
|
||||
- `func (s Set[T]) Size() int`
|
||||
- `func (s Set[T]) ToSlice() []T`
|
||||
- `func (s Set[T]) ToSortedSlice() []T` — returns a sorted slice (ascending order)
|
||||
- `func (s Set[T]) Union(other Set[T]) Set[T]`
|
||||
- `func (s Set[T]) Intersection(other Set[T]) Set[T]`
|
||||
- `func (s Set[T]) Difference(other Set[T]) Set[T]`
|
||||
- `func (s Set[T]) Equal(other Set[T]) bool`
|
||||
|
||||
## Running Tests
|
||||
|
||||
From the package directory run:
|
||||
|
||||
```bash
|
||||
go test ./... -v
|
||||
```
|
||||
|
||||
The repository includes unit tests (`set_test.go`) demonstrating expected
|
||||
behavior for union, intersection, difference, membership, and conversions.
|
||||
|
||||
## Notes
|
||||
|
||||
- Because the implementation uses `constraints.Ordered`, only ordered types are
|
||||
supported (numbers, strings). If you need sets for non-ordered types (e.g.
|
||||
structs) you can adapt the code to use `comparable` and provide a custom
|
||||
ordering for `ToSortedSlice`.
|
||||
- The `Equal` method uses `maps.Equal` from the standard library to compare
|
||||
underlying maps.
|
||||
|
||||
If you'd like, I can update the README with an import path (module name),
|
||||
add more examples for strings or custom types, or include benchmarks.
|
||||
5
go.mod
Normal file
5
go.mod
Normal file
@ -0,0 +1,5 @@
|
||||
module code.wmdillon.com/wmdillon/settings
|
||||
|
||||
go 1.25.3
|
||||
|
||||
require golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39
|
||||
2
go.sum
Normal file
2
go.sum
Normal file
@ -0,0 +1,2 @@
|
||||
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 h1:DHNhtq3sNNzrvduZZIiFyXWOL9IWaDPHqTnLJp+rCBY=
|
||||
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0=
|
||||
86
set.go
Normal file
86
set.go
Normal file
@ -0,0 +1,86 @@
|
||||
package set
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"sort"
|
||||
|
||||
"golang.org/x/exp/constraints"
|
||||
)
|
||||
|
||||
type Set[T constraints.Ordered] map[T]struct{}
|
||||
|
||||
func New[T constraints.Ordered](elements ...T) Set[T] {
|
||||
s := Set[T]{}
|
||||
for _, e := range elements {
|
||||
s.Add(e)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s Set[T]) Equal(other Set[T]) bool {
|
||||
return maps.Equal(s, other)
|
||||
}
|
||||
|
||||
func (s Set[T]) Add(element T) {
|
||||
s[element] = struct{}{}
|
||||
}
|
||||
|
||||
func (s Set[T]) Remove(element T) {
|
||||
delete(s, element)
|
||||
}
|
||||
|
||||
func (s Set[T]) Contains(element T) bool {
|
||||
_, exists := s[element]
|
||||
return exists
|
||||
}
|
||||
|
||||
func (s Set[T]) Size() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func (s Set[T]) ToSlice() []T {
|
||||
elements := make([]T, 0, len(s))
|
||||
for e := range s {
|
||||
elements = append(elements, e)
|
||||
}
|
||||
return elements
|
||||
}
|
||||
|
||||
func (s Set[T]) ToSortedSlice() []T {
|
||||
elements := s.ToSlice()
|
||||
sort.Slice(elements, func(i, j int) bool {
|
||||
return elements[i] < elements[j]
|
||||
})
|
||||
return elements
|
||||
}
|
||||
|
||||
func (s Set[T]) Union(other Set[T]) Set[T] {
|
||||
result := New[T]()
|
||||
for e := range s {
|
||||
result.Add(e)
|
||||
}
|
||||
for e := range other {
|
||||
result.Add(e)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (s Set[T]) Intersection(other Set[T]) Set[T] {
|
||||
result := New[T]()
|
||||
for e := range s {
|
||||
if other.Contains(e) {
|
||||
result.Add(e)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (s Set[T]) Difference(other Set[T]) Set[T] {
|
||||
result := New[T]()
|
||||
for e := range s {
|
||||
if !other.Contains(e) {
|
||||
result.Add(e)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
208
set_test.go
Normal file
208
set_test.go
Normal file
@ -0,0 +1,208 @@
|
||||
package set
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestIntersection(t *testing.T) {
|
||||
s1 := New[int](1, 2, 3, 4)
|
||||
s2 := New[int](3, 4, 5, 6)
|
||||
want := New[int](3, 4)
|
||||
got := s1.Intersection(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Intersection() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntersection_Empty(t *testing.T) {
|
||||
s1 := New[int](1, 2)
|
||||
s2 := New[int](3, 4)
|
||||
want := New[int]()
|
||||
got := s1.Intersection(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Intersection() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntersection_Identical(t *testing.T) {
|
||||
s1 := New[int](1, 2, 3)
|
||||
s2 := New[int](1, 2, 3)
|
||||
want := New[int](1, 2, 3)
|
||||
got := s1.Intersection(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Intersection() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntersection_Subset(t *testing.T) {
|
||||
s1 := New[int](1, 2, 3, 4, 5)
|
||||
s2 := New[int](2, 3)
|
||||
want := New[int](2, 3)
|
||||
got := s1.Intersection(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Intersection() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntersection_EmptySet(t *testing.T) {
|
||||
s1 := New[int](1, 2, 3)
|
||||
s2 := New[int]()
|
||||
want := New[int]()
|
||||
got := s1.Intersection(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Intersection() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntersection_BothEmpty(t *testing.T) {
|
||||
s1 := New[int]()
|
||||
s2 := New[int]()
|
||||
want := New[int]()
|
||||
got := s1.Intersection(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Intersection() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDifference(t *testing.T) {
|
||||
s1 := New[int](1, 2, 3, 4)
|
||||
s2 := New[int](3, 4, 5, 6)
|
||||
want := New[int](1, 2)
|
||||
got := s1.Difference(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Difference() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDifference_Empty(t *testing.T) {
|
||||
s1 := New[int](1, 2)
|
||||
s2 := New[int](1, 2)
|
||||
want := New[int]()
|
||||
got := s1.Difference(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Difference() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDifference_Subset(t *testing.T) {
|
||||
s1 := New[int](1, 2, 3, 4, 5)
|
||||
s2 := New[int](2, 3)
|
||||
want := New[int](1, 4, 5)
|
||||
got := s1.Difference(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Difference() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDifference_EmptySet(t *testing.T) {
|
||||
s1 := New[int](1, 2, 3)
|
||||
s2 := New[int]()
|
||||
want := New[int](1, 2, 3)
|
||||
got := s1.Difference(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Difference() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDifference_BothEmpty(t *testing.T) {
|
||||
s1 := New[int]()
|
||||
s2 := New[int]()
|
||||
want := New[int]()
|
||||
got := s1.Difference(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Difference() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnion(t *testing.T) {
|
||||
s1 := New[int](1, 2, 3)
|
||||
s2 := New[int](3, 4, 5)
|
||||
want := New[int](1, 2, 3, 4, 5)
|
||||
got := s1.Union(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Union() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnion_EmptySet(t *testing.T) {
|
||||
s1 := New[int](1, 2, 3)
|
||||
s2 := New[int]()
|
||||
want := New[int](1, 2, 3)
|
||||
got := s1.Union(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Union() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnion_BothEmpty(t *testing.T) {
|
||||
s1 := New[int]()
|
||||
s2 := New[int]()
|
||||
want := New[int]()
|
||||
got := s1.Union(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Union() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdd(t *testing.T) {
|
||||
want := New[int](1, 2, 3)
|
||||
got := New[int]()
|
||||
got.Add(1)
|
||||
got.Add(2)
|
||||
got.Add(3)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Add() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemov(t *testing.T) {
|
||||
want := New[int](1, 3)
|
||||
got := New[int](1, 2, 3)
|
||||
got.Remove(2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Remove() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
func TestContains(t *testing.T) {
|
||||
s := New[int](1, 2, 3)
|
||||
if !s.Contains(2) {
|
||||
t.Errorf("Contains(2) = false; want true")
|
||||
}
|
||||
if s.Contains(4) {
|
||||
t.Errorf("Contains(4) = true; want false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSize(t *testing.T) {
|
||||
s := New[int](1, 2, 3, 4)
|
||||
want := 4
|
||||
got := s.Size()
|
||||
if got != want {
|
||||
t.Errorf("Size() = %d; want %d", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToSlice(t *testing.T) {
|
||||
s := New[int](1, 2, 3)
|
||||
want := []int{1, 2, 3}
|
||||
got := s.ToSlice()
|
||||
mapping := make(map[int]bool)
|
||||
for _, v := range got {
|
||||
mapping[v] = true
|
||||
}
|
||||
for _, v := range want {
|
||||
if !mapping[v] {
|
||||
t.Errorf("ToSlice() missing element %d; got %v", v, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestToSortedSlice(t *testing.T) {
|
||||
s := New[int](3, 1, 2)
|
||||
want := []int{1, 2, 3}
|
||||
got := s.ToSortedSlice()
|
||||
for i := range want {
|
||||
if got[i] != want[i] {
|
||||
t.Errorf("ToSortedSlice() = %v; want %v", got, want)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user