diff --git a/src/Graphs/abstractgraph.jl b/src/Graphs/abstractgraph.jl index 3c50f00..40dac2e 100644 --- a/src/Graphs/abstractgraph.jl +++ b/src/Graphs/abstractgraph.jl @@ -74,6 +74,18 @@ function subgraph(f::Function, graph::AbstractGraph) return induced_subgraph(graph, filter(f, vertices(graph)))[1] end +function degrees(graph::AbstractGraph, vertices=vertices(graph)) + return map(vertex -> degree(graph, vertex), vertices) +end + +function indegrees(graph::AbstractGraph, vertices=vertices(graph)) + return map(vertex -> indegree(graph, vertex), vertices) +end + +function outdegrees(graph::AbstractGraph, vertices=vertices(graph)) + return map(vertex -> outdegree(graph, vertex), vertices) +end + # Used for tree iteration. # Assumes the graph is a [rooted directed tree](https://en.wikipedia.org/wiki/Tree_(graph_theory)#Rooted_tree). struct TreeGraph{G,V} diff --git a/src/NamedGraphs.jl b/src/NamedGraphs.jl index 09aef8f..409856b 100644 --- a/src/NamedGraphs.jl +++ b/src/NamedGraphs.jl @@ -19,13 +19,18 @@ import Graphs: bfs_parents, bfs_tree, blockdiag, + common_neighbors, + degree, + degree_histogram, dst, dfs_parents, dfs_tree, edges, edgetype, has_edge, + has_path, has_vertex, + indegree, induced_subgraph, inneighbors, is_connected, @@ -35,6 +40,21 @@ import Graphs: is_weakly_connected, ne, neighbors, + neighborhood, + neighborhood_dists, + outdegree, + a_star, + bellman_ford_shortest_paths, + enumerate_paths, + desopo_pape_shortest_paths, + dijkstra_shortest_paths, + floyd_warshall_shortest_paths, + johnson_shortest_paths, + spfa_shortest_paths, + yen_k_shortest_paths, + boruvka_mst, + kruskal_mst, + prim_mst, nv, outneighbors, rem_vertex!, @@ -43,11 +63,11 @@ import Graphs: tree, vertices -import Base: show, eltype, copy, getindex, convert, hcat, vcat, hvncat, union +import Base: show, eltype, copy, getindex, convert, hcat, vcat, hvncat, union, zero # abstractnamededge.jl import Base: Pair, Tuple, show, ==, hash, eltype, convert -import Graphs: AbstractEdge, src, dst, reverse +import Graphs: AbstractEdge, src, dst, reverse, reverse! include(joinpath("Dictionaries", "dictionary.jl")) include(joinpath("Graphs", "abstractgraph.jl")) @@ -75,6 +95,12 @@ export NamedGraph, post_order_dfs_vertices, post_order_dfs_edges, rename_vertices, + degree, + degrees, + indegree, + indegrees, + outdegree, + outdegrees, # Operations for tree-like graphs is_leaf, is_tree, diff --git a/src/abstractnamedgraph.jl b/src/abstractnamedgraph.jl index 46999e7..c28f12b 100644 --- a/src/abstractnamedgraph.jl +++ b/src/abstractnamedgraph.jl @@ -11,7 +11,20 @@ parent_graph(graph::AbstractNamedGraph) = not_implemented() # typeof(parent_graph(graph)) # ? parent_graph_type(graph::AbstractNamedGraph) = not_implemented() -vertex_to_parent_vertex(graph::AbstractNamedGraph) = not_implemented() + +# Convert vertex to parent vertex +# Inverse map of `parent_vertex_to_vertex`. +vertex_to_parent_vertex(graph::AbstractNamedGraph, vertex) = not_implemented() + +# Convert parent vertex to vertex. +# Use `vertices`, assumes `vertices` is indexed by a parent vertex (a Vector for linear indexed parent vertices, a dictionary in general). +function parent_vertex_to_vertex(graph::AbstractNamedGraph, parent_vertex) + return vertices(graph)[parent_vertex] +end + +# Convenient shorthands for using in higher order functions like `map`. +vertex_to_parent_vertex(graph::AbstractNamedGraph) = Base.Fix1(vertex_to_parent_vertex, graph) +parent_vertex_to_vertex(graph::AbstractNamedGraph) = Base.Fix1(parent_vertex_to_vertex, graph) # TODO: rename `edge_type`? edgetype(graph::AbstractNamedGraph) = not_implemented() @@ -36,6 +49,8 @@ convert_vertextype(::Type, ::AbstractNamedGraph) = not_implemented() # end copy(graph::AbstractNamedGraph) = not_implemented() +zero(G::Type{<:AbstractNamedGraph}) = G() + # TODO: Implement using `copyto!`? function directed_graph(graph::AbstractNamedGraph) digraph = directed_graph(typeof(graph))(vertices(graph)) @@ -51,19 +66,6 @@ eltype(graph::AbstractNamedGraph) = eltype(vertices(graph)) parent_eltype(graph::AbstractNamedGraph) = eltype(parent_graph(graph)) -# By default, assume `vertex_to_parent_vertex(graph)` -# returns a data structure that you index into to map -# from a vertex to a parent vertex. -function vertex_to_parent_vertex(graph::AbstractNamedGraph, vertex) - return vertex_to_parent_vertex(graph)[vertex] -end - -# Convert parent vertex to vertex. -# Use `vertices`, assumes `vertices` is indexed by a parent vertex (a Vector for linear indexed parent vertices, a dictionary in general). -function parent_vertex_to_vertex(graph::AbstractNamedGraph, parent_vertex) - return vertices(graph)[parent_vertex] -end - # This should be overloaded for multi-dimensional indexing. # Get the subset of vertices of the graph, for example # for an input slice `subvertices(graph, "X", :)`. @@ -76,9 +78,15 @@ function subvertices(graph::AbstractNamedGraph{V}, vertices::Vector{V}) where {V end function vertices_to_parent_vertices( - graph::AbstractNamedGraph{V}, vertices::Vector{V} -) where {V} - return parent_eltype(graph)[vertex_to_parent_vertex(graph, vertex) for vertex in vertices] + graph::AbstractNamedGraph, vertices +) + return map(vertex_to_parent_vertex(graph), vertices) +end + +function parent_vertices_to_vertices( + graph::AbstractNamedGraph, parent_vertices +) + return map(parent_vertex_to_vertex(graph), parent_vertices) end parent_vertices(graph::AbstractNamedGraph) = vertices(parent_graph(graph)) @@ -95,13 +103,33 @@ function edge_to_parent_edge(graph::AbstractNamedGraph, edge) return edge_to_parent_edge(graph, edgetype(graph)(edge)) end +edge_to_parent_edge(graph::AbstractNamedGraph) = Base.Fix1(edge_to_parent_edge, graph) + +function edges_to_parent_edges(graph::AbstractNamedGraph, edges) + return map(edge_to_parent_edge(graph), edges) +end + +function parent_edge_to_edge(graph::AbstractNamedGraph, parent_edge::AbstractEdge) + source = parent_vertex_to_vertex(graph, src(parent_edge)) + destination = parent_vertex_to_vertex(graph, dst(parent_edge)) + return edgetype(graph)(source, destination) +end + +function parent_edge_to_edge(graph::AbstractNamedGraph, parent_edge) + return parent_edge_to_edge(graph, parent_edgetype(parent_edge)) +end + +parent_edge_to_edge(graph::AbstractNamedGraph) = Base.Fix1(parent_edge_to_edge, graph) + +function parent_edges_to_edges(graph::AbstractNamedGraph, parent_edges) + return map(parent_edge_to_edge(graph), parent_edges) +end + # TODO: This is `O(nv(g))`, use `haskey(vertex_to_parent_vertex(g), v)` instead? has_vertex(g::AbstractNamedGraph, v) = v in vertices(g) function edges(graph::AbstractNamedGraph) - vertex(parent_vertex) = vertices(graph)[parent_vertex] - edge(parent_edge) = edgetype(graph)(vertex(src(parent_edge)), vertex(dst(parent_edge))) - return map(edge, parent_edges(graph)) + return parent_edges_to_edges(graph, parent_edges(graph)) end # TODO: write in terms of a generic function. @@ -109,17 +137,122 @@ for f in [:outneighbors, :inneighbors, :all_neighbors, :neighbors] @eval begin function $f(graph::AbstractNamedGraph, vertex) parent_vertices = $f(parent_graph(graph), vertex_to_parent_vertex(graph, vertex)) - return [ - parent_vertex_to_vertex(graph, parent_vertex) for parent_vertex in parent_vertices - ] + return parent_vertices_to_vertices(graph, parent_vertices) end # Ambiguity errors with Graphs.jl function $f(graph::AbstractNamedGraph, vertex::Integer) parent_vertices = $f(parent_graph(graph), vertex_to_parent_vertex(graph, vertex)) - return [ - parent_vertex_to_vertex(graph, parent_vertex) for parent_vertex in parent_vertices - ] + return parent_vertices_to_vertices(graph, parent_vertices) + end + end +end + +common_neighbors(g::AbstractNamedGraph, u, v) = intersect(neighbors(g, u), neighbors(g, v)) + +indegree(graph::AbstractNamedGraph, vertex) = length(inneighbors(graph, vertex)) +outdegree(graph::AbstractNamedGraph, vertex) = length(outneighbors(graph, vertex)) + +@traitfn degree(graph::AbstractNamedGraph::IsDirected, vertex) = indegree(graph, vertex) + outdegree(graph, vertex) +@traitfn degree(graph::AbstractNamedGraph::(!IsDirected), vertex) = indegree(graph, vertex) + +function degree_histogram(g::AbstractNamedGraph, degfn=degree) + hist = Dictionary{Int,Int}() + for v in vertices(g) # minimize allocations by + for d in degfn(g, v) # iterating over vertices + set!(hist, d, get(hist, d, 0) + 1) + end + end + return hist +end + +function dist_matrix_to_parent_dist_matrix(graph::AbstractNamedGraph, distmx) + not_implemented() +end + +function dist_matrix_to_parent_dist_matrix(graph::AbstractNamedGraph, distmx::Graphs.DefaultDistance) + return distmx +end + +function neighborhood(graph::AbstractNamedGraph, vertex, d, distmx=weights(graph); dir=:out) + parent_distmx = dist_matrix_to_parent_dist_matrix(graph, distmx) + parent_vertices = neighborhood(parent_graph(graph), vertex_to_parent_vertex(graph, vertex), d, parent_distmx; dir) + return [ + parent_vertex_to_vertex(graph, parent_vertex) for parent_vertex in parent_vertices + ] +end + +function neighborhood_dists(graph::AbstractNamedGraph, vertex, d, distmx=weights(graph); dir=:out) + parent_distmx = dist_matrix_to_parent_dist_matrix(graph, distmx) + parent_vertices_and_dists = neighborhood_dists(parent_graph(graph), vertex_to_parent_vertex(graph, vertex), d, parent_distmx; dir) + return [ + (parent_vertex_to_vertex(graph, parent_vertex), dist) for (parent_vertex, dist) in parent_vertices_and_dists + ] +end + +function a_star( + graph::AbstractNamedGraph, + source, + destination, + distmx=weights(graph), + heuristic::Function=(v -> zero(eltype(distmx))), + edgetype_to_return=edgetype(graph), +) + parent_distmx = dist_matrix_to_parent_dist_matrix(graph, distmx) + parent_shortest_path = a_star( + parent_graph(graph), + vertex_to_parent_vertex(graph, source), + vertex_to_parent_vertex(graph, destination), + dist_matrix_to_parent_dist_matrix(graph, distmx), + heuristic, + SimpleEdge, + ) + return parent_edges_to_edges(graph, parent_shortest_path) +end + +function spfa_shortest_paths(graph::AbstractNamedGraph, vertex, distmx=weights(graph)) + parent_distmx = dist_matrix_to_parent_dist_matrix(graph, distmx) + parent_shortest_paths = spfa_shortest_paths(parent_graph(graph), vertex_to_parent_vertex(graph, vertex), parent_distmx) + return Dictionary(vertices(graph), parent_shortest_paths) +end + +function boruvka_mst( + g::AbstractNamedGraph, + distmx::AbstractMatrix{<:Real}=weights(g); + minimize=true, +) + parent_mst, weights = boruvka_mst(parent_graph(g), distmx; minimize) + return parent_edges_to_edges(g, parent_mst), weights +end + +function kruskal_mst( + g::AbstractNamedGraph, + distmx::AbstractMatrix{<:Real}=weights(g); + minimize=true, +) + parent_mst = kruskal_mst(parent_graph(g), distmx; minimize) + return parent_edges_to_edges(g, parent_mst) +end + +function prim_mst( + g::AbstractNamedGraph, + distmx::AbstractMatrix{<:Real}=weights(g), +) + parent_mst = prim_mst(parent_graph(g), distmx) + return parent_edges_to_edges(g, parent_mst) +end + +for f in [ + :bellman_ford_shortest_paths, + :desopo_pape_shortest_paths, + :dijkstra_shortest_paths, + :floyd_warshall_shortest_paths, + :johnson_shortest_paths, + :yen_k_shortest_paths, +] + @eval begin + function $f(graph::AbstractNamedGraph, args...; kwargs...) + return not_implemented() end end end @@ -146,6 +279,15 @@ end has_edge(g::AbstractNamedGraph, edge) = has_edge(g, edgetype(g)(edge)) has_edge(g::AbstractNamedGraph, src, dst) = has_edge(g, edgetype(g)(src, dst)) +function has_path(graph::AbstractNamedGraph, source, destination; exclude_vertices=vertextype(graph)[]) + return has_path( + parent_graph(graph), + vertex_to_parent_vertex(graph, source), + vertex_to_parent_vertex(graph, destination); + exclude_vertices=vertices_to_parent_vertices(graph, exclude_vertices), + ) +end + function union(graph1::AbstractNamedGraph, graph2::AbstractNamedGraph) union_graph = promote_type(typeof(graph1), typeof(graph2))() union_vertices = union(vertices(graph1), vertices(graph2)) @@ -169,7 +311,8 @@ function add_vertex!(graph::AbstractNamedGraph, vertex) # Update the vertex list push!(vertices(graph), vertex) # Update the reverse map - insert!(vertex_to_parent_vertex(graph), vertex, last(parent_vertices(graph))) + # TODO: Make this more generic + insert!(graph.vertex_to_parent_vertex, vertex, last(parent_vertices(graph))) return graph end @@ -185,8 +328,10 @@ function rem_vertex!(graph::AbstractNamedGraph, vertex) # Insert the last vertex into the position of the vertex # that is being deleted, then remove the last vertex. - vertex_to_parent_vertex(graph)[last_vertex] = parent_vertex - delete!(vertex_to_parent_vertex(graph), vertex) + # TODO: Make this more generic + graph.vertex_to_parent_vertex[last_vertex] = parent_vertex + # TODO: Make this more generic + delete!(graph.vertex_to_parent_vertex, vertex) return graph end @@ -206,6 +351,16 @@ is_connected(graph::AbstractNamedGraph) = is_connected(parent_graph(graph)) is_cyclic(graph::AbstractNamedGraph) = is_cyclic(parent_graph(graph)) +@traitfn function reverse(graph::AbstractNamedGraph::IsDirected) + reversed_parent_graph = reverse(parent_graph(graph)) + return h +end + +@traitfn function reverse!(g::AbstractNamedGraph::IsDirected) + g.fadjlist, g.badjlist = g.badjlist, g.fadjlist + return g +end + # TODO: Move to namedgraph.jl, or make the output generic? function blockdiag(graph1::AbstractNamedGraph, graph2::AbstractNamedGraph) new_parent_graph = blockdiag(parent_graph(graph1), parent_graph(graph2)) @@ -229,42 +384,52 @@ end # Overload Graphs.tree. Used for bfs_tree and dfs_tree # traversal algorithms. -function tree(graph::AbstractNamedGraph, parents::AbstractVector) +function tree(graph::AbstractNamedGraph, parents) n = length(parents) # TODO: Use `directed_graph` here to make more generic? - t = GenericNamedGraph(DiGraph(n), vertices(graph)) - for (parent_v, u) in enumerate(parents) - v = vertices(graph)[parent_v] - if u != v - add_edge!(t, u, v) + ## t = GenericNamedGraph(DiGraph(n), vertices(graph)) + t = directed_graph(typeof(graph))(vertices(graph)) + for destination in eachindex(parents) + source = parents[destination] + if source != destination + add_edge!(t, source, destination) end end return t end -bfs_tree(g::AbstractNamedGraph, s; kwargs...) = tree(g, bfs_parents(g, s; kwargs...)) - +_bfs_tree(graph::AbstractNamedGraph, vertex; kwargs...) = tree(graph, bfs_parents(graph, vertex; kwargs...)) # Disambiguation from Graphs.bfs_tree -bfs_tree(g::AbstractNamedGraph, s::Integer; kwargs...) = bfs_tree(g, tuple(s); kwargs...) +bfs_tree(graph::AbstractNamedGraph, vertex::Integer; kwargs...) = _bfs_tree(graph, vertex; kwargs...) +bfs_tree(graph::AbstractNamedGraph, vertex; kwargs...) = _bfs_tree(graph, vertex; kwargs...) -function bfs_parents(graph::AbstractNamedGraph, s; kwargs...) +# Returns a Dictionary mapping a vertex to it's parent +# vertex in the traversal/spanning tree. +function _bfs_parents(graph::AbstractNamedGraph, vertex; kwargs...) parent_bfs_parents = bfs_parents( - parent_graph(graph), vertex_to_parent_vertex(graph)[s]; kwargs... + parent_graph(graph), vertex_to_parent_vertex(graph, vertex); kwargs... ) - return [vertices(graph)[parent_vertex] for parent_vertex in parent_bfs_parents] + return Dictionary(vertices(graph), parent_vertices_to_vertices(graph, parent_bfs_parents)) end +# Disambiguation from Graphs.bfs_tree +bfs_parents(graph::AbstractNamedGraph, vertex::Integer; kwargs...) = _bfs_parents(graph, vertex; kwargs...) +bfs_parents(graph::AbstractNamedGraph, vertex; kwargs...) = _bfs_parents(graph, vertex; kwargs...) -dfs_tree(g::AbstractNamedGraph, s; kwargs...) = tree(g, dfs_parents(g, s; kwargs...)) - -# Disambiguation from Graphs.dfs_tree -dfs_tree(g::AbstractNamedGraph, s::Integer; kwargs...) = dfs_tree(g, tuple(s); kwargs...) +_dfs_tree(graph::AbstractNamedGraph, vertex; kwargs...) = tree(graph, dfs_parents(graph, vertex; kwargs...)) +dfs_tree(graph::AbstractNamedGraph, vertex::Integer; kwargs...) = _dfs_tree(graph, vertex; kwargs...) +dfs_tree(graph::AbstractNamedGraph, vertex; kwargs...) = _dfs_tree(graph, vertex; kwargs...) -function dfs_parents(graph::AbstractNamedGraph, s; kwargs...) +# Returns a Dictionary mapping a vertex to it's parent +# vertex in the traversal/spanning tree. +function _dfs_parents(graph::AbstractNamedGraph, vertex; kwargs...) parent_dfs_parents = dfs_parents( - parent_graph(graph), vertex_to_parent_vertex(graph)[s]; kwargs... + parent_graph(graph), vertex_to_parent_vertex(graph, vertex); kwargs... ) - return [vertices(graph)[parent_vertex] for parent_vertex in parent_dfs_parents] + return Dictionary(vertices(graph), parent_vertices_to_vertices(graph, parent_dfs_parents)) end +# Disambiguation from Graphs.dfs_tree +dfs_parents(graph::AbstractNamedGraph, vertex::Integer; kwargs...) = _dfs_parents(graph, vertex; kwargs...) +dfs_parents(graph::AbstractNamedGraph, vertex; kwargs...) = _dfs_parents(graph, vertex; kwargs...) # # Printing diff --git a/src/namedgraph.jl b/src/namedgraph.jl index 322f0d5..eee805b 100644 --- a/src/namedgraph.jl +++ b/src/namedgraph.jl @@ -8,7 +8,7 @@ end parent_graph_type(G::Type{<:GenericNamedGraph}) = fieldtype(G, :parent_graph) parent_graph(graph::GenericNamedGraph) = getfield(graph, :parent_graph) vertices(graph::GenericNamedGraph) = getfield(graph, :vertices) -vertex_to_parent_vertex(graph::GenericNamedGraph) = getfield(graph, :vertex_to_parent_vertex) +vertex_to_parent_vertex(graph::GenericNamedGraph, vertex) = graph.vertex_to_parent_vertex[vertex] function convert_vertextype(V::Type, graph::GenericNamedGraph) return GenericNamedGraph(parent_graph(graph), convert(Vector{V}, vertices(graph))) diff --git a/test/test_namedgraph.jl b/test/test_namedgraph.jl index 591d1c6..e3aa05f 100644 --- a/test/test_namedgraph.jl +++ b/test/test_namedgraph.jl @@ -1,30 +1,256 @@ using Graphs using NamedGraphs +using NamedGraphs.Dictionaries using Test +@testset "NamedEdge" begin + @test is_ordered(NamedEdge("A", "B")) + @test !is_ordered(NamedEdge("B", "A")) +end + @testset "NamedGraph" begin - g = NamedGraph(grid((4,)), ["A", "B", "C", "D"]) + @testset "Basics" begin + g = NamedGraph(grid((4,)), ["A", "B", "C", "D"]) + + @test nv(g) == 4 + @test ne(g) == 3 + @test sum(g) == 3 + @test has_vertex(g, "A") + @test has_vertex(g, "B") + @test has_vertex(g, "C") + @test has_vertex(g, "D") + @test has_edge(g, "A" => "B") + @test issetequal(common_neighbors(g, "A", "C"), ["B"]) + @test isempty(common_neighbors(g, "A", "D")) + + zg = zero(g) + @test zg isa NamedGraph{String} + @test nv(zg) == 0 + @test ne(zg) == 0 + + @test degree(g, "A") == 1 + @test degree(g, "B") == 2 + + add_vertex!(g, "E") + @test has_vertex(g, "E") + + rem_vertex!(g, "E") + @test !has_vertex(g, "E") + + io = IOBuffer() + show(io, "text/plain", g) + @test String(take!(io)) isa String + + add_edge!(g, "A" => "C") + + @test has_edge(g, "A" => "C") + @test issetequal(neighbors(g, "A"), ["B", "C"]) + @test issetequal(neighbors(g, "B"), ["A", "C"]) + + g_sub = g[["A", "B"]] + + @test has_vertex(g_sub, "A") + @test has_vertex(g_sub, "B") + @test !has_vertex(g_sub, "C") + @test !has_vertex(g_sub, "D") + + g = NamedGraph(["A", "B", "C", "D", "E"]) + add_edge!(g, "A" => "B") + add_edge!(g, "B" => "C") + add_edge!(g, "D" => "E") + @test has_path(g, "A", "B") + @test has_path(g, "A", "C") + @test has_path(g, "D", "E") + @test !has_path(g, "A", "E") + end + @testset "Basics (directed)" begin + g = NamedDiGraph(["A", "B", "C", "D"]) + add_edge!(g, "A" => "B") + add_edge!(g, "B" => "C") + @test has_edge(g, "A" => "B") + @test has_edge(g, "B" => "C") + @test !has_edge(g, "B" => "A") + @test !has_edge(g, "C" => "B") + @test indegree(g, "A") == 0 + @test outdegree(g, "A") == 1 + @test indegree(g, "B") == 1 + @test outdegree(g, "B") == 1 + @test indegree(g, "C") == 1 + @test outdegree(g, "C") == 0 + @test indegree(g, "D") == 0 + @test outdegree(g, "D") == 0 + + @test degrees(g) == [1, 2, 1, 0] + @test degrees(g, ["B", "C"]) == [2, 1] + @test degrees(g, Indices(["B", "C"])) == Dictionary(["B", "C"], [2, 1]) + @test indegrees(g) == [0, 1, 1, 0] + @test outdegrees(g) == [1, 1, 0, 0] + + h = degree_histogram(g) + @test h[0] == 1 + @test h[1] == 2 + @test h[2] == 1 + + h = degree_histogram(g, indegree) + @test h[0] == 2 + @test h[1] == 2 + end + @testset "BFS traversal" begin + g = named_grid((3, 3)) + t = bfs_tree(g, (1, 1)) + @test is_directed(t) + @test t isa NamedDiGraph{Tuple{Int,Int}} + @test ne(t) == 8 + edges = [ + (1, 1) => (1, 2), + (1, 2) => (1, 3), + (1, 1) => (2, 1), + (2, 1) => (2, 2), + (2, 2) => (2, 3), + (2, 1) => (3, 1), + (3, 1) => (3, 2), + (3, 2) => (3, 3), + ] + for e in edges + @test has_edge(t, e) + end + + p = bfs_parents(g, (1, 1)) + @test length(p) == 9 + vertices_g = [ + (1, 1), + (2, 1), + (3, 1), + (1, 2), + (2, 2), + (3, 2), + (1, 3), + (2, 3), + (3, 3), + ] + parent_vertices = [ + (1, 1), + (1, 1), + (2, 1), + (1, 1), + (2, 1), + (3, 1), + (1, 2), + (2, 2), + (3, 2), + ] + d = Dictionary(vertices_g, parent_vertices) + for v in vertices(g) + @test p[v] == d[v] + end + + g = named_grid(3) + t = bfs_tree(g, 2) + @test is_directed(t) + @test t isa NamedDiGraph{Int} + @test ne(t) == 2 + @test has_edge(g, 2 => 1) + @test has_edge(g, 2 => 3) + end + @testset "DFS traversal" begin + g = named_grid((3, 3)) + t = dfs_tree(g, (1, 1)) + @test is_directed(t) + @test t isa NamedDiGraph{Tuple{Int,Int}} + @test ne(t) == 8 + edges = [ + (1, 1) => (2, 1), + (2, 1) => (3, 1), + (3, 1) => (3, 2), + (3, 2) => (2, 2), + (2, 2) => (1, 2), + (1, 2) => (1, 3), + (1, 3) => (2, 3), + (2, 3) => (3, 3), + ] + for e in edges + @test has_edge(t, e) + end + + p = dfs_parents(g, (1, 1)) + @test length(p) == 9 + vertices_g = [ + (1, 1), + (2, 1), + (3, 1), + (1, 2), + (2, 2), + (3, 2), + (1, 3), + (2, 3), + (3, 3), + ] + parent_vertices = [ + (1, 1), + (1, 1), + (2, 1), + (2, 2), + (3, 2), + (3, 1), + (1, 2), + (1, 3), + (2, 3), + ] + d = Dictionary(vertices_g, parent_vertices) + for v in vertices(g) + @test p[v] == d[v] + end - @test has_vertex(g, "A") - @test has_vertex(g, "B") - @test has_vertex(g, "C") - @test has_vertex(g, "D") - @test has_edge(g, "A" => "B") + g = named_grid(3) + t = dfs_tree(g, 2) + @test is_directed(t) + @test t isa NamedDiGraph{Int} + @test ne(t) == 2 + @test has_edge(g, 2 => 1) + @test has_edge(g, 2 => 3) + end + @testset "Shortest paths" begin + g = named_grid((10, 10)) + p = a_star(g, (1, 1), (10, 10)) + @test length(p) == 18 + @test eltype(p) == edgetype(g) + @test eltype(p) == NamedEdge{Tuple{Int,Int}} - io = IOBuffer() - show(io, "text/plain", g) - @test String(take!(io)) isa String + ps = spfa_shortest_paths(g, (1, 1)) + @test ps isa Dictionary{Tuple{Int,Int},Int} + @test length(ps) == 100 + @test ps[(8, 1)] == 7 - add_edge!(g, "A" => "C") + es, weights = boruvka_mst(g) + @test length(es) == 99 + @test weights == 99 + @test es isa Vector{NamedEdge{Tuple{Int,Int}}} - @test has_edge(g, "A" => "C") - @test issetequal(neighbors(g, "A"), ["B", "C"]) - @test issetequal(neighbors(g, "B"), ["A", "C"]) + es = kruskal_mst(g) + @test length(es) == 99 + @test es isa Vector{NamedEdge{Tuple{Int,Int}}} - g_sub = g[["A", "B"]] + es = prim_mst(g) + @test length(es) == 99 + @test es isa Vector{NamedEdge{Tuple{Int,Int}}} - @test has_vertex(g_sub, "A") - @test has_vertex(g_sub, "B") - @test !has_vertex(g_sub, "C") - @test !has_vertex(g_sub, "D") + for f in ( + bellman_ford_shortest_paths, + desopo_pape_shortest_paths, + dijkstra_shortest_paths, + floyd_warshall_shortest_paths, + johnson_shortest_paths, + yen_k_shortest_paths, + ) + @test_broken f(g, "A") + end + @testset "has_self_loops" begin + g = NamedGraph(2) + @test g isa NamedGraph{Int} + add_edge!(g, 1, 2) + @test !has_self_loops(g) + add_edge!(g, 1, 1) + @test has_self_loops(g) + end + end end