Repository: networkx/networkx

Issue:
Include default keyword argument to minimum_spanning_edges function
As pointed out in https://github.com/networkx/networkx/pull/1741#commitcomment-12726208, it would be nice to have a `default` keyword argument to `minimum_spanning_edges`, identical to the keyword argument with the same name in the `Graph.edges()` method. This makes function/method parameters more consistent.



Discussion:

Resolving patch:
diff --git a/networkx/algorithms/tree/mst.py b/networkx/algorithms/tree/mst.py
--- a/networkx/algorithms/tree/mst.py
+++ b/networkx/algorithms/tree/mst.py
@@ -1,48 +1,52 @@
 # -*- coding: utf-8 -*-
-"""
-Algorithms for calculating min/max spanning trees/forests.
-
-"""
-#    Copyright (C) 2015 NetworkX Developers
+#    Copyright (C) 2017 NetworkX Developers
 #    Aric Hagberg <hagberg@lanl.gov>
 #    Dan Schult <dschult@colgate.edu>
 #    Pieter Swart <swart@lanl.gov>
 #    Loïc Séguin-C. <loicseguin@gmail.com>
 #    All rights reserved.
 #    BSD license.
+"""
+Algorithms for calculating min/max spanning trees/forests.
 
-__all__ = [
-    'minimum_spanning_edges', 'maximum_spanning_edges',
-    'minimum_spanning_tree', 'maximum_spanning_tree',
-]
-
+"""
 from heapq import heappop, heappush
 from itertools import count
 
 import networkx as nx
 from networkx.utils import UnionFind, not_implemented_for
 
+__all__ = [
+    'minimum_spanning_edges', 'maximum_spanning_edges',
+    'minimum_spanning_tree', 'maximum_spanning_tree',
+]
+
 
 @not_implemented_for('multigraph')
 def boruvka_mst_edges(G, minimum=True, weight='weight', keys=False, data=True):
-    """Iterates over the edges of a minimum spanning tree as computed by
-    Borůvka's algorithm.
+    """Iterate over edges of a Borůvka's algorithm min/max spanning tree.
+
+    Parameters
+    ----------
+    G : NetworkX Graph
+        The edges of `G` must have distinct weights,
+        otherwise the edges may not form a tree.
 
-    `G` is a NetworkX graph. Also, the edges must have distinct weights,
-    otherwise the edges may not form a tree.
+    minimum : bool (default: True)
+        Find the minimum (True) or maximum (False) spanning tree.
 
-    `weight` is the edge attribute that stores the edge weights. Each
-    edge in the graph must have such an attribute, otherwise a
-    :exc:`KeyError` will be raised.
+    weight : string (default: 'weight')
+        The name of the edge attribute holding the edge weights.
 
-    If `data` is True, this iterator yields edges of the form
-    ``(u, v, d)``, where ``u`` and ``v`` are nodes and ``d`` is the edge
-    attribute dictionary. Otherwise, it yields edges of the form
-    ``(u, v)``.
+    keys : bool (default: True)
+        This argument is ignored since this function is not
+        implemented for multigraphs; it exists only for consistency
+        with the other minimum spanning tree functions.
 
