Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added iterators for dfs and bfs #163

Merged
merged 40 commits into from
May 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
d5922fc
added iterators for dfs and bfs
kylebeggs Aug 16, 2022
fa40087
fixed exisintg identifier issue
kylebeggs Aug 17, 2022
1d513a5
added iterators for dfs and bfs
kylebeggs Aug 16, 2022
a279eca
fixed exisintg identifier issue
kylebeggs Aug 17, 2022
d6b120c
add abstract iterator state and fix first iter arg
kylebeggs Aug 22, 2022
9e941c8
iterator refactor
kylebeggs Sep 19, 2022
0ff24ef
created iterators src folder, added kruskal
kylebeggs Sep 20, 2022
1f59073
created iterators src folder, added kruskal
kylebeggs Mar 15, 2023
3a5794a
Merge branch 'JuliaGraphs:master' into master
kylebeggs Jul 8, 2023
1bcc1c1
multi source version for bfs and dfs
Tortar Jan 29, 2024
9bfafbe
fix
Tortar Jan 29, 2024
48a2512
Merge branch 'master' into master
Tortar Jan 29, 2024
72c6840
remove kruskal
Tortar Jan 29, 2024
dd63b60
Update Graphs.jl
Tortar Jan 29, 2024
c6fdf27
remove kruskal from pages
Tortar Jan 29, 2024
27aa945
format
Tortar Jan 29, 2024
b5df963
format 2
Tortar Jan 29, 2024
c14111f
format branch
Tortar Jan 29, 2024
790572e
improve perf of bfs
Tortar Jan 29, 2024
4f6bd50
optimize bfs
Tortar Jan 29, 2024
8f4223f
use copy of source nodes
Tortar Jan 29, 2024
a56ced8
Update bfs.jl
Tortar Jan 29, 2024
19a4aa6
improve dfs
Tortar Jan 29, 2024
e52f03a
Update bfs.jl
Tortar Jan 29, 2024
a96d943
use size unknown
Tortar Mar 2, 2024
f5f60cc
Update bfs.jl
Tortar Mar 2, 2024
09f1edd
Merge branch 'JuliaGraphs:master' into master
Tortar Mar 2, 2024
80c1d0f
Clean up
gdalle Mar 5, 2024
a48b4c0
Fix typo
gdalle Mar 5, 2024
971c790
Fix tests and length
gdalle Mar 5, 2024
af494f6
Remove ref
gdalle Mar 5, 2024
74c003b
Fix eltype
gdalle Mar 5, 2024
22df731
Merge branch 'JuliaGraphs:master' into master
Tortar Apr 28, 2024
9022e24
address review comments
Tortar Apr 28, 2024
c89176e
some more comments
Tortar Apr 28, 2024
9fd1a3a
Update bfs.jl
Tortar Apr 28, 2024
a3b7a9e
Update bfs.jl
Tortar Apr 28, 2024
a26a165
Update dfs.jl
Tortar Apr 28, 2024
de59874
formatting
Tortar Apr 28, 2024
10e7c6e
Apply suggestions from code review
gdalle May 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions docs/src/algorithms/iterators.md
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
```
6 changes: 6 additions & 0 deletions src/Graphs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,10 @@ export
dfs_tree,
dfs_parents,

# iterators
DFSIterator,
BFSIterator,

# random
randomwalk,
self_avoiding_walk,
Expand Down Expand Up @@ -499,6 +503,8 @@ include("traversals/dfs.jl")
include("traversals/maxadjvisit.jl")
include("traversals/randomwalks.jl")
include("traversals/diffusion.jl")
include("iterators/bfs.jl")
include("iterators/dfs.jl")
include("traversals/eulerian.jl")
include("traversals/all_simple_paths.jl")
include("connectivity.jl")
Expand Down
109 changes: 109 additions & 0 deletions src/iterators/bfs.jl
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
gdalle marked this conversation as resolved.
Show resolved Hide resolved
visited::BitVector
queue::Vector{Int}
gdalle marked this conversation as resolved.
Show resolved Hide resolved
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)
gdalle marked this conversation as resolved.
Show resolved Hide resolved
graph, visited, queue = t.graph, state.visited, state.queue
while !isempty(queue)
if state.n_visited == nv(graph)
gdalle marked this conversation as resolved.
Show resolved Hide resolved
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
98 changes: 98 additions & 0 deletions src/iterators/dfs.jl
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
gdalle marked this conversation as resolved.
Show resolved Hide resolved
visited::BitVector
queue::Vector{Int}
gdalle marked this conversation as resolved.
Show resolved Hide resolved
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)
gdalle marked this conversation as resolved.
Show resolved Hide resolved
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
41 changes: 41 additions & 0 deletions test/iterators/bfs.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
@testset "BFSIterator" begin
gdalle marked this conversation as resolved.
Show resolved Hide resolved
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
41 changes: 41 additions & 0 deletions test/iterators/dfs.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
@testset "DFSIterator" begin
gdalle marked this conversation as resolved.
Show resolved Hide resolved
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
2 changes: 2 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ tests = [
"traversals/maxadjvisit",
"traversals/randomwalks",
"traversals/diffusion",
"iterators/bfs",
"iterators/dfs",
"traversals/eulerian",
"traversals/all_simple_paths",
"community/cliques",
Expand Down
Loading