// Adapted from https://github.com/philopon/go-toposort under the MIT License // Original License: // // Copyright (c) 2017 Hirotomo Moriwaki // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of // the Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package dag import ( "errors" "sort" "golang.org/x/exp/maps" "golang.org/x/exp/slices" ) type Graph struct { nodes []string outputs map[string]map[string]struct{} // node: number of parents inputs map[string]int } func NewGraph() *Graph { return &Graph{ nodes: []string{}, inputs: make(map[string]int), outputs: make(map[string]map[string]struct{}), } } func (g *Graph) AddNode(name string) error { g.nodes = append(g.nodes, name) if _, ok := g.outputs[name]; ok { return errors.New("duplicate detected") } g.outputs[name] = make(map[string]struct{}) g.inputs[name] = 0 return nil } func (g *Graph) AddNodes(names ...string) error { for _, name := range names { if err := g.AddNode(name); err != nil { return err } } return nil } func (g *Graph) AddEdge(from, to string) error { m, ok := g.outputs[from] if !ok { return errors.New("node does not exist") } m[to] = struct{}{} g.inputs[to]++ return nil } func (g *Graph) Toposort() ([]string, error) { outputs := map[string]map[string]struct{}{} for key, value := range g.outputs { outputs[key] = map[string]struct{}{} for k, v := range value { outputs[key][k] = v } } L := make([]string, 0, len(g.nodes)) S := make([]string, 0, len(g.nodes)) sort.Strings(g.nodes) for _, n := range g.nodes { if g.inputs[n] == 0 { S = append(S, n) } } for len(S) > 0 { var n string n, S = S[0], S[1:] L = append(L, n) ms := make([]string, len(outputs[n])) keys := maps.Keys(outputs[n]) slices.Sort(keys) for _, k := range keys { m := k // i := outputs[n][m] // ms[i-1] = m ms = append(ms, m) } for _, m := range ms { delete(outputs[n], m) g.inputs[m]-- if g.inputs[m] == 0 { S = append(S, m) } } } N := 0 for _, v := range g.inputs { N += v } if N > 0 { return L, errors.New("cycle detected") } return L, nil } func (g *Graph) GetParents(id string) []string { var parents []string for node, targets := range g.outputs { if _, ok := targets[id]; ok { parents = append(parents, node) } } sort.Strings(parents) return parents } func (g *Graph) GetRoot() (string, error) { var roots []string for n, parents := range g.inputs { if parents == 0 { roots = append(roots, n) } } if len(roots) != 1 { return "", errors.New("more than one root") } return roots[0], nil }