From 8461c26d42fec08f35856a04b6fee9ae6eba8871 Mon Sep 17 00:00:00 2001 From: Acid Date: Sun, 19 Jul 2026 02:14:50 -0400 Subject: [PATCH] changed strings union find > generic union find --- .gitignore | 3 + README.md | 16 +++++ sets/genericUnionFind.go | 69 ++++++++++++++++++ sets/stringsUnionFind.go | 65 ----------------- ...nFind_test.go => genericUnionFind_test.go} | 72 ++++++++++++++----- 5 files changed, 142 insertions(+), 83 deletions(-) create mode 100644 sets/genericUnionFind.go delete mode 100644 sets/stringsUnionFind.go rename tests/setsTests/{stringsUnionFind_test.go => genericUnionFind_test.go} (67%) diff --git a/.gitignore b/.gitignore index 76168bc..b3f3a1b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ datastructures main.go scratch +CLAUDE.md +.env +.secrets diff --git a/README.md b/README.md index 870b821..06a6d12 100644 --- a/README.md +++ b/README.md @@ -35,14 +35,26 @@ # Documentation +> linear Structures + ```bash go doc -all ./linear | bat -l go ``` +> algorithms + ```bash go doc -all ./algo | bat -l go ``` +> Sets + +```bash +go doc -all ./sets/ | bat -l go +``` + +> trees + ```bash for p in ./trees/ ./trees/avl ./trees/heap; do go doc -all "$p"; done | bat -l go ``` @@ -53,6 +65,10 @@ for p in ./trees/ ./trees/avl ./trees/heap; do go doc -all "$p"; done | bat -l g go test ./tests/... ``` +# External references + +https://xlinux.nist.gov/dads/ + ## Disclosure Unit tests and explanations written by claude diff --git a/sets/genericUnionFind.go b/sets/genericUnionFind.go new file mode 100644 index 0000000..e055b8f --- /dev/null +++ b/sets/genericUnionFind.go @@ -0,0 +1,69 @@ +package sets + +import "cmp" + +// GenericUnionFind{ +// unionFind *UnionFind +// index map[T]int +// names []T +// } +// A union-find over any cmp.Ordered type, backed by an integer UnionFind. +type GenericUnionFind[T cmp.Ordered] struct { + unionFind *UnionFind + index map[T]int + names []T +} + +// NewGenericUnion() -> creates a disjoint set from (generic comparable) +// the constructor will deduplicate and return a GenericUnionFind +func NewGenericUnion[T cmp.Ordered](params []T) *GenericUnionFind[T] { + genericUnion := &GenericUnionFind[T]{ + index: make(map[T]int, len(params)), + } + + for _, name := range params { + if _, seen := genericUnion.index[name]; !seen { + genericUnion.index[name] = len(genericUnion.names) + genericUnion.names = append(genericUnion.names, name) + } + } + + genericUnion.unionFind = NewUnionFind(len(genericUnion.names)) + + return genericUnion +} + +// Union() -> creates an Union of 2 values, returns true if success +func (n *GenericUnionFind[T]) Union(a, b T) bool { + paramA, ok1 := n.index[a] + paramB, ok2 := n.index[b] + if !ok1 || !ok2 { + return false + } + return n.unionFind.Union(paramA, paramB) +} + +// IsUnion() -> returns true if the parameters have the same root +func (n *GenericUnionFind[T]) IsUnion(a T, b T) bool { + paramA, ok1 := n.index[a] + paramB, ok2 := n.index[b] + if !ok1 || !ok2 { + return false + } + return n.unionFind.IsUnion(paramA, paramB) +} + +// Rep() -> returns the representative of group, and false if the root is unknown. +func (n *GenericUnionFind[T]) Rep(a T) (T, bool) { + ia, ok := n.index[a] + if !ok { + var zero T + return zero, false + } + return n.names[n.unionFind.Find(ia)], true +} + +// Disjointed() -> returns the count of disjointed sets remaining +func (n *GenericUnionFind[T]) Disjointed() int { + return n.unionFind.Disjointed() +} diff --git a/sets/stringsUnionFind.go b/sets/stringsUnionFind.go deleted file mode 100644 index 0409271..0000000 --- a/sets/stringsUnionFind.go +++ /dev/null @@ -1,65 +0,0 @@ -package sets - -// NamedUnionFind{ -// unionFind *UnionFind -// index map[string]int -// names []string -// } -type NamedUnionFind struct { - unionFind *UnionFind - index map[string]int - names []string -} - -// NewStringsUnion() -> creates a disjoint set from a slice of strings, -// the constructor will deduplicate and return a NamedUnionFind -func NewStringsUnion(params []string) *NamedUnionFind { - namedUnion := &NamedUnionFind{ - index: make(map[string]int, len(params)), - } - - for _, name := range params { - if _, seen := namedUnion.index[name]; !seen { - namedUnion.index[name] = len(namedUnion.names) - namedUnion.names = append(namedUnion.names, name) - } - } - - namedUnion.unionFind = NewUnionFind(len(namedUnion.names)) - - return namedUnion -} - -// Union() -> creates an Union of 2 values, returns true if success -func (n *NamedUnionFind) Union(a, b string) bool { - paramA, ok1 := n.index[a] - paramB, ok2 := n.index[b] - if !ok1 || !ok2 { - return false - } - return n.unionFind.Union(paramA, paramB) -} - -// IsUnion() -> returns true if the parameters have the same root -func (n *NamedUnionFind) IsUnion(a, b string) bool { - paramA, ok1 := n.index[a] - paramB, ok2 := n.index[b] - if !ok1 || !ok2 { - return false - } - return n.unionFind.IsUnion(paramA, paramB) -} - -// Rep() -> returns the representative name of group, and false if the name is unknown. -func (n *NamedUnionFind) Rep(a string) (string, bool) { - ia, ok := n.index[a] - if !ok { - return "", false - } - return n.names[n.unionFind.Find(ia)], true -} - -// Disjointed() -> returns the count of disjointed sets remaining -func (n *NamedUnionFind) Disjointed() int { - return n.unionFind.Disjointed() -} diff --git a/tests/setsTests/stringsUnionFind_test.go b/tests/setsTests/genericUnionFind_test.go similarity index 67% rename from tests/setsTests/stringsUnionFind_test.go rename to tests/setsTests/genericUnionFind_test.go index bd78094..687a76c 100644 --- a/tests/setsTests/stringsUnionFind_test.go +++ b/tests/setsTests/genericUnionFind_test.go @@ -9,14 +9,14 @@ import ( // --- NewStringsUnion --- func TestNewStringsUnionStartsFullyDisjointed(t *testing.T) { - u := sets.NewStringsUnion([]string{"a", "b", "c", "d"}) + u := sets.NewGenericUnion([]string{"a", "b", "c", "d"}) if got := u.Disjointed(); got != 4 { t.Errorf("expected 4 disjoint sets initially, got %d", got) } } func TestNewStringsUnionDeduplicates(t *testing.T) { - u := sets.NewStringsUnion([]string{"a", "b", "a", "c", "b", "a"}) + u := sets.NewGenericUnion([]string{"a", "b", "a", "c", "b", "a"}) // only a, b, c are unique if got := u.Disjointed(); got != 3 { t.Errorf("expected 3 disjoint sets after dedup, got %d", got) @@ -25,7 +25,7 @@ func TestNewStringsUnionDeduplicates(t *testing.T) { func TestNewStringsUnionEachNameIsOwnRep(t *testing.T) { names := []string{"alice", "bob", "carol"} - u := sets.NewStringsUnion(names) + u := sets.NewGenericUnion(names) for _, name := range names { rep, ok := u.Rep(name) if !ok { @@ -38,7 +38,7 @@ func TestNewStringsUnionEachNameIsOwnRep(t *testing.T) { } func TestNewStringsUnionEmptyInput(t *testing.T) { - u := sets.NewStringsUnion(nil) + u := sets.NewGenericUnion[string](nil) if got := u.Disjointed(); got != 0 { t.Errorf("expected 0 disjoint sets for empty input, got %d", got) } @@ -47,14 +47,14 @@ func TestNewStringsUnionEmptyInput(t *testing.T) { // --- Union --- func TestNamedUnionReturnsTrueOnMerge(t *testing.T) { - u := sets.NewStringsUnion([]string{"a", "b", "c"}) + u := sets.NewGenericUnion([]string{"a", "b", "c"}) if !u.Union("a", "b") { t.Error("expected Union(a,b) to return true on a fresh merge") } } func TestNamedUnionReturnsFalseWhenAlreadyJoined(t *testing.T) { - u := sets.NewStringsUnion([]string{"a", "b", "c"}) + u := sets.NewGenericUnion([]string{"a", "b", "c"}) u.Union("a", "b") if u.Union("a", "b") { t.Error("expected Union(a,b) to return false when already in the same set") @@ -62,7 +62,7 @@ func TestNamedUnionReturnsFalseWhenAlreadyJoined(t *testing.T) { } func TestNamedUnionReturnsFalseTransitively(t *testing.T) { - u := sets.NewStringsUnion([]string{"a", "b", "c"}) + u := sets.NewGenericUnion([]string{"a", "b", "c"}) u.Union("a", "b") u.Union("b", "c") // a and c are now connected through b @@ -72,7 +72,7 @@ func TestNamedUnionReturnsFalseTransitively(t *testing.T) { } func TestNamedUnionUnknownNameReturnsFalse(t *testing.T) { - u := sets.NewStringsUnion([]string{"a", "b"}) + u := sets.NewGenericUnion([]string{"a", "b"}) if u.Union("a", "ghost") { t.Error("expected Union with an unknown name to return false") } @@ -83,7 +83,7 @@ func TestNamedUnionUnknownNameReturnsFalse(t *testing.T) { } func TestNamedUnionDecrementsDisjointedCount(t *testing.T) { - u := sets.NewStringsUnion([]string{"a", "b", "c", "d"}) + u := sets.NewGenericUnion([]string{"a", "b", "c", "d"}) u.Union("a", "b") // 4 -> 3 u.Union("c", "d") // 3 -> 2 if got := u.Disjointed(); got != 2 { @@ -92,7 +92,7 @@ func TestNamedUnionDecrementsDisjointedCount(t *testing.T) { } func TestNamedUnionIsSymmetric(t *testing.T) { - u := sets.NewStringsUnion([]string{"a", "b"}) + u := sets.NewGenericUnion([]string{"a", "b"}) u.Union("b", "a") if !u.IsUnion("a", "b") { t.Error("expected Union(b,a) to connect a and b regardless of argument order") @@ -102,7 +102,7 @@ func TestNamedUnionIsSymmetric(t *testing.T) { // --- IsUnion --- func TestNamedIsUnionTrueAfterMerge(t *testing.T) { - u := sets.NewStringsUnion([]string{"a", "b", "c"}) + u := sets.NewGenericUnion([]string{"a", "b", "c"}) u.Union("a", "b") if !u.IsUnion("a", "b") { t.Error("expected a and b to be in the same set after Union") @@ -110,7 +110,7 @@ func TestNamedIsUnionTrueAfterMerge(t *testing.T) { } func TestNamedIsUnionTransitive(t *testing.T) { - u := sets.NewStringsUnion([]string{"a", "b", "c", "d"}) + u := sets.NewGenericUnion([]string{"a", "b", "c", "d"}) u.Union("a", "b") u.Union("b", "c") if !u.IsUnion("a", "c") { @@ -122,7 +122,7 @@ func TestNamedIsUnionTransitive(t *testing.T) { } func TestNamedIsUnionUnknownNameReturnsFalse(t *testing.T) { - u := sets.NewStringsUnion([]string{"a", "b"}) + u := sets.NewGenericUnion([]string{"a", "b"}) if u.IsUnion("a", "ghost") { t.Error("expected IsUnion with an unknown name to return false") } @@ -131,7 +131,7 @@ func TestNamedIsUnionUnknownNameReturnsFalse(t *testing.T) { // --- Rep --- func TestRepIsSharedAfterUnion(t *testing.T) { - u := sets.NewStringsUnion([]string{"a", "b", "c"}) + u := sets.NewGenericUnion([]string{"a", "b", "c"}) u.Union("a", "b") u.Union("b", "c") @@ -144,14 +144,14 @@ func TestRepIsSharedAfterUnion(t *testing.T) { } func TestRepUnknownNameReturnsFalse(t *testing.T) { - u := sets.NewStringsUnion([]string{"a", "b"}) + u := sets.NewGenericUnion([]string{"a", "b"}) if rep, ok := u.Rep("ghost"); ok || rep != "" { t.Errorf("expected (\"\", false) for an unknown name, got (%q, %v)", rep, ok) } } func TestRepDistinctForSeparateSets(t *testing.T) { - u := sets.NewStringsUnion([]string{"a", "b", "c", "d"}) + u := sets.NewGenericUnion([]string{"a", "b", "c", "d"}) u.Union("a", "b") u.Union("c", "d") @@ -162,16 +162,52 @@ func TestRepDistinctForSeparateSets(t *testing.T) { } } +// --- Generics: works with non-string ordered types --- + +func TestGenericUnionWithIntegers(t *testing.T) { + ports := sets.NewGenericUnion([]int{80, 443, 8080, 8443}) + if disjointCount := ports.Disjointed(); disjointCount != 4 { + t.Errorf("expected 4 disjoint sets initially, got %d", disjointCount) + } + + ports.Union(80, 443) + ports.Union(8080, 8443) + if disjointCount := ports.Disjointed(); disjointCount != 2 { + t.Errorf("expected 2 disjoint sets after merges, got %d", disjointCount) + } + if !ports.IsUnion(80, 443) { + t.Error("expected 80 and 443 to be in the same set") + } + if ports.IsUnion(80, 8080) { + t.Error("expected 80 and 8080 to be in different sets") + } + + representative, known := ports.Rep(80) + if !known { + t.Error("expected 80 to be a known element") + } + if otherRepresentative, _ := ports.Rep(443); representative != otherRepresentative { + t.Errorf("expected 80 and 443 to share a representative, got %d and %d", representative, otherRepresentative) + } +} + +func TestGenericUnionIntZeroValueRepOnUnknown(t *testing.T) { + ports := sets.NewGenericUnion([]int{1, 2}) + if representative, known := ports.Rep(999); known || representative != 0 { + t.Errorf("expected (0, false) for an unknown element, got (%d, %v)", representative, known) + } +} + // --- Scenario: grouping people into friend circles --- func TestFriendCircles(t *testing.T) { friendships := [][2]string{ {"alice", "bob"}, {"bob", "carol"}, // circle A: alice-bob-carol - {"dave", "erin"}, // circle B: dave-erin + {"dave", "erin"}, // circle B: dave-erin {"frank", "grace"}, {"grace", "frank"}, // circle C: frank-grace (redundant) } - circles := sets.NewStringsUnion([]string{ + circles := sets.NewGenericUnion([]string{ "alice", "bob", "carol", "dave", "erin", "frank", "grace", }) for _, f := range friendships {