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

ENH : added sort_neighbors to all functions in depth_first_search.py #7196

Merged
merged 22 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
78201d7
added sort_children to all dfs funcs, added dfs tests, updated links …
Schefflera-Arboricola Dec 29, 2023
03ce2f7
updated sorting of children nodes- using iter
Schefflera-Arboricola Jan 1, 2024
bc44d9a
bug : fixed get_children
Schefflera-Arboricola Jan 1, 2024
9f9c27f
added notes to docs
Schefflera-Arboricola Jan 2, 2024
39d7943
Merge branch 'networkx:main' into dfs_sort
Schefflera-Arboricola Jan 8, 2024
a09da19
changed sort_children to sort_neighbors
Schefflera-Arboricola Jan 8, 2024
2b5b256
removed note and updated sort_neighbors desc
Schefflera-Arboricola Jan 8, 2024
ba825a0
updated sort_neighbors desc in bfs
Schefflera-Arboricola Jan 8, 2024
c323edd
style fix and typo fix
Schefflera-Arboricola Jan 8, 2024
5bd671b
style fix and updated dfs tests
Schefflera-Arboricola Jan 8, 2024
4e9bef6
Merge branch 'main' into dfs_sort
Schefflera-Arboricola Jan 13, 2024
052113e
rm callable
Schefflera-Arboricola Jan 13, 2024
632d696
rm sorted_children
Schefflera-Arboricola Jan 13, 2024
67f2d84
updated sort_neighbors docs
Schefflera-Arboricola Jan 13, 2024
09a18b1
Cosmetic changes to added tests.
rossbar Jan 17, 2024
e5649e9
making get_children def. concise
Schefflera-Arboricola Jan 21, 2024
ebe17d8
Merge branch 'networkx:main' into dfs_sort
Schefflera-Arboricola Jan 21, 2024
9860cd6
making get_children def concise
Schefflera-Arboricola Jan 21, 2024
0afea09
style fix
Schefflera-Arboricola Jan 21, 2024
3197aa8
style fix
Schefflera-Arboricola Jan 22, 2024
0d72845
converting sort_neighbors keyword arg only
Schefflera-Arboricola Jan 22, 2024
f650c2c
style fix
Schefflera-Arboricola Jan 22, 2024
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
90 changes: 65 additions & 25 deletions networkx/algorithms/traversal/depth_first_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@


@nx._dispatch
def dfs_edges(G, source=None, depth_limit=None):
def dfs_edges(G, source=None, depth_limit=None, sort_children=None):
"""Iterate over edges in a depth-first-search (DFS).

Perform a depth-first-search over the nodes of `G` and yield
Expand All @@ -33,6 +33,10 @@ def dfs_edges(G, source=None, depth_limit=None):
depth_limit : int, optional (default=len(G))
Specify the maximum search depth.

sort_children : function
A function that takes the list of children of given node as input, and
returns an *iterator* over these children but with custom ordering.

Yields
------
edge: 2-tuple of nodes
Expand Down Expand Up @@ -83,7 +87,7 @@ def dfs_edges(G, source=None, depth_limit=None):
if start in visited:
continue
visited.add(start)
stack = [(start, iter(G[start]))]
stack = [(start, iter(sort_children(G[start]) if sort_children else G[start]))]
depth_now = 1
while stack:
parent, children = stack[-1]
Expand All @@ -92,7 +96,10 @@ def dfs_edges(G, source=None, depth_limit=None):
yield parent, child
visited.add(child)
if depth_now < depth_limit:
stack.append((child, iter(G[child])))
sorted_children = (
sort_children(G[child]) if sort_children else G[child]
)
stack.append((child, iter(sorted_children)))
depth_now += 1
break
else:
Expand All @@ -101,7 +108,7 @@ def dfs_edges(G, source=None, depth_limit=None):


@nx._dispatch
def dfs_tree(G, source=None, depth_limit=None):
def dfs_tree(G, source=None, depth_limit=None, sort_children=None):
"""Returns oriented tree constructed from a depth-first-search from source.

Parameters
Expand All @@ -114,6 +121,10 @@ def dfs_tree(G, source=None, depth_limit=None):
depth_limit : int, optional (default=len(G))
Specify the maximum search depth.

sort_children : function
A function that takes the list of children of given node as input, and
returns an *iterator* over these children but with custom ordering.

Returns
-------
T : NetworkX DiGraph
Expand All @@ -134,20 +145,20 @@ def dfs_tree(G, source=None, depth_limit=None):
dfs_preorder_nodes
dfs_postorder_nodes
dfs_labeled_edges
edge_dfs
bfs_tree
:func:`~networkx.algorithms.traversal.edgedfs.edge_dfs`
:func:`~networkx.algorithms.traversal.breadth_first_search.bfs_tree`
"""
T = nx.DiGraph()
if source is None:
T.add_nodes_from(G)
else:
T.add_node(source)
T.add_edges_from(dfs_edges(G, source, depth_limit))
T.add_edges_from(dfs_edges(G, source, depth_limit, sort_children))
return T


@nx._dispatch
def dfs_predecessors(G, source=None, depth_limit=None):
def dfs_predecessors(G, source=None, depth_limit=None, sort_children=None):
"""Returns dictionary of predecessors in depth-first-search from source.

Parameters
Expand All @@ -163,6 +174,10 @@ def dfs_predecessors(G, source=None, depth_limit=None):
depth_limit : int, optional (default=len(G))
Specify the maximum search depth.

sort_children : function
A function that takes the list of children of given node as input, and
returns an *iterator* over these children but with custom ordering.

Returns
-------
pred: dict
Expand Down Expand Up @@ -194,14 +209,14 @@ def dfs_predecessors(G, source=None, depth_limit=None):
dfs_preorder_nodes
dfs_postorder_nodes
dfs_labeled_edges
edge_dfs
bfs_tree
:func:`~networkx.algorithms.traversal.edgedfs.edge_dfs`
:func:`~networkx.algorithms.traversal.breadth_first_search.bfs_tree`
"""
return {t: s for s, t in dfs_edges(G, source, depth_limit)}
return {t: s for s, t in dfs_edges(G, source, depth_limit, sort_children)}


@nx._dispatch
def dfs_successors(G, source=None, depth_limit=None):
def dfs_successors(G, source=None, depth_limit=None, sort_children=None):
"""Returns dictionary of successors in depth-first-search from source.

Parameters
Expand All @@ -217,6 +232,10 @@ def dfs_successors(G, source=None, depth_limit=None):
depth_limit : int, optional (default=len(G))
Specify the maximum search depth.

sort_children : function
A function that takes the list of children of given node as input, and
returns an *iterator* over these children but with custom ordering.

Returns
-------
succ: dict
Expand Down Expand Up @@ -248,17 +267,19 @@ def dfs_successors(G, source=None, depth_limit=None):
dfs_preorder_nodes
dfs_postorder_nodes
dfs_labeled_edges
edge_dfs
bfs_tree
:func:`~networkx.algorithms.traversal.edgedfs.edge_dfs`
:func:`~networkx.algorithms.traversal.breadth_first_search.bfs_tree`
"""
d = defaultdict(list)
for s, t in dfs_edges(G, source=source, depth_limit=depth_limit):
for s, t in dfs_edges(
G, source=source, depth_limit=depth_limit, sort_children=sort_children
):
d[s].append(t)
return dict(d)


@nx._dispatch
def dfs_postorder_nodes(G, source=None, depth_limit=None):
def dfs_postorder_nodes(G, source=None, depth_limit=None, sort_children=None):
"""Generate nodes in a depth-first-search post-ordering starting at source.

Parameters
Expand All @@ -271,6 +292,10 @@ def dfs_postorder_nodes(G, source=None, depth_limit=None):
depth_limit : int, optional (default=len(G))
Specify the maximum search depth.

sort_children : function
A function that takes the list of children of given node as input, and
returns an *iterator* over these children but with custom ordering.

Returns
-------
nodes: generator
Expand Down Expand Up @@ -302,15 +327,17 @@ def dfs_postorder_nodes(G, source=None, depth_limit=None):
dfs_edges
dfs_preorder_nodes
dfs_labeled_edges
edge_dfs
bfs_tree
:func:`~networkx.algorithms.traversal.edgedfs.edge_dfs`
:func:`~networkx.algorithms.traversal.breadth_first_search.bfs_tree`
"""
edges = nx.dfs_labeled_edges(G, source=source, depth_limit=depth_limit)
edges = nx.dfs_labeled_edges(
G, source=source, depth_limit=depth_limit, sort_children=sort_children
)
return (v for u, v, d in edges if d == "reverse")


@nx._dispatch
def dfs_preorder_nodes(G, source=None, depth_limit=None):
def dfs_preorder_nodes(G, source=None, depth_limit=None, sort_children=None):
"""Generate nodes in a depth-first-search pre-ordering starting at source.

Parameters
Expand All @@ -324,6 +351,10 @@ def dfs_preorder_nodes(G, source=None, depth_limit=None):
depth_limit : int, optional (default=len(G))
Specify the maximum search depth.

sort_children : function
A function that takes the list of children of given node as input, and
returns an *iterator* over these children but with custom ordering.

Returns
-------
nodes: generator
Expand Down Expand Up @@ -355,14 +386,16 @@ def dfs_preorder_nodes(G, source=None, depth_limit=None):
dfs_edges
dfs_postorder_nodes
dfs_labeled_edges
bfs_edges
:func:`~networkx.algorithms.traversal.breadth_first_search.bfs_edges`
"""
edges = nx.dfs_labeled_edges(G, source=source, depth_limit=depth_limit)
edges = nx.dfs_labeled_edges(
G, source=source, depth_limit=depth_limit, sort_children=sort_children
)
return (v for u, v, d in edges if d == "forward")


@nx._dispatch
def dfs_labeled_edges(G, source=None, depth_limit=None):
def dfs_labeled_edges(G, source=None, depth_limit=None, sort_children=None):
"""Iterate over edges in a depth-first-search (DFS) labeled by type.

Parameters
Expand All @@ -376,6 +409,10 @@ def dfs_labeled_edges(G, source=None, depth_limit=None):
depth_limit : int, optional (default=len(G))
Specify the maximum search depth.

sort_children : function
A function that takes the list of children of given node as input, and
returns an *iterator* over these children but with custom ordering.

Returns
-------
edges: generator
Expand Down Expand Up @@ -445,7 +482,7 @@ def dfs_labeled_edges(G, source=None, depth_limit=None):
continue
yield start, start, "forward"
visited.add(start)
stack = [(start, iter(G[start]))]
stack = [(start, iter(sort_children(G[start]) if sort_children else G[start]))]
depth_now = 1
while stack:
parent, children = stack[-1]
Expand All @@ -456,7 +493,10 @@ def dfs_labeled_edges(G, source=None, depth_limit=None):
yield parent, child, "forward"
visited.add(child)
if depth_now < depth_limit:
stack.append((child, iter(G[child])))
sorted_children = (
sort_children(G[child]) if sort_children else G[child]
)
stack.append((child, iter(sorted_children)))
depth_now += 1
break
else:
Expand Down
56 changes: 56 additions & 0 deletions networkx/algorithms/traversal/tests/test_dfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@ def test_dfs_edges(self):
edges = nx.dfs_edges(self.D)
assert list(edges) == [(0, 1), (2, 3)]

def test_dfs_edges_sorting(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (1, 2), (1, 3), (2, 4), (3, 0), (0, 4)])
edges_asc = nx.dfs_edges(G, source=0, sort_children=sorted)
sorted_desc = lambda x: sorted(x, reverse=True)
edges_desc = nx.dfs_edges(G, source=0, sort_children=sorted_desc)
assert list(edges_asc) == [(0, 1), (1, 2), (2, 4), (1, 3)]
assert list(edges_desc) == [(0, 4), (4, 2), (2, 1), (1, 3)]

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"]
Expand All @@ -80,6 +89,53 @@ def test_dfs_labeled_edges(self):
(0, 0, "reverse"),
]

def test_dfs_labeled_edges_sorting(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (1, 2), (1, 3), (2, 4), (3, 0), (0, 4)])
edges_asc = nx.dfs_labeled_edges(G, source=0, sort_children=sorted)
sorted_desc = lambda x: sorted(x, reverse=True)
edges_desc = nx.dfs_labeled_edges(G, source=0, sort_children=sorted_desc)
assert list(edges_asc) == [
(0, 0, "forward"),
(0, 1, "forward"),
(1, 0, "nontree"),
(1, 2, "forward"),
(2, 1, "nontree"),
(2, 4, "forward"),
(4, 0, "nontree"),
(4, 2, "nontree"),
(2, 4, "reverse"),
(1, 2, "reverse"),
(1, 3, "forward"),
(3, 0, "nontree"),
(3, 1, "nontree"),
(1, 3, "reverse"),
(0, 1, "reverse"),
(0, 3, "nontree"),
(0, 4, "nontree"),
(0, 0, "reverse"),
]
assert list(edges_desc) == [
(0, 0, "forward"),
(0, 4, "forward"),
(4, 2, "forward"),
(2, 4, "nontree"),
(2, 1, "forward"),
(1, 3, "forward"),
(3, 1, "nontree"),
(3, 0, "nontree"),
(1, 3, "reverse"),
(1, 2, "nontree"),
(1, 0, "nontree"),
(2, 1, "reverse"),
(4, 2, "reverse"),
(4, 0, "nontree"),
(0, 4, "reverse"),
(0, 3, "nontree"),
(0, 1, "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"]
Expand Down