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 all commits
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
39 changes: 21 additions & 18 deletions networkx/algorithms/traversal/breadth_first_search.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"""Basic algorithms for breadth-first searching the nodes of a graph."""
import math
from collections import deque

import networkx as nx
Expand Down Expand Up @@ -44,17 +43,17 @@ def generic_bfs_edges(G, source, neighbors=None, depth_limit=None, sort_neighbor
depth_limit : int, optional(default=len(G))
Specify the maximum search depth.

sort_neighbors : Callable
sort_neighbors : Callable (default=None)

.. deprecated:: 3.2

The sort_neighbors parameter is deprecated and will be removed in
version 3.4. A custom (e.g. sorted) ordering of neighbors can be
specified with the `neighbors` parameter.

A function that takes the list of neighbors of a given node as input,
and returns an iterator over these neighbors but with a custom
ordering.
A function that takes an iterator over nodes as the input, and
returns an iterable of the same nodes with a custom ordering.
For example, `sorted` will sort the nodes in increasing order.

Yields
------
Expand Down Expand Up @@ -149,9 +148,10 @@ def bfs_edges(G, source, reverse=False, depth_limit=None, sort_neighbors=None):
depth_limit : int, optional(default=len(G))
Specify the maximum search depth

sort_neighbors : function
A function that takes the list of neighbors of given node as input, and
returns an *iterator* over these neighbors but with custom ordering.
sort_neighbors : function (default=None)
A function that takes an iterator over nodes as the input, and
returns an iterable of the same nodes with a custom ordering.
For example, `sorted` will sort the nodes in increasing order.

Yields
------
Expand Down Expand Up @@ -210,7 +210,7 @@ def bfs_edges(G, source, reverse=False, depth_limit=None, sort_neighbors=None):
else:
successors = G.neighbors

if callable(sort_neighbors):
if sort_neighbors is not None:
yield from generic_bfs_edges(
G, source, lambda node: iter(sort_neighbors(successors(node))), depth_limit
)
Expand All @@ -236,9 +236,10 @@ def bfs_tree(G, source, reverse=False, depth_limit=None, sort_neighbors=None):
depth_limit : int, optional(default=len(G))
Specify the maximum search depth

sort_neighbors : function
A function that takes the list of neighbors of given node as input, and
returns an *iterator* over these neighbors but with custom ordering.
sort_neighbors : function (default=None)
A function that takes an iterator over nodes as the input, and
returns an iterable of the same nodes with a custom ordering.
For example, `sorted` will sort the nodes in increasing order.

Returns
-------
Expand Down Expand Up @@ -299,9 +300,10 @@ def bfs_predecessors(G, source, depth_limit=None, sort_neighbors=None):
depth_limit : int, optional(default=len(G))
Specify the maximum search depth

sort_neighbors : function
A function that takes the list of neighbors of given node as input, and
returns an *iterator* over these neighbors but with custom ordering.
sort_neighbors : function (default=None)
A function that takes an iterator over nodes as the input, and
returns an iterable of the same nodes with a custom ordering.
For example, `sorted` will sort the nodes in increasing order.

Returns
-------
Expand Down Expand Up @@ -364,9 +366,10 @@ def bfs_successors(G, source, depth_limit=None, sort_neighbors=None):
depth_limit : int, optional(default=len(G))
Specify the maximum search depth

sort_neighbors : function
A function that takes the list of neighbors of given node as input, and
returns an *iterator* over these neighbors but with custom ordering.
sort_neighbors : function (default=None)
A function that takes an iterator over nodes as the input, and
returns an iterable of the same nodes with a custom ordering.
For example, `sorted` will sort the nodes in increasing order.

Returns
-------
Expand Down
109 changes: 84 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._dispatchable
def dfs_edges(G, source=None, depth_limit=None):
def dfs_edges(G, source=None, depth_limit=None, *, sort_neighbors=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,11 @@ def dfs_edges(G, source=None, depth_limit=None):
depth_limit : int, optional (default=len(G))
Specify the maximum search depth.

sort_neighbors : function (default=None)
A function that takes an iterator over nodes as the input, and
returns an iterable of the same nodes with a custom ordering.
For example, `sorted` will sort the nodes in increasing order.

Yields
------
edge: 2-tuple of nodes
Expand Down Expand Up @@ -78,12 +83,18 @@ def dfs_edges(G, source=None, depth_limit=None):
if depth_limit is None:
depth_limit = len(G)

get_children = (
G.neighbors
if sort_neighbors is None
else lambda n: iter(sort_neighbors(G.neighbors(n)))
)

visited = set()
for start in nodes:
if start in visited:
continue
visited.add(start)
stack = [(start, iter(G[start]))]
stack = [(start, get_children(start))]
depth_now = 1
while stack:
parent, children = stack[-1]
Expand All @@ -92,7 +103,7 @@ 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])))
stack.append((child, get_children(child)))
depth_now += 1
break
else:
Expand All @@ -101,7 +112,7 @@ def dfs_edges(G, source=None, depth_limit=None):


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

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

