Repository: networkx/networkx

Issue:
dfs_predecessors not working as expected
Hi, I have the following simple program
(NetworkX 1.8.1 installed via pip on ubuntu 12.04, python 2.7.3)

G = nx.DiGraph()
G.add_edge('a','b')
G.add_edge('b','c')
G.add_edge('c','d')

print nx.dfs_successors(G,'a')
print nx.dfs_predecessors(G,'a')
print nx.dfs_successors(G,'d')
print nx.dfs_predecessors(G,'d')

I expect as output
{'a': ['b'], 'c': ['d'], 'b': ['c']}
{}
{}
{'a': ['b'], 'c': ['d'], 'b': ['c']}

but the outcome is
{'a': ['b'], 'c': ['d'], 'b': ['c']}
{'c': 'b', 'b': 'a', 'd': 'c'}
{}
{}

It seems that something is wrong with the dfs_predecessors function.



Discussion:
The algorithm runs a DFS from the node you specify and outputs dictionaries of predecessors and successors.  

When you start with node 'a' the DFS is A->B->C->D - you get the successors by following the head of the arrow and predecessors by following the tail.  

When you start with node 'd' the DFS is empty since there are no nodes reachable from 'd'.

Ok, now I understand how it works. Maybe you can add this example to the documentation, I really thought "predecessors" meant that the dfs was run on reverse, like with the ancestors function. (I can do it if you tell me how)

I still have one question though, why the return of dfs_successors has lists as values and the return of dfs_predecessors has nodes?

P.D: Thanks for the quick answer!

Good idea to add an example.
It might make sense to have predecessors return a list to match successors - it's not a list because there can be only one predecessor in a DFS.


