Repository: networkx/networkx

Issue:
maximal_matching and max_weight_matching have different return types
The former returns a set of edges, the latter a dictionary. Should these return the same type of object?



Discussion:
Let's return a set of edges for `max_weight_matching` to make them the same.

Resolving patch:
diff --git a/networkx/algorithms/covering.py b/networkx/algorithms/covering.py
--- a/networkx/algorithms/covering.py
+++ b/networkx/algorithms/covering.py
@@ -72,9 +72,12 @@ def min_edge_cover(G, matching_algorithm=None):
                                      maxcardinality=True)
     maximum_matching = matching_algorithm(G)
     # ``min_cover`` is superset of ``maximum_matching``
-    min_cover = set(maximum_matching.items())
+    try:
+        min_cover = set(maximum_matching.items()) # bipartite matching case returns dict
+    except AttributeError:
+        min_cover = maximum_matching
     # iterate for uncovered nodes
-    uncovered_nodes = set(G) - {v for u, v in min_cover}
+    uncovered_nodes = set(G) - {v for u, v in min_cover} - {u for u, v in min_cover}
     for v in uncovered_nodes:
         # Since `v` is uncovered, each edge incident to `v` will join it
         # with a covered node (otherwise, if there were an edge joining
diff --git a/networkx/algorithms/matching.py b/networkx/algorithms/matching.py
--- a/networkx/algorithms/matching.py
+++ b/networkx/algorithms/matching.py
@@ -69,7 +69,8 @@ def matching_dict_to_set(matching):
     # appear in `matching.items()`, so we use a set of sets. This way,
     # only the (frozen)set `{u, v}` appears as an element in the
     # returned set.
-    return set(map(frozenset, matching.items()))
+
+    return set((u,v) for (u,v) in set(map(frozenset, matching.items())))
 
 
 def is_matching(G, matching):
@@ -172,11 +173,8 @@ def max_weight_matching(G, maxcardinality=False, weight='weight'):
 
     Returns
     -------
-    mate : dictionary
-       The matching is returned as a dictionary, mate, such that
-       mate[v] == w if node v is matched to node w.  Unmatched nodes do not
-       occur as a key in mate.
-
+    matching : set
+        A maximal matching of the graph.
 
     Notes
     -----
@@ -249,7 +247,7 @@ def leaves(self):
     # Get a list of vertices.
     gnodes = list(G)
     if not gnodes:
-        return {}  # don't bother with empty graphs
+        return set( ) # don't bother with empty graphs
 
     # Find the maximum edge weight.
     maxweight = 0
@@ -931,4 +929,4 @@ def verifyOptimum():
     if allinteger:
         verifyOptimum()
 
-    return mate
+    return matching_dict_to_set(mate)


Test patch:
diff --git a/networkx/algorithms/tests/test_covering.py b/networkx/algorithms/tests/test_covering.py
--- a/networkx/algorithms/tests/test_covering.py
+++ b/networkx/algorithms/tests/test_covering.py
@@ -24,7 +24,7 @@ def test_graph_single_edge(self):
         G = nx.Graph()
         G.add_edge(0, 1)
         assert_equal(nx.min_edge_cover(G),
-                     {(0, 1), (1, 0)})
+                     {(0, 1)})
 
     def test_bipartite_explicit(self):
         G = nx.Graph()
@@ -34,6 +34,7 @@ def test_bipartite_explicit(self):
                           (2, 'c'), (3, 'c'), (4, 'a')])
         min_cover = nx.min_edge_cover(G, nx.algorithms.bipartite.matching.
                                       eppstein_matching)
+        min_cover2 = nx.min_edge_cover(G)
         assert_true(nx.is_edge_cover(G, min_cover))
         assert_equal(len(min_cover), 8)
 
@@ -41,7 +42,7 @@ def test_complete_graph(self):
         G = nx.complete_graph(10)
         min_cover = nx.min_edge_cover(G)
         assert_true(nx.is_edge_cover(G, min_cover))
