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

toposort.gno

5.89 Kb · 217 lines
  1// Package toposort orders a dependency graph so that every node comes after
  2// everything it depends on — the "install these packages in a safe order"
  3// problem — as a pure, reusable package.
  4//
  5// It uses Kahn's algorithm, and it is *deterministic*: ready nodes are always
  6// taken in lexicographic order, so a given graph always yields the exact same
  7// ordering. That matters on-chain, where a Render that reshuffled between
  8// identical calls would be a bug. Adjacency is kept in sorted slices rather
  9// than maps for the same reason — Go/gno map iteration order is unspecified.
 10//
 11// A cycle is reported as an error naming the nodes still stuck, rather than
 12// panicking or silently dropping them.
 13//
 14// A live demo of this package is at
 15// [r/moul/x/daily/toposortdemo](/r/moul/x/daily/toposortdemo/v0).
 16package toposort
 17
 18import (
 19	"errors"
 20	"sort"
 21	"strings"
 22)
 23
 24// ErrCycle is returned by Sort when the graph is not a DAG. Use CycleNodes to
 25// recover which nodes are involved.
 26var ErrCycle = errors.New("toposort: graph has a cycle")
 27
 28// Graph is a directed dependency graph. The zero value is not usable — build
 29// one with New.
 30type Graph struct {
 31	nodes []string            // every known node, kept sorted and unique
 32	deps  map[string][]string // node -> the nodes it depends on (sorted, unique)
 33}
 34
 35// New returns an empty Graph.
 36func New() *Graph {
 37	return &Graph{deps: map[string][]string{}}
 38}
 39
 40// Add registers a node with no dependencies. Adding twice is a no-op; it is
 41// how you declare a leaf that nothing depends on.
 42func (g *Graph) Add(node string) {
 43	if node == "" {
 44		return
 45	}
 46	g.addNode(node)
 47}
 48
 49// DependOn records that node depends on dep, so dep must come first. Both
 50// endpoints are registered. A self-dependency is ignored (it would be a
 51// trivial cycle and is never what the caller means). Duplicate edges collapse.
 52func (g *Graph) DependOn(node, dep string) {
 53	if node == "" || dep == "" {
 54		return
 55	}
 56	g.addNode(node)
 57	g.addNode(dep)
 58	if node == dep {
 59		// A self-edge is a trivial cycle and never what the caller means, but
 60		// the node was still named, so it stays in the graph as a leaf.
 61		return
 62	}
 63	cur := g.deps[node]
 64	i := sort.SearchStrings(cur, dep)
 65	if i < len(cur) && cur[i] == dep {
 66		return // already recorded
 67	}
 68	cur = append(cur, "")
 69	copy(cur[i+1:], cur[i:])
 70	cur[i] = dep
 71	g.deps[node] = cur
 72}
 73
 74// addNode inserts node into the sorted node list if absent.
 75func (g *Graph) addNode(node string) {
 76	i := sort.SearchStrings(g.nodes, node)
 77	if i < len(g.nodes) && g.nodes[i] == node {
 78		return
 79	}
 80	g.nodes = append(g.nodes, "")
 81	copy(g.nodes[i+1:], g.nodes[i:])
 82	g.nodes[i] = node
 83}
 84
 85// Len returns how many nodes the graph holds.
 86func (g *Graph) Len() int { return len(g.nodes) }
 87
 88// Nodes returns every node in lexicographic order.
 89func (g *Graph) Nodes() []string {
 90	out := make([]string, len(g.nodes))
 91	copy(out, g.nodes)
 92	return out
 93}
 94
 95// DependenciesOf returns node's direct dependencies, sorted.
 96func (g *Graph) DependenciesOf(node string) []string {
 97	d := g.deps[node]
 98	out := make([]string, len(d))
 99	copy(out, d)
100	return out
101}
102
103// Sort returns the nodes ordered so every node follows its dependencies.
104//
105// Among nodes that are simultaneously ready, the lexicographically smallest is
106// emitted first, which makes the result unique for a given graph. On a cycle it
107// returns ErrCycle along with the partial order computed so far.
108func (g *Graph) Sort() ([]string, error) {
109	// indegree[n] = how many of n's dependencies are still unresolved.
110	indegree := map[string]int{}
111	// dependents[d] = nodes waiting on d.
112	dependents := map[string][]string{}
113	for _, n := range g.nodes {
114		indegree[n] = len(g.deps[n])
115		for _, d := range g.deps[n] {
116			dependents[d] = append(dependents[d], n)
117		}
118	}
119
120	// ready holds nodes with no unresolved dependency, kept sorted so the
121	// smallest is always taken first.
122	ready := []string{}
123	for _, n := range g.nodes { // g.nodes is already sorted
124		if indegree[n] == 0 {
125			ready = append(ready, n)
126		}
127	}
128
129	out := make([]string, 0, len(g.nodes))
130	for len(ready) > 0 {
131		n := ready[0]
132		ready = ready[1:]
133		out = append(out, n)
134
135		// Releasing dependents can make several ready at once; collect them and
136		// merge into the sorted queue so ordering stays deterministic.
137		freed := []string{}
138		for _, m := range dependents[n] {
139			indegree[m]--
140			if indegree[m] == 0 {
141				freed = append(freed, m)
142			}
143		}
144		if len(freed) > 0 {
145			sort.Strings(freed)
146			ready = mergeSorted(ready, freed)
147		}
148	}
149
150	if len(out) != len(g.nodes) {
151		return out, ErrCycle
152	}
153	return out, nil
154}
155
156// CycleNodes returns the nodes that could not be ordered — i.e. those on or
157// downstream of a cycle — in lexicographic order. Empty when the graph is a DAG.
158func (g *Graph) CycleNodes() []string {
159	done, err := g.Sort()
160	if err == nil {
161		return []string{}
162	}
163	placed := map[string]bool{}
164	for _, n := range done {
165		placed[n] = true
166	}
167	stuck := []string{}
168	for _, n := range g.nodes {
169		if !placed[n] {
170			stuck = append(stuck, n)
171		}
172	}
173	return stuck
174}
175
176// mergeSorted merges two sorted string slices into one sorted slice.
177func mergeSorted(a, b []string) []string {
178	out := make([]string, 0, len(a)+len(b))
179	i, j := 0, 0
180	for i < len(a) && j < len(b) {
181		if a[i] <= b[j] {
182			out = append(out, a[i])
183			i++
184		} else {
185			out = append(out, b[j])
186			j++
187		}
188	}
189	out = append(out, a[i:]...)
190	out = append(out, b[j:]...)
191	return out
192}
193
194// FromPairs builds a Graph from "node depends on dep" pairs. Each pair is
195// {node, dep}. Handy for tests and literal declarations.
196func FromPairs(pairs [][2]string) *Graph {
197	g := New()
198	for _, p := range pairs {
199		g.DependOn(p[0], p[1])
200	}
201	return g
202}
203
204// String renders the graph as "node <- dep1, dep2" lines, sorted. Useful for
205// debugging and for demos.
206func (g *Graph) String() string {
207	var b strings.Builder
208	for _, n := range g.nodes {
209		b.WriteString(n)
210		if d := g.deps[n]; len(d) > 0 {
211			b.WriteString(" <- ")
212			b.WriteString(strings.Join(d, ", "))
213		}
214		b.WriteString("\n")
215	}
216	return b.String()
217}