Skip to content

Commit e73945c

Browse files
committed
add topological prune
1 parent 8b53832 commit e73945c

7 files changed

Lines changed: 393 additions & 72 deletions

File tree

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,49 @@ Sorting and pruning of DAG graphs.
1010

1111
Ideas borrowed from [python graphlib](https://github.com/python/cpython/blob/3.14/Lib/graphlib.py)
1212

13+
# How to install
14+
```bash
15+
go get -u github.com/aio-arch/graphlib
16+
```
17+
18+
# How to use
19+
```golang
20+
import "github.com/aio-arch/graphlib"
21+
22+
// New A graph
23+
24+
// for string type
25+
g1 := graphlib.NewGraph[string]()
26+
27+
// for int type
28+
g2 := graphlib.NewGraph[int]()
29+
30+
// for add node and add edge
31+
g1.AddNode("A1")
32+
g1.AddNode("B2")
33+
g1.AddEdge("A1", "B2") // edge: A1 -> B2
34+
35+
// for add mulit edge,add node inline
36+
g2.Add(10, 1, 9) // edge: 1 -> 10 and 9 -> 10
37+
g2.Add(100, 10, 90) // edge: 10 -> 100 and 90 -> 100
38+
39+
// topological order
40+
topo, err := graphlib.TopologicalOrder(g1)
41+
if err != nil {
42+
fmt.Println(err.Error())
43+
}
44+
fmt.Printf("Topological Order:%v\n", topo)
45+
46+
// topological prune
47+
g3, err := graphlib.TopologicalPrune(g2, []int{10, 90})
48+
if err != nil {
49+
fmt.Println(err.Error())
50+
}
51+
_ = g3
52+
53+
```
54+
55+
1356
# For Go Veriosn < 1.21
1457
```bash
1558
go mod edit -replace golang.org/x/exp=golang.org/x/exp@v0.0.0-20240904232852-e7e105dedf7e

example/main.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/aio-arch/graphlib"
7+
)
8+
9+
func main() {
10+
11+
/*
12+
┌────► A-1-1
13+
14+
┌────────────────────────► A-1
15+
│ ▲│
16+
A ┌─────► A-2-1 │└────► A-1-2
17+
│ │ │
18+
└────► A-2 │
19+
│ │
20+
└─────► A-2-2 │
21+
│ │
22+
├─────► C-1
23+
24+
B ───► B-1 ───► B-1-1
25+
*/
26+
g := graphlib.NewGraph[string]()
27+
28+
// add node
29+
g.AddNode("A-1-1")
30+
g.AddNode("A-1")
31+
32+
//add edge
33+
g.AddEdge("A-1", "A-1-1") // edge: A-1 -> A-1-1
34+
35+
g.AddNode("A-1-2")
36+
g.AddEdge("A-1", "A-1-2") // edge: A-1 -> A-1-2
37+
38+
// add multiple edges and add node inline
39+
g.Add("A-1", "A", "C-1") // edge: A -> A-1 and C-1 -> A-1
40+
g.Add("A-2-1", "A-2") // edge: A-2 -> A-2-1
41+
g.Add("A-2-2", "A-2")
42+
g.Add("A-2", "A")
43+
g.Add("B-1-1", "B-1")
44+
g.Add("B-1", "B")
45+
g.Add("A") // add node A
46+
g.Add("B") // add node B
47+
g.Add("C-1", "A-2-2", "B-1-1")
48+
49+
topo, err := graphlib.TopologicalOrder(g)
50+
if err != nil {
51+
panic(err)
52+
}
53+
fmt.Printf("Topological Order:%v\n", topo)
54+
//Topological Order:[A B A-2 B-1 A-2-1 A-2-2 B-1-1 C-1 A-1 A-1-1 A-1-2]
55+
56+
g2, err := graphlib.TopologicalPrune(g, []string{"A-1-1"})
57+
if err != nil {
58+
panic(err)
59+
}
60+
topo2, err := graphlib.TopologicalOrder(g2)
61+
if err != nil {
62+
panic(err)
63+
}
64+
fmt.Printf("Pruned Topological Order:%v\n", topo2)
65+
//Pruned Topological Order:[A B A-2 B-1 A-2-2 B-1-1 C-1 A-1 A-1-1]
66+
}

graph.go

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
package graphlib
22

33
type nodeInfo[V comparable] struct {
4-
SortIdx uint //Sort idx
5-
PredecessorNums uint //Number of parent nodes
6-
SuccessorNums uint //Record next Successor append index
7-
Successor map[V]uint //Child Nodes
4+
SortIdx uint //Sort idx
5+
PredecessorNums uint //Number of parent nodes
6+
Successors []V //Child Nodes
87
}
98

10-
func (n *nodeInfo[V]) SuccessorSortSet() []V {
11-
set := make([]V, n.SuccessorNums)
12-
for node, idx := range n.Successor {
13-
set[idx] = node
9+
func (ni *nodeInfo[V]) hasSuccessor(node V) bool {
10+
for _, successor := range ni.Successors {
11+
if successor == node {
12+
return true
13+
}
1414
}
15-
return set
15+
return false
1616
}
1717

1818
type Graph[V comparable] struct {
@@ -31,8 +31,8 @@ func (g *Graph[V]) AddNode(node V) *nodeInfo[V] {
3131
return result
3232
}
3333
result := nodeInfo[V]{
34-
SortIdx: g.nodeNums,
35-
Successor: make(map[V]uint, 2),
34+
SortIdx: g.nodeNums,
35+
Successors: make([]V, 0, 2),
3636
}
3737
g.node2info[node] = &result
3838
g.nodeNums++
@@ -48,9 +48,8 @@ func (g *Graph[V]) AddEdge(from, to V) {
4848
if t, has = g.node2info[to]; !has {
4949
panic(ErrUnknownNode[V]{node: to})
5050
}
51-
if _, has := f.Successor[to]; !has {
52-
f.Successor[to] = f.SuccessorNums
53-
f.SuccessorNums++
51+
if !f.hasSuccessor(to) {
52+
f.Successors = append(f.Successors, to)
5453
t.PredecessorNums++
5554
}
5655
}
@@ -98,7 +97,7 @@ func (g *Graph[V]) IsAcyclic() ([]V, bool) {
9897
} else {
9998
seen[node] = struct{}{}
10099
itStack = append(itStack, iterItem[V]{isEnd: true})
101-
for _, successor := range g.node2info[node].SuccessorSortSet() {
100+
for _, successor := range g.node2info[node].Successors {
102101
itStack = append(itStack, iterItem[V]{val: successor})
103102
}
104103
node2stacki[node] = len(stack)

order.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package graphlib
2+
3+
type topologicalOrder[V comparable] struct {
4+
graph *Graph[V]
5+
traversedNums uint
6+
traversed map[V]uint
7+
notReadyNodes map[V]uint
8+
}
9+
10+
func (ts *topologicalOrder[V]) done(nodes *[]V) []V {
11+
readyNodes := make([]V, 0, 8)
12+
for _, node := range *nodes {
13+
ts.traversed[node] = ts.traversedNums
14+
ts.traversedNums++
15+
16+
nodeInfo := ts.graph.node2info[node]
17+
for _, successor := range nodeInfo.Successors {
18+
successorInfo := ts.graph.node2info[successor]
19+
if successorInfo.PredecessorNums == 1 || ts.notReadyNodes[successor] == 1 {
20+
readyNodes = append(readyNodes, successor)
21+
delete(ts.notReadyNodes, successor)
22+
} else {
23+
ts.notReadyNodes[successor] = successorInfo.PredecessorNums - 1
24+
}
25+
}
26+
}
27+
return readyNodes
28+
}
29+
30+
func (ts *topologicalOrder[V]) traverse() {
31+
//find roots
32+
rootNodes := make([]V, 0, len(ts.graph.node2info)/2)
33+
for _, v := range ts.graph.NodeSortSet() {
34+
nodeInfo := ts.graph.node2info[v]
35+
if nodeInfo.PredecessorNums == 0 {
36+
rootNodes = append(rootNodes, v)
37+
}
38+
}
39+
reayNodes := ts.done(&rootNodes)
40+
for len(reayNodes) > 0 {
41+
reayNodes = ts.done(&reayNodes)
42+
}
43+
}
44+
45+
func graphOrder[V comparable](g *Graph[V]) []V {
46+
nodeLen := len(g.node2info)
47+
48+
ts := topologicalOrder[V]{
49+
graph: g,
50+
traversed: make(map[V]uint, nodeLen),
51+
notReadyNodes: make(map[V]uint, nodeLen),
52+
}
53+
ts.traverse()
54+
55+
set := make([]V, ts.traversedNums)
56+
for node, idx := range ts.traversed {
57+
set[idx] = node
58+
}
59+
return set
60+
}

prune.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package graphlib
2+
3+
type topologicalPrune[V comparable] struct {
4+
graph *Graph[V]
5+
target *Graph[V]
6+
}
7+
8+
// func (tp *topologicalPrune[V]) sortWaitingNodes(nodes []V) {
9+
// sort.Slice(nodes, func(i, j int) bool {
10+
// return tp.graph.node2info[nodes[i]].SortIdx < tp.graph.node2info[nodes[j]].SortIdx
11+
// })
12+
// }
13+
14+
func (tp *topologicalPrune[V]) prune(nodes []V) *Graph[V] {
15+
16+
traversed := make(map[V]int, len(nodes))
17+
readyNodes := make(map[V]uint, len(nodes))
18+
childNodes := make(map[V]uint, len(nodes))
19+
for _, node := range nodes {
20+
nodeInfo := tp.graph.node2info[node]
21+
readyNodes[node] = nodeInfo.PredecessorNums
22+
}
23+
24+
for len(readyNodes) > 0 {
25+
for node, predecessorNums := range readyNodes {
26+
27+
delete(readyNodes, node)
28+
traversed[node] = 0
29+
if predecessorNums == 0 {
30+
tp.target.AddNode(node)
31+
} else {
32+
childNodes[node] = predecessorNums
33+
}
34+
}
35+
36+
for node, info := range tp.graph.node2info {
37+
traversedSuccessorNums, ok := traversed[node]
38+
if ok && len(info.Successors) == traversedSuccessorNums {
39+
continue
40+
}
41+
42+
var ready bool
43+
for _, successor := range info.Successors {
44+
if successorPredecessorNums, has := childNodes[successor]; has {
45+
ready = true
46+
tp.target.Add(successor, node)
47+
traversed[node] = traversedSuccessorNums + 1
48+
if successorPredecessorNums == 1 {
49+
delete(childNodes, successor)
50+
} else {
51+
childNodes[successor] = successorPredecessorNums - 1
52+
}
53+
}
54+
55+
}
56+
if ready && info.PredecessorNums > 0 {
57+
readyNodes[node] = info.PredecessorNums
58+
}
59+
if len(childNodes) == 0 {
60+
break
61+
}
62+
}
63+
}
64+
65+
// sort by graph order
66+
var idx uint
67+
for _, node := range tp.graph.NodeSortSet() {
68+
if info, has := tp.target.node2info[node]; has {
69+
info.SortIdx = idx
70+
idx++
71+
}
72+
if idx == tp.target.nodeNums {
73+
break
74+
}
75+
}
76+
77+
return tp.target
78+
}
79+
80+
func graphPrune[V comparable](g *Graph[V], nodes []V) *Graph[V] {
81+
tp := topologicalPrune[V]{
82+
graph: g,
83+
target: NewGraph[V](),
84+
}
85+
tp.prune(nodes)
86+
return tp.target
87+
}

0 commit comments

Comments
 (0)