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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

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}