-    The `keys` argument is ignored, since this function is not
-    implemented for multigraphs; it exists only for consistency with the
-    other minimum spanning tree functions.
+    data : bool (default: True)
+        Flag for whether to yield edge attribute dicts.
+        If True, yield edges `(u, v, d)`, where `d` is the attribute dict.
+        If False, yield edges `(u, v)`.
 
     """
     opt = min if minimum else max
@@ -107,6 +111,29 @@ def best_edge(component):
 
 
 def kruskal_mst_edges(G, minimum, weight='weight', keys=True, data=True):
+    """Iterate over edges of a Kruskal's algorithm min/max spanning tree.
+
+    Parameters
+    ----------
+    G : NetworkX Graph
+        The graph holding the tree of interest.
+
+    minimum : bool (default: True)
+        Find the minimum (True) or maximum (False) spanning tree.
+
+    weight : string (default: 'weight')
+        The name of the edge attribute holding the edge weights.
+
+    keys : bool (default: True)
+        If `G` is a multigraph, `keys` controls whether edge keys ar yielded.
+        Otherwise `keys` is ignored.
+
+    data : bool (default: True)
+        Flag for whether to yield edge attribute dicts.
+        If True, yield edges `(u, v, d)`, where `d` is the attribute dict.
+        If False, yield edges `(u, v)`.
+
+    """
     subtrees = UnionFind()
     if G.is_multigraph():
         edges = G.edges(keys=True, data=True)
@@ -114,21 +141,20 @@ def kruskal_mst_edges(G, minimum, weight='weight', keys=True, data=True):
         edges = G.edges(data=True)
     getweight = lambda t: t[-1].get(weight, 1)
     edges = sorted(edges, key=getweight, reverse=not minimum)
-    is_multigraph = G.is_multigraph()
     # Multigraphs need to handle edge keys in addition to edge data.
-    if is_multigraph:
+    if G.is_multigraph():
         for u, v, k, d in edges:
             if subtrees[u] != subtrees[v]:
                 if keys:
                     if data:
-                        yield (u, v, k, d)
+                        yield u, v, k, d
                     else:
-                        yield (u, v, k)
+                        yield u, v, k
                 else:
                     if data:
-                        yield (u, v, d)
+                        yield u, v, d
                     else:
-                        yield (u, v)
+                        yield u, v
                 subtrees.union(u, v)
     else:
         for u, v, d in edges:
@@ -157,43 +183,49 @@ def prim_mst_edges(G, minimum, weight='weight', keys=True, data=True):
         frontier = []
         visited = [u]
         if is_multigraph:
-            for u, v, k, d in G.edges(u, keys=True, data=True):
-                push(frontier, (d.get(weight, 1) * sign, next(c), u, v, k))
+            for v, keydict in G.adj[u].items():
+                for k, d in keydict.items():
+                    wt = d.get(weight, 1) * sign
+                    push(frontier, (wt, next(c), u, v, k, d))
         else:
-            for u, v, d in G.edges(u, data=True):
-                push(frontier, (d.get(weight, 1) * sign, next(c), u, v))
+            for v, d in G.adj[u].items():
+                wt = d.get(weight, 1) * sign
+                push(frontier, (wt, next(c), u, v, d))
         while frontier:
             if is_multigraph:
-                W, _, u, v, k = pop(frontier)
+                W, _, u, v, k, d = pop(frontier)
             else:
-                W, _, u, v = pop(frontier)
+                W, _, u, v, d = pop(frontier)
             if v in visited:
                 continue
-            visited.append(v)
-            nodes.remove(v)
-            if is_multigraph:
-                for _, w, k2, d2 in G.edges(v, keys=True, data=True):
-                    if w in visited:
-                        continue
-                    new_weight = d2.get(weight, 1) * sign
-                    push(frontier, (new_weight, next(c), v, w, k2))
-            else:
-                for _, w, d2 in G.edges(v, data=True):
-                    if w in visited:
-                        continue
-                    new_weight = d2.get(weight, 1) * sign
-                    push(frontier, (new_weight, next(c), v, w))
             # Multigraphs need to handle edge keys in addition to edge data.
             if is_multigraph and keys:
                 if data:
-                    yield u, v, k, G[u][v]
+                    yield u, v, k, d
                 else:
                     yield u, v, k
             else:
                 if data:
-                    yield u, v, G[u][v]
+                    yield u, v, d
                 else:
                     yield u, v
+            # update frontier
+            visited.append(v)
+            nodes.remove(v)
+            if is_multigraph:
+                for w, keydict in G.adj[v].items():
+                    if w in visited:
+                        continue
+                    for k2, d2 in keydict.items():
+                        new_weight = d2.get(weight, 1) * sign
+                        push(frontier, (new_weight, next(c), v, w, k2, d2))
+            else:
+                for w, d2 in G.adj[v].items():
+                    if w in visited:
+                        continue
+                    new_weight = d2.get(weight, 1) * sign
+                    push(frontier, (new_weight, next(c), v, w, d2))
+
 
 ALGORITHMS = {
     'boruvka': boruvka_mst_edges,
@@ -204,19 +236,8 @@ def prim_mst_edges(G, minimum, weight='weight', keys=True, data=True):
 
 
 @not_implemented_for('directed')
-def _spanning_edges(G, minimum, algorithm='kruskal', weight='weight',
-                    keys=True, data=True):
-    try:
-        algo = ALGORITHMS[algorithm]
-    except KeyError:
-        msg = '{} is not a valid choice for an algorithm.'.format(algorithm)
-        raise ValueError(msg)
-
-    return algo(G, minimum=minimum, weight=weight, keys=keys, data=data)
-
-
-def minimum_spanning_edges(G, algorithm='kruskal', weight='weight', keys=True,
-                           data=True):
+def minimum_spanning_edges(G, algorithm='kruskal', weight='weight',
+                           keys=True, data=True):
     """Generate edges in a minimum spanning forest of an undirected
     weighted graph.
 
@@ -232,15 +253,14 @@ def minimum_spanning_edges(G, algorithm='kruskal', weight='weight', keys=True,
 
     algorithm : string
        The algorithm to use when finding a minimum spanning tree. Valid
-       choices are 'kruskal', 'prim', or 'boruvka'. The default is
-       'kruskal'.
+       choices are 'kruskal', 'prim', or 'boruvka'. The default is 'kruskal'.
 
     weight : string
        Edge data key to use for weight (default 'weight').
 
     keys : bool
-       Whether to yield edge key in multigraphs in addition to the
-       edge. If `G` is not a multigraph, this is ignored.
+       Whether to yield edge key in multigraphs in addition to the edge.
+       If `G` is not a multigraph, this is ignored.
 
     data : bool, optional
        If True yield the edge data along with the edge.
@@ -248,20 +268,16 @@ def minimum_spanning_edges(G, algorithm='kruskal', weight='weight', keys=True,
     Returns
     -------
     edges : iterator
-       An iterator over tuples representing edges in a minimum spanning
-       tree of `G`.
+       An iterator over edges in a maximum spanning tree of `G`.
+       Edges connecting nodes `u` and `v` are represented as tuples:
+       `(u, v, k, d)` or `(u, v, k)` or `(u, v, d)` or `(u, v)`
 
-       If `G` is a multigraph and both `keys` and `data` are
-       True, then the tuples are four-tuples of the form `(u, v, k,
-       w)`, where `(u, v)` is an edge, `k` is the edge key
-       identifying the particular edge joining `u` with `v`, and
-       `w` is the weight of the edge. If `keys` is True but
-       `data` is False, the tuples are three-tuples of the form
-       `(u, v, k)`.
+       If `G` is a multigraph, `keys` indicates whether the edge key `k` will
+       be reported in the third position in the edge tuple. `data` indicates
+       whether the edge datadict `d` will appear at the end of the edge tuple.
 
-       If `G` is not a multigraph, the tuples are of the form `(u, v,
-       w)` if `data` is True or `(u, v)` if `data` is
-       False.
+       If `G` is not a multigraph, the tuples are `(u, v, d)` if `data` is True
+       or `(u, v)` if `data` is False.
 
     Examples
     --------
@@ -297,11 +313,18 @@ def minimum_spanning_edges(G, algorithm='kruskal', weight='weight', keys=True,
     http://www.ics.uci.edu/~eppstein/PADS/
 
     """
-    return _spanning_edges(G, minimum=True, algorithm=algorithm,
-                           weight=weight, keys=keys, data=data)
+    try:
+        algo = ALGORITHMS[algorithm]
+    except KeyError:
+        msg = '{} is not a valid choice for an algorithm.'.format(algorithm)
+        raise ValueError(msg)
 
+    return algo(G, minimum=True, weight=weight, keys=keys, data=data)
 
-def maximum_spanning_edges(G, algorithm='kruskal', weight='weight', data=True):
+
+@not_implemented_for('directed')
+def maximum_spanning_edges(G, algorithm='kruskal', weight='weight',
+                           keys=True, data=True):
     """Generate edges in a maximum spanning forest of an undirected
     weighted graph.
 
@@ -317,15 +340,14 @@ def maximum_spanning_edges(G, algorithm='kruskal', weight='weight', data=True):
 
     algorithm : string
        The algorithm to use when finding a maximum spanning tree. Valid
-       choices are 'kruskal', 'prim', or 'boruvka'. The default is
-       'kruskal'.
+       choices are 'kruskal', 'prim', or 'boruvka'. The default is 'kruskal'.
 
     weight : string
        Edge data key to use for weight (default 'weight').
 
     keys : bool
-       Whether to yield edge key in multigraphs in addition to the
-       edge. If `G` is not a multigraph, this is ignored.
+       Whether to yield edge key in multigraphs in addition to the edge.
+       If `G` is not a multigraph, this is ignored.
 
     data : bool, optional
        If True yield the edge data along with the edge.
@@ -333,20 +355,16 @@ def maximum_spanning_edges(G, algorithm='kruskal', weight='weight', data=True):
     Returns
     -------
     edges : iterator
-       An iterator over tuples representing edges in a maximum spanning
-       tree of `G`.
+       An iterator over edges in a maximum spanning tree of `G`.
+       Edges connecting nodes `u` and `v` are represented as tuples:
+       `(u, v, k, d)` or `(u, v, k)` or `(u, v, d)` or `(u, v)`
 
-       If `G` is a multigraph and both `keys` and `data` are
-       True, then the tuples are four-tuples of the form `(u, v, k,
-       w)`, where `(u, v)` is an edge, `k` is the edge key
-       identifying the particular edge joining `u` with `v`, and
-       `w` is the weight of the edge. If `keys` is True but
-       `data` is False, the tuples are three-tuples of the form
-       `(u, v, k)`.
+       If `G` is a multigraph, `keys` indicates whether the edge key `k` will
+       be reported in the third position in the edge tuple. `data` indicates
+       whether the edge datadict `d` will appear at the end of the edge tuple.
 
-       If `G` is not a multigraph, the tuples are of the form `(u, v,
-       w)` if `data` is True or `(u, v)` if `data` is
-       False.
+       If `G` is not a multigraph, the tuples are `(u, v, d)` if `data` is True
+       or `(u, v)` if `data` is False.
 
     Examples
     --------
@@ -381,29 +399,13 @@ def maximum_spanning_edges(G, algorithm='kruskal', weight='weight', data=True):
     Modified code from David Eppstein, April 2006
     http://www.ics.uci.edu/~eppstein/PADS/
     """
-    return _spanning_edges(G, minimum=False, algorithm=algorithm,
-                           weight=weight, data=data)
-
-
-@not_implemented_for('directed')
-def _optimum_spanning_tree(G, algorithm, minimum, weight='weight'):
-    # When creating the spanning tree, we can ignore the key used to
-    # identify multigraph edges, since a tree is guaranteed to have no
-    # multiedges. This is why we use `keys=False`.
-    edges = _spanning_edges(G, minimum, algorithm=algorithm, weight=weight,
-                            keys=False, data=True)
-    T = nx.Graph(edges)
-
-    # Add isolated nodes
-    if len(T) != len(G):
-        T.add_nodes_from(nx.isolates(G))
-
-    # Add node and graph attributes as shallow copy
-    for n in T:
-        T.node[n].update(G.node[n])
-    T.graph = G.graph.copy()
+    try:
+        algo = ALGORITHMS[algorithm]
+    except KeyError:
+        msg = '{} is not a valid choice for an algorithm.'.format(algorithm)
+        raise ValueError(msg)
 
-    return T
+    return algo(G, minimum=False, weight=weight, keys=keys, data=data)
 
 
 def minimum_spanning_tree(G, weight='weight', algorithm='kruskal'):
@@ -449,8 +451,12 @@ def minimum_spanning_tree(G, weight='weight', algorithm='kruskal'):
     See :mod:`networkx.tree.recognition` for more detailed definitions.
 
     """
-    return _optimum_spanning_tree(G, algorithm=algorithm, minimum=True,
-                                  weight=weight)
+    edges = minimum_spanning_edges(G, algorithm, weight, keys=True, data=True)
+    T = G.__class__()  # Same graph class as G
+    T.graph.update(G.graph)
+    T.add_nodes_from(G.node.items())
+    T.add_edges_from(edges)
+    return T
 
 
 def maximum_spanning_tree(G, weight='weight', algorithm='kruskal'):
@@ -498,5 +504,10 @@ def maximum_spanning_tree(G, weight='weight', algorithm='kruskal'):
     See :mod:`networkx.tree.recognition` for more detailed definitions.
 
     """
-    return _optimum_spanning_tree(G, algorithm=algorithm, minimum=False,
-                                  weight=weight)
+    edges = maximum_spanning_edges(G, algorithm, weight, keys=True, data=True)
+    edges = list(edges)
+    T = G.__class__()  # Same graph class as G
+    T.graph.update(G.graph)
+    T.add_nodes_from(G.node.items())
+    T.add_edges_from(edges)
+    return T


Test patch: