Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dfs_labeled_edges reporting of reverse edges due to depth_limit. #6240

Merged
merged 3 commits into from
Dec 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 9 additions & 4 deletions networkx/algorithms/traversal/depth_first_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,12 +364,15 @@ def dfs_labeled_edges(G, source=None, depth_limit=None):
edges: generator
A generator of triples of the form (*u*, *v*, *d*), where (*u*,
*v*) is the edge being explored in the depth-first search and *d*
is one of the strings 'forward', 'nontree', or 'reverse'. A
'forward' edge is one in which *u* has been visited but *v* has
is one of the strings 'forward', 'nontree', 'reverse', or 'reverse-depth_limit'.
A 'forward' edge is one in which *u* has been visited but *v* has
not. A 'nontree' edge is one in which both *u* and *v* have been
visited but the edge is not in the DFS tree. A 'reverse' edge is
on in which both *u* and *v* have been visited and the edge is in
the DFS tree.
one in which both *u* and *v* have been visited and the edge is in
the DFS tree. When the `depth_limit` is reached via a 'forward' edge,
a 'reverse' edge is immediately generated rather than the subtree
being explored. To indicate this flavor of 'reverse' edge, the string
yielded is 'reverse-depth_limit'.

Examples
--------
Expand Down Expand Up @@ -436,6 +439,8 @@ def dfs_labeled_edges(G, source=None, depth_limit=None):
visited.add(child)
if depth_now > 1:
stack.append((child, depth_now - 1, iter(G[child])))
else:
yield parent, child, "reverse-depth_limit"
except StopIteration:
stack.pop()
if stack:
Expand Down
103 changes: 101 additions & 2 deletions networkx/algorithms/traversal/tests/test_dfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,43 @@ def test_dfs_labeled_edges(self):
edges = list(nx.dfs_labeled_edges(self.G, source=0))
forward = [(u, v) for (u, v, d) in edges if d == "forward"]
assert forward == [(0, 0), (0, 1), (1, 2), (2, 4), (1, 3)]
assert edges == [
(0, 0, "forward"),
(0, 1, "forward"),
(1, 0, "nontree"),
(1, 2, "forward"),
(2, 1, "nontree"),
(2, 4, "forward"),
(4, 2, "nontree"),
(4, 0, "nontree"),
(2, 4, "reverse"),
(1, 2, "reverse"),
(1, 3, "forward"),
(3, 1, "nontree"),
(3, 0, "nontree"),
(1, 3, "reverse"),
(0, 1, "reverse"),
(0, 3, "nontree"),
(0, 4, "nontree"),
(0, 0, "reverse"),
]

def test_dfs_labeled_disconnected_edges(self):
edges = list(nx.dfs_labeled_edges(self.D))
forward = [(u, v) for (u, v, d) in edges if d == "forward"]
assert forward == [(0, 0), (0, 1), (2, 2), (2, 3)]
assert edges == [
(0, 0, "forward"),
(0, 1, "forward"),
(1, 0, "nontree"),
(0, 1, "reverse"),
(0, 0, "reverse"),
(2, 2, "forward"),
(2, 3, "forward"),
(3, 2, "nontree"),
(2, 3, "reverse"),
(2, 2, "reverse"),
]

def test_dfs_tree_isolates(self):
G = nx.Graph()
Expand Down Expand Up @@ -141,12 +173,79 @@ def test_dls_edges(self):
edges = nx.dfs_edges(self.G, source=9, depth_limit=4)
assert list(edges) == [(9, 8), (8, 7), (7, 2), (2, 1), (2, 3), (9, 10)]

def test_dls_labeled_edges(self):
def test_dls_labeled_edges_depth_1(self):
edges = list(nx.dfs_labeled_edges(self.G, source=5, depth_limit=1))
forward = [(u, v) for (u, v, d) in edges if d == "forward"]
assert forward == [(5, 5), (5, 4), (5, 6)]
# Note: reverse-depth_limit edge types were not reported before gh-6240
assert edges == [
(5, 5, "forward"),
(5, 4, "forward"),
(5, 4, "reverse-depth_limit"),
(5, 6, "forward"),
(5, 6, "reverse-depth_limit"),
(5, 5, "reverse"),
]

def test_dls_labeled_disconnected_edges(self):
def test_dls_labeled_edges_depth_2(self):
edges = list(nx.dfs_labeled_edges(self.G, source=6, depth_limit=2))
forward = [(u, v) for (u, v, d) in edges if d == "forward"]
assert forward == [(6, 6), (6, 5), (5, 4)]
assert edges == [
(6, 6, "forward"),
(6, 5, "forward"),
(5, 4, "forward"),
(5, 4, "reverse-depth_limit"),
(5, 6, "nontree"),
(6, 5, "reverse"),
(6, 6, "reverse"),
]

def test_dls_labeled_disconnected_edges(self):
edges = list(nx.dfs_labeled_edges(self.D, depth_limit=1))
assert edges == [
(0, 0, "forward"),
(0, 1, "forward"),
(0, 1, "reverse-depth_limit"),
(0, 0, "reverse"),
(2, 2, "forward"),
(2, 3, "forward"),
(2, 3, "reverse-depth_limit"),
(2, 7, "forward"),
(2, 7, "reverse-depth_limit"),
(2, 2, "reverse"),
(8, 8, "forward"),
(8, 7, "nontree"),
(8, 9, "forward"),
(8, 9, "reverse-depth_limit"),
(8, 8, "reverse"),
(10, 10, "forward"),
(10, 9, "nontree"),
(10, 10, "reverse"),
]
# large depth_limit has no impact
edges = list(nx.dfs_labeled_edges(self.D, depth_limit=19))
assert edges == [
(0, 0, "forward"),
(0, 1, "forward"),
(1, 0, "nontree"),
(0, 1, "reverse"),
(0, 0, "reverse"),
(2, 2, "forward"),
(2, 3, "forward"),
(3, 2, "nontree"),
(2, 3, "reverse"),
(2, 7, "forward"),
(7, 2, "nontree"),
(7, 8, "forward"),
(8, 7, "nontree"),
(8, 9, "forward"),
(9, 8, "nontree"),
(9, 10, "forward"),
(10, 9, "nontree"),
(9, 10, "reverse"),
(8, 9, "reverse"),
(7, 8, "reverse"),
(2, 7, "reverse"),
(2, 2, "reverse"),
]