Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

disjointset.gno

3.78 Kb · 131 lines
  1// Package disjointset is union-find (a disjoint-set forest) as a pure,
  2// reusable package: it tracks a partition of [0, n) into disjoint groups and
  3// answers "are these two in the same group?" in near-constant time.
  4//
  5// Both classic optimisations are implemented, and they matter together: path
  6// compression flattens a tree on every Find, union by rank keeps the shallower
  7// tree under the deeper one. With both, operations are O(α(n)) — inverse
  8// Ackermann, effectively constant. With neither, a chain of unions degrades to
  9// O(n) per query, which on chain is the difference between a cheap call and an
 10// out-of-gas one.
 11//
 12// A live demo of this package is at
 13// [r/moul/x/daily/disjointsetdemo](/r/moul/x/daily/disjointsetdemo/v0).
 14package disjointset
 15
 16// MaxN bounds a set so allocation stays predictable.
 17const MaxN = 1 << 16
 18
 19// DisjointSet is a partition of [0, n) into disjoint groups.
 20type DisjointSet struct {
 21	parent []int
 22	rank   []int
 23	groups int
 24}
 25
 26// New returns n singleton groups. n is clamped to [0, MaxN].
 27func New(n int) *DisjointSet {
 28	if n < 0 {
 29		n = 0
 30	}
 31	if n > MaxN {
 32		n = MaxN
 33	}
 34	d := &DisjointSet{parent: make([]int, n), rank: make([]int, n), groups: n}
 35	for i := 0; i < n; i++ {
 36		d.parent[i] = i // every element starts as its own root
 37	}
 38	return d
 39}
 40
 41// Len returns the number of elements.
 42func (d *DisjointSet) Len() int { return len(d.parent) }
 43
 44// Groups returns how many disjoint groups remain.
 45func (d *DisjointSet) Groups() int { return d.groups }
 46
 47// InRange reports whether i is a valid element.
 48func (d *DisjointSet) InRange(i int) bool { return i >= 0 && i < len(d.parent) }
 49
 50// Find returns the representative of i's group, or -1 when i is out of range.
 51//
 52// Path compression: every node visited is re-pointed straight at the root, so
 53// the next Find on any of them is O(1). Done iteratively rather than
 54// recursively — a deep chain would otherwise risk the call stack.
 55func (d *DisjointSet) Find(i int) int {
 56	if !d.InRange(i) {
 57		return -1
 58	}
 59	root := i
 60	for d.parent[root] != root {
 61		root = d.parent[root]
 62	}
 63	for d.parent[i] != root { // second pass: re-point everything at the root
 64		next := d.parent[i]
 65		d.parent[i] = root
 66		i = next
 67	}
 68	return root
 69}
 70
 71// Union merges the groups of a and b and reports whether they were merged.
 72// False means they were already together, or an index was out of range.
 73func (d *DisjointSet) Union(a, b int) bool {
 74	ra, rb := d.Find(a), d.Find(b)
 75	if ra < 0 || rb < 0 || ra == rb {
 76		return false
 77	}
 78	// Union by rank: hang the shallower tree off the deeper one so depth only
 79	// grows when both sides are equally deep.
 80	if d.rank[ra] < d.rank[rb] {
 81		ra, rb = rb, ra
 82	}
 83	d.parent[rb] = ra
 84	if d.rank[ra] == d.rank[rb] {
 85		d.rank[ra]++
 86	}
 87	d.groups--
 88	return true
 89}
 90
 91// Connected reports whether a and b are in the same group. Out-of-range
 92// indices are not connected to anything, including themselves.
 93func (d *DisjointSet) Connected(a, b int) bool {
 94	ra, rb := d.Find(a), d.Find(b)
 95	return ra >= 0 && ra == rb
 96}
 97
 98// Size returns how many elements share i's group, or 0 when out of range.
 99func (d *DisjointSet) Size(i int) int {
100	r := d.Find(i)
101	if r < 0 {
102		return 0
103	}
104	n := 0
105	for j := 0; j < len(d.parent); j++ {
106		if d.Find(j) == r {
107			n++
108		}
109	}
110	return n
111}
112
113// Partition returns the groups, each sorted ascending, ordered by their
114// smallest member — deterministic regardless of the union order, which is what
115// makes it safe to render.
116func (d *DisjointSet) Partition() [][]int {
117	byRoot := map[int][]int{}
118	order := []int{}
119	for i := 0; i < len(d.parent); i++ {
120		r := d.Find(i)
121		if _, seen := byRoot[r]; !seen {
122			order = append(order, r) // first sighting is the smallest member
123		}
124		byRoot[r] = append(byRoot[r], i)
125	}
126	out := [][]int{}
127	for _, r := range order { // iterate the slice, never the map
128		out = append(out, byRoot[r])
129	}
130	return out
131}