@@ -3,6 +3,9 @@ package tree
33import (
44 "errors"
55 "fmt"
6+ "sort"
7+
8+ "github.com/xlab/treeprint"
69)
710
811// ErrCyclicDependencyEncountered is triggered a tree has a cyclic dependency
@@ -46,51 +49,86 @@ func (t *MultiRootTree) GetNodeByName(dagName string) (*TreeNode, bool) {
4649 return value , ok
4750}
4851
49- // IsCyclic - detects if there are any cycles in the tree
50- func (t * MultiRootTree ) IsCyclic () error {
51- visitedMap := make (map [string ]bool )
52- for _ , node := range t .dataMap {
53- if _ , visited := visitedMap [node .GetName ()]; ! visited {
54- pathMap := make (map [string ]bool )
55- err := t .hasCycle (node , visitedMap , pathMap )
56- if err != nil {
57- return err
58- }
59- }
52+ // get sorted nodeNames from dataMap
53+ func (t * MultiRootTree ) getSortedNodeNames () []string {
54+ nodeNames := []string {}
55+ for nodeName := range t .dataMap {
56+ nodeNames = append (nodeNames , nodeName )
6057 }
61- return nil
58+ sort .Strings (nodeNames )
59+ return nodeNames
6260}
6361
64- // runs a DFS on a given tree using visitor pattern
65- func (t * MultiRootTree ) hasCycle (root * TreeNode , visited , pathMap map [string ]bool ) error {
66- _ , isNodeVisited := visited [root .GetName ()]
67- if ! isNodeVisited || ! visited [root .GetName ()] {
68- pathMap [root .GetName ()] = true
69- visited [root .GetName ()] = true
70- var cyclicErr error
71- for _ , child := range root .Dependents {
72- n , _ := t .GetNodeByName (child .GetName ())
73- _ , isChildVisited := visited [child .GetName ()]
74- if ! isChildVisited || ! visited [child .GetName ()] {
75- cyclicErr = t .hasCycle (n , visited , pathMap )
62+ // ValidateCyclic - detects if there are any cycles in the tree
63+ func (t * MultiRootTree ) ValidateCyclic () error {
64+ // runs a DFS on a given tree using visitor pattern
65+ var checkCyclic func (* TreeNode , map [string ]bool , map [string ]bool , * []string ) error
66+ checkCyclic = func (node * TreeNode , visitedNodeNames , visitedPaths map [string ]bool , orderedVisitedPaths * []string ) error {
67+ _ , isNodeVisited := visitedNodeNames [node .GetName ()]
68+ if ! isNodeVisited || ! visitedNodeNames [node .GetName ()] {
69+ visitedPaths [node .GetName ()] = true
70+ visitedNodeNames [node .GetName ()] = true
71+ * orderedVisitedPaths = append (* orderedVisitedPaths , node .GetName ())
72+ var cyclicErr error
73+ for _ , child := range node .Dependents {
74+ n , _ := t .GetNodeByName (child .GetName ())
75+ _ , isChildVisited := visitedNodeNames [child .GetName ()]
76+ if ! isChildVisited || ! visitedNodeNames [child .GetName ()] {
77+ cyclicErr = checkCyclic (n , visitedNodeNames , visitedPaths , orderedVisitedPaths )
78+ }
79+ if cyclicErr != nil {
80+ return cyclicErr
81+ }
82+
83+ if isVisited , ok := visitedPaths [child .GetName ()]; ok && isVisited {
84+ * orderedVisitedPaths = append (* orderedVisitedPaths , child .GetName ())
85+ cyclicErr = fmt .Errorf ("%w: %s" , ErrCyclicDependencyEncountered , prettifyPaths (* orderedVisitedPaths ))
86+ }
87+ if cyclicErr != nil {
88+ return cyclicErr
89+ }
7690 }
77- if cyclicErr != nil {
78- return cyclicErr
91+ visitedPaths [node .GetName ()] = false
92+ i := 0
93+ for i < len (* orderedVisitedPaths ) && (* orderedVisitedPaths )[i ] != node .GetName () {
94+ i ++
7995 }
96+ * orderedVisitedPaths = append ((* orderedVisitedPaths )[:i ], (* orderedVisitedPaths )[i + 1 :]... )
97+ }
98+ return nil
99+ }
80100
81- _ , childAlreadyInPath := pathMap [child .GetName ()] // 1 -> 2 -> 1
82- if childAlreadyInPath && pathMap [child .GetName ()] {
83- cyclicErr = fmt .Errorf ("%w: %s" , ErrCyclicDependencyEncountered , root .GetName ())
84- }
85- if cyclicErr != nil {
86- return cyclicErr
101+ visitedNodeNames := make (map [string ]bool )
102+ nodeNames := t .getSortedNodeNames ()
103+ for _ , nodeName := range nodeNames {
104+ node := t .dataMap [nodeName ]
105+ if _ , visited := visitedNodeNames [node .GetName ()]; ! visited {
106+ visitedPaths := map [string ]bool {}
107+ orderedVisitedPaths := []string {}
108+ if err := checkCyclic (node , visitedNodeNames , visitedPaths , & orderedVisitedPaths ); err != nil {
109+ return err
87110 }
88111 }
89- pathMap [root .GetName ()] = false
90112 }
113+
91114 return nil
92115}
93116
117+ func prettifyPaths (paths []string ) string {
118+ if len (paths ) == 0 {
119+ return ""
120+ }
121+ i := len (paths ) - 1
122+ root := treeprint .NewWithRoot (paths [i ])
123+ tree := root
124+
125+ for i -- ; i >= 0 ; i -- {
126+ tree = tree .AddBranch (paths [i ])
127+ }
128+
129+ return "\n " + root .String ()
130+ }
131+
94132// NewMultiRootTree returns an instance of multi root dag tree
95133func NewMultiRootTree () * MultiRootTree {
96134 return & MultiRootTree {
0 commit comments