2022-08-22 23:28:53 +02:00
|
|
|
package topo
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
2023-02-17 20:01:26 +01:00
|
|
|
|
2023-03-07 22:04:06 +01:00
|
|
|
"github.com/Jguer/yay/v12/pkg/text"
|
2022-08-22 23:28:53 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
type (
|
2023-02-17 20:01:26 +01:00
|
|
|
NodeSet[T comparable] map[T]bool
|
|
|
|
DepMap[T comparable] map[T]NodeSet[T]
|
2022-08-22 23:28:53 +02:00
|
|
|
)
|
|
|
|
|
2022-09-07 01:03:29 +02:00
|
|
|
type NodeInfo[V any] struct {
|
|
|
|
Color string
|
|
|
|
Background string
|
|
|
|
Value V
|
2022-08-23 18:33:40 +02:00
|
|
|
}
|
|
|
|
|
2022-12-29 13:34:53 +01:00
|
|
|
type CheckFn[T comparable, V any] func(T, V) error
|
|
|
|
|
2022-09-07 01:03:29 +02:00
|
|
|
type Graph[T comparable, V any] struct {
|
2023-02-17 20:01:26 +01:00
|
|
|
nodes NodeSet[T]
|
2022-08-22 23:28:53 +02:00
|
|
|
|
2022-08-23 18:33:40 +02:00
|
|
|
// node info map
|
2022-09-07 01:03:29 +02:00
|
|
|
nodeInfo map[T]*NodeInfo[V]
|
2022-08-23 18:33:40 +02:00
|
|
|
|
2022-08-22 23:28:53 +02:00
|
|
|
// `dependencies` tracks child -> parents.
|
|
|
|
dependencies DepMap[T]
|
|
|
|
// `dependents` tracks parent -> children.
|
|
|
|
dependents DepMap[T]
|
|
|
|
}
|
|
|
|
|
2022-09-07 01:03:29 +02:00
|
|
|
func New[T comparable, V any]() *Graph[T, V] {
|
|
|
|
return &Graph[T, V]{
|
2022-08-22 23:28:53 +02:00
|
|
|
nodes: make(NodeSet[T]),
|
|
|
|
dependencies: make(DepMap[T]),
|
|
|
|
dependents: make(DepMap[T]),
|
2022-09-07 01:03:29 +02:00
|
|
|
nodeInfo: make(map[T]*NodeInfo[V]),
|
2022-08-22 23:28:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-07 01:03:29 +02:00
|
|
|
func (g *Graph[T, V]) Len() int {
|
2022-09-04 23:45:40 +02:00
|
|
|
return len(g.nodes)
|
|
|
|
}
|
|
|
|
|
2022-09-07 01:03:29 +02:00
|
|
|
func (g *Graph[T, V]) Exists(node T) bool {
|
2022-09-04 23:45:40 +02:00
|
|
|
_, ok := g.nodes[node]
|
2022-09-07 01:03:29 +02:00
|
|
|
|
2022-09-04 23:45:40 +02:00
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
2022-09-07 01:03:29 +02:00
|
|
|
func (g *Graph[T, V]) AddNode(node T) {
|
2022-08-22 23:28:53 +02:00
|
|
|
g.nodes[node] = true
|
|
|
|
}
|
|
|
|
|
2023-02-17 20:01:26 +01:00
|
|
|
func (g *Graph[T, V]) ForEach(f CheckFn[T, V]) error {
|
|
|
|
for node := range g.nodes {
|
|
|
|
if err := f(node, g.nodeInfo[node].Value); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-08-23 18:33:40 +02:00
|
|
|
}
|
2022-11-15 15:44:50 +01:00
|
|
|
|
2023-02-17 20:01:26 +01:00
|
|
|
return nil
|
2022-09-06 23:25:44 +02:00
|
|
|
}
|
|
|
|
|
2022-09-07 01:03:29 +02:00
|
|
|
func (g *Graph[T, V]) SetNodeInfo(node T, nodeInfo *NodeInfo[V]) {
|
2023-02-17 20:01:26 +01:00
|
|
|
g.nodeInfo[node] = nodeInfo
|
2022-09-07 01:03:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (g *Graph[T, V]) GetNodeInfo(node T) *NodeInfo[V] {
|
2023-02-17 20:01:26 +01:00
|
|
|
return g.nodeInfo[node]
|
2022-08-23 18:33:40 +02:00
|
|
|
}
|
|
|
|
|
2022-09-07 01:03:29 +02:00
|
|
|
func (g *Graph[T, V]) DependOn(child, parent T) error {
|
2022-08-22 23:28:53 +02:00
|
|
|
if child == parent {
|
|
|
|
return ErrSelfReferential
|
|
|
|
}
|
|
|
|
|
|
|
|
if g.DependsOn(parent, child) {
|
|
|
|
return ErrCircular
|
|
|
|
}
|
|
|
|
|
|
|
|
g.AddNode(parent)
|
|
|
|
g.AddNode(child)
|
|
|
|
|
|
|
|
// Add nodes.
|
|
|
|
g.nodes[parent] = true
|
|
|
|
g.nodes[child] = true
|
|
|
|
|
|
|
|
// Add edges.
|
|
|
|
g.dependents.addNodeToNodeset(parent, child)
|
|
|
|
g.dependencies.addNodeToNodeset(child, parent)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-09-07 01:03:29 +02:00
|
|
|
func (g *Graph[T, V]) String() string {
|
2022-08-22 23:28:53 +02:00
|
|
|
var sb strings.Builder
|
2022-11-15 15:44:50 +01:00
|
|
|
|
2022-08-22 23:28:53 +02:00
|
|
|
sb.WriteString("digraph {\n")
|
2022-08-23 18:33:40 +02:00
|
|
|
sb.WriteString("compound=true;\n")
|
|
|
|
sb.WriteString("concentrate=true;\n")
|
2022-08-22 23:28:53 +02:00
|
|
|
sb.WriteString("node [shape = record, ordering=out];\n")
|
2022-08-23 18:33:40 +02:00
|
|
|
|
2022-08-22 23:28:53 +02:00
|
|
|
for node := range g.nodes {
|
2022-08-23 18:33:40 +02:00
|
|
|
extra := ""
|
2022-09-07 01:03:29 +02:00
|
|
|
|
2022-08-23 18:33:40 +02:00
|
|
|
if info, ok := g.nodeInfo[node]; ok {
|
2022-09-07 01:03:29 +02:00
|
|
|
if info.Background != "" || info.Color != "" {
|
2022-09-17 14:45:07 +02:00
|
|
|
extra = fmt.Sprintf("[color = %s, style = filled, fillcolor = %s]", info.Color, info.Background)
|
2022-08-23 18:33:40 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sb.WriteString(fmt.Sprintf("\t\"%v\"%s;\n", node, extra))
|
2022-08-22 23:28:53 +02:00
|
|
|
}
|
2022-08-23 18:33:40 +02:00
|
|
|
|
2022-08-22 23:28:53 +02:00
|
|
|
for parent, children := range g.dependencies {
|
|
|
|
for child := range children {
|
|
|
|
sb.WriteString(fmt.Sprintf("\t\"%v\" -> \"%v\";\n", parent, child))
|
|
|
|
}
|
|
|
|
}
|
2022-09-09 17:38:48 +02:00
|
|
|
|
2022-08-22 23:28:53 +02:00
|
|
|
sb.WriteString("}")
|
2022-09-09 17:38:48 +02:00
|
|
|
|
2022-08-22 23:28:53 +02:00
|
|
|
return sb.String()
|
|
|
|
}
|
|
|
|
|
2022-09-07 01:03:29 +02:00
|
|
|
func (g *Graph[T, V]) DependsOn(child, parent T) bool {
|
2022-08-22 23:28:53 +02:00
|
|
|
deps := g.Dependencies(child)
|
|
|
|
_, ok := deps[parent]
|
|
|
|
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
2022-09-07 01:03:29 +02:00
|
|
|
func (g *Graph[T, V]) HasDependent(parent, child T) bool {
|
2022-08-22 23:28:53 +02:00
|
|
|
deps := g.Dependents(parent)
|
|
|
|
_, ok := deps[child]
|
|
|
|
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
2023-02-17 20:01:26 +01:00
|
|
|
// leavesMap returns a map of leaves with the node as key and the node info value as value.
|
|
|
|
func (g *Graph[T, V]) leavesMap() map[T]V {
|
2022-09-09 17:38:48 +02:00
|
|
|
leaves := make(map[T]V, 0)
|
|
|
|
|
|
|
|
for node := range g.nodes {
|
|
|
|
if _, ok := g.dependencies[node]; !ok {
|
|
|
|
nodeInfo := g.GetNodeInfo(node)
|
|
|
|
if nodeInfo == nil {
|
|
|
|
nodeInfo = &NodeInfo[V]{}
|
|
|
|
}
|
|
|
|
|
|
|
|
leaves[node] = nodeInfo.Value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return leaves
|
|
|
|
}
|
|
|
|
|
2022-11-15 15:44:50 +01:00
|
|
|
// TopoSortedLayerMap returns a slice of all of the graph nodes in topological sort order with their node info.
|
2022-12-29 13:34:53 +01:00
|
|
|
func (g *Graph[T, V]) TopoSortedLayerMap(checkFn CheckFn[T, V]) []map[T]V {
|
2022-09-09 17:38:48 +02:00
|
|
|
layers := []map[T]V{}
|
|
|
|
|
|
|
|
// Copy the graph
|
|
|
|
shrinkingGraph := g.clone()
|
|
|
|
|
|
|
|
for {
|
2023-02-17 20:01:26 +01:00
|
|
|
leaves := shrinkingGraph.leavesMap()
|
2022-09-09 17:38:48 +02:00
|
|
|
if len(leaves) == 0 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
layers = append(layers, leaves)
|
|
|
|
|
|
|
|
for leafNode := range leaves {
|
2022-12-29 13:34:53 +01:00
|
|
|
if checkFn != nil {
|
|
|
|
if err := checkFn(leafNode, leaves[leafNode]); err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
2022-09-09 17:38:48 +02:00
|
|
|
shrinkingGraph.remove(leafNode)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return layers
|
|
|
|
}
|
|
|
|
|
2023-02-17 20:01:26 +01:00
|
|
|
// returns if it was the last
|
|
|
|
func (dm DepMap[T]) removeFromDepmap(key, node T) bool {
|
2022-08-22 23:28:53 +02:00
|
|
|
if nodes := dm[key]; len(nodes) == 1 {
|
|
|
|
// The only element in the nodeset must be `node`, so we
|
|
|
|
// can delete the entry entirely.
|
|
|
|
delete(dm, key)
|
2023-02-17 20:01:26 +01:00
|
|
|
return true
|
2022-08-22 23:28:53 +02:00
|
|
|
} else {
|
|
|
|
// Otherwise, remove the single node from the nodeset.
|
|
|
|
delete(nodes, node)
|
2023-02-17 20:01:26 +01:00
|
|
|
return false
|
2022-08-22 23:28:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-17 20:01:26 +01:00
|
|
|
// Prune removes the node,
|
|
|
|
// its dependencies if there are no other dependents
|
|
|
|
// and its dependents
|
2023-03-05 18:31:11 +01:00
|
|
|
func (g *Graph[T, V]) Prune(node T) []T {
|
|
|
|
pruned := []T{node}
|
2022-08-22 23:28:53 +02:00
|
|
|
// Remove edges from things that depend on `node`.
|
|
|
|
for dependent := range g.dependents[node] {
|
2023-02-17 20:01:26 +01:00
|
|
|
last := g.dependencies.removeFromDepmap(dependent, node)
|
|
|
|
text.Debugln("pruning dependent", dependent, last)
|
|
|
|
if last {
|
2023-03-05 18:31:11 +01:00
|
|
|
pruned = append(pruned, g.Prune(dependent)...)
|
2023-02-17 20:01:26 +01:00
|
|
|
}
|
2022-08-22 23:28:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
delete(g.dependents, node)
|
|
|
|
|
|
|
|
// Remove all edges from node to the things it depends on.
|
|
|
|
for dependency := range g.dependencies[node] {
|
2023-02-17 20:01:26 +01:00
|
|
|
last := g.dependents.removeFromDepmap(dependency, node)
|
|
|
|
text.Debugln("pruning dependency", dependency, last)
|
|
|
|
if last {
|
2023-03-05 18:31:11 +01:00
|
|
|
pruned = append(pruned, g.Prune(dependency)...)
|
2023-02-17 20:01:26 +01:00
|
|
|
}
|
2022-08-22 23:28:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
delete(g.dependencies, node)
|
|
|
|
|
|
|
|
// Finally, remove the node itself.
|
|
|
|
delete(g.nodes, node)
|
2023-03-05 18:31:11 +01:00
|
|
|
return pruned
|
2022-08-22 23:28:53 +02:00
|
|
|
}
|
|
|
|
|
2023-02-17 20:01:26 +01:00
|
|
|
func (g *Graph[T, V]) remove(node T) {
|
|
|
|
// Remove edges from things that depend on `node`.
|
|
|
|
for dependent := range g.dependents[node] {
|
|
|
|
g.dependencies.removeFromDepmap(dependent, node)
|
2022-08-22 23:28:53 +02:00
|
|
|
}
|
|
|
|
|
2023-02-17 20:01:26 +01:00
|
|
|
delete(g.dependents, node)
|
2022-08-22 23:28:53 +02:00
|
|
|
|
2023-02-17 20:01:26 +01:00
|
|
|
// Remove all edges from node to the things it depends on.
|
|
|
|
for dependency := range g.dependencies[node] {
|
|
|
|
g.dependents.removeFromDepmap(dependency, node)
|
2022-08-22 23:28:53 +02:00
|
|
|
}
|
|
|
|
|
2023-02-17 20:01:26 +01:00
|
|
|
delete(g.dependencies, node)
|
|
|
|
|
|
|
|
// Finally, remove the node itself.
|
|
|
|
delete(g.nodes, node)
|
2022-08-22 23:28:53 +02:00
|
|
|
}
|
|
|
|
|
2022-09-07 01:03:29 +02:00
|
|
|
func (g *Graph[T, V]) Dependencies(child T) NodeSet[T] {
|
2022-08-22 23:28:53 +02:00
|
|
|
return g.buildTransitive(child, g.immediateDependencies)
|
|
|
|
}
|
|
|
|
|
2022-09-07 01:03:29 +02:00
|
|
|
func (g *Graph[T, V]) immediateDependencies(node T) NodeSet[T] {
|
2022-08-22 23:28:53 +02:00
|
|
|
return g.dependencies[node]
|
|
|
|
}
|
|
|
|
|
2022-09-07 01:03:29 +02:00
|
|
|
func (g *Graph[T, V]) Dependents(parent T) NodeSet[T] {
|
2022-08-22 23:28:53 +02:00
|
|
|
return g.buildTransitive(parent, g.immediateDependents)
|
|
|
|
}
|
|
|
|
|
2022-09-07 01:03:29 +02:00
|
|
|
func (g *Graph[T, V]) immediateDependents(node T) NodeSet[T] {
|
2022-08-22 23:28:53 +02:00
|
|
|
return g.dependents[node]
|
|
|
|
}
|
|
|
|
|
2022-09-07 01:03:29 +02:00
|
|
|
func (g *Graph[T, V]) clone() *Graph[T, V] {
|
|
|
|
return &Graph[T, V]{
|
2022-08-22 23:28:53 +02:00
|
|
|
dependencies: g.dependencies.copy(),
|
|
|
|
dependents: g.dependents.copy(),
|
|
|
|
nodes: g.nodes.copy(),
|
2022-09-09 17:38:48 +02:00
|
|
|
nodeInfo: g.nodeInfo, // not copied, as it is not modified
|
2022-08-22 23:28:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// buildTransitive starts at `root` and continues calling `nextFn` to keep discovering more nodes until
|
|
|
|
// the graph cannot produce any more. It returns the set of all discovered nodes.
|
2022-09-07 01:03:29 +02:00
|
|
|
func (g *Graph[T, V]) buildTransitive(root T, nextFn func(T) NodeSet[T]) NodeSet[T] {
|
2022-08-22 23:28:53 +02:00
|
|
|
if _, ok := g.nodes[root]; !ok {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
out := make(NodeSet[T])
|
|
|
|
searchNext := []T{root}
|
|
|
|
|
|
|
|
for len(searchNext) > 0 {
|
|
|
|
// List of new nodes from this layer of the dependency graph. This is
|
|
|
|
// assigned to `searchNext` at the end of the outer "discovery" loop.
|
|
|
|
discovered := []T{}
|
|
|
|
|
|
|
|
for _, node := range searchNext {
|
|
|
|
// For each node to discover, find the next nodes.
|
|
|
|
for nextNode := range nextFn(node) {
|
|
|
|
// If we have not seen the node before, add it to the output as well
|
|
|
|
// as the list of nodes to traverse in the next iteration.
|
|
|
|
if _, ok := out[nextNode]; !ok {
|
|
|
|
out[nextNode] = true
|
|
|
|
|
|
|
|
discovered = append(discovered, nextNode)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
searchNext = discovered
|
|
|
|
}
|
|
|
|
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s NodeSet[T]) copy() NodeSet[T] {
|
|
|
|
out := make(NodeSet[T], len(s))
|
|
|
|
for k, v := range s {
|
|
|
|
out[k] = v
|
|
|
|
}
|
|
|
|
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
2022-11-15 15:44:50 +01:00
|
|
|
func (dm DepMap[T]) copy() DepMap[T] {
|
|
|
|
out := make(DepMap[T], len(dm))
|
|
|
|
for k := range dm {
|
|
|
|
out[k] = dm[k].copy()
|
2022-08-22 23:28:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
|
|
|
func (dm DepMap[T]) addNodeToNodeset(key, node T) {
|
|
|
|
nodes, ok := dm[key]
|
|
|
|
if !ok {
|
|
|
|
nodes = make(NodeSet[T])
|
|
|
|
dm[key] = nodes
|
|
|
|
}
|
|
|
|
|
|
|
|
nodes[node] = true
|
|
|
|
}
|