-        assert_equal(len(min_cover), 10)
+        assert_equal(len(min_cover), 5)
 
 
 class TestIsEdgeCover:
diff --git a/networkx/algorithms/tests/test_matching.py b/networkx/algorithms/tests/test_matching.py
--- a/networkx/algorithms/tests/test_matching.py
+++ b/networkx/algorithms/tests/test_matching.py
@@ -5,7 +5,8 @@
 from nose.tools import assert_false
 from nose.tools import assert_true
 import networkx as nx
-
+from networkx.algorithms.matching import matching_dict_to_set
+from networkx.testing import assert_edges_equal
 
 class TestMaxWeightMatching(object):
     """Unit tests for the
@@ -16,28 +17,28 @@ class TestMaxWeightMatching(object):
     def test_trivial1(self):
         """Empty graph"""
         G = nx.Graph()
-        assert_equal(nx.max_weight_matching(G), {})
+        assert_equal(nx.max_weight_matching(G), set())
 
     def test_trivial2(self):
         """Self loop"""
         G = nx.Graph()
         G.add_edge(0, 0, weight=100)
-        assert_equal(nx.max_weight_matching(G), {})
+        assert_equal(nx.max_weight_matching(G), set())
 
     def test_trivial3(self):
         """Single edge"""
         G = nx.Graph()
         G.add_edge(0, 1)
-        assert_equal(nx.max_weight_matching(G),
-                     {0: 1, 1: 0})
+        assert_edges_equal(nx.max_weight_matching(G),
+                     matching_dict_to_set({0: 1, 1: 0}))
 
     def test_trivial4(self):
         """Small graph"""
         G = nx.Graph()
         G.add_edge('one', 'two', weight=10)
         G.add_edge('two', 'three', weight=11)
-        assert_equal(nx.max_weight_matching(G),
-                     {'three': 'two', 'two': 'three'})
+        assert_edges_equal(nx.max_weight_matching(G),
+                     matching_dict_to_set({'three': 'two', 'two': 'three'}))
 
     def test_trivial5(self):
         """Path"""
@@ -45,18 +46,18 @@ def test_trivial5(self):
         G.add_edge(1, 2, weight=5)
         G.add_edge(2, 3, weight=11)
         G.add_edge(3, 4, weight=5)
-        assert_equal(nx.max_weight_matching(G),
-                     {2: 3, 3: 2})
-        assert_equal(nx.max_weight_matching(G, 1),
-                     {1: 2, 2: 1, 3: 4, 4: 3})
+        assert_edges_equal(nx.max_weight_matching(G),
+                     matching_dict_to_set({2: 3, 3: 2}))
+        assert_edges_equal(nx.max_weight_matching(G, 1),
+                    matching_dict_to_set({1: 2, 2: 1, 3: 4, 4: 3}))
 
     def test_trivial6(self):
         """Small graph with arbitrary weight attribute"""
         G = nx.Graph()
         G.add_edge('one', 'two', weight=10, abcd=11)
         G.add_edge('two', 'three', weight=11, abcd=10)
-        assert_equal(nx.max_weight_matching(G, weight='abcd'),
-                     {'one': 'two', 'two': 'one'})
+        assert_edges_equal(nx.max_weight_matching(G, weight='abcd'),
+                     matching_dict_to_set({'one': 'two', 'two': 'one'}))
 
     def test_floating_point_weights(self):
         """Floating point weights"""
@@ -65,8 +66,8 @@ def test_floating_point_weights(self):
         G.add_edge(2, 3, weight=math.exp(1))
         G.add_edge(1, 3, weight=3.0)
         G.add_edge(1, 4, weight=math.sqrt(2.0))
-        assert_equal(nx.max_weight_matching(G),
-                     {1: 4, 2: 3, 3: 2, 4: 1})
+        assert_edges_equal(nx.max_weight_matching(G),
+                    matching_dict_to_set({1: 4, 2: 3, 3: 2, 4: 1}))
 
     def test_negative_weights(self):
         """Negative weights"""
@@ -76,38 +77,38 @@ def test_negative_weights(self):
         G.add_edge(2, 3, weight=1)
         G.add_edge(2, 4, weight=-1)
         G.add_edge(3, 4, weight=-6)
-        assert_equal(nx.max_weight_matching(G),
-                     {1: 2, 2: 1})
-        assert_equal(nx.max_weight_matching(G, 1),
-                     {1: 3, 2: 4, 3: 1, 4: 2})
+        assert_edges_equal(nx.max_weight_matching(G),
+                     matching_dict_to_set({1: 2, 2: 1}))
+        assert_edges_equal(nx.max_weight_matching(G, 1),
+                     matching_dict_to_set({1: 3, 2: 4, 3: 1, 4: 2}))
 
     def test_s_blossom(self):
         """Create S-blossom and use it for augmentation:"""
         G = nx.Graph()
         G.add_weighted_edges_from([(1, 2, 8), (1, 3, 9),
                                    (2, 3, 10), (3, 4, 7)])
-        assert_equal(nx.max_weight_matching(G),
-                     {1: 2, 2: 1, 3: 4, 4: 3})
+        assert_edges_equal(nx.max_weight_matching(G),
+                     matching_dict_to_set({1: 2, 2: 1, 3: 4, 4: 3}))
 
         G.add_weighted_edges_from([(1, 6, 5), (4, 5, 6)])
-        assert_equal(nx.max_weight_matching(G),
-                     {1: 6, 2: 3, 3: 2, 4: 5, 5: 4, 6: 1})
+        assert_edges_equal(nx.max_weight_matching(G),
+                     matching_dict_to_set({1: 6, 2: 3, 3: 2, 4: 5, 5: 4, 6: 1}))
 
     def test_s_t_blossom(self):
         """Create S-blossom, relabel as T-blossom, use for augmentation:"""
         G = nx.Graph()
         G.add_weighted_edges_from([(1, 2, 9), (1, 3, 8), (2, 3, 10),
                                    (1, 4, 5), (4, 5, 4), (1, 6, 3)])
-        assert_equal(nx.max_weight_matching(G),
-                     {1: 6, 2: 3, 3: 2, 4: 5, 5: 4, 6: 1})
+        assert_edges_equal(nx.max_weight_matching(G),
+                     matching_dict_to_set({1: 6, 2: 3, 3: 2, 4: 5, 5: 4, 6: 1}))
         G.add_edge(4, 5, weight=3)
         G.add_edge(1, 6, weight=4)
-        assert_equal(nx.max_weight_matching(G),
-                     {1: 6, 2: 3, 3: 2, 4: 5, 5: 4, 6: 1})
+        assert_edges_equal(nx.max_weight_matching(G),
+                     matching_dict_to_set({1: 6, 2: 3, 3: 2, 4: 5, 5: 4, 6: 1}))
         G.remove_edge(1, 6)
         G.add_edge(3, 6, weight=4)
-        assert_equal(nx.max_weight_matching(G),
-                     {1: 2, 2: 1, 3: 6, 4: 5, 5: 4, 6: 3})
+        assert_edges_equal(nx.max_weight_matching(G),
+                     matching_dict_to_set({1: 2, 2: 1, 3: 6, 4: 5, 5: 4, 6: 3}))
 
     def test_nested_s_blossom(self):
         """Create nested S-blossom, use for augmentation:"""
@@ -117,7 +118,7 @@ def test_nested_s_blossom(self):
                                    (2, 4, 8), (3, 5, 8), (4, 5, 10),
                                    (5, 6, 6)])
         assert_equal(nx.max_weight_matching(G),
-                     {1: 3, 2: 4, 3: 1, 4: 2, 5: 6, 6: 5})
+                     matching_dict_to_set({1: 3, 2: 4, 3: 1, 4: 2, 5: 6, 6: 5}))
 
     def test_nested_s_blossom_relabel(self):
         """Create S-blossom, relabel as S, include in nested S-blossom:"""
@@ -125,8 +126,8 @@ def test_nested_s_blossom_relabel(self):
         G.add_weighted_edges_from([(1, 2, 10), (1, 7, 10), (2, 3, 12),
                                    (3, 4, 20), (3, 5, 20), (4, 5, 25),
                                    (5, 6, 10), (6, 7, 10), (7, 8, 8)])
-        assert_equal(nx.max_weight_matching(G),
-                     {1: 2, 2: 1, 3: 4, 4: 3, 5: 6, 6: 5, 7: 8, 8: 7})
+        assert_edges_equal(nx.max_weight_matching(G),
+                     matching_dict_to_set({1: 2, 2: 1, 3: 4, 4: 3, 5: 6, 6: 5, 7: 8, 8: 7}))
 
     def test_nested_s_blossom_expand(self):
         """Create nested S-blossom, augment, expand recursively:"""
@@ -135,8 +136,8 @@ def test_nested_s_blossom_expand(self):
                                    (2, 4, 12), (3, 5, 12), (4, 5, 14),
                                    (4, 6, 12), (5, 7, 12), (6, 7, 14),
                                    (7, 8, 12)])
-        assert_equal(nx.max_weight_matching(G),
-                     {1: 2, 2: 1, 3: 5, 4: 6, 5: 3, 6: 4, 7: 8, 8: 7})
+        assert_edges_equal(nx.max_weight_matching(G),
+                     matching_dict_to_set({1: 2, 2: 1, 3: 5, 4: 6, 5: 3, 6: 4, 7: 8, 8: 7}))
 
     def test_s_blossom_relabel_expand(self):
         """Create S-blossom, relabel as T, expand:"""
@@ -144,8 +145,8 @@ def test_s_blossom_relabel_expand(self):
         G.add_weighted_edges_from([(1, 2, 23), (1, 5, 22), (1, 6, 15),
                                    (2, 3, 25), (3, 4, 22), (4, 5, 25),
                                    (4, 8, 14), (5, 7, 13)])
-        assert_equal(nx.max_weight_matching(G),
-                     {1: 6, 2: 3, 3: 2, 4: 8, 5: 7, 6: 1, 7: 5, 8: 4})
+        assert_edges_equal(nx.max_weight_matching(G),
+                     matching_dict_to_set({1: 6, 2: 3, 3: 2, 4: 8, 5: 7, 6: 1, 7: 5, 8: 4}))
 
     def test_nested_s_blossom_relabel_expand(self):
         """Create nested S-blossom, relabel as T, expand:"""
@@ -153,8 +154,8 @@ def test_nested_s_blossom_relabel_expand(self):
         G.add_weighted_edges_from([(1, 2, 19), (1, 3, 20), (1, 8, 8),
                                    (2, 3, 25), (2, 4, 18), (3, 5, 18),
                                    (4, 5, 13), (4, 7, 7), (5, 6, 7)])
-        assert_equal(nx.max_weight_matching(G),
-                     {1: 8, 2: 3, 3: 2, 4: 7, 5: 6, 6: 5, 7: 4, 8: 1})
+        assert_edges_equal(nx.max_weight_matching(G),
+                     matching_dict_to_set({1: 8, 2: 3, 3: 2, 4: 7, 5: 6, 6: 5, 7: 4, 8: 1}))
 
     def test_nasty_blossom1(self):
         """Create blossom, relabel as T in more than one way, expand,