Resolving patch:
diff --git a/networkx/algorithms/traversal/breadth_first_search.py b/networkx/algorithms/traversal/breadth_first_search.py
--- a/networkx/algorithms/traversal/breadth_first_search.py
+++ b/networkx/algorithms/traversal/breadth_first_search.py
@@ -5,23 +5,50 @@
 
 Basic algorithms for breadth-first searching.
 """
-__author__ = """\n""".join(['Aric Hagberg <hagberg@lanl.gov>'])
-
-__all__ = ['bfs_edges', 'bfs_tree',
-           'bfs_predecessors', 'bfs_successors']
-
 import networkx as nx
 from collections import defaultdict, deque
+__author__ = """\n""".join(['Aric Hagberg <aric.hagberg@gmail.com>'])
+__all__ = ['bfs_edges', 'bfs_tree', 'bfs_predecessors', 'bfs_successors']
 
 def bfs_edges(G, source, reverse=False):
-    """Produce edges in a breadth-first-search starting at source."""
-    # Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py
-    # by D. Eppstein, July 2004.
+    """Produce edges in a breadth-first-search starting at source.
+
+    Parameters
+    ----------
+    G : NetworkX graph
+
+    source : node, optional
+       Specify starting node for breadth-first search and return edges in
+       the component reachable from source.
+
+    reverse : bool, optional
+       If True traverse a directed graph in the reverse direction
+
+    Returns
+    -------
+    edges: generator
+       A generator of edges in the breadth-first-search.
+
+    Examples
+    --------
+    >>> G = nx.Graph()
+    >>> G.add_path([0,1,2])
+    >>> print(list(nx.bfs_edges(G,0)))
+    [(0, 1), (1, 2)]
+
+    Notes
+    -----
+    Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py
+    by D. Eppstein, July 2004.
+
+    If a source is not specified then a source is chosen arbitrarily and
+    repeatedly until all components in the graph are searched.
+    """
     if reverse and isinstance(G, nx.DiGraph):
         neighbors = G.predecessors_iter
     else:
         neighbors = G.neighbors_iter
-    visited=set([source])
+    visited = set([source])
     queue = deque([(source, neighbors(source))])
     while queue:
         parent, children = queue[0]
@@ -35,19 +62,110 @@ def bfs_edges(G, source, reverse=False):
             queue.popleft()
 
 def bfs_tree(G, source, reverse=False):
-    """Return directed tree of breadth-first-search from source."""
+    """Return an oriented tree constructed from of a breadth-first-search
+    starting at source.
+
+    Parameters
+    ----------
+    G : NetworkX graph
+
+    source : node, optional
+       Specify starting node for breadth-first search and return edges in
+       the component reachable from source.
+
+    reverse : bool, optional
+       If True traverse a directed graph in the reverse direction
+
+    Returns
+    -------
+    T: NetworkX DiGraph
+       An oriented tree
+
+    Examples
+    --------
+    >>> G = nx.Graph()
+    >>> G.add_path([0,1,2])
+    >>> print(list(nx.bfs_edges(G,0)))
+    [(0, 1), (1, 2)]
+
+    Notes
+    -----
+    Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py
+    by D. Eppstein, July 2004.
+
+    If a source is not specified then a source is chosen arbitrarily and
+    repeatedly until all components in the graph are searched.
+    """
     T = nx.DiGraph()
     T.add_node(source)
     T.add_edges_from(bfs_edges(G,source,reverse=reverse))
     return T
 
 def bfs_predecessors(G, source):
-    """Return dictionary of predecessors in breadth-first-search from source."""
+    """Return dictionary of predecessors in breadth-first-search from source.
+
+    Parameters
+    ----------
+    G : NetworkX graph
+
+    source : node, optional
+       Specify starting node for breadth-first search and return edges in
+       the component reachable from source.
+
+    Returns
+    -------
+    pred: dict
+       A dictionary with nodes as keys and predecessor nodes as values.
+
+    Examples
+    --------
+    >>> G = nx.Graph()
+    >>> G.add_path([0,1,2])
+    >>> print(nx.bfs_predecessors(G,0))
+    {1: 0, 2: 1}
+
+    Notes
+    -----
+    Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py
+    by D. Eppstein, July 2004.
+
+    If a source is not specified then a source is chosen arbitrarily and
+    repeatedly until all components in the graph are searched.
+    """
     return dict((t,s) for s,t in bfs_edges(G,source))
 
 def bfs_successors(G, source):
-    """Return dictionary of successors in breadth-first-search from source."""
-    d=defaultdict(list)
+    """Return dictionary of successors in breadth-first-search from source.
+
+    Parameters
+    ----------
+    G : NetworkX graph
+
+    source : node, optional
+       Specify starting node for breadth-first search and return edges in
+       the component reachable from source.
+
+    Returns
+    -------
+    succ: dict
+       A dictionary with nodes as keys and list of succssors nodes as values.
+
+    Examples
+    --------
+    >>> G = nx.Graph()
+    >>> G.add_path([0,1,2])
+    >>> print(nx.bfs_successors(G,0))
+    {0: [1], 1: [2]}
+
+    Notes
+    -----
+    Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py
+    by D. Eppstein, July 2004.
+
+    If a source is not specified then a source is chosen arbitrarily and
+    repeatedly until all components in the graph are searched.
+    """
+    d = defaultdict(list)
     for s,t in bfs_edges(G,source):
         d[s].append(t)
     return dict(d)
diff --git a/networkx/algorithms/traversal/depth_first_search.py b/networkx/algorithms/traversal/depth_first_search.py
--- a/networkx/algorithms/traversal/depth_first_search.py
+++ b/networkx/algorithms/traversal/depth_first_search.py
@@ -8,26 +8,51 @@
 Based on http://www.ics.uci.edu/~eppstein/PADS/DFS.py
 by D. Eppstein, July 2004.
 """
-__author__ = """\n""".join(['Aric Hagberg <hagberg@lanl.gov>'])
-
+import networkx as nx
+from collections import defaultdict
+__author__ = """\n""".join(['Aric Hagberg <aric.hagberg@gmail.com>'])
 __all__ = ['dfs_edges', 'dfs_tree',
            'dfs_predecessors', 'dfs_successors',
            'dfs_preorder_nodes','dfs_postorder_nodes',
            'dfs_labeled_edges']
 
-import networkx as nx
-from collections import defaultdict
+def dfs_edges(G, source=None):
+    """Produce edges in a depth-first-search (DFS).
 
-def dfs_edges(G,source=None):
-    """Produce edges in a depth-first-search starting at source."""
-    # Based on http://www.ics.uci.edu/~eppstein/PADS/DFS.py
-    # by D. Eppstein, July 2004.
+    Parameters
+    ----------
+    G : NetworkX graph
+
+    source : node, optional
+       Specify starting node for depth-first search and return edges in
+       the component reachable from source.
+
+    Returns
+    -------
+    edges: generator
+       A generator of edges in the depth-first-search.
+
+    Examples
+    --------
+    >>> G = nx.Graph()
+    >>> G.add_path([0,1,2])
+    >>> print(list(nx.dfs_edges(G,0)))
+    [(0, 1), (1, 2)]
+
+    Notes
+    -----
+    Based on http://www.ics.uci.edu/~eppstein/PADS/DFS.py
+    by D. Eppstein, July 2004.
+
+    If a source is not specified then a source is chosen arbitrarily and
+    repeatedly until all components in the graph are searched.
+    """
     if source is None:
         # produce edges for all components
-        nodes=G
+        nodes = G
     else:
         # produce edges for components with source
-        nodes=[source]
+        nodes = [source]
     visited=set()
     for start in nodes:
         if start in visited:
@@ -46,7 +71,28 @@ def dfs_edges(G,source=None):
                 stack.pop()
 
 def dfs_tree(G, source):
-    """Return directed tree of depth-first-search from source."""
+    """Return oriented tree constructed from a depth-first-search from source.
+
+    Parameters
+    ----------
+    G : NetworkX graph
+
+    source : node, optional
+       Specify starting node for depth-first search.
+
+    Returns
+    -------
+    T : NetworkX DiGraph
+       An oriented tree
+
+    Examples
+    --------
+    >>> G = nx.Graph()
+    >>> G.add_path([0,1,2])
+    >>> T = nx.dfs_tree(G,0)
+    >>> print(T.edges())
+    [(0, 1), (1, 2)]
+    """
     T = nx.DiGraph()
     if source is None:
         T.add_nodes_from(G)
@@ -56,13 +102,71 @@ def dfs_tree(G, source):
     return T
 
 def dfs_predecessors(G, source=None):
-    """Return dictionary of predecessors in depth-first-search from source."""
+    """Return dictionary of predecessors in depth-first-search from source.
+
+    Parameters
+    ----------
+    G : NetworkX graph
+
+    source : node, optional
+       Specify starting node for depth-first search and return edges in
+       the component reachable from source.
+
+    Returns
+    -------
+    pred: dict
+       A dictionary with nodes as keys and predecessor nodes as values.
+
+    Examples
+    --------
+    >>> G = nx.Graph()
+    >>> G.add_path([0,1,2])
+    >>> print(nx.dfs_predecessors(G,0))
+    {1: 0, 2: 1}
+
+    Notes
+    -----
+    Based on http://www.ics.uci.edu/~eppstein/PADS/DFS.py
+    by D. Eppstein, July 2004.
+
+    If a source is not specified then a source is chosen arbitrarily and
+    repeatedly until all components in the graph are searched.
+    """
     return dict((t,s) for s,t in dfs_edges(G,source=source))
 
 
 def dfs_successors(G, source=None):
-    """Return dictionary of successors in depth-first-search from source."""
-    d=defaultdict(list)
+    """Return dictionary of successors in depth-first-search from source.
+
+    Parameters
+    ----------
+    G : NetworkX graph
+
+    source : node, optional
+       Specify starting node for depth-first search and return edges in
+       the component reachable from source.
+
+    Returns
+    -------
+    succ: dict
+       A dictionary with nodes as keys and list of successor nodes as values.
+
+    Examples
+    --------
+    >>> G = nx.Graph()
+    >>> G.add_path([0,1,2])
+    >>> print(nx.dfs_successors(G,0))
+    {0: [1], 1: [2]}
+
+    Notes
+    -----
+    Based on http://www.ics.uci.edu/~eppstein/PADS/DFS.py
+    by D. Eppstein, July 2004.
+
+    If a source is not specified then a source is chosen arbitrarily and
+    repeatedly until all components in the graph are searched.
+    """
+    d = defaultdict(list)
     for s,t in dfs_edges(G,source=source):
         d[s].append(t)
     return dict(d)
@@ -71,36 +175,121 @@ def dfs_successors(G, source=None):
 def dfs_postorder_nodes(G,source=None):
     """Produce nodes in a depth-first-search post-ordering starting
     from source.
+
+    Parameters
+    ----------
+    G : NetworkX graph
+
+    source : node, optional
+       Specify starting node for depth-first search and return edges in
+       the component reachable from source.
+
+    Returns
+    -------
+    nodes: generator
+       A generator of nodes in a depth-first-search post-ordering.
+
+    Examples
+    --------
+    >>> G = nx.Graph()
+    >>> G.add_path([0,1,2])
+    >>> print(list(nx.dfs_postorder_nodes(G,0)))
+    [2, 1, 0]
+
+    Notes
+    -----
+    Based on http://www.ics.uci.edu/~eppstein/PADS/DFS.py
+    by D. Eppstein, July 2004.
+
+    If a source is not specified then a source is chosen arbitrarily and
+    repeatedly until all components in the graph are searched.
     """
     post=(v for u,v,d in nx.dfs_labeled_edges(G,source=source)
           if d['dir']=='reverse')
-    # chain source to end of pre-ordering
-#    return chain(post,[source])
+    # potential modification: chain source to end of post-ordering
+    # return chain(post,[source])
     return post
 
 
-def dfs_preorder_nodes(G,source=None):
-    """Produce nodes in a depth-first-search pre-ordering starting at source."""
+def dfs_preorder_nodes(G, source=None):
+    """Produce nodes in a depth-first-search pre-ordering starting
+    from source.
+
+    Parameters
+    ----------
+    G : NetworkX graph
+
+    source : node, optional
+       Specify starting node for depth-first search and return edges in
+       the component reachable from source.
+
+    Returns
+    -------
+    nodes: generator
+       A generator of nodes in a depth-first-search pre-ordering.
+
+    Examples
+    --------
+    >>> G = nx.Graph()
+    >>> G.add_path([0,1,2])
+    >>> print(list(nx.dfs_preorder_nodes(G,0)))
+    [0, 1, 2]
+
+    Notes
+    -----
+    Based on http://www.ics.uci.edu/~eppstein/PADS/DFS.py
+    by D. Eppstein, July 2004.
+
+    If a source is not specified then a source is chosen arbitrarily and
+    repeatedly until all components in the graph are searched.
+    """
     pre=(v for u,v,d in nx.dfs_labeled_edges(G,source=source)
          if d['dir']=='forward')
-    # chain source to beginning of pre-ordering
-#    return chain([source],pre)
+    # potential modification: chain source to beginning of pre-ordering
+    # return chain([source],pre)
     return pre
 
 
-def dfs_labeled_edges(G,source=None):
-    """Produce edges in a depth-first-search starting at source and
-    labeled by direction type (forward, reverse, nontree).
+def dfs_labeled_edges(G, source=None):
+    """Produce 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.
+
+    Returns
+    -------
+    edges: generator
+       A generator of edges in the depth-first-search labeled with 'forward',
+       'nontree', and 'reverse'.
+
+    Examples
+    --------
+    >>> G = nx.Graph()
+    >>> G.add_path([0,1,2])
+    >>> edges = (list(nx.dfs_labeled_edges(G,0)))
+
+    Notes
+    -----
+    Based on http://www.ics.uci.edu/~eppstein/PADS/DFS.py
+    by D. Eppstein, July 2004.
+
+    If a source is not specified then a source is chosen arbitrarily and
+    repeatedly until all components in the graph are searched.
     """
     # Based on http://www.ics.uci.edu/~eppstein/PADS/DFS.py
     # by D. Eppstein, July 2004.
     if source is None:
         # produce edges for all components
-        nodes=G
+        nodes = G
     else:
         # produce edges for components with source
-        nodes=[source]
-    visited=set()
+        nodes = [source]
+    visited = set()
     for start in nodes:
         if start in visited:
             continue


Test patch: