Skip to content

Commit

Permalink
special-case u in vs input: include 0-length path [u] in iterates
Browse files Browse the repository at this point in the history
  • Loading branch information
thchr committed Mar 22, 2024
1 parent bbb5d98 commit 0991093
Show file tree
Hide file tree
Showing 2 changed files with 19 additions and 8 deletions.
14 changes: 12 additions & 2 deletions src/traversals/all_simple_paths.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ the graph `g` from a source vertex `u` to a target vertex `v` or iterable of tar
The iterator's elements (i.e., the paths) can be materialized via `collect` or `iterate`.
Paths are iterated in the order of a depth-first search.
If the requested path has identical source and target vertices, i.e., if `u = v`, a
zero-length path `[u]` is included among the iterates.
## Keyword arguments
The maximum path length (i.e., number of edges) is limited by the keyword argument `cutoff`
(default, `nv(g)-1`). If a path's path length is greater than or equal to `cutoff`, it is
Expand Down Expand Up @@ -74,14 +77,15 @@ mutable struct SimplePathIteratorState{T<:Integer}
# (parent vertex, index of children)
visited::Stack{T} # current path candidate
queued::Vector{T} # remaining targets if path length reached cutoff
self_visited::Bool # in case `u ∈ vs`, we want to return a `[u]` path once only
end
function SimplePathIteratorState(spi::SimplePathIterator{T}) where {T<:Integer}
stack = Stack{Tuple{T,T}}()
visited = Stack{T}()
queued = Vector{T}()
push!(visited, spi.u) # add a starting vertex to the path candidate
push!(stack, (spi.u, 1)) # add a child node with index 1
return SimplePathIteratorState{T}(stack, visited, queued)
return SimplePathIteratorState{T}(stack, visited, queued, false)
end

function _stepback!(state::SimplePathIteratorState) # updates iterator state.
Expand Down Expand Up @@ -112,7 +116,7 @@ function Base.iterate(
end

child = children[next_child_index]
next_child_index′ = pop!(state.stack)[2] # move child index forward
next_child_index′ = pop!(state.stack)[2] # move child index forward
push!(state.stack, (parent_node, next_child_index′ + 1)) #
child in state.visited && continue

Expand Down Expand Up @@ -146,4 +150,10 @@ function Base.iterate(
end
end
end

# special-case: when `vs` includes `u`, return also a 1-vertex, 0-length path `[u]`
if spi.u in spi.vs && !state.self_visited
state.self_visited = true
return [spi.u], state
end
end
13 changes: 7 additions & 6 deletions test/traversals/all_simple_paths.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
# printing
@test sprint(show, paths) == "SimplePathIterator{SimpleGraph{Int64}}(1 → 4)"


# single path with cutoff
g = complete_graph(4)
@test collect(all_simple_paths(g, 1, 4; cutoff=2)) == [[1, 2, 4], [1, 3, 4], [1, 4]]
Expand Down Expand Up @@ -82,12 +81,14 @@
paths′ = all_simple_paths(g, 1, 6; cutoff=typemax(Int))
@test Set(paths) == Set(paths′) == Set([[1, 2, 3, 4, 5, 6]])

# source equals targets
g = SimpleGraph(4)
paths = all_simple_paths(g, 1, 1)
@test Set(paths) == Set{Int}()
# same source and target vertex
g = path_graph(4)
@test Set(all_simple_paths(g, 1, 1)) == Set([[1]])
@test Set(all_simple_paths(g, 3, 3)) == Set([[3]])
@test Set(all_simple_paths(g, 1, [1, 1])) == Set([[1]])
@test Set(all_simple_paths(g, 1, [1, 4])) == Set([[1], [1, 2, 3, 4]])

# cutoff prunes paths (note: path length is node - 1)
# cutoff prunes paths (note: maximum path length below is `nv(g) - 1`)
g = complete_graph(4)
paths = all_simple_paths(g, 1, 2; cutoff=1)
@test Set(paths) == Set([[1, 2]])
Expand Down

0 comments on commit 0991093

Please sign in to comment.