From 2224594b88284774a44ac2cce4973ada7887ced1 Mon Sep 17 00:00:00 2001 From: Tortar <68152031+Tortar@users.noreply.github.com> Date: Tue, 4 Jun 2024 03:25:16 +0200 Subject: [PATCH 01/16] Fix bfs iterator for multiple source nodes --- src/iterators/bfs.jl | 70 +++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 37 deletions(-) diff --git a/src/iterators/bfs.jl b/src/iterators/bfs.jl index 57779243..4a7a1aa5 100644 --- a/src/iterators/bfs.jl +++ b/src/iterators/bfs.jl @@ -42,8 +42,10 @@ in a `BitVector` so that to skip those nodes. """ mutable struct BFSVertexIteratorState visited::BitVector - queue::Vector{Int} - neighbor_idx::Int + added::BitVector + curr_level::Vector{Int} + next_level::Vector{Int} + node_idx::Int n_visited::Int end @@ -56,16 +58,17 @@ Base.eltype(::Type{BFSIterator{S,G}}) where {S,G} = eltype(G) 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)) + visited, added = falses(nv(t.graph)), falses(nv(t.graph)) + visited[t.source] = false + state = BFSVertexIteratorState(visited, added, [t.source], Int[], 0, 0) + return Base.iterate(t, state) 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) + visited, added = falses(nv(t.graph)), falses(nv(t.graph)) + visited[first(t.source)] = false + state = BFSVertexIteratorState(visited, added, copy(t.source), Int[], 0, 0) + return Base.iterate(t, state) end """ @@ -74,36 +77,29 @@ end 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 + state.n_visited == nv(t.graph) && return nothing + # we fill nodes in this level + if state.node_idx == length(state.curr_level) + @inbounds for node in state.curr_level + for adj_node in outneighbors(t.graph, node) + if !state.visited[adj_node] && !state.added[adj_node] + push!(state.next_level, adj_node) + state.added[adj_node] = true + end + end end - # we visit the first node in the queue - node_start = first(queue) - if !visited[node_start] - visited[node_start] = true + state.curr_level, state.next_level = state.next_level, Int[] + state.node_idx = 0 + end + # we visit all nodes in this level + @inbounds while state.node_idx < length(state.curr_level) + state.node_idx += 1 + node = state.curr_level[state.node_idx] + if !state.visited[node] + state.visited[node] = 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 + return (node, state) end end + return nothing end From 813cbe22d8050b9c31e1b2265849d3ce6f152fa0 Mon Sep 17 00:00:00 2001 From: Tortar <68152031+Tortar@users.noreply.github.com> Date: Tue, 4 Jun 2024 03:29:15 +0200 Subject: [PATCH 02/16] Update bfs.jl --- test/iterators/bfs.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/iterators/bfs.jl b/test/iterators/bfs.jl index c70bdf36..14740e90 100644 --- a/test/iterators/bfs.jl +++ b/test/iterators/bfs.jl @@ -35,7 +35,7 @@ end end nodes_visited = collect(BFSIterator(g2, [1, 6])) - @test nodes_visited == [1, 2, 3, 6, 5, 7, 4] + @test nodes_visited == [1, 6, 2, 3, 5, 7, 4] nodes_visited = collect(BFSIterator(g2, [8, 1, 6])) - @test nodes_visited == [8, 9, 1, 2, 3, 6, 5, 7, 4] + @test nodes_visited == [8, 1, 6, 9, 2, 3, 5, 7, 4] end From 2946a4003148d68de10868b9a424259363b4e67e Mon Sep 17 00:00:00 2001 From: Tortar <68152031+Tortar@users.noreply.github.com> Date: Tue, 4 Jun 2024 03:40:29 +0200 Subject: [PATCH 03/16] fix another possible source of problems --- src/iterators/bfs.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iterators/bfs.jl b/src/iterators/bfs.jl index 4a7a1aa5..dc4171d9 100644 --- a/src/iterators/bfs.jl +++ b/src/iterators/bfs.jl @@ -67,7 +67,7 @@ end function Base.iterate(t::BFSIterator{<:AbstractArray}) visited, added = falses(nv(t.graph)), falses(nv(t.graph)) visited[first(t.source)] = false - state = BFSVertexIteratorState(visited, added, copy(t.source), Int[], 0, 0) + state = BFSVertexIteratorState(visited, added, [s for s in t.source], Int[], 0, 0) return Base.iterate(t, state) end From f16b695cb7ec960d9ec12538f45fbb29518c28c1 Mon Sep 17 00:00:00 2001 From: Tortar <68152031+Tortar@users.noreply.github.com> Date: Tue, 4 Jun 2024 03:51:07 +0200 Subject: [PATCH 04/16] actually we can go faster --- src/iterators/bfs.jl | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/iterators/bfs.jl b/src/iterators/bfs.jl index dc4171d9..496d3ca4 100644 --- a/src/iterators/bfs.jl +++ b/src/iterators/bfs.jl @@ -67,7 +67,8 @@ end function Base.iterate(t::BFSIterator{<:AbstractArray}) visited, added = falses(nv(t.graph)), falses(nv(t.graph)) visited[first(t.source)] = false - state = BFSVertexIteratorState(visited, added, [s for s in t.source], Int[], 0, 0) + curr_level = unique(s for s in t.source) + state = BFSVertexIteratorState(visited, added, curr_level, Int[], 0, 0) return Base.iterate(t, state) end @@ -95,11 +96,9 @@ function Base.iterate(t::BFSIterator, state::BFSVertexIteratorState) @inbounds while state.node_idx < length(state.curr_level) state.node_idx += 1 node = state.curr_level[state.node_idx] - if !state.visited[node] - state.visited[node] = true - state.n_visited += 1 - return (node, state) - end + state.n_visited += 1 + state.visited[node] = true + return (node, state) end return nothing end From a627fdf72892fcf669bee3d536338e604fe9cbe9 Mon Sep 17 00:00:00 2001 From: Tortar <68152031+Tortar@users.noreply.github.com> Date: Tue, 4 Jun 2024 04:04:31 +0200 Subject: [PATCH 05/16] Update bfs.jl --- src/iterators/bfs.jl | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/iterators/bfs.jl b/src/iterators/bfs.jl index 496d3ca4..3fce8885 100644 --- a/src/iterators/bfs.jl +++ b/src/iterators/bfs.jl @@ -42,7 +42,6 @@ in a `BitVector` so that to skip those nodes. """ mutable struct BFSVertexIteratorState visited::BitVector - added::BitVector curr_level::Vector{Int} next_level::Vector{Int} node_idx::Int @@ -58,17 +57,17 @@ Base.eltype(::Type{BFSIterator{S,G}}) where {S,G} = eltype(G) First iteration to visit vertices in a graph using breadth-first search. """ function Base.iterate(t::BFSIterator{<:Integer}) - visited, added = falses(nv(t.graph)), falses(nv(t.graph)) - visited[t.source] = false - state = BFSVertexIteratorState(visited, added, [t.source], Int[], 0, 0) + visited = falses(nv(t.graph)) + visited[t.source] = true + state = BFSVertexIteratorState(visited, [t.source], Int[], 0, 0) return Base.iterate(t, state) end function Base.iterate(t::BFSIterator{<:AbstractArray}) - visited, added = falses(nv(t.graph)), falses(nv(t.graph)) - visited[first(t.source)] = false + visited = falses(nv(t.graph)) curr_level = unique(s for s in t.source) - state = BFSVertexIteratorState(visited, added, curr_level, Int[], 0, 0) + visited[curr_level] .= true + state = BFSVertexIteratorState(visited, curr_level, Int[], 0, 0) return Base.iterate(t, state) end @@ -83,9 +82,9 @@ function Base.iterate(t::BFSIterator, state::BFSVertexIteratorState) if state.node_idx == length(state.curr_level) @inbounds for node in state.curr_level for adj_node in outneighbors(t.graph, node) - if !state.visited[adj_node] && !state.added[adj_node] + if !state.visited[adj_node] push!(state.next_level, adj_node) - state.added[adj_node] = true + state.visited[adj_node] = true end end end @@ -97,7 +96,6 @@ function Base.iterate(t::BFSIterator, state::BFSVertexIteratorState) state.node_idx += 1 node = state.curr_level[state.node_idx] state.n_visited += 1 - state.visited[node] = true return (node, state) end return nothing From a73a5ba246b3a53dea23bec0cd84c56333831039 Mon Sep 17 00:00:00 2001 From: Tortar <68152031+Tortar@users.noreply.github.com> Date: Tue, 4 Jun 2024 14:52:36 +0200 Subject: [PATCH 06/16] sort for faster bfs --- src/iterators/bfs.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/iterators/bfs.jl b/src/iterators/bfs.jl index 3fce8885..84090f66 100644 --- a/src/iterators/bfs.jl +++ b/src/iterators/bfs.jl @@ -66,6 +66,7 @@ end function Base.iterate(t::BFSIterator{<:AbstractArray}) visited = falses(nv(t.graph)) curr_level = unique(s for s in t.source) + sort!(curr_level) visited[curr_level] .= true state = BFSVertexIteratorState(visited, curr_level, Int[], 0, 0) return Base.iterate(t, state) @@ -89,6 +90,7 @@ function Base.iterate(t::BFSIterator, state::BFSVertexIteratorState) end end state.curr_level, state.next_level = state.next_level, Int[] + sort!(state.curr_level) state.node_idx = 0 end # we visit all nodes in this level From bad4117fcf9950a1c19af20c5064ca3471886e8d Mon Sep 17 00:00:00 2001 From: Tortar <68152031+Tortar@users.noreply.github.com> Date: Tue, 4 Jun 2024 14:55:46 +0200 Subject: [PATCH 07/16] better not --- src/iterators/bfs.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/iterators/bfs.jl b/src/iterators/bfs.jl index 84090f66..3fce8885 100644 --- a/src/iterators/bfs.jl +++ b/src/iterators/bfs.jl @@ -66,7 +66,6 @@ end function Base.iterate(t::BFSIterator{<:AbstractArray}) visited = falses(nv(t.graph)) curr_level = unique(s for s in t.source) - sort!(curr_level) visited[curr_level] .= true state = BFSVertexIteratorState(visited, curr_level, Int[], 0, 0) return Base.iterate(t, state) @@ -90,7 +89,6 @@ function Base.iterate(t::BFSIterator, state::BFSVertexIteratorState) end end state.curr_level, state.next_level = state.next_level, Int[] - sort!(state.curr_level) state.node_idx = 0 end # we visit all nodes in this level From 86254e18ad1575c5e2fcbfc6321e748967d2609a Mon Sep 17 00:00:00 2001 From: Tortar <68152031+Tortar@users.noreply.github.com> Date: Tue, 4 Jun 2024 16:49:56 +0200 Subject: [PATCH 08/16] simpler --- src/iterators/bfs.jl | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/iterators/bfs.jl b/src/iterators/bfs.jl index 3fce8885..4c0d43dd 100644 --- a/src/iterators/bfs.jl +++ b/src/iterators/bfs.jl @@ -88,15 +88,13 @@ function Base.iterate(t::BFSIterator, state::BFSVertexIteratorState) end end end + length(state.next_level) == 0 && return nothing state.curr_level, state.next_level = state.next_level, Int[] state.node_idx = 0 end # we visit all nodes in this level - @inbounds while state.node_idx < length(state.curr_level) - state.node_idx += 1 - node = state.curr_level[state.node_idx] - state.n_visited += 1 - return (node, state) - end - return nothing + state.n_visited += 1 + state.node_idx += 1 + @inbounds node = state.curr_level[state.node_idx] + return (node, state) end From cf5203ca3bbc347308b21359192deebd6b4e2128 Mon Sep 17 00:00:00 2001 From: Tortar <68152031+Tortar@users.noreply.github.com> Date: Tue, 4 Jun 2024 17:34:36 +0200 Subject: [PATCH 09/16] Update bfs.jl --- src/iterators/bfs.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/iterators/bfs.jl b/src/iterators/bfs.jl index 4c0d43dd..c4e039c6 100644 --- a/src/iterators/bfs.jl +++ b/src/iterators/bfs.jl @@ -77,9 +77,10 @@ end Iterator to visit vertices in a graph using breadth-first search. """ function Base.iterate(t::BFSIterator, state::BFSVertexIteratorState) - state.n_visited == nv(t.graph) && return nothing # we fill nodes in this level if state.node_idx == length(state.curr_level) + state.n_visited += length(state.curr_level) + state.n_visited == nv(t.graph) && return nothing @inbounds for node in state.curr_level for adj_node in outneighbors(t.graph, node) if !state.visited[adj_node] @@ -93,7 +94,6 @@ function Base.iterate(t::BFSIterator, state::BFSVertexIteratorState) state.node_idx = 0 end # we visit all nodes in this level - state.n_visited += 1 state.node_idx += 1 @inbounds node = state.curr_level[state.node_idx] return (node, state) From 49ec54dc01addd4aa121cab5a847c6ca3f5c14b8 Mon Sep 17 00:00:00 2001 From: Tortar <68152031+Tortar@users.noreply.github.com> Date: Tue, 4 Jun 2024 17:58:49 +0200 Subject: [PATCH 10/16] Update bfs.jl --- src/iterators/bfs.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iterators/bfs.jl b/src/iterators/bfs.jl index c4e039c6..5e07e741 100644 --- a/src/iterators/bfs.jl +++ b/src/iterators/bfs.jl @@ -90,7 +90,7 @@ function Base.iterate(t::BFSIterator, state::BFSVertexIteratorState) end end length(state.next_level) == 0 && return nothing - state.curr_level, state.next_level = state.next_level, Int[] + state.curr_level, state.next_level = state.next_level, empty!(state.curr_level) state.node_idx = 0 end # we visit all nodes in this level From f067c3d1fd84420c8ef2545b4f4c6c61e3d66d7d Mon Sep 17 00:00:00 2001 From: Tortar <68152031+Tortar@users.noreply.github.com> Date: Wed, 19 Jun 2024 20:39:40 +0200 Subject: [PATCH 11/16] Update doc --- src/iterators/bfs.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/iterators/bfs.jl b/src/iterators/bfs.jl index 5e07e741..0171ec61 100644 --- a/src/iterators/bfs.jl +++ b/src/iterators/bfs.jl @@ -35,10 +35,10 @@ 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. +in BFS which is needed for the `Base.iterate()` function. We use two vectors, +one for the current level nodes and one from the next level nodes to visit +the graph. 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 From 04e4109248b0deaf417ae509443f0e812049369d Mon Sep 17 00:00:00 2001 From: Tortar <68152031+Tortar@users.noreply.github.com> Date: Wed, 19 Jun 2024 23:15:45 +0200 Subject: [PATCH 12/16] Update src/iterators/bfs.jl Co-authored-by: Guillaume Dalle <22795598+gdalle@users.noreply.github.com> --- src/iterators/bfs.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iterators/bfs.jl b/src/iterators/bfs.jl index 0171ec61..51a9c184 100644 --- a/src/iterators/bfs.jl +++ b/src/iterators/bfs.jl @@ -38,7 +38,7 @@ end in BFS which is needed for the `Base.iterate()` function. We use two vectors, one for the current level nodes and one from the next level nodes to visit the graph. 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. +we also keep track of that in a `BitVector` so as to skip those nodes. """ mutable struct BFSVertexIteratorState visited::BitVector From 2566b177cc40d2557cc7e538952c4e44c47b350a Mon Sep 17 00:00:00 2001 From: Tortar <68152031+Tortar@users.noreply.github.com> Date: Wed, 19 Jun 2024 23:16:52 +0200 Subject: [PATCH 13/16] Update bfs.jl --- src/iterators/bfs.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iterators/bfs.jl b/src/iterators/bfs.jl index 51a9c184..7d3e7072 100644 --- a/src/iterators/bfs.jl +++ b/src/iterators/bfs.jl @@ -37,7 +37,7 @@ end `BFSVertexIteratorState` is a struct to hold the current state of iteration in BFS which is needed for the `Base.iterate()` function. We use two vectors, one for the current level nodes and one from the next level nodes to visit -the graph. Since the queue can contains repetitions of already visited nodes, +the graph. Since new levels can contains repetitions of already visited nodes, we also keep track of that in a `BitVector` so as to skip those nodes. """ mutable struct BFSVertexIteratorState From c020f26686b48a1e595b41a76042a09a84f7e5ab Mon Sep 17 00:00:00 2001 From: Tortar <68152031+Tortar@users.noreply.github.com> Date: Fri, 21 Jun 2024 19:26:08 +0200 Subject: [PATCH 14/16] sort is applicable --- src/iterators/bfs.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/iterators/bfs.jl b/src/iterators/bfs.jl index 7d3e7072..982e435e 100644 --- a/src/iterators/bfs.jl +++ b/src/iterators/bfs.jl @@ -91,6 +91,7 @@ function Base.iterate(t::BFSIterator, state::BFSVertexIteratorState) end length(state.next_level) == 0 && return nothing state.curr_level, state.next_level = state.next_level, empty!(state.curr_level) + sort!(state.curr_level) state.node_idx = 0 end # we visit all nodes in this level From 2b163859506f039b2f84863ce813401480d124f0 Mon Sep 17 00:00:00 2001 From: Tortar <68152031+Tortar@users.noreply.github.com> Date: Fri, 21 Jun 2024 19:28:00 +0200 Subject: [PATCH 15/16] Update bfs.jl --- src/iterators/bfs.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/iterators/bfs.jl b/src/iterators/bfs.jl index 982e435e..d45f7793 100644 --- a/src/iterators/bfs.jl +++ b/src/iterators/bfs.jl @@ -66,6 +66,7 @@ end function Base.iterate(t::BFSIterator{<:AbstractArray}) visited = falses(nv(t.graph)) curr_level = unique(s for s in t.source) + sort!(curr_level) visited[curr_level] .= true state = BFSVertexIteratorState(visited, curr_level, Int[], 0, 0) return Base.iterate(t, state) From 5d1275c91a4b85c3d13af18c8f1395066d2a8e3d Mon Sep 17 00:00:00 2001 From: Tortar <68152031+Tortar@users.noreply.github.com> Date: Tue, 25 Jun 2024 13:19:34 +0200 Subject: [PATCH 16/16] adjust tests --- test/iterators/bfs.jl | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/test/iterators/bfs.jl b/test/iterators/bfs.jl index 14740e90..f459eafa 100644 --- a/test/iterators/bfs.jl +++ b/test/iterators/bfs.jl @@ -35,7 +35,14 @@ end end nodes_visited = collect(BFSIterator(g2, [1, 6])) - @test nodes_visited == [1, 6, 2, 3, 5, 7, 4] + levels = ([1, 6], [2, 3, 5, 7], [4]) + @test sort(nodes_visited[1:2]) == sort(levels[1]) + @test sort(nodes_visited[3:6]) == sort(levels[2]) + @test sort(nodes_visited[7:end]) == sort(levels[3]) + nodes_visited = collect(BFSIterator(g2, [8, 1, 6])) - @test nodes_visited == [8, 1, 6, 9, 2, 3, 5, 7, 4] + levels = ([8, 1, 6], [2, 3, 5, 7, 9], [4]) + @test sort(nodes_visited[1:3]) == sort(levels[1]) + @test sort(nodes_visited[4:8]) == sort(levels[2]) + @test sort(nodes_visited[9:end]) == sort(levels[3]) end