From 0ff24efbc654c97b388ff5f2f26b14fad7826b94 Mon Sep 17 00:00:00 2001 From: Kyle Beggs Date: Tue, 20 Sep 2022 08:57:56 -0400 Subject: [PATCH] created iterators src folder, added kruskal --- docs/src/algorithms/iterators.md | 21 ++++ docs/src/algorithms/traversals.md | 1 - src/Graphs.jl | 4 +- src/iterators/bfs.jl | 104 ++++++++++++++++++++ src/iterators/dfs.jl | 104 ++++++++++++++++++++ src/iterators/iterators.jl | 65 ++++++++++++ src/iterators/kruskal.jl | 67 +++++++++++++ test/{traversals => iterators}/iterators.jl | 0 test/runtests.jl | 2 +- 9 files changed, 364 insertions(+), 4 deletions(-) create mode 100644 docs/src/algorithms/iterators.md create mode 100644 src/iterators/bfs.jl create mode 100644 src/iterators/dfs.jl create mode 100644 src/iterators/iterators.jl create mode 100644 src/iterators/kruskal.jl rename test/{traversals => iterators}/iterators.jl (100%) diff --git a/docs/src/algorithms/iterators.md b/docs/src/algorithms/iterators.md new file mode 100644 index 000000000..6c20e2905 --- /dev/null +++ b/docs/src/algorithms/iterators.md @@ -0,0 +1,21 @@ +# 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", + "iterators/kruskal.jl", +] +``` diff --git a/docs/src/algorithms/traversals.md b/docs/src/algorithms/traversals.md index f4dd09028..e6f6f26b8 100644 --- a/docs/src/algorithms/traversals.md +++ b/docs/src/algorithms/traversals.md @@ -20,6 +20,5 @@ Pages = [ "traversals/greedy_color.jl", "traversals/maxadjvisit.jl", "traversals/randomwalks.jl", - "traversals/iterators.jl", ] ``` diff --git a/src/Graphs.jl b/src/Graphs.jl index 85c7de79f..808afdf73 100644 --- a/src/Graphs.jl +++ b/src/Graphs.jl @@ -183,7 +183,7 @@ export is_cyclic, topological_sort_by_dfs, dfs_tree, dfs_parents, # iterators -VertexIterator, DFSIterator, BFSIterator, iterate, +DFSIterator, BFSIterator, KruskalIterator, # random randomwalk, @@ -489,7 +489,7 @@ include("traversals/dfs.jl") include("traversals/maxadjvisit.jl") include("traversals/randomwalks.jl") include("traversals/diffusion.jl") -include("traversals/iterators.jl") +include("iterators/iterators.jl") include("connectivity.jl") include("distance.jl") include("editdist.jl") diff --git a/src/iterators/bfs.jl b/src/iterators/bfs.jl new file mode 100644 index 000000000..8947615a2 --- /dev/null +++ b/src/iterators/bfs.jl @@ -0,0 +1,104 @@ +""" + 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. + +# 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 + +julia> for node in BFSIterator(g,[1,3]) + display(node) + end +1 +2 +3 +4 +5 +3 +1 +4 +5 +2 +``` +""" +struct BFSIterator{S} <: VertexIterator + graph::AbstractGraph + source::S +end + +BFSIterator(g::AbstractGraph) = BFSIterator(g, one(eltype(g))) + + +""" + Base.iterate(t::BFSIterator) + +First iteration to visit each vertex in a graph using breadth-first search. +""" +function Base.iterate(t::BFSIterator) + visited = falses(nv(t.graph)) + if t.source isa Number + visited[t.source] = true + return (t.source, SingleSourceIteratorState(visited, [t.source])) + else + init_source = first(t.source) + visited[init_source] = true + return (init_source, MultiSourceIteratorState(visited, [init_source], 1)) + end +end + + +""" + Base.iterate(t::BFSIterator, state::SingleSourceIteratorState) + +Iterator to visit each vertex in a graph using breadth-first search. +""" +function Base.iterate(t::BFSIterator, state::SingleSourceIteratorState) + while !isempty(state.queue) + for node in outneighbors(t.graph, state.queue[1]) + if !state.visited[node] + push!(state.queue, node) + state.visited[node] = true + return (node, state) + end + end + popfirst!(state.queue) + end +end + + +""" + Base.iterate(t::BFSIterator, state::SingleSourceIteratorState) + +Iterator to visit each vertex in a graph using breadth-first search. +""" +function Base.iterate(t::BFSIterator, state::MultiSourceIteratorState) + while !isempty(state.queue) + for node in outneighbors(t.graph, state.queue[1]) + if !state.visited[node] + push!(state.queue, node) + state.visited[node] = true + return (node, state) + end + end + popfirst!(state.queue) + end + # reset state and begin traversal at next source + state.source_id += 1 + state.source_id > length(t.source) && return nothing + init_source =t.source[state.source_id] + state.visited .= false + state.visited[init_source] = true + state.queue = [init_source] + return (init_source, state) +end \ No newline at end of file diff --git a/src/iterators/dfs.jl b/src/iterators/dfs.jl new file mode 100644 index 000000000..f4f33dba2 --- /dev/null +++ b/src/iterators/dfs.jl @@ -0,0 +1,104 @@ +""" + 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 + +julia> for node in DFSIterator(g,[1,5]) + display(node) + end +1 +2 +4 +3 +5 +5 +3 +1 +2 +4 +``` +""" +struct DFSIterator{S} <: VertexIterator + graph::AbstractGraph + source::S +end + +DFSIterator(g::AbstractGraph) = DFSIterator(g, one(eltype(g))) + + +""" + Base.iterate(t::DFSIterator) + +First iteration to visit each vertex in a graph using depth-first search. +""" +function Base.iterate(t::DFSIterator) + visited = falses(nv(t.graph)) + if t.source isa Number + visited[t.source] = true + return (t.source, SingleSourceIteratorState(visited, [t.source])) + else + init_source = first(t.source) + visited[init_source] = true + return (init_source, MultiSourceIteratorState(visited, [init_source], 1)) + end +end + + +""" + Base.iterate(t::DFSIterator, state::SingleSourceIteratorState) + +Iterator to visit each vertex in a graph using depth-first search. +""" +function Base.iterate(t::DFSIterator, state::SingleSourceIteratorState) + while !isempty(state.queue) + for node in outneighbors(t.graph, state.queue[end]) + if !state.visited[node] + push!(state.queue, node) + state.visited[node] = true + return (node, state) + end + end + pop!(state.queue) + end +end + + +""" + Base.iterate(t::DFSIterator, state::MultiSourceIteratorState) + +Iterator to visit each vertex in a graph using depth-first search. +""" +function Base.iterate(t::DFSIterator, state::MultiSourceIteratorState) + while !isempty(state.queue) + for node in outneighbors(t.graph, state.queue[end]) + if !state.visited[node] + push!(state.queue, node) + state.visited[node] = true + return (node, state) + end + end + pop!(state.queue) + end + # reset state and begin traversal at next source + state.source_id += 1 + state.source_id > length(t.source) && return nothing + init_source =t.source[state.source_id] + state.visited .= false + state.visited[init_source] = true + state.queue = [init_source] + return (init_source, state) +end \ No newline at end of file diff --git a/src/iterators/iterators.jl b/src/iterators/iterators.jl new file mode 100644 index 000000000..980235dc2 --- /dev/null +++ b/src/iterators/iterators.jl @@ -0,0 +1,65 @@ +""" + abstract type Iterator + +`Iterator` is an abstract type which specifies a particular algorithm to use when iterating through a graph. +""" +abstract type Iterator end + + +""" + abstract type VertexIterator <: Iterator + +`VertexIterator` is an abstract type to iterate through graph vertices. +""" +abstract type VertexIterator <: Iterator end + + +""" + abstract type EdgeIterator <: Iterator + +`EdgeIterator` is an abstract type to iterate through graph edges. +""" +abstract type EdgeIterator <: Iterator end + + +""" + abstract type AbstractIteratorState + +`IteratorState` is an abstract type to hold the current state of iteration which is need for the Base.iterate() function. +""" +abstract type AbstractIteratorState end + + +""" + mutable struct SingleSourceIteratorState + +`SingleSourceIteratorState` is a struct to hold the current state of iteration which is need for the Base.iterate() function. It is a basic implementation used for depth-first or breadth-first iterators when a single source is supplied. +""" +mutable struct SingleSourceIteratorState <: AbstractIteratorState + visited::BitArray + queue::Vector{Int} +end + + +""" + mutable struct MultiSourceIteratorState + +`MultiSourceIteratorState` is a struct to hold the current state of iteration which is need for Julia's Base.iterate() function. It is a basic implementation used for depth-first or breadth-first iterators when mutltiple sources are supplied. +""" +mutable struct MultiSourceIteratorState <: AbstractIteratorState + visited::BitArray + queue::Vector{Int} + source_id::Int +end + + +include("bfs.jl") +include("dfs.jl") +include("kruskal.jl") + + + + + + + diff --git a/src/iterators/kruskal.jl b/src/iterators/kruskal.jl new file mode 100644 index 000000000..a2bd62b80 --- /dev/null +++ b/src/iterators/kruskal.jl @@ -0,0 +1,67 @@ +# The kruskal algorithms are largely copied from Graphs/src/spanningtrees/kruskal.jl + +struct KruskalIterator <: EdgeIterator + graph::AbstractGraph + connected_vs::IntDisjointSets + distmx::AbstractMatrix + edge_list + + function KruskalIterator(graph, distmx=weights(g); minimize=true) + is_directed(graph) && throw(ArgumentError("$graph is a directed graph.")) + weights = Vector{eltype(distmx)}() + sizehint!(weights, ne(graph)) + edge_list = collect(edges(graph)) + for e in edge_list + push!(weights, distmx[src(e), dst(e)]) + end + e = edge_list[sortperm(weights; rev=!minimize)] + new(graph, IntDisjointSets(nv(graph)), distmx, e) + end +end + + +""" + mutable struct KruskalIteratorState + +`KruskalIteratorState` is a struct to hold the current state of iteration which is need for the Base.iterate() function. +""" +mutable struct KruskalIteratorState <: AbstractIteratorState + edge_id::Int + mst_len::Int +end + + +function Base.iterate(t::KruskalIterator, state::KruskalIteratorState=KruskalIteratorState(1,1)) + while state.mst_len <= (nv(t.graph)-1) + i = state.edge_id + if !in_same_set(t.connected_vs, src(t.edge_list[i]), dst(t.edge_list[i])) + union!(t.connected_vs, src(t.edge_list[i]), dst(t.edge_list[i])) + state.mst_len += 1 + return (t.edge_list[i], state) + end + state.edge_id += 1 + end +end + + +function traverse_kruskal_mst(t::EdgeIterator, state::SingleSourceIteratorState) + connected_vs = IntDisjointSets(nv(g)) + + mst = Vector{edgetype(g)}() + sizehint!(mst, nv(g) - 1) + + weights = Vector{T}() + sizehint!(weights, ne(g)) + edge_list = collect(edges(g)) + for e in edge_list + push!(weights, distmx[src(e), dst(e)]) + end + + for e in edge_list[sortperm(weights; rev=!minimize)] + if !in_same_set(connected_vs, src(e), dst(e)) + union!(connected_vs, src(e), dst(e)) + push!(mst, e) + (length(mst) >= nv(g) - 1) && break + end + end +end \ No newline at end of file diff --git a/test/traversals/iterators.jl b/test/iterators/iterators.jl similarity index 100% rename from test/traversals/iterators.jl rename to test/iterators/iterators.jl diff --git a/test/runtests.jl b/test/runtests.jl index d24b4e028..6249a87a9 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -84,7 +84,7 @@ tests = [ "traversals/maxadjvisit", "traversals/randomwalks", "traversals/diffusion", - "traversals/iterators", + "iterators/iterators", "community/cliques", "community/core-periphery", "community/label_propagation",