graph/graph.go (99 lines of code) (raw):

package graph import ( "bytes" "fmt" ) // NodeConstrain is a constraint for a node in a graph type NodeConstrain interface { // Name of the node, should be unique in the graph GetName() string // DotSpec returns the dot spec for this node DotSpec() *DotNodeSpec } // EdgeSpecFunc is a function that returns the DOT specification for an edge. type EdgeSpecFunc[T NodeConstrain] func(from, to T) *DotEdgeSpec type Edge[NT NodeConstrain] struct { From NT To NT } // DotNodeSpec is the specification for a node in a DOT graph type DotNodeSpec struct { // id of the node Name string // display text of the node DisplayName string Tooltip string Shape string Style string FillColor string } // DotEdgeSpec is the specification for an edge in DOT graph type DotEdgeSpec struct { FromNodeName string ToNodeName string Tooltip string Style string Color string } // Graph hold the nodes and edges of a graph type Graph[NT NodeConstrain] struct { nodes map[string]NT nodeEdges map[string][]*Edge[NT] edgeSpecFunc EdgeSpecFunc[NT] } // NewGraph creates a new graph func NewGraph[NT NodeConstrain](edgeSpecFunc EdgeSpecFunc[NT]) *Graph[NT] { return &Graph[NT]{ nodes: make(map[string]NT), nodeEdges: make(map[string][]*Edge[NT]), edgeSpecFunc: edgeSpecFunc, } } // AddNode adds a node to the graph func (g *Graph[NT]) AddNode(n NT) error { nodeKey := n.GetName() if _, ok := g.nodes[nodeKey]; ok { return NewGraphError(ErrDuplicateNode, fmt.Sprintf("node with key %s already exists in this graph", nodeKey)) } g.nodes[nodeKey] = n return nil } func (g *Graph[NT]) Connect(from, to NT) error { fromNodeKey := from.GetName() toNodeKey := to.GetName() var ok bool if from, ok = g.nodes[fromNodeKey]; !ok { return NewGraphError(ErrConnectNotExistingNode, fmt.Sprintf("cannot connect node %s, it's not added in this graph yet", fromNodeKey)) } if to, ok = g.nodes[toNodeKey]; !ok { return NewGraphError(ErrConnectNotExistingNode, fmt.Sprintf("cannot connect node %s, it's not added in this graph yet", toNodeKey)) } g.nodeEdges[fromNodeKey] = append(g.nodeEdges[fromNodeKey], &Edge[NT]{From: from, To: to}) return nil } // https://en.wikipedia.org/wiki/DOT_(graph_description_language) func (g *Graph[NT]) ToDotGraph() (string, error) { nodes := make([]*DotNodeSpec, 0) for _, node := range g.nodes { nodes = append(nodes, node.DotSpec()) } edges := make([]*DotEdgeSpec, 0) for _, nodeEdges := range g.nodeEdges { for _, edge := range nodeEdges { edges = append(edges, g.edgeSpecFunc(edge.From, edge.To)) } } buf := new(bytes.Buffer) err := digraphTemplate.Execute(buf, templateRef{Nodes: nodes, Edges: edges}) if err != nil { return "", err } return buf.String(), nil } func (g *Graph[NT]) TopologicalSort() []NT { visited := make(map[string]bool) stack := make([]NT, 0) for _, node := range g.nodes { if !visited[node.GetName()] { g.topologicalSortInternal(node, &visited, &stack) } } return stack } func (g *Graph[NT]) topologicalSortInternal(node NT, visited *map[string]bool, stack *[]NT) { (*visited)[node.GetName()] = true for _, edge := range g.nodeEdges[node.GetName()] { if !(*visited)[edge.To.GetName()] { g.topologicalSortInternal(edge.To, visited, stack) } } *stack = append([]NT{node}, *stack...) }