sort_neighbors : function (default=None)
A function that takes an iterator over nodes as the input, and
returns an iterable of the same nodes with a custom ordering.
For example, `sorted` will sort the nodes in increasing order.

Returns
-------
T : NetworkX DiGraph
Expand All @@ -134,20 +150,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_neighbors=sort_neighbors))
return T


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

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

sort_neighbors : function (default=None)
A function that takes an iterator over nodes as the input, and
returns an iterable of the same nodes with a custom ordering.
For example, `sorted` will sort the nodes in increasing order.

Returns
-------
pred: dict
Expand Down Expand Up @@ -194,14 +215,17 @@ 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_neighbors=sort_neighbors)
}


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

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

sort_neighbors : function (default=None)
A function that takes an iterator over nodes as the input, and
returns an iterable of the same nodes with a custom ordering.
For example, `sorted` will sort the nodes in increasing order.

Returns
-------
succ: dict
Expand Down Expand Up @@ -248,17 +277,22 @@ 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_neighbors=sort_neighbors,
):
d[s].append(t)
return dict(d)


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

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

sort_neighbors : function (default=None)
A function that takes an iterator over nodes as the input, and
returns an iterable of the same nodes with a custom ordering.
For example, `sorted` will sort the nodes in increasing order.

Returns
-------
nodes: generator
Expand Down Expand Up @@ -302,15 +341,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_neighbors=sort_neighbors
)
return (v for u, v, d in edges if d == "reverse")


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

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

sort_neighbors : function (default=None)
A function that takes an iterator over nodes as the input, and
returns an iterable of the same nodes with a custom ordering.
For example, `sorted` will sort the nodes in increasing order.

Returns
-------
nodes: generator
Expand Down Expand Up @@ -355,14 +401,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_neighbors=sort_neighbors
)
return (v for u, v, d in edges if d == "forward")


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

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

sort_neighbors : function (default=None)
A function that takes an iterator over nodes as the input, and
returns an iterable of the same nodes with a custom ordering.
For example, `sorted` will sort the nodes in increasing order.

Returns
-------
edges: generator
Expand Down Expand Up @@ -439,13 +492,19 @@ def dfs_labeled_edges(G, source=None, depth_limit=None):
if depth_limit is None:
depth_limit = len(G)

get_children = (
G.neighbors
if sort_neighbors is None
else lambda n: iter(sort_neighbors(G.neighbors(n)))
)

visited = set()
for start in nodes:
if start in visited:
continue
yield start, start, "forward"
visited.add(start)
stack = [(start, iter(G[start]))]
stack = [(start, get_children(start))]
depth_now = 1
while stack:
parent, children = stack[-1]
Expand All @@ -456,7 +515,7 @@ 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])))
stack.append((child, iter(get_children(child))))
depth_now += 1
break
else:
Expand Down