Fixed database typo and removed unnecessary class identifier.
This commit is contained in:
parent
00ad49a143
commit
45fb349a7d
5098 changed files with 952558 additions and 85 deletions
|
@ -0,0 +1,5 @@
|
|||
from .beamsearch import *
|
||||
from .breadth_first_search import *
|
||||
from .depth_first_search import *
|
||||
from .edgedfs import *
|
||||
from .edgebfs import *
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,87 @@
|
|||
"""Basic algorithms for breadth-first searching the nodes of a graph."""
|
||||
|
||||
from .breadth_first_search import generic_bfs_edges
|
||||
|
||||
__all__ = ["bfs_beam_edges"]
|
||||
|
||||
|
||||
def bfs_beam_edges(G, source, value, width=None):
|
||||
"""Iterates over edges in a beam search.
|
||||
|
||||
The beam search is a generalized breadth-first search in which only
|
||||
the "best" *w* neighbors of the current node are enqueued, where *w*
|
||||
is the beam width and "best" is an application-specific
|
||||
heuristic. In general, a beam search with a small beam width might
|
||||
not visit each node in the graph.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
|
||||
source : node
|
||||
Starting node for the breadth-first search; this function
|
||||
iterates over only those edges in the component reachable from
|
||||
this node.
|
||||
|
||||
value : function
|
||||
A function that takes a node of the graph as input and returns a
|
||||
real number indicating how "good" it is. A higher value means it
|
||||
is more likely to be visited sooner during the search. When
|
||||
visiting a new node, only the `width` neighbors with the highest
|
||||
`value` are enqueued (in decreasing order of `value`).
|
||||
|
||||
width : int (default = None)
|
||||
The beam width for the search. This is the number of neighbors
|
||||
(ordered by `value`) to enqueue when visiting each new node.
|
||||
|
||||
Yields
|
||||
------
|
||||
edge
|
||||
Edges in the beam search starting from `source`, given as a pair
|
||||
of nodes.
|
||||
|
||||
Examples
|
||||
--------
|
||||
To give nodes with, for example, a higher centrality precedence
|
||||
during the search, set the `value` function to return the centrality
|
||||
value of the node::
|
||||
|
||||
>>> G = nx.karate_club_graph()
|
||||
>>> centrality = nx.eigenvector_centrality(G)
|
||||
>>> source = 0
|
||||
>>> width = 5
|
||||
>>> for u, v in nx.bfs_beam_edges(G, source, centrality.get, width):
|
||||
... print((u, v)) # doctest: +SKIP
|
||||
|
||||
"""
|
||||
|
||||
if width is None:
|
||||
width = len(G)
|
||||
|
||||
def successors(v):
|
||||
"""Returns a list of the best neighbors of a node.
|
||||
|
||||
`v` is a node in the graph `G`.
|
||||
|
||||
The "best" neighbors are chosen according to the `value`
|
||||
function (higher is better). Only the `width` best neighbors of
|
||||
`v` are returned.
|
||||
|
||||
The list returned by this function is in decreasing value as
|
||||
measured by the `value` function.
|
||||
|
||||
"""
|
||||
# TODO The Python documentation states that for small values, it
|
||||
# is better to use `heapq.nlargest`. We should determine the
|
||||
# threshold at which its better to use `heapq.nlargest()`
|
||||
# instead of `sorted()[:]` and apply that optimization here.
|
||||
#
|
||||
# If `width` is greater than the number of neighbors of `v`, all
|
||||
# neighbors are returned by the semantics of slicing in
|
||||
# Python. This occurs in the special case that the user did not
|
||||
# specify a `width`: in this case all neighbors are always
|
||||
# returned, so this is just a (slower) implementation of
|
||||
# `bfs_edges(G, source)` but with a sorted enqueue step.
|
||||
return iter(sorted(G.neighbors(v), key=value, reverse=True)[:width])
|
||||
|
||||
yield from generic_bfs_edges(G, source, successors)
|
|
@ -0,0 +1,399 @@
|
|||
"""Basic algorithms for breadth-first searching the nodes of a graph."""
|
||||
import networkx as nx
|
||||
from collections import deque
|
||||
|
||||
__all__ = [
|
||||
"bfs_edges",
|
||||
"bfs_tree",
|
||||
"bfs_predecessors",
|
||||
"bfs_successors",
|
||||
"descendants_at_distance",
|
||||
]
|
||||
|
||||
|
||||
def generic_bfs_edges(G, source, neighbors=None, depth_limit=None, sort_neighbors=None):
|
||||
"""Iterate over edges in a breadth-first search.
|
||||
|
||||
The breadth-first search begins at `source` and enqueues the
|
||||
neighbors of newly visited nodes specified by the `neighbors`
|
||||
function.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
|
||||
source : node
|
||||
Starting node for the breadth-first search; this function
|
||||
iterates over only those edges in the component reachable from
|
||||
this node.
|
||||
|
||||
neighbors : function
|
||||
A function that takes a newly visited node of the graph as input
|
||||
and returns an *iterator* (not just a list) of nodes that are
|
||||
neighbors of that node. If not specified, this is just the
|
||||
``G.neighbors`` method, but in general it can be any function
|
||||
that returns an iterator over some or all of the neighbors of a
|
||||
given node, in any order.
|
||||
|
||||
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.
|
||||
|
||||
Yields
|
||||
------
|
||||
edge
|
||||
Edges in the breadth-first search starting from `source`.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.path_graph(3)
|
||||
>>> print(list(nx.bfs_edges(G, 0)))
|
||||
[(0, 1), (1, 2)]
|
||||
>>> print(list(nx.bfs_edges(G, source=0, depth_limit=1)))
|
||||
[(0, 1)]
|
||||
|
||||
Notes
|
||||
-----
|
||||
This implementation is from `PADS`_, which was in the public domain
|
||||
when it was first accessed in July, 2004. The modifications
|
||||
to allow depth limits are based on the Wikipedia article
|
||||
"`Depth-limited-search`_".
|
||||
|
||||
.. _PADS: http://www.ics.uci.edu/~eppstein/PADS/BFS.py
|
||||
.. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search
|
||||
"""
|
||||
if callable(sort_neighbors):
|
||||
_neighbors = neighbors
|
||||
neighbors = lambda node: iter(sort_neighbors(_neighbors(node)))
|
||||
|
||||
visited = {source}
|
||||
if depth_limit is None:
|
||||
depth_limit = len(G)
|
||||
queue = deque([(source, depth_limit, neighbors(source))])
|
||||
while queue:
|
||||
parent, depth_now, children = queue[0]
|
||||
try:
|
||||
child = next(children)
|
||||
if child not in visited:
|
||||
yield parent, child
|
||||
visited.add(child)
|
||||
if depth_now > 1:
|
||||
queue.append((child, depth_now - 1, neighbors(child)))
|
||||
except StopIteration:
|
||||
queue.popleft()
|
||||
|
||||
|
||||
def bfs_edges(G, source, reverse=False, depth_limit=None, sort_neighbors=None):
|
||||
"""Iterate over edges in a breadth-first-search starting at source.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
|
||||
source : node
|
||||
Specify starting node for breadth-first search; this function
|
||||
iterates over only those edges in the component reachable from
|
||||
this node.
|
||||
|
||||
reverse : bool, optional
|
||||
If True traverse a directed graph in the reverse direction
|
||||
|
||||
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.
|
||||
|
||||
Returns
|
||||
-------
|
||||
edges: generator
|
||||
A generator of edges in the breadth-first-search.
|
||||
|
||||
Examples
|
||||
--------
|
||||
To get the edges in a breadth-first search::
|
||||
|
||||
>>> G = nx.path_graph(3)
|
||||
>>> list(nx.bfs_edges(G, 0))
|
||||
[(0, 1), (1, 2)]
|
||||
>>> list(nx.bfs_edges(G, source=0, depth_limit=1))
|
||||
[(0, 1)]
|
||||
|
||||
To get the nodes in a breadth-first search order::
|
||||
|
||||
>>> G = nx.path_graph(3)
|
||||
>>> root = 2
|
||||
>>> edges = nx.bfs_edges(G, root)
|
||||
>>> nodes = [root] + [v for u, v in edges]
|
||||
>>> nodes
|
||||
[2, 1, 0]
|
||||
|
||||
Notes
|
||||
-----
|
||||
The naming of this function is very similar to edge_bfs. The difference
|
||||
is that 'edge_bfs' yields edges even if they extend back to an already
|
||||
explored node while 'bfs_edges' yields the edges of the tree that results
|
||||
from a breadth-first-search (BFS) so no edges are reported if they extend
|
||||
to already explored nodes. That means 'edge_bfs' reports all edges while
|
||||
'bfs_edges' only reports those traversed by a node-based BFS. Yet another
|
||||
description is that 'bfs_edges' reports the edges traversed during BFS
|
||||
while 'edge_bfs' reports all edges in the order they are explored.
|
||||
|
||||
Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py.
|
||||
by D. Eppstein, July 2004. The modifications
|
||||
to allow depth limits based on the Wikipedia article
|
||||
"`Depth-limited-search`_".
|
||||
|
||||
.. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search
|
||||
|
||||
See Also
|
||||
--------
|
||||
bfs_tree
|
||||
dfs_edges
|
||||
edge_bfs
|
||||
|
||||
"""
|
||||
if reverse and G.is_directed():
|
||||
successors = G.predecessors
|
||||
else:
|
||||
successors = G.neighbors
|
||||
yield from generic_bfs_edges(G, source, successors, depth_limit, sort_neighbors)
|
||||
|
||||
|
||||
def bfs_tree(G, source, reverse=False, depth_limit=None, sort_neighbors=None):
|
||||
"""Returns an oriented tree constructed from of a breadth-first-search
|
||||
starting at source.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
|
||||
source : node
|
||||
Specify starting node for breadth-first search
|
||||
|
||||
reverse : bool, optional
|
||||
If True traverse a directed graph in the reverse direction
|
||||
|
||||
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.
|
||||
|
||||
Returns
|
||||
-------
|
||||
T: NetworkX DiGraph
|
||||
An oriented tree
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.path_graph(3)
|
||||
>>> print(list(nx.bfs_tree(G, 1).edges()))
|
||||
[(1, 0), (1, 2)]
|
||||
>>> H = nx.Graph()
|
||||
>>> nx.add_path(H, [0, 1, 2, 3, 4, 5, 6])
|
||||
>>> nx.add_path(H, [2, 7, 8, 9, 10])
|
||||
>>> print(sorted(list(nx.bfs_tree(H, source=3, depth_limit=3).edges())))
|
||||
[(1, 0), (2, 1), (2, 7), (3, 2), (3, 4), (4, 5), (5, 6), (7, 8)]
|
||||
|
||||
|
||||
Notes
|
||||
-----
|
||||
Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py
|
||||
by D. Eppstein, July 2004. The modifications
|
||||
to allow depth limits based on the Wikipedia article
|
||||
"`Depth-limited-search`_".
|
||||
|
||||
.. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search
|
||||
|
||||
See Also
|
||||
--------
|
||||
dfs_tree
|
||||
bfs_edges
|
||||
edge_bfs
|
||||
"""
|
||||
T = nx.DiGraph()
|
||||
T.add_node(source)
|
||||
edges_gen = bfs_edges(
|
||||
G,
|
||||
source,
|
||||
reverse=reverse,
|
||||
depth_limit=depth_limit,
|
||||
sort_neighbors=sort_neighbors,
|
||||
)
|
||||
T.add_edges_from(edges_gen)
|
||||
return T
|
||||
|
||||
|
||||
def bfs_predecessors(G, source, depth_limit=None, sort_neighbors=None):
|
||||
"""Returns an iterator of predecessors in breadth-first-search from source.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
|
||||
source : node
|
||||
Specify starting node for breadth-first search
|
||||
|
||||
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.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pred: iterator
|
||||
(node, predecessors) iterator where predecessors is the list of
|
||||
predecessors of the node.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.path_graph(3)
|
||||
>>> print(dict(nx.bfs_predecessors(G, 0)))
|
||||
{1: 0, 2: 1}
|
||||
>>> H = nx.Graph()
|
||||
>>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)])
|
||||
>>> print(dict(nx.bfs_predecessors(H, 0)))
|
||||
{1: 0, 2: 0, 3: 1, 4: 1, 5: 2, 6: 2}
|
||||
>>> M = nx.Graph()
|
||||
>>> nx.add_path(M, [0, 1, 2, 3, 4, 5, 6])
|
||||
>>> nx.add_path(M, [2, 7, 8, 9, 10])
|
||||
>>> print(sorted(nx.bfs_predecessors(M, source=1, depth_limit=3)))
|
||||
[(0, 1), (2, 1), (3, 2), (4, 3), (7, 2), (8, 7)]
|
||||
|
||||
|
||||
Notes
|
||||
-----
|
||||
Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py
|
||||
by D. Eppstein, July 2004. The modifications
|
||||
to allow depth limits based on the Wikipedia article
|
||||
"`Depth-limited-search`_".
|
||||
|
||||
.. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search
|
||||
|
||||
See Also
|
||||
--------
|
||||
bfs_tree
|
||||
bfs_edges
|
||||
edge_bfs
|
||||
"""
|
||||
for s, t in bfs_edges(
|
||||
G, source, depth_limit=depth_limit, sort_neighbors=sort_neighbors
|
||||
):
|
||||
yield (t, s)
|
||||
|
||||
|
||||
def bfs_successors(G, source, depth_limit=None, sort_neighbors=None):
|
||||
"""Returns an iterator of successors in breadth-first-search from source.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
|
||||
source : node
|
||||
Specify starting node for breadth-first search
|
||||
|
||||
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.
|
||||
|
||||
Returns
|
||||
-------
|
||||
succ: iterator
|
||||
(node, successors) iterator where successors is the list of
|
||||
successors of the node.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.path_graph(3)
|
||||
>>> print(dict(nx.bfs_successors(G, 0)))
|
||||
{0: [1], 1: [2]}
|
||||
>>> H = nx.Graph()
|
||||
>>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)])
|
||||
>>> print(dict(nx.bfs_successors(H, 0)))
|
||||
{0: [1, 2], 1: [3, 4], 2: [5, 6]}
|
||||
>>> G = nx.Graph()
|
||||
>>> nx.add_path(G, [0, 1, 2, 3, 4, 5, 6])
|
||||
>>> nx.add_path(G, [2, 7, 8, 9, 10])
|
||||
>>> print(dict(nx.bfs_successors(G, source=1, depth_limit=3)))
|
||||
{1: [0, 2], 2: [3, 7], 3: [4], 7: [8]}
|
||||
|
||||
|
||||
Notes
|
||||
-----
|
||||
Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py
|
||||
by D. Eppstein, July 2004.The modifications
|
||||
to allow depth limits based on the Wikipedia article
|
||||
"`Depth-limited-search`_".
|
||||
|
||||
.. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search
|
||||
|
||||
See Also
|
||||
--------
|
||||
bfs_tree
|
||||
bfs_edges
|
||||
edge_bfs
|
||||
"""
|
||||
parent = source
|
||||
children = []
|
||||
for p, c in bfs_edges(
|
||||
G, source, depth_limit=depth_limit, sort_neighbors=sort_neighbors
|
||||
):
|
||||
if p == parent:
|
||||
children.append(c)
|
||||
continue
|
||||
yield (parent, children)
|
||||
children = [c]
|
||||
parent = p
|
||||
yield (parent, children)
|
||||
|
||||
|
||||
def descendants_at_distance(G, source, distance):
|
||||
"""Returns all nodes at a fixed `distance` from `source` in `G`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX DiGraph
|
||||
A directed graph
|
||||
source : node in `G`
|
||||
distance : the distance of the wanted nodes from `source`
|
||||
|
||||
Returns
|
||||
-------
|
||||
set()
|
||||
The descendants of `source` in `G` at the given `distance` from `source`
|
||||
"""
|
||||
if not G.has_node(source):
|
||||
raise nx.NetworkXError(f"The node {source} is not in the graph.")
|
||||
current_distance = 0
|
||||
queue = {source}
|
||||
visited = {source}
|
||||
|
||||
# this is basically BFS, except that the queue only stores the nodes at
|
||||
# current_distance from source at each iteration
|
||||
while queue:
|
||||
if current_distance == distance:
|
||||
return queue
|
||||
|
||||
current_distance += 1
|
||||
|
||||
next_vertices = set()
|
||||
for vertex in queue:
|
||||
for child in G[vertex]:
|
||||
if child not in visited:
|
||||
visited.add(child)
|
||||
next_vertices.add(child)
|
||||
|
||||
queue = next_vertices
|
||||
|
||||
return set()
|
|
@ -0,0 +1,439 @@
|
|||
"""Basic algorithms for depth-first searching the nodes of a graph."""
|
||||
import networkx as nx
|
||||
from collections import defaultdict
|
||||
|
||||
__all__ = [
|
||||
"dfs_edges",
|
||||
"dfs_tree",
|
||||
"dfs_predecessors",
|
||||
"dfs_successors",
|
||||
"dfs_preorder_nodes",
|
||||
"dfs_postorder_nodes",
|
||||
"dfs_labeled_edges",
|
||||
]
|
||||
|
||||
|
||||
def dfs_edges(G, source=None, depth_limit=None):
|
||||
"""Iterate over edges in a depth-first-search (DFS).
|
||||
|
||||
Perform a depth-first-search over the nodes of G and yield
|
||||
the edges in order. This may not generate all edges in G (see edge_dfs).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
|
||||
source : node, optional
|
||||
Specify starting node for depth-first search and return edges in
|
||||
the component reachable from source.
|
||||
|
||||
depth_limit : int, optional (default=len(G))
|
||||
Specify the maximum search depth.
|
||||
|
||||
Returns
|
||||
-------
|
||||
edges: generator
|
||||
A generator of edges in the depth-first-search.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.path_graph(5)
|
||||
>>> list(nx.dfs_edges(G, source=0))
|
||||
[(0, 1), (1, 2), (2, 3), (3, 4)]
|
||||
>>> list(nx.dfs_edges(G, source=0, depth_limit=2))
|
||||
[(0, 1), (1, 2)]
|
||||
|
||||
Notes
|
||||
-----
|
||||
If a source is not specified then a source is chosen arbitrarily and
|
||||
repeatedly until all components in the graph are searched.
|
||||
|
||||
The implementation of this function is adapted from David Eppstein's
|
||||
depth-first search function in `PADS`_, with modifications
|
||||
to allow depth limits based on the Wikipedia article
|
||||
"`Depth-limited search`_".
|
||||
|
||||
.. _PADS: http://www.ics.uci.edu/~eppstein/PADS
|
||||
.. _Depth-limited search: https://en.wikipedia.org/wiki/Depth-limited_search
|
||||
|
||||
See Also
|
||||
--------
|
||||
dfs_preorder_nodes
|
||||
dfs_postorder_nodes
|
||||
dfs_labeled_edges
|
||||
edge_dfs
|
||||
bfs_edges
|
||||
"""
|
||||
if source is None:
|
||||
# edges for all components
|
||||
nodes = G
|
||||
else:
|
||||
# edges for components with source
|
||||
nodes = [source]
|
||||
visited = set()
|
||||
if depth_limit is None:
|
||||
depth_limit = len(G)
|
||||
for start in nodes:
|
||||
if start in visited:
|
||||
continue
|
||||
visited.add(start)
|
||||
stack = [(start, depth_limit, iter(G[start]))]
|
||||
while stack:
|
||||
parent, depth_now, children = stack[-1]
|
||||
try:
|
||||
child = next(children)
|
||||
if child not in visited:
|
||||
yield parent, child
|
||||
visited.add(child)
|
||||
if depth_now > 1:
|
||||
stack.append((child, depth_now - 1, iter(G[child])))
|
||||
except StopIteration:
|
||||
stack.pop()
|
||||
|
||||
|
||||
def dfs_tree(G, source=None, depth_limit=None):
|
||||
"""Returns oriented tree constructed from a depth-first-search from source.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
|
||||
source : node, optional
|
||||
Specify starting node for depth-first search.
|
||||
|
||||
depth_limit : int, optional (default=len(G))
|
||||
Specify the maximum search depth.
|
||||
|
||||
Returns
|
||||
-------
|
||||
T : NetworkX DiGraph
|
||||
An oriented tree
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.path_graph(5)
|
||||
>>> T = nx.dfs_tree(G, source=0, depth_limit=2)
|
||||
>>> list(T.edges())
|
||||
[(0, 1), (1, 2)]
|
||||
>>> T = nx.dfs_tree(G, source=0)
|
||||
>>> list(T.edges())
|
||||
[(0, 1), (1, 2), (2, 3), (3, 4)]
|
||||
|
||||
See Also
|
||||
--------
|
||||
dfs_preorder_nodes
|
||||
dfs_postorder_nodes
|
||||
dfs_labeled_edges
|
||||
edge_dfs
|
||||
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))
|
||||
return T
|
||||
|
||||
|
||||
def dfs_predecessors(G, source=None, depth_limit=None):
|
||||
"""Returns dictionary of predecessors in depth-first-search from source.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
|
||||
source : node, optional
|
||||
Specify starting node for depth-first search.
|
||||
|
||||
depth_limit : int, optional (default=len(G))
|
||||
Specify the maximum search depth.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pred: dict
|
||||
A dictionary with nodes as keys and predecessor nodes as values.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.path_graph(4)
|
||||
>>> nx.dfs_predecessors(G, source=0)
|
||||
{1: 0, 2: 1, 3: 2}
|
||||
>>> nx.dfs_predecessors(G, source=0, depth_limit=2)
|
||||
{1: 0, 2: 1}
|
||||
|
||||
Notes
|
||||
-----
|
||||
If a source is not specified then a source is chosen arbitrarily and
|
||||
repeatedly until all components in the graph are searched.
|
||||
|
||||
The implementation of this function is adapted from David Eppstein's
|
||||
depth-first search function in `PADS`_, with modifications
|
||||
to allow depth limits based on the Wikipedia article
|
||||
"`Depth-limited search`_".
|
||||
|
||||
.. _PADS: http://www.ics.uci.edu/~eppstein/PADS
|
||||
.. _Depth-limited search: https://en.wikipedia.org/wiki/Depth-limited_search
|
||||
|
||||
See Also
|
||||
--------
|
||||
dfs_preorder_nodes
|
||||
dfs_postorder_nodes
|
||||
dfs_labeled_edges
|
||||
edge_dfs
|
||||
bfs_tree
|
||||
"""
|
||||
return {t: s for s, t in dfs_edges(G, source, depth_limit)}
|
||||
|
||||
|
||||
def dfs_successors(G, source=None, depth_limit=None):
|
||||
"""Returns dictionary of successors in depth-first-search from source.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
|
||||
source : node, optional
|
||||
Specify starting node for depth-first search.
|
||||
|
||||
depth_limit : int, optional (default=len(G))
|
||||
Specify the maximum search depth.
|
||||
|
||||
Returns
|
||||
-------
|
||||
succ: dict
|
||||
A dictionary with nodes as keys and list of successor nodes as values.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.path_graph(5)
|
||||
>>> nx.dfs_successors(G, source=0)
|
||||
{0: [1], 1: [2], 2: [3], 3: [4]}
|
||||
>>> nx.dfs_successors(G, source=0, depth_limit=2)
|
||||
{0: [1], 1: [2]}
|
||||
|
||||
Notes
|
||||
-----
|
||||
If a source is not specified then a source is chosen arbitrarily and
|
||||
repeatedly until all components in the graph are searched.
|
||||
|
||||
The implementation of this function is adapted from David Eppstein's
|
||||
depth-first search function in `PADS`_, with modifications
|
||||
to allow depth limits based on the Wikipedia article
|
||||
"`Depth-limited search`_".
|
||||
|
||||
.. _PADS: http://www.ics.uci.edu/~eppstein/PADS
|
||||
.. _Depth-limited search: https://en.wikipedia.org/wiki/Depth-limited_search
|
||||
|
||||
See Also
|
||||
--------
|
||||
dfs_preorder_nodes
|
||||
dfs_postorder_nodes
|
||||
dfs_labeled_edges
|
||||
edge_dfs
|
||||
bfs_tree
|
||||
"""
|
||||
d = defaultdict(list)
|
||||
for s, t in dfs_edges(G, source=source, depth_limit=depth_limit):
|
||||
d[s].append(t)
|
||||
return dict(d)
|
||||
|
||||
|
||||
def dfs_postorder_nodes(G, source=None, depth_limit=None):
|
||||
"""Generate nodes in a depth-first-search post-ordering starting at source.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
|
||||
source : node, optional
|
||||
Specify starting node for depth-first search.
|
||||
|
||||
depth_limit : int, optional (default=len(G))
|
||||
Specify the maximum search depth.
|
||||
|
||||
Returns
|
||||
-------
|
||||
nodes: generator
|
||||
A generator of nodes in a depth-first-search post-ordering.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.path_graph(5)
|
||||
>>> list(nx.dfs_postorder_nodes(G, source=0))
|
||||
[4, 3, 2, 1, 0]
|
||||
>>> list(nx.dfs_postorder_nodes(G, source=0, depth_limit=2))
|
||||
[1, 0]
|
||||
|
||||
Notes
|
||||
-----
|
||||
If a source is not specified then a source is chosen arbitrarily and
|
||||
repeatedly until all components in the graph are searched.
|
||||
|
||||
The implementation of this function is adapted from David Eppstein's
|
||||
depth-first search function in `PADS`_, with modifications
|
||||
to allow depth limits based on the Wikipedia article
|
||||
"`Depth-limited search`_".
|
||||
|
||||
.. _PADS: http://www.ics.uci.edu/~eppstein/PADS
|
||||
.. _Depth-limited search: https://en.wikipedia.org/wiki/Depth-limited_search
|
||||
|
||||
See Also
|
||||
--------
|
||||
dfs_edges
|
||||
dfs_preorder_nodes
|
||||
dfs_labeled_edges
|
||||
edge_dfs
|
||||
bfs_tree
|
||||
"""
|
||||
edges = nx.dfs_labeled_edges(G, source=source, depth_limit=depth_limit)
|
||||
return (v for u, v, d in edges if d == "reverse")
|
||||
|
||||
|
||||
def dfs_preorder_nodes(G, source=None, depth_limit=None):
|
||||
"""Generate nodes in a depth-first-search pre-ordering starting at source.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
|
||||
source : node, optional
|
||||
Specify starting node for depth-first search and return nodes in
|
||||
the component reachable from source.
|
||||
|
||||
depth_limit : int, optional (default=len(G))
|
||||
Specify the maximum search depth.
|
||||
|
||||
Returns
|
||||
-------
|
||||
nodes: generator
|
||||
A generator of nodes in a depth-first-search pre-ordering.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.path_graph(5)
|
||||
>>> list(nx.dfs_preorder_nodes(G, source=0))
|
||||
[0, 1, 2, 3, 4]
|
||||
>>> list(nx.dfs_preorder_nodes(G, source=0, depth_limit=2))
|
||||
[0, 1, 2]
|
||||
|
||||
Notes
|
||||
-----
|
||||
If a source is not specified then a source is chosen arbitrarily and
|
||||
repeatedly until all components in the graph are searched.
|
||||
|
||||
The implementation of this function is adapted from David Eppstein's
|
||||
depth-first search function in `PADS`_, with modifications
|
||||
to allow depth limits based on the Wikipedia article
|
||||
"`Depth-limited search`_".
|
||||
|
||||
.. _PADS: http://www.ics.uci.edu/~eppstein/PADS
|
||||
.. _Depth-limited search: https://en.wikipedia.org/wiki/Depth-limited_search
|
||||
|
||||
See Also
|
||||
--------
|
||||
dfs_edges
|
||||
dfs_postorder_nodes
|
||||
dfs_labeled_edges
|
||||
bfs_edges
|
||||
"""
|
||||
edges = nx.dfs_labeled_edges(G, source=source, depth_limit=depth_limit)
|
||||
return (v for u, v, d in edges if d == "forward")
|
||||
|
||||
|
||||
def dfs_labeled_edges(G, source=None, depth_limit=None):
|
||||
"""Iterate over edges in a depth-first-search (DFS) labeled by type.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
|
||||
source : node, optional
|
||||
Specify starting node for depth-first search and return edges in
|
||||
the component reachable from source.
|
||||
|
||||
depth_limit : int, optional (default=len(G))
|
||||
Specify the maximum search depth.
|
||||
|
||||
Returns
|
||||
-------
|
||||
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
|
||||
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.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
The labels reveal the complete transcript of the depth-first search
|
||||
algorithm in more detail than, for example, :func:`dfs_edges`::
|
||||
|
||||
>>> from pprint import pprint
|
||||
>>>
|
||||
>>> G = nx.DiGraph([(0, 1), (1, 2), (2, 1)])
|
||||
>>> pprint(list(nx.dfs_labeled_edges(G, source=0)))
|
||||
[(0, 0, 'forward'),
|
||||
(0, 1, 'forward'),
|
||||
(1, 2, 'forward'),
|
||||
(2, 1, 'nontree'),
|
||||
(1, 2, 'reverse'),
|
||||
(0, 1, 'reverse'),
|
||||
(0, 0, 'reverse')]
|
||||
|
||||
Notes
|
||||
-----
|
||||
If a source is not specified then a source is chosen arbitrarily and
|
||||
repeatedly until all components in the graph are searched.
|
||||
|
||||
The implementation of this function is adapted from David Eppstein's
|
||||
depth-first search function in `PADS`_, with modifications
|
||||
to allow depth limits based on the Wikipedia article
|
||||
"`Depth-limited search`_".
|
||||
|
||||
.. _PADS: http://www.ics.uci.edu/~eppstein/PADS
|
||||
.. _Depth-limited search: https://en.wikipedia.org/wiki/Depth-limited_search
|
||||
|
||||
See Also
|
||||
--------
|
||||
dfs_edges
|
||||
dfs_preorder_nodes
|
||||
dfs_postorder_nodes
|
||||
"""
|
||||
# Based on http://www.ics.uci.edu/~eppstein/PADS/DFS.py
|
||||
# by D. Eppstein, July 2004.
|
||||
if source is None:
|
||||
# edges for all components
|
||||
nodes = G
|
||||
else:
|
||||
# edges for components with source
|
||||
nodes = [source]
|
||||
visited = set()
|
||||
if depth_limit is None:
|
||||
depth_limit = len(G)
|
||||
for start in nodes:
|
||||
if start in visited:
|
||||
continue
|
||||
yield start, start, "forward"
|
||||
visited.add(start)
|
||||
stack = [(start, depth_limit, iter(G[start]))]
|
||||
while stack:
|
||||
parent, depth_now, children = stack[-1]
|
||||
try:
|
||||
child = next(children)
|
||||
if child in visited:
|
||||
yield parent, child, "nontree"
|
||||
else:
|
||||
yield parent, child, "forward"
|
||||
visited.add(child)
|
||||
if depth_now > 1:
|
||||
stack.append((child, depth_now - 1, iter(G[child])))
|
||||
except StopIteration:
|
||||
stack.pop()
|
||||
if stack:
|
||||
yield stack[-1][0], parent, "reverse"
|
||||
yield start, start, "reverse"
|
175
venv/Lib/site-packages/networkx/algorithms/traversal/edgebfs.py
Normal file
175
venv/Lib/site-packages/networkx/algorithms/traversal/edgebfs.py
Normal file
|
@ -0,0 +1,175 @@
|
|||
"""
|
||||
=============================
|
||||
Breadth First Search on Edges
|
||||
=============================
|
||||
|
||||
Algorithms for a breadth-first traversal of edges in a graph.
|
||||
|
||||
"""
|
||||
from collections import deque
|
||||
import networkx as nx
|
||||
|
||||
FORWARD = "forward"
|
||||
REVERSE = "reverse"
|
||||
|
||||
__all__ = ["edge_bfs"]
|
||||
|
||||
|
||||
def edge_bfs(G, source=None, orientation=None):
|
||||
"""A directed, breadth-first-search of edges in `G`, beginning at `source`.
|
||||
|
||||
Yield the edges of G in a breadth-first-search order continuing until
|
||||
all edges are generated.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A directed/undirected graph/multigraph.
|
||||
|
||||
source : node, list of nodes
|
||||
The node from which the traversal begins. If None, then a source
|
||||
is chosen arbitrarily and repeatedly until all edges from each node in
|
||||
the graph are searched.
|
||||
|
||||
orientation : None | 'original' | 'reverse' | 'ignore' (default: None)
|
||||
For directed graphs and directed multigraphs, edge traversals need not
|
||||
respect the original orientation of the edges.
|
||||
When set to 'reverse' every edge is traversed in the reverse direction.
|
||||
When set to 'ignore', every edge is treated as undirected.
|
||||
When set to 'original', every edge is treated as directed.
|
||||
In all three cases, the yielded edge tuples add a last entry to
|
||||
indicate the direction in which that edge was traversed.
|
||||
If orientation is None, the yielded edge has no direction indicated.
|
||||
The direction is respected, but not reported.
|
||||
|
||||
Yields
|
||||
------
|
||||
edge : directed edge
|
||||
A directed edge indicating the path taken by the breadth-first-search.
|
||||
For graphs, `edge` is of the form `(u, v)` where `u` and `v`
|
||||
are the tail and head of the edge as determined by the traversal.
|
||||
For multigraphs, `edge` is of the form `(u, v, key)`, where `key` is
|
||||
the key of the edge. When the graph is directed, then `u` and `v`
|
||||
are always in the order of the actual directed edge.
|
||||
If orientation is not None then the edge tuple is extended to include
|
||||
the direction of traversal ('forward' or 'reverse') on that edge.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> nodes = [0, 1, 2, 3]
|
||||
>>> edges = [(0, 1), (1, 0), (1, 0), (2, 0), (2, 1), (3, 1)]
|
||||
|
||||
>>> list(nx.edge_bfs(nx.Graph(edges), nodes))
|
||||
[(0, 1), (0, 2), (1, 2), (1, 3)]
|
||||
|
||||
>>> list(nx.edge_bfs(nx.DiGraph(edges), nodes))
|
||||
[(0, 1), (1, 0), (2, 0), (2, 1), (3, 1)]
|
||||
|
||||
>>> list(nx.edge_bfs(nx.MultiGraph(edges), nodes))
|
||||
[(0, 1, 0), (0, 1, 1), (0, 1, 2), (0, 2, 0), (1, 2, 0), (1, 3, 0)]
|
||||
|
||||
>>> list(nx.edge_bfs(nx.MultiDiGraph(edges), nodes))
|
||||
[(0, 1, 0), (1, 0, 0), (1, 0, 1), (2, 0, 0), (2, 1, 0), (3, 1, 0)]
|
||||
|
||||
>>> list(nx.edge_bfs(nx.DiGraph(edges), nodes, orientation="ignore"))
|
||||
[(0, 1, 'forward'), (1, 0, 'reverse'), (2, 0, 'reverse'), (2, 1, 'reverse'), (3, 1, 'reverse')]
|
||||
|
||||
>>> list(nx.edge_bfs(nx.MultiDiGraph(edges), nodes, orientation="ignore"))
|
||||
[(0, 1, 0, 'forward'), (1, 0, 0, 'reverse'), (1, 0, 1, 'reverse'), (2, 0, 0, 'reverse'), (2, 1, 0, 'reverse'), (3, 1, 0, 'reverse')]
|
||||
|
||||
Notes
|
||||
-----
|
||||
The goal of this function is to visit edges. It differs from the more
|
||||
familiar breadth-first-search of nodes, as provided by
|
||||
:func:`networkx.algorithms.traversal.breadth_first_search.bfs_edges`, in
|
||||
that it does not stop once every node has been visited. In a directed graph
|
||||
with edges [(0, 1), (1, 2), (2, 1)], the edge (2, 1) would not be visited
|
||||
if not for the functionality provided by this function.
|
||||
|
||||
The naming of this function is very similar to bfs_edges. The difference
|
||||
is that 'edge_bfs' yields edges even if they extend back to an already
|
||||
explored node while 'bfs_edges' yields the edges of the tree that results
|
||||
from a breadth-first-search (BFS) so no edges are reported if they extend
|
||||
to already explored nodes. That means 'edge_bfs' reports all edges while
|
||||
'bfs_edges' only report those traversed by a node-based BFS. Yet another
|
||||
description is that 'bfs_edges' reports the edges traversed during BFS
|
||||
while 'edge_bfs' reports all edges in the order they are explored.
|
||||
|
||||
See Also
|
||||
--------
|
||||
bfs_edges
|
||||
bfs_tree
|
||||
edge_dfs
|
||||
|
||||
"""
|
||||
nodes = list(G.nbunch_iter(source))
|
||||
if not nodes:
|
||||
return
|
||||
|
||||
directed = G.is_directed()
|
||||
kwds = {"data": False}
|
||||
if G.is_multigraph() is True:
|
||||
kwds["keys"] = True
|
||||
|
||||
# set up edge lookup
|
||||
if orientation is None:
|
||||
|
||||
def edges_from(node):
|
||||
return iter(G.edges(node, **kwds))
|
||||
|
||||
elif not directed or orientation == "original":
|
||||
|
||||
def edges_from(node):
|
||||
for e in G.edges(node, **kwds):
|
||||
yield e + (FORWARD,)
|
||||
|
||||
elif orientation == "reverse":
|
||||
|
||||
def edges_from(node):
|
||||
for e in G.in_edges(node, **kwds):
|
||||
yield e + (REVERSE,)
|
||||
|
||||
elif orientation == "ignore":
|
||||
|
||||
def edges_from(node):
|
||||
for e in G.edges(node, **kwds):
|
||||
yield e + (FORWARD,)
|
||||
for e in G.in_edges(node, **kwds):
|
||||
yield e + (REVERSE,)
|
||||
|
||||
else:
|
||||
raise nx.NetworkXError("invalid orientation argument.")
|
||||
|
||||
if directed:
|
||||
neighbors = G.successors
|
||||
|
||||
def edge_id(edge):
|
||||
# remove direction indicator
|
||||
return edge[:-1] if orientation is not None else edge
|
||||
|
||||
else:
|
||||
neighbors = G.neighbors
|
||||
|
||||
def edge_id(edge):
|
||||
return (frozenset(edge[:2]),) + edge[2:]
|
||||
|
||||
check_reverse = directed and orientation in ("reverse", "ignore")
|
||||
|
||||
# start BFS
|
||||
visited_nodes = {n for n in nodes}
|
||||
visited_edges = set()
|
||||
queue = deque([(n, edges_from(n)) for n in nodes])
|
||||
while queue:
|
||||
parent, children_edges = queue.popleft()
|
||||
for edge in children_edges:
|
||||
if check_reverse and edge[-1] == REVERSE:
|
||||
child = edge[0]
|
||||
else:
|
||||
child = edge[1]
|
||||
if child not in visited_nodes:
|
||||
visited_nodes.add(child)
|
||||
queue.append((child, edges_from(child)))
|
||||
edgeid = edge_id(edge)
|
||||
if edgeid not in visited_edges:
|
||||
visited_edges.add(edgeid)
|
||||
yield edge
|
174
venv/Lib/site-packages/networkx/algorithms/traversal/edgedfs.py
Normal file
174
venv/Lib/site-packages/networkx/algorithms/traversal/edgedfs.py
Normal file
|
@ -0,0 +1,174 @@
|
|||
"""
|
||||
===========================
|
||||
Depth First Search on Edges
|
||||
===========================
|
||||
|
||||
Algorithms for a depth-first traversal of edges in a graph.
|
||||
|
||||
"""
|
||||
import networkx as nx
|
||||
|
||||
FORWARD = "forward"
|
||||
REVERSE = "reverse"
|
||||
|
||||
__all__ = ["edge_dfs"]
|
||||
|
||||
|
||||
def edge_dfs(G, source=None, orientation=None):
|
||||
"""A directed, depth-first-search of edges in `G`, beginning at `source`.
|
||||
|
||||
Yield the edges of G in a depth-first-search order continuing until
|
||||
all edges are generated.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A directed/undirected graph/multigraph.
|
||||
|
||||
source : node, list of nodes
|
||||
The node from which the traversal begins. If None, then a source
|
||||
is chosen arbitrarily and repeatedly until all edges from each node in
|
||||
the graph are searched.
|
||||
|
||||
orientation : None | 'original' | 'reverse' | 'ignore' (default: None)
|
||||
For directed graphs and directed multigraphs, edge traversals need not
|
||||
respect the original orientation of the edges.
|
||||
When set to 'reverse' every edge is traversed in the reverse direction.
|
||||
When set to 'ignore', every edge is treated as undirected.
|
||||
When set to 'original', every edge is treated as directed.
|
||||
In all three cases, the yielded edge tuples add a last entry to
|
||||
indicate the direction in which that edge was traversed.
|
||||
If orientation is None, the yielded edge has no direction indicated.
|
||||
The direction is respected, but not reported.
|
||||
|
||||
Yields
|
||||
------
|
||||
edge : directed edge
|
||||
A directed edge indicating the path taken by the depth-first traversal.
|
||||
For graphs, `edge` is of the form `(u, v)` where `u` and `v`
|
||||
are the tail and head of the edge as determined by the traversal.
|
||||
For multigraphs, `edge` is of the form `(u, v, key)`, where `key` is
|
||||
the key of the edge. When the graph is directed, then `u` and `v`
|
||||
are always in the order of the actual directed edge.
|
||||
If orientation is not None then the edge tuple is extended to include
|
||||
the direction of traversal ('forward' or 'reverse') on that edge.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> nodes = [0, 1, 2, 3]
|
||||
>>> edges = [(0, 1), (1, 0), (1, 0), (2, 1), (3, 1)]
|
||||
|
||||
>>> list(nx.edge_dfs(nx.Graph(edges), nodes))
|
||||
[(0, 1), (1, 2), (1, 3)]
|
||||
|
||||
>>> list(nx.edge_dfs(nx.DiGraph(edges), nodes))
|
||||
[(0, 1), (1, 0), (2, 1), (3, 1)]
|
||||
|
||||
>>> list(nx.edge_dfs(nx.MultiGraph(edges), nodes))
|
||||
[(0, 1, 0), (1, 0, 1), (0, 1, 2), (1, 2, 0), (1, 3, 0)]
|
||||
|
||||
>>> list(nx.edge_dfs(nx.MultiDiGraph(edges), nodes))
|
||||
[(0, 1, 0), (1, 0, 0), (1, 0, 1), (2, 1, 0), (3, 1, 0)]
|
||||
|
||||
>>> list(nx.edge_dfs(nx.DiGraph(edges), nodes, orientation="ignore"))
|
||||
[(0, 1, 'forward'), (1, 0, 'forward'), (2, 1, 'reverse'), (3, 1, 'reverse')]
|
||||
|
||||
>>> list(nx.edge_dfs(nx.MultiDiGraph(edges), nodes, orientation="ignore"))
|
||||
[(0, 1, 0, 'forward'), (1, 0, 0, 'forward'), (1, 0, 1, 'reverse'), (2, 1, 0, 'reverse'), (3, 1, 0, 'reverse')]
|
||||
|
||||
Notes
|
||||
-----
|
||||
The goal of this function is to visit edges. It differs from the more
|
||||
familiar depth-first traversal of nodes, as provided by
|
||||
:func:`networkx.algorithms.traversal.depth_first_search.dfs_edges`, in
|
||||
that it does not stop once every node has been visited. In a directed graph
|
||||
with edges [(0, 1), (1, 2), (2, 1)], the edge (2, 1) would not be visited
|
||||
if not for the functionality provided by this function.
|
||||
|
||||
See Also
|
||||
--------
|
||||
dfs_edges
|
||||
|
||||
"""
|
||||
nodes = list(G.nbunch_iter(source))
|
||||
if not nodes:
|
||||
return
|
||||
|
||||
directed = G.is_directed()
|
||||
kwds = {"data": False}
|
||||
if G.is_multigraph() is True:
|
||||
kwds["keys"] = True
|
||||
|
||||
# set up edge lookup
|
||||
if orientation is None:
|
||||
|
||||
def edges_from(node):
|
||||
return iter(G.edges(node, **kwds))
|
||||
|
||||
elif not directed or orientation == "original":
|
||||
|
||||
def edges_from(node):
|
||||
for e in G.edges(node, **kwds):
|
||||
yield e + (FORWARD,)
|
||||
|
||||
elif orientation == "reverse":
|
||||
|
||||
def edges_from(node):
|
||||
for e in G.in_edges(node, **kwds):
|
||||
yield e + (REVERSE,)
|
||||
|
||||
elif orientation == "ignore":
|
||||
|
||||
def edges_from(node):
|
||||
for e in G.edges(node, **kwds):
|
||||
yield e + (FORWARD,)
|
||||
for e in G.in_edges(node, **kwds):
|
||||
yield e + (REVERSE,)
|
||||
|
||||
else:
|
||||
raise nx.NetworkXError("invalid orientation argument.")
|
||||
|
||||
# set up formation of edge_id to easily look up if edge already returned
|
||||
if directed:
|
||||
|
||||
def edge_id(edge):
|
||||
# remove direction indicator
|
||||
return edge[:-1] if orientation is not None else edge
|
||||
|
||||
else:
|
||||
|
||||
def edge_id(edge):
|
||||
# single id for undirected requires frozenset on nodes
|
||||
return (frozenset(edge[:2]),) + edge[2:]
|
||||
|
||||
# Basic setup
|
||||
check_reverse = directed and orientation in ("reverse", "ignore")
|
||||
|
||||
visited_edges = set()
|
||||
visited_nodes = set()
|
||||
edges = {}
|
||||
|
||||
# start DFS
|
||||
for start_node in nodes:
|
||||
stack = [start_node]
|
||||
while stack:
|
||||
current_node = stack[-1]
|
||||
if current_node not in visited_nodes:
|
||||
edges[current_node] = edges_from(current_node)
|
||||
visited_nodes.add(current_node)
|
||||
|
||||
try:
|
||||
edge = next(edges[current_node])
|
||||
except StopIteration:
|
||||
# No more edges from the current node.
|
||||
stack.pop()
|
||||
else:
|
||||
edgeid = edge_id(edge)
|
||||
if edgeid not in visited_edges:
|
||||
visited_edges.add(edgeid)
|
||||
# Mark the traversed "to" node as to-be-explored.
|
||||
if check_reverse and edge[-1] == REVERSE:
|
||||
stack.append(edge[0])
|
||||
else:
|
||||
stack.append(edge[1])
|
||||
yield edge
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,27 @@
|
|||
"""Unit tests for the beam search functions."""
|
||||
|
||||
import networkx as nx
|
||||
|
||||
|
||||
def identity(x):
|
||||
return x
|
||||
|
||||
|
||||
class TestBeamSearch:
|
||||
"""Unit tests for the beam search function."""
|
||||
|
||||
def test_narrow(self):
|
||||
"""Tests that a narrow beam width may cause an incomplete search."""
|
||||
# In this search, we enqueue only the neighbor 3 at the first
|
||||
# step, then only the neighbor 2 at the second step. Once at
|
||||
# node 2, the search chooses node 3, since it has a higher value
|
||||
# that node 1, but node 3 has already been visited, so the
|
||||
# search terminates.
|
||||
G = nx.cycle_graph(4)
|
||||
edges = nx.bfs_beam_edges(G, 0, identity, width=1)
|
||||
assert list(edges) == [(0, 3), (3, 2)]
|
||||
|
||||
def test_wide(self):
|
||||
G = nx.cycle_graph(4)
|
||||
edges = nx.bfs_beam_edges(G, 0, identity, width=2)
|
||||
assert list(edges) == [(0, 3), (0, 1), (3, 2)]
|
|
@ -0,0 +1,100 @@
|
|||
from functools import partial
|
||||
import networkx as nx
|
||||
|
||||
|
||||
class TestBFS:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
# simple graph
|
||||
G = nx.Graph()
|
||||
G.add_edges_from([(0, 1), (1, 2), (1, 3), (2, 4), (3, 4)])
|
||||
cls.G = G
|
||||
|
||||
def test_successor(self):
|
||||
assert dict(nx.bfs_successors(self.G, source=0)) == {0: [1], 1: [2, 3], 2: [4]}
|
||||
|
||||
def test_predecessor(self):
|
||||
assert dict(nx.bfs_predecessors(self.G, source=0)) == {1: 0, 2: 1, 3: 1, 4: 2}
|
||||
|
||||
def test_bfs_tree(self):
|
||||
T = nx.bfs_tree(self.G, source=0)
|
||||
assert sorted(T.nodes()) == sorted(self.G.nodes())
|
||||
assert sorted(T.edges()) == [(0, 1), (1, 2), (1, 3), (2, 4)]
|
||||
|
||||
def test_bfs_edges(self):
|
||||
edges = nx.bfs_edges(self.G, source=0)
|
||||
assert list(edges) == [(0, 1), (1, 2), (1, 3), (2, 4)]
|
||||
|
||||
def test_bfs_edges_reverse(self):
|
||||
D = nx.DiGraph()
|
||||
D.add_edges_from([(0, 1), (1, 2), (1, 3), (2, 4), (3, 4)])
|
||||
edges = nx.bfs_edges(D, source=4, reverse=True)
|
||||
assert list(edges) == [(4, 2), (4, 3), (2, 1), (1, 0)]
|
||||
|
||||
def test_bfs_edges_sorting(self):
|
||||
D = nx.DiGraph()
|
||||
D.add_edges_from([(0, 1), (0, 2), (1, 4), (1, 3), (2, 5)])
|
||||
sort_desc = partial(sorted, reverse=True)
|
||||
edges_asc = nx.bfs_edges(D, source=0, sort_neighbors=sorted)
|
||||
edges_desc = nx.bfs_edges(D, source=0, sort_neighbors=sort_desc)
|
||||
assert list(edges_asc) == [(0, 1), (0, 2), (1, 3), (1, 4), (2, 5)]
|
||||
assert list(edges_desc) == [(0, 2), (0, 1), (2, 5), (1, 4), (1, 3)]
|
||||
|
||||
def test_bfs_tree_isolates(self):
|
||||
G = nx.Graph()
|
||||
G.add_node(1)
|
||||
G.add_node(2)
|
||||
T = nx.bfs_tree(G, source=1)
|
||||
assert sorted(T.nodes()) == [1]
|
||||
assert sorted(T.edges()) == []
|
||||
|
||||
|
||||
class TestBreadthLimitedSearch:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
# a tree
|
||||
G = nx.Graph()
|
||||
nx.add_path(G, [0, 1, 2, 3, 4, 5, 6])
|
||||
nx.add_path(G, [2, 7, 8, 9, 10])
|
||||
cls.G = G
|
||||
# a disconnected graph
|
||||
D = nx.Graph()
|
||||
D.add_edges_from([(0, 1), (2, 3)])
|
||||
nx.add_path(D, [2, 7, 8, 9, 10])
|
||||
cls.D = D
|
||||
|
||||
def test_limited_bfs_successor(self):
|
||||
assert dict(nx.bfs_successors(self.G, source=1, depth_limit=3)) == {
|
||||
1: [0, 2],
|
||||
2: [3, 7],
|
||||
3: [4],
|
||||
7: [8],
|
||||
}
|
||||
result = {
|
||||
n: sorted(s) for n, s in nx.bfs_successors(self.D, source=7, depth_limit=2)
|
||||
}
|
||||
assert result == {8: [9], 2: [3], 7: [2, 8]}
|
||||
|
||||
def test_limited_bfs_predecessor(self):
|
||||
assert dict(nx.bfs_predecessors(self.G, source=1, depth_limit=3)) == {
|
||||
0: 1,
|
||||
2: 1,
|
||||
3: 2,
|
||||
4: 3,
|
||||
7: 2,
|
||||
8: 7,
|
||||
}
|
||||
assert dict(nx.bfs_predecessors(self.D, source=7, depth_limit=2)) == {
|
||||
2: 7,
|
||||
3: 2,
|
||||
8: 7,
|
||||
9: 8,
|
||||
}
|
||||
|
||||
def test_limited_bfs_tree(self):
|
||||
T = nx.bfs_tree(self.G, source=3, depth_limit=1)
|
||||
assert sorted(T.edges()) == [(3, 2), (3, 4)]
|
||||
|
||||
def test_limited_bfs_edges(self):
|
||||
edges = nx.bfs_edges(self.G, source=9, depth_limit=4)
|
||||
assert list(edges) == [(9, 8), (9, 10), (8, 7), (7, 2), (2, 1), (2, 3)]
|
|
@ -0,0 +1,148 @@
|
|||
import networkx as nx
|
||||
|
||||
|
||||
class TestDFS:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
# simple graph
|
||||
G = nx.Graph()
|
||||
G.add_edges_from([(0, 1), (1, 2), (1, 3), (2, 4), (3, 4)])
|
||||
cls.G = G
|
||||
# simple graph, disconnected
|
||||
D = nx.Graph()
|
||||
D.add_edges_from([(0, 1), (2, 3)])
|
||||
cls.D = D
|
||||
|
||||
def test_preorder_nodes(self):
|
||||
assert list(nx.dfs_preorder_nodes(self.G, source=0)) == [0, 1, 2, 4, 3]
|
||||
assert list(nx.dfs_preorder_nodes(self.D)) == [0, 1, 2, 3]
|
||||
|
||||
def test_postorder_nodes(self):
|
||||
assert list(nx.dfs_postorder_nodes(self.G, source=0)) == [3, 4, 2, 1, 0]
|
||||
assert list(nx.dfs_postorder_nodes(self.D)) == [1, 0, 3, 2]
|
||||
|
||||
def test_successor(self):
|
||||
assert nx.dfs_successors(self.G, source=0) == {0: [1], 1: [2], 2: [4], 4: [3]}
|
||||
assert nx.dfs_successors(self.D) == {0: [1], 2: [3]}
|
||||
|
||||
def test_predecessor(self):
|
||||
assert nx.dfs_predecessors(self.G, source=0) == {1: 0, 2: 1, 3: 4, 4: 2}
|
||||
assert nx.dfs_predecessors(self.D) == {1: 0, 3: 2}
|
||||
|
||||
def test_dfs_tree(self):
|
||||
exp_nodes = sorted(self.G.nodes())
|
||||
exp_edges = [(0, 1), (1, 2), (2, 4), (4, 3)]
|
||||
# Search from first node
|
||||
T = nx.dfs_tree(self.G, source=0)
|
||||
assert sorted(T.nodes()) == exp_nodes
|
||||
assert sorted(T.edges()) == exp_edges
|
||||
# Check source=None
|
||||
T = nx.dfs_tree(self.G, source=None)
|
||||
assert sorted(T.nodes()) == exp_nodes
|
||||
assert sorted(T.edges()) == exp_edges
|
||||
# Check source=None is the default
|
||||
T = nx.dfs_tree(self.G)
|
||||
assert sorted(T.nodes()) == exp_nodes
|
||||
assert sorted(T.edges()) == exp_edges
|
||||
|
||||
def test_dfs_edges(self):
|
||||
edges = nx.dfs_edges(self.G, source=0)
|
||||
assert list(edges) == [(0, 1), (1, 2), (2, 4), (4, 3)]
|
||||
edges = nx.dfs_edges(self.D)
|
||||
assert list(edges) == [(0, 1), (2, 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"]
|
||||
assert forward == [(0, 0), (0, 1), (1, 2), (2, 4), (4, 3)]
|
||||
|
||||
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)]
|
||||
|
||||
def test_dfs_tree_isolates(self):
|
||||
G = nx.Graph()
|
||||
G.add_node(1)
|
||||
G.add_node(2)
|
||||
T = nx.dfs_tree(G, source=1)
|
||||
assert sorted(T.nodes()) == [1]
|
||||
assert sorted(T.edges()) == []
|
||||
T = nx.dfs_tree(G, source=None)
|
||||
assert sorted(T.nodes()) == [1, 2]
|
||||
assert sorted(T.edges()) == []
|
||||
|
||||
|
||||
class TestDepthLimitedSearch:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
# a tree
|
||||
G = nx.Graph()
|
||||
nx.add_path(G, [0, 1, 2, 3, 4, 5, 6])
|
||||
nx.add_path(G, [2, 7, 8, 9, 10])
|
||||
cls.G = G
|
||||
# a disconnected graph
|
||||
D = nx.Graph()
|
||||
D.add_edges_from([(0, 1), (2, 3)])
|
||||
nx.add_path(D, [2, 7, 8, 9, 10])
|
||||
cls.D = D
|
||||
|
||||
def test_dls_preorder_nodes(self):
|
||||
assert list(nx.dfs_preorder_nodes(self.G, source=0, depth_limit=2)) == [0, 1, 2]
|
||||
assert list(nx.dfs_preorder_nodes(self.D, source=1, depth_limit=2)) == ([1, 0])
|
||||
|
||||
def test_dls_postorder_nodes(self):
|
||||
assert list(nx.dfs_postorder_nodes(self.G, source=3, depth_limit=3)) == [
|
||||
1,
|
||||
7,
|
||||
2,
|
||||
5,
|
||||
4,
|
||||
3,
|
||||
]
|
||||
assert list(nx.dfs_postorder_nodes(self.D, source=2, depth_limit=2)) == (
|
||||
[3, 7, 2]
|
||||
)
|
||||
|
||||
def test_dls_successor(self):
|
||||
result = nx.dfs_successors(self.G, source=4, depth_limit=3)
|
||||
assert {n: set(v) for n, v in result.items()} == {
|
||||
2: {1, 7},
|
||||
3: {2},
|
||||
4: {3, 5},
|
||||
5: {6},
|
||||
}
|
||||
result = nx.dfs_successors(self.D, source=7, depth_limit=2)
|
||||
assert {n: set(v) for n, v in result.items()} == {8: {9}, 2: {3}, 7: {8, 2}}
|
||||
|
||||
def test_dls_predecessor(self):
|
||||
assert nx.dfs_predecessors(self.G, source=0, depth_limit=3) == {
|
||||
1: 0,
|
||||
2: 1,
|
||||
3: 2,
|
||||
7: 2,
|
||||
}
|
||||
assert nx.dfs_predecessors(self.D, source=2, depth_limit=3) == {
|
||||
8: 7,
|
||||
9: 8,
|
||||
3: 2,
|
||||
7: 2,
|
||||
}
|
||||
|
||||
def test_dls_tree(self):
|
||||
T = nx.dfs_tree(self.G, source=3, depth_limit=1)
|
||||
assert sorted(T.edges()) == [(3, 2), (3, 4)]
|
||||
|
||||
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):
|
||||
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)]
|
||||
|
||||
def test_dls_labeled_disconnected_edges(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)]
|
|
@ -0,0 +1,151 @@
|
|||
import pytest
|
||||
|
||||
import networkx as nx
|
||||
|
||||
edge_bfs = nx.edge_bfs
|
||||
|
||||
FORWARD = nx.algorithms.edgedfs.FORWARD
|
||||
REVERSE = nx.algorithms.edgedfs.REVERSE
|
||||
|
||||
|
||||
class TestEdgeBFS:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
cls.nodes = [0, 1, 2, 3]
|
||||
cls.edges = [(0, 1), (1, 0), (1, 0), (2, 0), (2, 1), (3, 1)]
|
||||
|
||||
def test_empty(self):
|
||||
G = nx.Graph()
|
||||
edges = list(edge_bfs(G))
|
||||
assert edges == []
|
||||
|
||||
def test_graph_single_source(self):
|
||||
G = nx.Graph(self.edges)
|
||||
G.add_edge(4, 5)
|
||||
x = list(edge_bfs(G, [0]))
|
||||
x_ = [(0, 1), (0, 2), (1, 2), (1, 3)]
|
||||
assert x == x_
|
||||
|
||||
def test_graph(self):
|
||||
G = nx.Graph(self.edges)
|
||||
x = list(edge_bfs(G, self.nodes))
|
||||
x_ = [(0, 1), (0, 2), (1, 2), (1, 3)]
|
||||
assert x == x_
|
||||
|
||||
def test_digraph(self):
|
||||
G = nx.DiGraph(self.edges)
|
||||
x = list(edge_bfs(G, self.nodes))
|
||||
x_ = [(0, 1), (1, 0), (2, 0), (2, 1), (3, 1)]
|
||||
assert x == x_
|
||||
|
||||
def test_digraph_orientation_invalid(self):
|
||||
G = nx.DiGraph(self.edges)
|
||||
edge_iterator = edge_bfs(G, self.nodes, orientation="hello")
|
||||
pytest.raises(nx.NetworkXError, list, edge_iterator)
|
||||
|
||||
def test_digraph_orientation_none(self):
|
||||
G = nx.DiGraph(self.edges)
|
||||
x = list(edge_bfs(G, self.nodes, orientation=None))
|
||||
x_ = [(0, 1), (1, 0), (2, 0), (2, 1), (3, 1)]
|
||||
assert x == x_
|
||||
|
||||
def test_digraph_orientation_original(self):
|
||||
G = nx.DiGraph(self.edges)
|
||||
x = list(edge_bfs(G, self.nodes, orientation="original"))
|
||||
x_ = [
|
||||
(0, 1, FORWARD),
|
||||
(1, 0, FORWARD),
|
||||
(2, 0, FORWARD),
|
||||
(2, 1, FORWARD),
|
||||
(3, 1, FORWARD),
|
||||
]
|
||||
assert x == x_
|
||||
|
||||
def test_digraph2(self):
|
||||
G = nx.DiGraph()
|
||||
nx.add_path(G, range(4))
|
||||
x = list(edge_bfs(G, [0]))
|
||||
x_ = [(0, 1), (1, 2), (2, 3)]
|
||||
assert x == x_
|
||||
|
||||
def test_digraph_rev(self):
|
||||
G = nx.DiGraph(self.edges)
|
||||
x = list(edge_bfs(G, self.nodes, orientation="reverse"))
|
||||
x_ = [
|
||||
(1, 0, REVERSE),
|
||||
(2, 0, REVERSE),
|
||||
(0, 1, REVERSE),
|
||||
(2, 1, REVERSE),
|
||||
(3, 1, REVERSE),
|
||||
]
|
||||
assert x == x_
|
||||
|
||||
def test_digraph_rev2(self):
|
||||
G = nx.DiGraph()
|
||||
nx.add_path(G, range(4))
|
||||
x = list(edge_bfs(G, [3], orientation="reverse"))
|
||||
x_ = [(2, 3, REVERSE), (1, 2, REVERSE), (0, 1, REVERSE)]
|
||||
assert x == x_
|
||||
|
||||
def test_multigraph(self):
|
||||
G = nx.MultiGraph(self.edges)
|
||||
x = list(edge_bfs(G, self.nodes))
|
||||
x_ = [(0, 1, 0), (0, 1, 1), (0, 1, 2), (0, 2, 0), (1, 2, 0), (1, 3, 0)]
|
||||
# This is an example of where hash randomization can break.
|
||||
# There are 3! * 2 alternative outputs, such as:
|
||||
# [(0, 1, 1), (1, 0, 0), (0, 1, 2), (1, 3, 0), (1, 2, 0)]
|
||||
# But note, the edges (1,2,0) and (1,3,0) always follow the (0,1,k)
|
||||
# edges. So the algorithm only guarantees a partial order. A total
|
||||
# order is guaranteed only if the graph data structures are ordered.
|
||||
assert x == x_
|
||||
|
||||
def test_multidigraph(self):
|
||||
G = nx.MultiDiGraph(self.edges)
|
||||
x = list(edge_bfs(G, self.nodes))
|
||||
x_ = [(0, 1, 0), (1, 0, 0), (1, 0, 1), (2, 0, 0), (2, 1, 0), (3, 1, 0)]
|
||||
assert x == x_
|
||||
|
||||
def test_multidigraph_rev(self):
|
||||
G = nx.MultiDiGraph(self.edges)
|
||||
x = list(edge_bfs(G, self.nodes, orientation="reverse"))
|
||||
x_ = [
|
||||
(1, 0, 0, REVERSE),
|
||||
(1, 0, 1, REVERSE),
|
||||
(2, 0, 0, REVERSE),
|
||||
(0, 1, 0, REVERSE),
|
||||
(2, 1, 0, REVERSE),
|
||||
(3, 1, 0, REVERSE),
|
||||
]
|
||||
assert x == x_
|
||||
|
||||
def test_digraph_ignore(self):
|
||||
G = nx.DiGraph(self.edges)
|
||||
x = list(edge_bfs(G, self.nodes, orientation="ignore"))
|
||||
x_ = [
|
||||
(0, 1, FORWARD),
|
||||
(1, 0, REVERSE),
|
||||
(2, 0, REVERSE),
|
||||
(2, 1, REVERSE),
|
||||
(3, 1, REVERSE),
|
||||
]
|
||||
assert x == x_
|
||||
|
||||
def test_digraph_ignore2(self):
|
||||
G = nx.DiGraph()
|
||||
nx.add_path(G, range(4))
|
||||
x = list(edge_bfs(G, [0], orientation="ignore"))
|
||||
x_ = [(0, 1, FORWARD), (1, 2, FORWARD), (2, 3, FORWARD)]
|
||||
assert x == x_
|
||||
|
||||
def test_multidigraph_ignore(self):
|
||||
G = nx.MultiDiGraph(self.edges)
|
||||
x = list(edge_bfs(G, self.nodes, orientation="ignore"))
|
||||
x_ = [
|
||||
(0, 1, 0, FORWARD),
|
||||
(1, 0, 0, REVERSE),
|
||||
(1, 0, 1, REVERSE),
|
||||
(2, 0, 0, REVERSE),
|
||||
(2, 1, 0, REVERSE),
|
||||
(3, 1, 0, REVERSE),
|
||||
]
|
||||
assert x == x_
|
|
@ -0,0 +1,134 @@
|
|||
import pytest
|
||||
|
||||
import networkx as nx
|
||||
|
||||
edge_dfs = nx.algorithms.edge_dfs
|
||||
|
||||
FORWARD = nx.algorithms.edgedfs.FORWARD
|
||||
REVERSE = nx.algorithms.edgedfs.REVERSE
|
||||
|
||||
# These tests can fail with hash randomization. The easiest and clearest way
|
||||
# to write these unit tests is for the edges to be output in an expected total
|
||||
# order, but we cannot guarantee the order amongst outgoing edges from a node,
|
||||
# unless each class uses an ordered data structure for neighbors. This is
|
||||
# painful to do with the current API. The alternative is that the tests are
|
||||
# written (IMO confusingly) so that there is not a total order over the edges,
|
||||
# but only a partial order. Due to the small size of the graphs, hopefully
|
||||
# failures due to hash randomization will not occur. For an example of how
|
||||
# this can fail, see TestEdgeDFS.test_multigraph.
|
||||
|
||||
|
||||
class TestEdgeDFS:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
cls.nodes = [0, 1, 2, 3]
|
||||
cls.edges = [(0, 1), (1, 0), (1, 0), (2, 1), (3, 1)]
|
||||
|
||||
def test_empty(self):
|
||||
G = nx.Graph()
|
||||
edges = list(edge_dfs(G))
|
||||
assert edges == []
|
||||
|
||||
def test_graph(self):
|
||||
G = nx.Graph(self.edges)
|
||||
x = list(edge_dfs(G, self.nodes))
|
||||
x_ = [(0, 1), (1, 2), (1, 3)]
|
||||
assert x == x_
|
||||
|
||||
def test_digraph(self):
|
||||
G = nx.DiGraph(self.edges)
|
||||
x = list(edge_dfs(G, self.nodes))
|
||||
x_ = [(0, 1), (1, 0), (2, 1), (3, 1)]
|
||||
assert x == x_
|
||||
|
||||
def test_digraph_orientation_invalid(self):
|
||||
G = nx.DiGraph(self.edges)
|
||||
edge_iterator = edge_dfs(G, self.nodes, orientation="hello")
|
||||
pytest.raises(nx.NetworkXError, list, edge_iterator)
|
||||
|
||||
def test_digraph_orientation_none(self):
|
||||
G = nx.DiGraph(self.edges)
|
||||
x = list(edge_dfs(G, self.nodes, orientation=None))
|
||||
x_ = [(0, 1), (1, 0), (2, 1), (3, 1)]
|
||||
assert x == x_
|
||||
|
||||
def test_digraph_orientation_original(self):
|
||||
G = nx.DiGraph(self.edges)
|
||||
x = list(edge_dfs(G, self.nodes, orientation="original"))
|
||||
x_ = [(0, 1, FORWARD), (1, 0, FORWARD), (2, 1, FORWARD), (3, 1, FORWARD)]
|
||||
assert x == x_
|
||||
|
||||
def test_digraph2(self):
|
||||
G = nx.DiGraph()
|
||||
nx.add_path(G, range(4))
|
||||
x = list(edge_dfs(G, [0]))
|
||||
x_ = [(0, 1), (1, 2), (2, 3)]
|
||||
assert x == x_
|
||||
|
||||
def test_digraph_rev(self):
|
||||
G = nx.DiGraph(self.edges)
|
||||
x = list(edge_dfs(G, self.nodes, orientation="reverse"))
|
||||
x_ = [(1, 0, REVERSE), (0, 1, REVERSE), (2, 1, REVERSE), (3, 1, REVERSE)]
|
||||
assert x == x_
|
||||
|
||||
def test_digraph_rev2(self):
|
||||
G = nx.DiGraph()
|
||||
nx.add_path(G, range(4))
|
||||
x = list(edge_dfs(G, [3], orientation="reverse"))
|
||||
x_ = [(2, 3, REVERSE), (1, 2, REVERSE), (0, 1, REVERSE)]
|
||||
assert x == x_
|
||||
|
||||
def test_multigraph(self):
|
||||
G = nx.MultiGraph(self.edges)
|
||||
x = list(edge_dfs(G, self.nodes))
|
||||
x_ = [(0, 1, 0), (1, 0, 1), (0, 1, 2), (1, 2, 0), (1, 3, 0)]
|
||||
# This is an example of where hash randomization can break.
|
||||
# There are 3! * 2 alternative outputs, such as:
|
||||
# [(0, 1, 1), (1, 0, 0), (0, 1, 2), (1, 3, 0), (1, 2, 0)]
|
||||
# But note, the edges (1,2,0) and (1,3,0) always follow the (0,1,k)
|
||||
# edges. So the algorithm only guarantees a partial order. A total
|
||||
# order is guaranteed only if the graph data structures are ordered.
|
||||
assert x == x_
|
||||
|
||||
def test_multidigraph(self):
|
||||
G = nx.MultiDiGraph(self.edges)
|
||||
x = list(edge_dfs(G, self.nodes))
|
||||
x_ = [(0, 1, 0), (1, 0, 0), (1, 0, 1), (2, 1, 0), (3, 1, 0)]
|
||||
assert x == x_
|
||||
|
||||
def test_multidigraph_rev(self):
|
||||
G = nx.MultiDiGraph(self.edges)
|
||||
x = list(edge_dfs(G, self.nodes, orientation="reverse"))
|
||||
x_ = [
|
||||
(1, 0, 0, REVERSE),
|
||||
(0, 1, 0, REVERSE),
|
||||
(1, 0, 1, REVERSE),
|
||||
(2, 1, 0, REVERSE),
|
||||
(3, 1, 0, REVERSE),
|
||||
]
|
||||
assert x == x_
|
||||
|
||||
def test_digraph_ignore(self):
|
||||
G = nx.DiGraph(self.edges)
|
||||
x = list(edge_dfs(G, self.nodes, orientation="ignore"))
|
||||
x_ = [(0, 1, FORWARD), (1, 0, FORWARD), (2, 1, REVERSE), (3, 1, REVERSE)]
|
||||
assert x == x_
|
||||
|
||||
def test_digraph_ignore2(self):
|
||||
G = nx.DiGraph()
|
||||
nx.add_path(G, range(4))
|
||||
x = list(edge_dfs(G, [0], orientation="ignore"))
|
||||
x_ = [(0, 1, FORWARD), (1, 2, FORWARD), (2, 3, FORWARD)]
|
||||
assert x == x_
|
||||
|
||||
def test_multidigraph_ignore(self):
|
||||
G = nx.MultiDiGraph(self.edges)
|
||||
x = list(edge_dfs(G, self.nodes, orientation="ignore"))
|
||||
x_ = [
|
||||
(0, 1, 0, FORWARD),
|
||||
(1, 0, 0, FORWARD),
|
||||
(1, 0, 1, REVERSE),
|
||||
(2, 1, 0, REVERSE),
|
||||
(3, 1, 0, REVERSE),
|
||||
]
|
||||
assert x == x_
|
Loading…
Add table
Add a link
Reference in a new issue