first commit

This commit is contained in:
William Dillon 2025-11-28 13:02:56 -05:00
commit e2bc788886
5 changed files with 417 additions and 0 deletions

116
README.md Normal file
View 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
View 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
View 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
View 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
View 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
}
}
}