-
Notifications
You must be signed in to change notification settings - Fork 93
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
added iterators for dfs and bfs (#163)
* added iterators for dfs and bfs * fixed exisintg identifier issue * added iterators for dfs and bfs * fixed exisintg identifier issue * add abstract iterator state and fix first iter arg * iterator refactor * created iterators src folder, added kruskal * created iterators src folder, added kruskal * multi source version for bfs and dfs * fix * remove kruskal * Update Graphs.jl * remove kruskal from pages * format * format 2 * format branch * improve perf of bfs * optimize bfs * use copy of source nodes * Update bfs.jl * improve dfs * Update bfs.jl * use size unknown * Update bfs.jl * Clean up * Fix typo * Fix tests and length * Remove ref * Fix eltype * address review comments * some more comments * Update bfs.jl * Update bfs.jl * Update dfs.jl * formatting * Apply suggestions from code review --------- Co-authored-by: Tortar <[email protected]> Co-authored-by: Tortar <[email protected]> Co-authored-by: Guillaume Dalle <[email protected]>
- Loading branch information
1 parent
599ef81
commit d183c26
Showing
7 changed files
with
330 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
# Iterators | ||
|
||
_Graphs.jl_ includes various routines for iterating through graphs. | ||
|
||
## Index | ||
|
||
```@index | ||
Pages = ["iterators.md"] | ||
``` | ||
|
||
## Full docs | ||
|
||
```@autodocs | ||
Modules = [Graphs] | ||
Pages = [ | ||
"iterators/iterators.jl", | ||
"iterators/bfs.jl", | ||
"iterators/dfs.jl", | ||
] | ||
Private = false | ||
``` | ||
|
||
The following names are internals, not part of the public API: | ||
|
||
```@autodocs | ||
Modules = [Graphs] | ||
Pages = [ | ||
"iterators/iterators.jl", | ||
"iterators/bfs.jl", | ||
"iterators/dfs.jl", | ||
] | ||
Public = false | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
""" | ||
BFSIterator | ||
`BFSIterator` is used to iterate through graph vertices using a breadth-first search. | ||
A source node(s) is optionally supplied as an `Int` or an array-like type that can be | ||
indexed if supplying multiple sources. | ||
# Examples | ||
```julia-repl | ||
julia> g = smallgraph(:house) | ||
{5, 6} undirected simple Int64 graph | ||
julia> for node in BFSIterator(g,3) | ||
display(node) | ||
end | ||
3 | ||
1 | ||
4 | ||
5 | ||
2 | ||
``` | ||
""" | ||
struct BFSIterator{S,G<:AbstractGraph} | ||
graph::G | ||
source::S | ||
function BFSIterator(graph::G, source::S) where {S,G} | ||
if any(node -> !has_vertex(graph, node), source) | ||
error("Some source nodes for the iterator are not in the graph") | ||
end | ||
return new{S,G}(graph, source) | ||
end | ||
end | ||
|
||
""" | ||
BFSVertexIteratorState | ||
`BFSVertexIteratorState` is a struct to hold the current state of iteration | ||
in BFS which is needed for the `Base.iterate()` function. A queue is used to | ||
keep track of the vertices which will be visited during BFS. Since the queue | ||
can contains repetitions of already visited nodes, we also keep track of that | ||
in a `BitVector` so that to skip those nodes. | ||
""" | ||
mutable struct BFSVertexIteratorState | ||
visited::BitVector | ||
queue::Vector{Int} | ||
neighbor_idx::Int | ||
n_visited::Int | ||
end | ||
|
||
Base.IteratorSize(::BFSIterator) = Base.SizeUnknown() | ||
Base.eltype(::Type{BFSIterator{S,G}}) where {S,G} = eltype(G) | ||
|
||
""" | ||
Base.iterate(t::BFSIterator) | ||
First iteration to visit vertices in a graph using breadth-first search. | ||
""" | ||
function Base.iterate(t::BFSIterator{<:Integer}) | ||
visited = falses(nv(t.graph)) | ||
visited[t.source] = true | ||
return (t.source, BFSVertexIteratorState(visited, [t.source], 1, 1)) | ||
end | ||
|
||
function Base.iterate(t::BFSIterator{<:AbstractArray}) | ||
visited = falses(nv(t.graph)) | ||
visited[first(t.source)] = true | ||
state = BFSVertexIteratorState(visited, copy(t.source), 1, 1) | ||
return (first(t.source), state) | ||
end | ||
|
||
""" | ||
Base.iterate(t::BFSIterator, state::VertexIteratorState) | ||
Iterator to visit vertices in a graph using breadth-first search. | ||
""" | ||
function Base.iterate(t::BFSIterator, state::BFSVertexIteratorState) | ||
graph, visited, queue = t.graph, state.visited, state.queue | ||
while !isempty(queue) | ||
if state.n_visited == nv(graph) | ||
return nothing | ||
end | ||
# we visit the first node in the queue | ||
node_start = first(queue) | ||
if !visited[node_start] | ||
visited[node_start] = true | ||
state.n_visited += 1 | ||
return (node_start, state) | ||
end | ||
# which means we arrive here when the first node was visited. | ||
neigh = outneighbors(graph, node_start) | ||
if state.neighbor_idx <= length(neigh) | ||
node = neigh[state.neighbor_idx] | ||
# we update the idx of the neighbor we will visit, | ||
# if it is already visited, we repeat | ||
state.neighbor_idx += 1 | ||
if !visited[node] | ||
push!(queue, node) | ||
state.visited[node] = true | ||
state.n_visited += 1 | ||
return (node, state) | ||
end | ||
else | ||
# when the first node and its neighbors are visited | ||
# we remove the first node of the queue | ||
popfirst!(queue) | ||
state.neighbor_idx = 1 | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
""" | ||
DFSIterator | ||
`DFSIterator` is used to iterate through graph vertices using a depth-first search. | ||
A source node(s) is optionally supplied as an `Int` or an array-like type that can be | ||
indexed if supplying multiple sources. | ||
# Examples | ||
```julia-repl | ||
julia> g = smallgraph(:house) | ||
{5, 6} undirected simple Int64 graph | ||
julia> for node in DFSIterator(g, 3) | ||
display(node) | ||
end | ||
1 | ||
2 | ||
4 | ||
3 | ||
5 | ||
``` | ||
""" | ||
struct DFSIterator{S,G<:AbstractGraph} | ||
graph::G | ||
source::S | ||
function DFSIterator(graph::G, source::S) where {S,G} | ||
if any(node -> !has_vertex(graph, node), source) | ||
error("Some source nodes for the iterator are not in the graph") | ||
end | ||
return new{S,G}(graph, source) | ||
end | ||
end | ||
|
||
""" | ||
DFSVertexIteratorState | ||
`DFSVertexIteratorState` is a struct to hold the current state of iteration | ||
in DFS which is needed for the `Base.iterate()` function. A queue is used to | ||
keep track of the vertices which will be visited during DFS. Since the queue | ||
can contains repetitions of already visited nodes, we also keep track of that | ||
in a `BitVector` so that to skip those nodes. | ||
""" | ||
mutable struct DFSVertexIteratorState | ||
visited::BitVector | ||
queue::Vector{Int} | ||
end | ||
|
||
Base.IteratorSize(::DFSIterator) = Base.SizeUnknown() | ||
Base.eltype(::Type{DFSIterator{S,G}}) where {S,G} = eltype(G) | ||
|
||
""" | ||
Base.iterate(t::DFSIterator) | ||
First iteration to visit vertices in a graph using depth-first search. | ||
""" | ||
function Base.iterate(t::DFSIterator{<:Integer}) | ||
visited = falses(nv(t.graph)) | ||
visited[t.source] = true | ||
return (t.source, DFSVertexIteratorState(visited, [t.source])) | ||
end | ||
|
||
function Base.iterate(t::DFSIterator{<:AbstractArray}) | ||
visited = falses(nv(t.graph)) | ||
source_rev = reverse(t.source) | ||
visited[last(source_rev)] = true | ||
state = DFSVertexIteratorState(visited, source_rev) | ||
return (last(source_rev), state) | ||
end | ||
|
||
""" | ||
Base.iterate(t::DFSIterator, state::VertexIteratorState) | ||
Iterator to visit vertices in a graph using depth-first search. | ||
""" | ||
function Base.iterate(t::DFSIterator, state::DFSVertexIteratorState) | ||
graph, visited, queue = t.graph, state.visited, state.queue | ||
while !isempty(queue) | ||
# we take the last node in the queue | ||
node_start = last(queue) | ||
# we first return it | ||
if !visited[node_start] | ||
visited[node_start] = true | ||
return (node_start, state) | ||
end | ||
# and then we visit a neighbor and push it at the | ||
# end of the queue | ||
for node in outneighbors(graph, node_start) | ||
if !visited[node] | ||
push!(queue, node) | ||
visited[node] = true | ||
return (node, state) | ||
end | ||
end | ||
# we pop the last node in the queue | ||
# when it and all its neighbors were visited | ||
pop!(queue) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
@testset "BFSIterator" begin | ||
g = Graph() | ||
error_exc = ErrorException("Some source nodes for the iterator are not in the graph") | ||
@test_throws error_exc BFSIterator(g, 3) | ||
g = path_graph(7) | ||
add_edge!(g, 6, 3) | ||
add_edge!(g, 3, 1) | ||
add_edge!(g, 4, 7) | ||
g2 = deepcopy(g) | ||
add_vertex!(g2) | ||
add_vertex!(g2) | ||
add_edge!(g2, 8, 9) | ||
|
||
for g in testgraphs(g) | ||
nodes_visited = fill(0, nv(g)) | ||
for (i, node) in enumerate(BFSIterator(g, 6)) | ||
nodes_visited[i] = node | ||
end | ||
@test nodes_visited[1] == 6 | ||
@test any(nodes_visited[2] .== [3, 5, 7]) | ||
if nodes_visited[2] == 3 | ||
@test nodes_visited[3:4] == [5, 7] || nodes_visited[3:4] == [7, 5] | ||
elseif nodes_visited[2] == 5 | ||
@test nodes_visited[3:4] == [3, 7] || nodes_visited[3:4] == [7, 3] | ||
else | ||
@test nodes_visited[3:4] == [3, 5] || nodes_visited[3:4] == [5, 3] | ||
end | ||
@test any(nodes_visited[5] .== [1, 2, 4]) | ||
if nodes_visited[5] == 1 | ||
@test nodes_visited[6:7] == [2, 4] || nodes_visited[6:7] == [4, 2] | ||
elseif nodes_visited[5] == 2 | ||
@test nodes_visited[6:7] == [1, 4] || nodes_visited[6:7] == [4, 1] | ||
else | ||
@test nodes_visited[6:7] == [1, 2] || nodes_visited[6:7] == [2, 1] | ||
end | ||
end | ||
nodes_visited = collect(BFSIterator(g2, [1, 6])) | ||
@test nodes_visited == [1, 2, 3, 6, 5, 7, 4] | ||
nodes_visited = collect(BFSIterator(g2, [8, 1, 6])) | ||
@test nodes_visited == [8, 9, 1, 2, 3, 6, 5, 7, 4] | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
@testset "DFSIterator" begin | ||
g = Graph() | ||
error_exc = ErrorException("Some source nodes for the iterator are not in the graph") | ||
@test_throws error_exc DFSIterator(g, 3) | ||
g = path_graph(7) | ||
add_edge!(g, 6, 3) | ||
add_edge!(g, 3, 1) | ||
add_edge!(g, 4, 7) | ||
g2 = deepcopy(g) | ||
add_vertex!(g2) | ||
add_vertex!(g2) | ||
add_edge!(g2, 8, 9) | ||
|
||
for g in testgraphs(g) | ||
nodes_visited = fill(0, nv(g)) | ||
for (i, node) in enumerate(DFSIterator(g, 6)) | ||
nodes_visited[i] = node | ||
end | ||
@test nodes_visited[1:2] == [6, 3] | ||
@test any(nodes_visited[3] .== [1, 4]) | ||
if nodes_visited[3] == 1 | ||
@test nodes_visited[4] == 2 | ||
@test nodes_visited[5] == 4 | ||
@test any(nodes_visited[6] .== [5, 7]) | ||
if nodes_visited[6] == 5 | ||
@test nodes_visited[7] == 7 | ||
end | ||
else | ||
@test any(nodes_visited[4] .== [5, 7]) | ||
if nodes_visited[4] == 5 | ||
@test nodes_visited[5] == 7 | ||
end | ||
@test nodes_visited[6] == 1 | ||
@test nodes_visited[7] == 2 | ||
end | ||
end | ||
nodes_visited = collect(DFSIterator(g2, [1, 6])) | ||
@test nodes_visited == [1, 2, 3, 4, 5, 6, 7] | ||
nodes_visited = collect(DFSIterator(g2, [8, 1, 6])) | ||
@test nodes_visited == [8, 9, 1, 2, 3, 4, 5, 6, 7] | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters