This commit is contained in:
@@ -1,3 +1,6 @@
|
|||||||
datastructures
|
datastructures
|
||||||
main.go
|
main.go
|
||||||
scratch
|
scratch
|
||||||
|
CLAUDE.md
|
||||||
|
.env
|
||||||
|
.secrets
|
||||||
|
|||||||
@@ -35,14 +35,26 @@
|
|||||||
|
|
||||||
# Documentation
|
# Documentation
|
||||||
|
|
||||||
|
> linear Structures
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go doc -all ./linear | bat -l go
|
go doc -all ./linear | bat -l go
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> algorithms
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go doc -all ./algo | bat -l go
|
go doc -all ./algo | bat -l go
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> Sets
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go doc -all ./sets/ | bat -l go
|
||||||
|
```
|
||||||
|
|
||||||
|
> trees
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
for p in ./trees/ ./trees/avl ./trees/heap; do go doc -all "$p"; done | bat -l go
|
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/...
|
go test ./tests/...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# External references
|
||||||
|
|
||||||
|
https://xlinux.nist.gov/dads/
|
||||||
|
|
||||||
## Disclosure
|
## Disclosure
|
||||||
|
|
||||||
Unit tests and explanations written by claude
|
Unit tests and explanations written by claude
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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()
|
|
||||||
}
|
|
||||||
+54
-18
@@ -9,14 +9,14 @@ import (
|
|||||||
// --- NewStringsUnion ---
|
// --- NewStringsUnion ---
|
||||||
|
|
||||||
func TestNewStringsUnionStartsFullyDisjointed(t *testing.T) {
|
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 {
|
if got := u.Disjointed(); got != 4 {
|
||||||
t.Errorf("expected 4 disjoint sets initially, got %d", got)
|
t.Errorf("expected 4 disjoint sets initially, got %d", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewStringsUnionDeduplicates(t *testing.T) {
|
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
|
// only a, b, c are unique
|
||||||
if got := u.Disjointed(); got != 3 {
|
if got := u.Disjointed(); got != 3 {
|
||||||
t.Errorf("expected 3 disjoint sets after dedup, got %d", got)
|
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) {
|
func TestNewStringsUnionEachNameIsOwnRep(t *testing.T) {
|
||||||
names := []string{"alice", "bob", "carol"}
|
names := []string{"alice", "bob", "carol"}
|
||||||
u := sets.NewStringsUnion(names)
|
u := sets.NewGenericUnion(names)
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
rep, ok := u.Rep(name)
|
rep, ok := u.Rep(name)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -38,7 +38,7 @@ func TestNewStringsUnionEachNameIsOwnRep(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestNewStringsUnionEmptyInput(t *testing.T) {
|
func TestNewStringsUnionEmptyInput(t *testing.T) {
|
||||||
u := sets.NewStringsUnion(nil)
|
u := sets.NewGenericUnion[string](nil)
|
||||||
if got := u.Disjointed(); got != 0 {
|
if got := u.Disjointed(); got != 0 {
|
||||||
t.Errorf("expected 0 disjoint sets for empty input, got %d", got)
|
t.Errorf("expected 0 disjoint sets for empty input, got %d", got)
|
||||||
}
|
}
|
||||||
@@ -47,14 +47,14 @@ func TestNewStringsUnionEmptyInput(t *testing.T) {
|
|||||||
// --- Union ---
|
// --- Union ---
|
||||||
|
|
||||||
func TestNamedUnionReturnsTrueOnMerge(t *testing.T) {
|
func TestNamedUnionReturnsTrueOnMerge(t *testing.T) {
|
||||||
u := sets.NewStringsUnion([]string{"a", "b", "c"})
|
u := sets.NewGenericUnion([]string{"a", "b", "c"})
|
||||||
if !u.Union("a", "b") {
|
if !u.Union("a", "b") {
|
||||||
t.Error("expected Union(a,b) to return true on a fresh merge")
|
t.Error("expected Union(a,b) to return true on a fresh merge")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNamedUnionReturnsFalseWhenAlreadyJoined(t *testing.T) {
|
func TestNamedUnionReturnsFalseWhenAlreadyJoined(t *testing.T) {
|
||||||
u := sets.NewStringsUnion([]string{"a", "b", "c"})
|
u := sets.NewGenericUnion([]string{"a", "b", "c"})
|
||||||
u.Union("a", "b")
|
u.Union("a", "b")
|
||||||
if u.Union("a", "b") {
|
if u.Union("a", "b") {
|
||||||
t.Error("expected Union(a,b) to return false when already in the same set")
|
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) {
|
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("a", "b")
|
||||||
u.Union("b", "c")
|
u.Union("b", "c")
|
||||||
// a and c are now connected through b
|
// a and c are now connected through b
|
||||||
@@ -72,7 +72,7 @@ func TestNamedUnionReturnsFalseTransitively(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestNamedUnionUnknownNameReturnsFalse(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") {
|
if u.Union("a", "ghost") {
|
||||||
t.Error("expected Union with an unknown name to return false")
|
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) {
|
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("a", "b") // 4 -> 3
|
||||||
u.Union("c", "d") // 3 -> 2
|
u.Union("c", "d") // 3 -> 2
|
||||||
if got := u.Disjointed(); got != 2 {
|
if got := u.Disjointed(); got != 2 {
|
||||||
@@ -92,7 +92,7 @@ func TestNamedUnionDecrementsDisjointedCount(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestNamedUnionIsSymmetric(t *testing.T) {
|
func TestNamedUnionIsSymmetric(t *testing.T) {
|
||||||
u := sets.NewStringsUnion([]string{"a", "b"})
|
u := sets.NewGenericUnion([]string{"a", "b"})
|
||||||
u.Union("b", "a")
|
u.Union("b", "a")
|
||||||
if !u.IsUnion("a", "b") {
|
if !u.IsUnion("a", "b") {
|
||||||
t.Error("expected Union(b,a) to connect a and b regardless of argument order")
|
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 ---
|
// --- IsUnion ---
|
||||||
|
|
||||||
func TestNamedIsUnionTrueAfterMerge(t *testing.T) {
|
func TestNamedIsUnionTrueAfterMerge(t *testing.T) {
|
||||||
u := sets.NewStringsUnion([]string{"a", "b", "c"})
|
u := sets.NewGenericUnion([]string{"a", "b", "c"})
|
||||||
u.Union("a", "b")
|
u.Union("a", "b")
|
||||||
if !u.IsUnion("a", "b") {
|
if !u.IsUnion("a", "b") {
|
||||||
t.Error("expected a and b to be in the same set after Union")
|
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) {
|
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("a", "b")
|
||||||
u.Union("b", "c")
|
u.Union("b", "c")
|
||||||
if !u.IsUnion("a", "c") {
|
if !u.IsUnion("a", "c") {
|
||||||
@@ -122,7 +122,7 @@ func TestNamedIsUnionTransitive(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestNamedIsUnionUnknownNameReturnsFalse(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") {
|
if u.IsUnion("a", "ghost") {
|
||||||
t.Error("expected IsUnion with an unknown name to return false")
|
t.Error("expected IsUnion with an unknown name to return false")
|
||||||
}
|
}
|
||||||
@@ -131,7 +131,7 @@ func TestNamedIsUnionUnknownNameReturnsFalse(t *testing.T) {
|
|||||||
// --- Rep ---
|
// --- Rep ---
|
||||||
|
|
||||||
func TestRepIsSharedAfterUnion(t *testing.T) {
|
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("a", "b")
|
||||||
u.Union("b", "c")
|
u.Union("b", "c")
|
||||||
|
|
||||||
@@ -144,14 +144,14 @@ func TestRepIsSharedAfterUnion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRepUnknownNameReturnsFalse(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 != "" {
|
if rep, ok := u.Rep("ghost"); ok || rep != "" {
|
||||||
t.Errorf("expected (\"\", false) for an unknown name, got (%q, %v)", rep, ok)
|
t.Errorf("expected (\"\", false) for an unknown name, got (%q, %v)", rep, ok)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRepDistinctForSeparateSets(t *testing.T) {
|
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("a", "b")
|
||||||
u.Union("c", "d")
|
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 ---
|
// --- Scenario: grouping people into friend circles ---
|
||||||
|
|
||||||
func TestFriendCircles(t *testing.T) {
|
func TestFriendCircles(t *testing.T) {
|
||||||
friendships := [][2]string{
|
friendships := [][2]string{
|
||||||
{"alice", "bob"}, {"bob", "carol"}, // circle A: alice-bob-carol
|
{"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)
|
{"frank", "grace"}, {"grace", "frank"}, // circle C: frank-grace (redundant)
|
||||||
}
|
}
|
||||||
|
|
||||||
circles := sets.NewStringsUnion([]string{
|
circles := sets.NewGenericUnion([]string{
|
||||||
"alice", "bob", "carol", "dave", "erin", "frank", "grace",
|
"alice", "bob", "carol", "dave", "erin", "frank", "grace",
|
||||||
})
|
})
|
||||||
for _, f := range friendships {
|
for _, f := range friendships {
|
||||||
Reference in New Issue
Block a user