@@ -165,9 +166,9 @@ def test_nasty_blossom1(self):
                                    (3, 4, 45), (4, 5, 50), (1, 6, 30),
                                    (3, 9, 35), (4, 8, 35), (5, 7, 26),
                                    (9, 10, 5)])
-        assert_equal(nx.max_weight_matching(G),
-                     {1: 6, 2: 3, 3: 2, 4: 8, 5: 7,
-                      6: 1, 7: 5, 8: 4, 9: 10, 10: 9})
+        assert_edges_equal(nx.max_weight_matching(G),
+                     matching_dict_to_set({1: 6, 2: 3, 3: 2, 4: 8, 5: 7,
+                      6: 1, 7: 5, 8: 4, 9: 10, 10: 9}))
 
     def test_nasty_blossom2(self):
         """Again but slightly different:"""
@@ -176,9 +177,9 @@ def test_nasty_blossom2(self):
                                    (3, 4, 45), (4, 5, 50), (1, 6, 30),
                                    (3, 9, 35), (4, 8, 26), (5, 7, 40),
                                    (9, 10, 5)])
-        assert_equal(nx.max_weight_matching(G),
-                     {1: 6, 2: 3, 3: 2, 4: 8, 5: 7,
-                      6: 1, 7: 5, 8: 4, 9: 10, 10: 9})
+        assert_edges_equal(nx.max_weight_matching(G),
+                     matching_dict_to_set({1: 6, 2: 3, 3: 2, 4: 8, 5: 7,
+                      6: 1, 7: 5, 8: 4, 9: 10, 10: 9}))
 
     def test_nasty_blossom_least_slack(self):
         """Create blossom, relabel as T, expand such that a new
@@ -189,9 +190,9 @@ def test_nasty_blossom_least_slack(self):
                                    (3, 4, 45), (4, 5, 50), (1, 6, 30),
                                    (3, 9, 35), (4, 8, 28), (5, 7, 26),
                                    (9, 10, 5)])
-        assert_equal(nx.max_weight_matching(G),
-                     {1: 6, 2: 3, 3: 2, 4: 8, 5: 7,
-                      6: 1, 7: 5, 8: 4, 9: 10, 10: 9})
+        assert_edges_equal(nx.max_weight_matching(G),
+                     matching_dict_to_set({1: 6, 2: 3, 3: 2, 4: 8, 5: 7,
+                      6: 1, 7: 5, 8: 4, 9: 10, 10: 9}))
 
     def test_nasty_blossom_augmenting(self):
         """Create nested blossom, relabel as T in more than one way"""
@@ -203,9 +204,9 @@ def test_nasty_blossom_augmenting(self):
                                    (5, 6, 94), (6, 7, 50), (1, 8, 30),
                                    (3, 11, 35), (5, 9, 36), (7, 10, 26),
                                    (11, 12, 5)])
-        assert_equal(nx.max_weight_matching(G),
-                     {1: 8, 2: 3, 3: 2, 4: 6, 5: 9, 6: 4,
-                      7: 10, 8: 1, 9: 5, 10: 7, 11: 12, 12: 11})
+        assert_edges_equal(nx.max_weight_matching(G),
+                     matching_dict_to_set({1: 8, 2: 3, 3: 2, 4: 6, 5: 9, 6: 4,
+                      7: 10, 8: 1, 9: 5, 10: 7, 11: 12, 12: 11}))
 
     def test_nasty_blossom_expand_recursively(self):
         """Create nested S-blossom, relabel as S, expand recursively:"""
@@ -214,9 +215,9 @@ def test_nasty_blossom_expand_recursively(self):
                                    (2, 4, 55), (3, 5, 55), (4, 5, 50),
                                    (1, 8, 15), (5, 7, 30), (7, 6, 10),
                                    (8, 10, 10), (4, 9, 30)])
-        assert_equal(nx.max_weight_matching(G),
-                     {1: 2, 2: 1, 3: 5, 4: 9, 5: 3,
-                      6: 7, 7: 6, 8: 10, 9: 4, 10: 8})
+        assert_edges_equal(nx.max_weight_matching(G),
+                     matching_dict_to_set({1: 2, 2: 1, 3: 5, 4: 9, 5: 3,
+                      6: 7, 7: 6, 8: 10, 9: 4, 10: 8}))
 
 
 class TestIsMatching(object):