From d242f80d70df1695563c5fc069c613760cbc32df Mon Sep 17 00:00:00 2001 From: Henrik Wolf Date: Thu, 2 Feb 2023 16:52:05 +0100 Subject: [PATCH 1/3] added path iterator type for FloydWarshallState --- src/Graphs.jl | 1 + src/core.jl | 7 ++++ src/shortestpaths/floyd-warshall.jl | 57 ++++++++++++++++++++++++++++ test/shortestpaths/floyd-warshall.jl | 41 ++++++++++++++++---- 4 files changed, 99 insertions(+), 7 deletions(-) diff --git a/src/Graphs.jl b/src/Graphs.jl index 57c850f90..935ff68c0 100644 --- a/src/Graphs.jl +++ b/src/Graphs.jl @@ -234,6 +234,7 @@ export maximum_adjacency_visit, # a-star, dijkstra, bellman-ford, floyd-warshall, desopo-pape, spfa + FloydWarshallIterator, a_star, dijkstra_shortest_paths, bellman_ford_shortest_paths, diff --git a/src/core.jl b/src/core.jl index a1c9ee9fc..c0c717755 100644 --- a/src/core.jl +++ b/src/core.jl @@ -5,6 +5,13 @@ An abstract type that provides information from shortest paths calculations. """ abstract type AbstractPathState end +""" + AbstractPathIterator + +An Abstract type that can be iterated over to get all paths encoded in an `AbstractPathState`. +""" +abstract type AbstractPathIterator end + """ is_ordered(e) diff --git a/src/shortestpaths/floyd-warshall.jl b/src/shortestpaths/floyd-warshall.jl index 782d10368..daf1eb8fa 100644 --- a/src/shortestpaths/floyd-warshall.jl +++ b/src/shortestpaths/floyd-warshall.jl @@ -10,6 +10,17 @@ struct FloydWarshallState{T,U<:Integer} <: AbstractPathState parents::Matrix{U} end +""" + struct FloydWarshallIterator{T, U} + +An [`AbstractPathIterator`](@ref) which, under iteration gives all shortes paths encoded in a FloydWarshallState. +When collected, returns a Matrix{Vector{U}} m, where m[s,d] is the shortes path from node s to node d if it exists, +otherwise []. +""" +struct FloydWarshallIterator{T,U<:Integer} <: AbstractPathIterator + path_state::FloydWarshallState{T,U} +end + @doc """ floyd_warshall_shortest_paths(g, distmx=weights(g)) @@ -97,3 +108,49 @@ function enumerate_paths(s::FloydWarshallState) return [enumerate_paths(s, v) for v in 1:size(s.parents, 1)] end enumerate_paths(st::FloydWarshallState, s::Integer, d::Integer) = enumerate_paths(st, s)[d] + +function enumerate_path_into!( + pathcontainer, iter::Graphs.FloydWarshallIterator, s::Integer, d::Integer +) + if iter.path_state.parents[s, d] == 0 || s == d + return @view pathcontainer[2:1:1] + else + pathcontainer[1] = d + current_node = 2 + while d != s + d = iter.path_state.parents[s, d] + pathcontainer[current_node] = d + current_node += 1 + end + return @view pathcontainer[(current_node - 1):-1:1] + end +end + +function Base.iterate(iter::FloydWarshallIterator{T,U}) where {T,U<:Integer} + pathcontainer = Vector{U}(undef, size(iter.path_state.dists)[1]) + pathview = enumerate_path_into!(pathcontainer, iter, 1, 1) + state = (source=1, destination=1, pathcontainer=pathcontainer) + return pathview, state +end + +function Base.iterate(iter::FloydWarshallIterator{T,U}, state) where {T,U<:Integer} + a1 = axes(iter.path_state.dists, 1) + s, d, pathcontainer = state + if d + 1 in a1 + d += 1 + elseif s + 1 in a1 + s += 1 + d = 1 + else + return nothing + end + pathview = enumerate_path_into!(pathcontainer, iter, s, d) + return pathview, (source=s, destination=d, pathcontainer=pathcontainer) +end + +Base.IteratorSize(::FloydWarshallIterator) = Base.HasShape{2}() + +Base.size(iter::FloydWarshallIterator) = size(iter.path_state.dists) +Base.length(iter::FloydWarshallIterator) = length(iter.path_state.dists) + +Base.collect(iter::FloydWarshallIterator) = permutedims([collect(i) for i in iter]) \ No newline at end of file diff --git a/test/shortestpaths/floyd-warshall.jl b/test/shortestpaths/floyd-warshall.jl index 0eb0f2992..9cfc4dd3d 100644 --- a/test/shortestpaths/floyd-warshall.jl +++ b/test/shortestpaths/floyd-warshall.jl @@ -6,12 +6,27 @@ @test z.dists[3, :][:] == [7, 6, 0, 11, 27] @test z.parents[3, :][:] == [2, 3, 0, 3, 4] - @test @inferred(enumerate_paths(z))[2][2] == [] - @test @inferred(enumerate_paths(z))[2][4] == + all_paths = @inferred(enumerate_paths(z)) + all_paths_flat = [all_paths...;] + @test all_paths[2][2] == [] + @test all_paths[2][4] == enumerate_paths(z, 2)[4] == enumerate_paths(z, 2, 4) == [2, 3, 4] + + z_iter = @inferred(FloydWarshallIterator(z)) + first_iter = iterate(z_iter) + @test first_iter[1] == all_paths[1][1] == [] + @test size(z_iter) == (5, 5) + @test length(z_iter) == 25 + @test mapreduce(==, &, z_iter, all_paths_flat) + + collected_iter = collect(z_iter) + @test mapreduce(&, eachrow(collected_iter), all_paths) do row, paths + mapreduce(==, &, row, paths) + end end + g4 = path_digraph(4) d = ones(4, 4) for g in testdigraphs(g4) @@ -19,6 +34,12 @@ @test length(enumerate_paths(z, 4, 3)) == 0 @test length(enumerate_paths(z, 4, 1)) == 0 @test length(enumerate_paths(z, 2, 3)) == 2 + + z_iter = @inferred(FloydWarshallIterator(z)) + iter_paths = collect(z_iter) + @test length(iter_paths[4, 3]) == 0 + @test length(iter_paths[4, 1]) == 0 + @test length(iter_paths[2, 3]) == 2 end g5 = DiGraph([1 1 1 0 1; 0 1 0 1 1; 0 1 1 0 0; 1 0 1 1 0; 0 0 0 1 1]) @@ -32,15 +53,21 @@ g = SimpleGraph(2) add_edge!(g, 1, 2) add_edge!(g, 2, 2) - @test enumerate_paths(floyd_warshall_shortest_paths(g)) == - Vector{Vector{Int}}[[[], [1, 2]], [[2, 1], []]] + z = floyd_warshall_shortest_paths(g) + @test enumerate_paths(z) == Vector{Vector{Int}}[[[], [1, 2]], [[2, 1], []]] - g = SimpleDiGraph(2) + @test mapreduce( + ==, &, eachrow(collect(FloydWarshallIterator(z))), enumerate_paths(z) + ) add_edge!(g, 1, 1) add_edge!(g, 1, 2) add_edge!(g, 2, 1) add_edge!(g, 2, 2) - @test enumerate_paths(floyd_warshall_shortest_paths(g)) == - Vector{Vector{Int}}[[[], [1, 2]], [[2, 1], []]] + z = floyd_warshall_shortest_paths(g) + @test enumerate_paths(z) == Vector{Vector{Int}}[[[], [1, 2]], [[2, 1], []]] + + @test mapreduce( + ==, &, eachrow(collect(FloydWarshallIterator(z))), enumerate_paths(z) + ) end end From e18ed92b1178916ce5c0d65a52bb5c22f60d0f9a Mon Sep 17 00:00:00 2001 From: Henrik Wolf Date: Thu, 2 Feb 2023 16:55:03 +0100 Subject: [PATCH 2/3] fixed wrong use of size --- src/shortestpaths/floyd-warshall.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shortestpaths/floyd-warshall.jl b/src/shortestpaths/floyd-warshall.jl index daf1eb8fa..bb132a43d 100644 --- a/src/shortestpaths/floyd-warshall.jl +++ b/src/shortestpaths/floyd-warshall.jl @@ -127,7 +127,7 @@ function enumerate_path_into!( end function Base.iterate(iter::FloydWarshallIterator{T,U}) where {T,U<:Integer} - pathcontainer = Vector{U}(undef, size(iter.path_state.dists)[1]) + pathcontainer = Vector{U}(undef, size(iter.path_state.dists, 1)) pathview = enumerate_path_into!(pathcontainer, iter, 1, 1) state = (source=1, destination=1, pathcontainer=pathcontainer) return pathview, state From 36bcc17ff5d204db0922235fb28daf3b16a8a53a Mon Sep 17 00:00:00 2001 From: Henrik Wolf Date: Thu, 2 Feb 2023 17:41:18 +0100 Subject: [PATCH 3/3] reworked enumerate_paths for floyd_warshall --- src/shortestpaths/floyd-warshall.jl | 43 ++++++++++++++--------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/src/shortestpaths/floyd-warshall.jl b/src/shortestpaths/floyd-warshall.jl index bb132a43d..2516fee94 100644 --- a/src/shortestpaths/floyd-warshall.jl +++ b/src/shortestpaths/floyd-warshall.jl @@ -80,34 +80,33 @@ function floyd_warshall_shortest_paths( end function enumerate_paths( - s::FloydWarshallState{T,U}, v::Integer + st::FloydWarshallState{T,U}, s::Integer, d::Integer ) where {T} where {U<:Integer} - pathinfo = s.parents[v, :] - paths = Vector{Vector{U}}() - for i in 1:length(pathinfo) - if (i == v) || (s.dists[v, i] == typemax(T)) - push!(paths, Vector{U}()) - else - path = Vector{U}() - currpathindex = U(i) - while currpathindex != 0 - push!(path, currpathindex) - if pathinfo[currpathindex] == currpathindex - currpathindex = zero(currpathindex) - else - currpathindex = pathinfo[currpathindex] - end + pathinfo = @view st.parents[s, :] + path = Vector{U}() + if (s == d) || (st.dists[s, d] == typemax(T)) + return path + else + currpathindex = U(d) + while currpathindex != 0 + push!(path, currpathindex) + if pathinfo[currpathindex] == currpathindex + currpathindex = zero(currpathindex) + else + currpathindex = pathinfo[currpathindex] end - push!(paths, reverse(path)) end + return reverse!(path) end - return paths end -function enumerate_paths(s::FloydWarshallState) - return [enumerate_paths(s, v) for v in 1:size(s.parents, 1)] +function enumerate_paths(st::FloydWarshallState) + return [enumerate_paths(st, s) for s in 1:size(st.parents, 1)] +end + +function enumerate_paths(st::FloydWarshallState, s::Integer) + return [enumerate_paths(st, s, d) for d in 1:size(st.parents, 1)] end -enumerate_paths(st::FloydWarshallState, s::Integer, d::Integer) = enumerate_paths(st, s)[d] function enumerate_path_into!( pathcontainer, iter::Graphs.FloydWarshallIterator, s::Integer, d::Integer @@ -153,4 +152,4 @@ Base.IteratorSize(::FloydWarshallIterator) = Base.HasShape{2}() Base.size(iter::FloydWarshallIterator) = size(iter.path_state.dists) Base.length(iter::FloydWarshallIterator) = length(iter.path_state.dists) -Base.collect(iter::FloydWarshallIterator) = permutedims([collect(i) for i in iter]) \ No newline at end of file +Base.collect(iter::FloydWarshallIterator) = permutedims([collect(i) for i in iter])