Skip to content

Commit

Permalink
feat(dominikbraun#124): full traverse
Browse files Browse the repository at this point in the history
  • Loading branch information
williamfzc committed May 16, 2023
1 parent 5ad3689 commit a9f566a
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 7 deletions.
29 changes: 22 additions & 7 deletions dag.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,7 @@ func TopologicalSort[K comparable, T any](g Graph[K, T]) ([]K, error) {
return nil, fmt.Errorf("failed to get predecessor map: %w", err)
}

queue := make([]K, 0)

for vertex, predecessors := range predecessorMap {
if len(predecessors) == 0 {
queue = append(queue, vertex)
}
}
queue := topologicalEntriesFromPredecessorMap(predecessorMap)

order := make([]K, 0, len(predecessorMap))
visited := make(map[K]struct{})
Expand Down Expand Up @@ -65,6 +59,27 @@ func TopologicalSort[K comparable, T any](g Graph[K, T]) ([]K, error) {
return order, nil
}

func TopologicalEntries[K comparable, T any](g Graph[K, T]) ([]K, error) {
predecessorMap, err := g.PredecessorMap()
if err != nil {
return nil, fmt.Errorf("failed to get predecessor map: %w", err)
}

return topologicalEntriesFromPredecessorMap(predecessorMap), nil
}

func topologicalEntriesFromPredecessorMap[K comparable](predecessorMap map[K]map[K]Edge[K]) []K {
queue := make([]K, 0)

for vertex, predecessors := range predecessorMap {
if len(predecessors) == 0 {
queue = append(queue, vertex)
}
}

return queue
}

// TransitiveReduction returns a new graph with the same vertices and the same
// reachability as the given graph, but with as few edges as possible. The graph
// must be a directed acyclic graph.
Expand Down
8 changes: 8 additions & 0 deletions dag_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ func TestDirectedTopologicalSort(t *testing.T) {
t.Errorf("%s: order expectancy doesn't match: expected %v at %d, got %v", name, expectedVertex, i, order[i])
}
}

entries, err := TopologicalEntries(graph)
if err != nil {
t.Errorf("failed to create topological entries")
}
if len(entries) == 0 {
t.Errorf("topological entries is empty")
}
}
}

Expand Down
15 changes: 15 additions & 0 deletions traversal.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,18 @@ func BFS[K comparable, T any](g Graph[K, T], start K, visit func(K) bool) error

return nil
}

func FullTraverse[K comparable, T any](g Graph[K, T], visit func(K) bool) error {
entries, err := TopologicalEntries(g)
if err != nil {
return err
}

for _, eachEntry := range entries {
err = BFS(g, eachEntry, visit)
if err != nil {
return err
}
}
return nil
}
69 changes: 69 additions & 0 deletions traversal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -399,3 +399,72 @@ func TestUndirectedBFS(t *testing.T) {
}
}
}

func TestFullTraverse(t *testing.T) {
tests := map[string]struct {
vertices []int
edges []Edge[int]
startHash int
expectedVisits []int
stopAtVertex int
}{
"traverse entire graph with 3 vertices": {
vertices: []int{1, 2, 3},
edges: []Edge[int]{
{Source: 1, Target: 2},
{Source: 1, Target: 3},
},
startHash: 1,
expectedVisits: []int{1, 2, 3},
stopAtVertex: -1,
},
"traverse graph with 6 vertices until vertex 4": {
vertices: []int{1, 2, 3, 4, 5, 6},
edges: []Edge[int]{
{Source: 1, Target: 2},
{Source: 1, Target: 3},
{Source: 2, Target: 4},
{Source: 2, Target: 5},
{Source: 3, Target: 6},
},
startHash: 1,
expectedVisits: []int{1, 2, 3, 4},
stopAtVertex: 4,
},
"traverse a disconnected graph": {
vertices: []int{1, 2, 3, 4},
edges: []Edge[int]{
{Source: 1, Target: 2},
{Source: 3, Target: 4},
},
startHash: 1,
expectedVisits: []int{1, 2},
stopAtVertex: -1,
},
}
for name, test := range tests {
graph := New(IntHash, Directed())

for _, vertex := range test.vertices {
_ = graph.AddVertex(vertex)
}

for _, edge := range test.edges {
if err := graph.AddEdge(edge.Source, edge.Target); err != nil {
t.Fatalf("%s: failed to add edge: %s", name, err.Error())
}
}

visited := map[int]struct{}{}
err := FullTraverse(graph, func(i int) bool {
visited[i] = struct{}{}
return false
})
if err != nil {
t.Fatalf("traverse failed")
}
if len(visited) != len(test.vertices) {
t.Fatalf("traversal did not walk all the nodes")
}
}
}

0 comments on commit a9f566a

Please sign in to comment.