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 29 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 @@ -185,6 +185,10 @@ export
dfs_tree,
dfs_parents,

# iterators
DFSIterator,
BFSIterator,

# random
randomwalk,
self_avoiding_walk,
Expand Down Expand Up @@ -492,6 +496,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("connectivity.jl")
include("distance.jl")
Expand Down
95 changes: 95 additions & 0 deletions src/iterators/bfs.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"""
BFSIterator

`BFSIterator` is a subtype of [`VertexIterator`](@ref) 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. If no source is provided, it defaults to the first vertex.
gdalle marked this conversation as resolved.
Show resolved Hide resolved

# 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
end

"""
BFSVertexIteratorState

`BFSVertexIteratorState` is a struct to hold the current state of iteration
in BFS which is needed for the `Base.iterate()` function.
"""
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

BFSIterator(g::AbstractGraph) = BFSIterator(g, first(vertices(g)))
gdalle marked this conversation as resolved.
Show resolved Hide resolved

Base.length(t::BFSIterator) = nv(t.graph)
gdalle marked this conversation as resolved.
Show resolved Hide resolved
Base.eltype(t::BFSIterator) = eltype(t.graph)

"""
Base.iterate(t::BFSIterator)

First iteration to visit each vertex 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 each vertex 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
node_start = first(queue)
if !visited[node_start]
visited[node_start] = true
state.n_visited += 1
return (node_start, state)
end
idx = state.neighbor_idx
neigh = outneighbors(graph, node_start)
if idx <= length(neigh)
node = neigh[idx]
gdalle marked this conversation as resolved.
Show resolved Hide resolved
state.neighbor_idx += 1
if !visited[node]
push!(queue, node)
state.visited[node] = true
state.n_visited += 1
return (node, state)
end
else
popfirst!(queue)
state.neighbor_idx = 1
end
end
end
83 changes: 83 additions & 0 deletions src/iterators/dfs.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""
DFSIterator

`DFSIterator` is a subtype of [`VertexIterator`](@ref) 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. If no source is provided, it defaults to the first vertex.

# 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
end

"""
DFSVertexIteratorState

`DFSVertexIteratorState` is a struct to hold the current state of iteration
in DFS which is needed for the `Base.iterate()` function.
"""
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

DFSIterator(g::AbstractGraph) = DFSIterator(g, first(vertices(g)))
gdalle marked this conversation as resolved.
Show resolved Hide resolved

Base.length(t::DFSIterator) = nv(t.graph)
gdalle marked this conversation as resolved.
Show resolved Hide resolved
Base.eltype(t::DFSIterator) = eltype(t.graph)

"""
Base.iterate(t::DFSIterator)

First iteration to visit each vertex 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 each vertex 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)
node_start = last(queue)
if !visited[node_start]
visited[node_start] = true
return (node_start, state)
end
for node in outneighbors(graph, node_start)
if !visited[node]
push!(queue, node)
visited[node] = true
return (node, state)
end
end
pop!(queue)
end
end
44 changes: 44 additions & 0 deletions test/iterators/bfs.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
@testset "BFSIterator" begin
gdalle marked this conversation as resolved.
Show resolved Hide resolved
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 = Int[]
for (i, node) in enumerate(BFSIterator(g2, [1, 6]))
push!(nodes_visited, node)
end
@test nodes_visited == [1, 2, 3, 6, 5, 7, 4]
nodes_visited = Int[]
for (i, node) in enumerate(BFSIterator(g2, [8, 1, 6]))
push!(nodes_visited, node)
end
@test nodes_visited == [8, 9, 1, 2, 3, 6, 5, 7, 4]
end
44 changes: 44 additions & 0 deletions test/iterators/dfs.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
@testset "DFSIterator" begin
gdalle marked this conversation as resolved.
Show resolved Hide resolved
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 = Int[]
for (i, node) in enumerate(DFSIterator(g2, [1, 6]))
push!(nodes_visited, node)
end
@test nodes_visited == [1, 2, 3, 4, 5, 6, 7]
nodes_visited = Int[]
for (i, node) in enumerate(DFSIterator(g2, [8, 1, 6]))
push!(nodes_visited, node)
end
@test nodes_visited == [8, 9, 1, 2, 3, 4, 5, 6, 7]
end
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ tests = [
"traversals/maxadjvisit",
"traversals/randomwalks",
"traversals/diffusion",
"iterators/iterators",
"traversals/eulerian",
"community/cliques",
"community/core-periphery",
Expand Down
Loading