Skip to content

Commit c5ea323

Browse files
Tortargdalle
andauthored
Fix bfs iterator for multiple source nodes (#382)
* Fix bfs iterator for multiple source nodes * Update bfs.jl * fix another possible source of problems * actually we can go faster * Update bfs.jl * sort for faster bfs * better not * simpler * Update bfs.jl * Update bfs.jl * Update doc * Update src/iterators/bfs.jl Co-authored-by: Guillaume Dalle <[email protected]> * Update bfs.jl * sort is applicable * Update bfs.jl * adjust tests --------- Co-authored-by: Guillaume Dalle <[email protected]>
1 parent 56e5604 commit c5ea323

File tree

2 files changed

+41
-41
lines changed

2 files changed

+41
-41
lines changed

src/iterators/bfs.jl

Lines changed: 32 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,16 @@ end
3535
BFSVertexIteratorState
3636
3737
`BFSVertexIteratorState` is a struct to hold the current state of iteration
38-
in BFS which is needed for the `Base.iterate()` function. A queue is used to
39-
keep track of the vertices which will be visited during BFS. Since the queue
40-
can contains repetitions of already visited nodes, we also keep track of that
41-
in a `BitVector` so that to skip those nodes.
38+
in BFS which is needed for the `Base.iterate()` function. We use two vectors,
39+
one for the current level nodes and one from the next level nodes to visit
40+
the graph. Since new levels can contains repetitions of already visited nodes,
41+
we also keep track of that in a `BitVector` so as to skip those nodes.
4242
"""
4343
mutable struct BFSVertexIteratorState
4444
visited::BitVector
45-
queue::Vector{Int}
46-
neighbor_idx::Int
45+
curr_level::Vector{Int}
46+
next_level::Vector{Int}
47+
node_idx::Int
4748
n_visited::Int
4849
end
4950

@@ -58,14 +59,17 @@ First iteration to visit vertices in a graph using breadth-first search.
5859
function Base.iterate(t::BFSIterator{<:Integer})
5960
visited = falses(nv(t.graph))
6061
visited[t.source] = true
61-
return (t.source, BFSVertexIteratorState(visited, [t.source], 1, 1))
62+
state = BFSVertexIteratorState(visited, [t.source], Int[], 0, 0)
63+
return Base.iterate(t, state)
6264
end
6365

6466
function Base.iterate(t::BFSIterator{<:AbstractArray})
6567
visited = falses(nv(t.graph))
66-
visited[first(t.source)] = true
67-
state = BFSVertexIteratorState(visited, copy(t.source), 1, 1)
68-
return (first(t.source), state)
68+
curr_level = unique(s for s in t.source)
69+
sort!(curr_level)
70+
visited[curr_level] .= true
71+
state = BFSVertexIteratorState(visited, curr_level, Int[], 0, 0)
72+
return Base.iterate(t, state)
6973
end
7074

7175
"""
@@ -74,36 +78,25 @@ end
7478
Iterator to visit vertices in a graph using breadth-first search.
7579
"""
7680
function Base.iterate(t::BFSIterator, state::BFSVertexIteratorState)
77-
graph, visited, queue = t.graph, state.visited, state.queue
78-
while !isempty(queue)
79-
if state.n_visited == nv(graph)
80-
return nothing
81-
end
82-
# we visit the first node in the queue
83-
node_start = first(queue)
84-
if !visited[node_start]
85-
visited[node_start] = true
86-
state.n_visited += 1
87-
return (node_start, state)
88-
end
89-
# which means we arrive here when the first node was visited.
90-
neigh = outneighbors(graph, node_start)
91-
if state.neighbor_idx <= length(neigh)
92-
node = neigh[state.neighbor_idx]
93-
# we update the idx of the neighbor we will visit,
94-
# if it is already visited, we repeat
95-
state.neighbor_idx += 1
96-
if !visited[node]
97-
push!(queue, node)
98-
state.visited[node] = true
99-
state.n_visited += 1
100-
return (node, state)
81+
# we fill nodes in this level
82+
if state.node_idx == length(state.curr_level)
83+
state.n_visited += length(state.curr_level)
84+
state.n_visited == nv(t.graph) && return nothing
85+
@inbounds for node in state.curr_level
86+
for adj_node in outneighbors(t.graph, node)
87+
if !state.visited[adj_node]
88+
push!(state.next_level, adj_node)
89+
state.visited[adj_node] = true
90+
end
10191
end
102-
else
103-
# when the first node and its neighbors are visited
104-
# we remove the first node of the queue
105-
popfirst!(queue)
106-
state.neighbor_idx = 1
10792
end
93+
length(state.next_level) == 0 && return nothing
94+
state.curr_level, state.next_level = state.next_level, empty!(state.curr_level)
95+
sort!(state.curr_level)
96+
state.node_idx = 0
10897
end
98+
# we visit all nodes in this level
99+
state.node_idx += 1
100+
@inbounds node = state.curr_level[state.node_idx]
101+
return (node, state)
109102
end

test/iterators/bfs.jl

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,14 @@
3535
end
3636
end
3737
nodes_visited = collect(BFSIterator(g2, [1, 6]))
38-
@test nodes_visited == [1, 2, 3, 6, 5, 7, 4]
38+
levels = ([1, 6], [2, 3, 5, 7], [4])
39+
@test sort(nodes_visited[1:2]) == sort(levels[1])
40+
@test sort(nodes_visited[3:6]) == sort(levels[2])
41+
@test sort(nodes_visited[7:end]) == sort(levels[3])
42+
3943
nodes_visited = collect(BFSIterator(g2, [8, 1, 6]))
40-
@test nodes_visited == [8, 9, 1, 2, 3, 6, 5, 7, 4]
44+
levels = ([8, 1, 6], [2, 3, 5, 7, 9], [4])
45+
@test sort(nodes_visited[1:3]) == sort(levels[1])
46+
@test sort(nodes_visited[4:8]) == sort(levels[2])
47+
@test sort(nodes_visited[9:end]) == sort(levels[3])
4148
end

0 commit comments

Comments
 (0)