Skip to content

Commit

Permalink
Allow more flexible multigraph add_edges_from options (networkx#2519)
Browse files Browse the repository at this point in the history
Can now do (u, v, d) or (u, v, k) with k used only when dict.update(d)
raises an exception.  Basically if d is a dict or iterator of 2-tuples
assume it is edge data. Otherwise assume it is a key.
  • Loading branch information
dschult authored Jul 17, 2017
1 parent 231c853 commit 7a138a1
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 38 deletions.
1 change: 0 additions & 1 deletion networkx/classes/digraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,6 @@ def add_edges_from(self, ebunch, **attr):
ne = len(e)
if ne == 3:
u, v, dd = e
assert hasattr(dd, "update")
elif ne == 2:
u, v = e
dd = {}
Expand Down
13 changes: 10 additions & 3 deletions networkx/classes/multigraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from networkx.classes.views import AtlasView3
from networkx.classes.views import MultiEdgeView, MultiDegreeView
from networkx import NetworkXError
from networkx.utils import iterable


class MultiGraph(Graph):
Expand Down Expand Up @@ -370,8 +371,9 @@ def add_edges_from(self, ebunch, **attr):
graph. The edges can be:
- 2-tuples (u, v) or
- 3-tuples (u, v, d) for an edge attribute dict d, or
- 4-tuples (u, v, k, d) for an edge identified by key k
- 3-tuples (u, v, d) for an edge data dict d, or
- 3-tuples (u, v, k) for not iterable key k, or
- 4-tuples (u, v, k, d) for an edge with data and key k
attr : keyword arguments, optional
Edge data (or labels or objects) can be assigned using
Expand Down Expand Up @@ -428,7 +430,12 @@ def add_edges_from(self, ebunch, **attr):
raise NetworkXError(msg.format(e))
ddd = {}
ddd.update(attr)
ddd.update(dd)
try:
ddd.update(dd)
except:
if ne != 3:
raise
key = dd
key = self.add_edge(u, v, key)
self[u][v][key].update(ddd)
keylist.append(key)
Expand Down
69 changes: 41 additions & 28 deletions networkx/classes/tests/test_multidigraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@
class BaseMultiDiGraphTester(BaseMultiGraphTester):
def test_edges(self):
G = self.K3
assert_equal(sorted(G.edges()), [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)])
edges = [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]
assert_equal(sorted(G.edges()), edges)
assert_equal(sorted(G.edges(0)), [(0, 1), (0, 2)])
assert_raises((KeyError, nx.NetworkXError), G.edges, -1)

def test_edges_data(self):
G = self.K3
assert_equal(sorted(G.edges(data=True)),
[(0, 1, {}), (0, 2, {}), (1, 0, {}), (1, 2, {}), (2, 0, {}), (2, 1, {})])
edges = [(0, 1, {}), (0, 2, {}), (1, 0, {}),
(1, 2, {}), (2, 0, {}), (2, 1, {})]
assert_equal(sorted(G.edges(data=True)), edges)
assert_equal(sorted(G.edges(0, data=True)), [(0, 1, {}), (0, 2, {})])
assert_raises((KeyError, nx.NetworkXError), G.neighbors, -1)

Expand Down Expand Up @@ -51,8 +53,12 @@ def test_out_edges_data(self):
assert_equal(sorted(G.edges(0, data=True)), [(0, 1, {}), (0, 2, {})])
G.remove_edge(0, 1)
G.add_edge(0, 1, data=1)
assert_equal(sorted(G.edges(0, data=True)), [(0, 1, {'data': 1}), (0, 2, {})])
assert_equal(sorted(G.edges(0, data='data')), [(0, 1, 1), (0, 2, None)])
assert_equal(sorted(G.edges(0, data=True)),
[(0, 1, {'data': 1}), (0, 2, {})])
assert_equal(sorted(G.edges(0, data='data')),
[(0, 1, 1), (0, 2, None)])
assert_equal(sorted(G.edges(0, data='data', default=-1)),
[(0, 1, 1), (0, 2, -1)])

def test_in_edges(self):
G = self.K3
Expand All @@ -75,16 +81,21 @@ def test_in_edges(self):
[(0, 1), (0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)])

assert_equal(sorted(G.in_edges(data=True, keys=False)),
[(0, 1, {}), (0, 1, {}), (0, 2, {}), (1, 0, {}), (1, 2, {}),
(2, 0, {}), (2, 1, {})])
[(0, 1, {}), (0, 1, {}), (0, 2, {}), (1, 0, {}),
(1, 2, {}), (2, 0, {}), (2, 1, {})])

def test_in_edges_data(self):
G = self.K3
assert_equal(sorted(G.in_edges(0, data=True)), [(1, 0, {}), (2, 0, {})])
assert_equal(sorted(G.in_edges(0, data=True)),
[(1, 0, {}), (2, 0, {})])
G.remove_edge(1, 0)
G.add_edge(1, 0, data=1)
assert_equal(sorted(G.in_edges(0, data=True)), [(1, 0, {'data': 1}), (2, 0, {})])
assert_equal(sorted(G.in_edges(0, data='data')), [(1, 0, 1), (2, 0, None)])
assert_equal(sorted(G.in_edges(0, data=True)),
[(1, 0, {'data': 1}), (2, 0, {})])
assert_equal(sorted(G.in_edges(0, data='data')),
[(1, 0, 1), (2, 0, None)])
assert_equal(sorted(G.in_edges(0, data='data', default=-1)),
[(1, 0, 1), (2, 0, -1)])

def is_shallow(self, H, G):
# graph
Expand Down Expand Up @@ -121,8 +132,8 @@ def test_to_undirected(self):
self.add_attributes(G)
H = nx.MultiGraph(G)
# self.is_shallow(H,G)
# the result is traversal order dependent so we can't use the is_shallow()
# test here.
# the result is traversal order dependent so we
# can't use the is_shallow() test here.
try:
assert_edges_equal(H.edges(), [(0, 1), (1, 2), (2, 0)])
except AssertionError:
Expand All @@ -135,11 +146,6 @@ def test_has_successor(self):
assert_equal(G.has_successor(0, 1), True)
assert_equal(G.has_successor(0, -1), False)

# def test_successors(self):
# G=self.K3
# assert_equal(sorted(G.successors(0)),[1,2])
# assert_raises((KeyError,nx.NetworkXError), G.successors,-1)

def test_successors(self):
G = self.K3
assert_equal(sorted(G.successors(0)), [1, 2])
Expand All @@ -150,11 +156,6 @@ def test_has_predecessor(self):
assert_equal(G.has_predecessor(0, 1), True)
assert_equal(G.has_predecessor(0, -1), False)

# def test_predecessors(self):
# G=self.K3
# assert_equal(sorted(G.predecessors(0)),[1,2])
# assert_raises((KeyError,nx.NetworkXError), G.predecessors,-1)

def test_predecessors(self):
G = self.K3
assert_equal(sorted(G.predecessors(0)), [1, 2])
Expand All @@ -167,8 +168,10 @@ def test_degree(self):
assert_equal(G.degree(0), 4)
assert_equal(list(G.degree(iter([0]))), [(0, 4)])
G.add_edge(0, 1, weight=0.3, other=1.2)
assert_equal(sorted(G.degree(weight='weight')), [(0, 4.3), (1, 4.3), (2, 4)])
assert_equal(sorted(G.degree(weight='other')), [(0, 5.2), (1, 5.2), (2, 4)])
assert_equal(sorted(G.degree(weight='weight')),
[(0, 4.3), (1, 4.3), (2, 4)])
assert_equal(sorted(G.degree(weight='other')),
[(0, 5.2), (1, 5.2), (2, 4)])

def test_in_degree(self):
G = self.K3
Expand Down Expand Up @@ -270,10 +273,20 @@ def test_add_edges_from(self):
2: {'weight': 2},
3: {'weight': 3}}}})

assert_raises(nx.NetworkXError, G.add_edges_from, [(0,)]) # too few in tuple
assert_raises(nx.NetworkXError, G.add_edges_from, [
(0, 1, 2, 3, 4)]) # too many in tuple
assert_raises(TypeError, G.add_edges_from, [0]) # not a tuple
G = self.Graph()
edges = [(0, 1, {'weight': 3}), (0, 1, (('weight', 2),)),
(0, 1, 5), (0, 1, 's')]
G.add_edges_from(edges)
keydict = {0: {'weight': 3}, 1: {'weight': 2}, 5: {}, 's': {}}
assert_equal(G._succ, {0: {1: keydict}, 1: {}})
assert_equal(G._pred, {1: {0: keydict}, 0: {}})

# too few in tuple
assert_raises(nx.NetworkXError, G.add_edges_from, [(0,)])
# too many in tuple
assert_raises(nx.NetworkXError, G.add_edges_from, [(0, 1, 2, 3, 4)])
# not a tuple
assert_raises(TypeError, G.add_edges_from, [0])

def test_remove_edge(self):
G = self.K3
Expand Down
22 changes: 16 additions & 6 deletions networkx/classes/tests/test_multigraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@ def test_edge_attr4(self):
G.adj[1][2][0]['listdata'] = [20, 200]
G.adj[1][2][0]['weight'] = 20
assert_edges_equal(G.edges(data=True),
[(1, 2, {'data': 21, 'spam': 'bar',
'bar': 'foo', 'listdata': [20, 200], 'weight':20})])
[(1, 2, {'data': 21, 'spam': 'bar', 'bar': 'foo',
'listdata': [20, 200], 'weight':20})])


class TestMultiGraph(BaseMultiGraphTester, TestGraph):
Expand All @@ -160,7 +160,8 @@ def setUp(self):
def test_data_input(self):
G = self.Graph(data={1: [2], 2: [1]}, name="test")
assert_equal(G.name, "test")
assert_equal(sorted(G.adj.items()), [(1, {2: {0: {}}}), (2, {1: {0: {}}})])
expected = [(1, {2: {0: {}}}), (2, {1: {0: {}}})]
assert_equal(sorted(G.adj.items()), expected)

def test_getitem(self):
G = self.K3
Expand Down Expand Up @@ -202,12 +203,19 @@ def test_add_edges_from(self):
2: {'weight': 2}, 3: {'weight': 3}}},
1: {0: {0: {}, 1: {'weight': 3},
2: {'weight': 2}, 3: {'weight': 3}}}})
G = self.Graph()
edges = [(0, 1, {'weight': 3}), (0, 1, (('weight', 2),)),
(0, 1, 5), (0, 1, 's')]
G.add_edges_from(edges)
keydict = {0: {'weight': 3}, 1: {'weight': 2}, 5: {}, 's': {}}
assert_equal(G._adj, {0: {1: keydict}, 1: {0: keydict}})

# too few in tuple
assert_raises(nx.NetworkXError, G.add_edges_from, [(0,)])
# too many in tuple
assert_raises(nx.NetworkXError, G.add_edges_from, [(0, 1, 2, 3, 4)])
assert_raises(TypeError, G.add_edges_from, [0]) # not a tuple
# not a tuple
assert_raises(TypeError, G.add_edges_from, [0])

def test_remove_edge(self):
G = self.K3
Expand All @@ -224,7 +232,8 @@ def test_remove_edge(self):
def test_remove_edges_from(self):
G = self.K3.copy()
G.remove_edges_from([(0, 1)])
assert_equal(G.adj, {0: {2: {0: {}}}, 1: {2: {0: {}}}, 2: {0: {0: {}}, 1: {0: {}}}})
kd = {0: {}}
assert_equal(G.adj, {0: {2: kd}, 1: {2: kd}, 2: {0: kd, 1: kd}})
G.remove_edges_from([(0, 0)]) # silent fail
self.K3.add_edge(0, 1)
G = self.K3.copy()
Expand All @@ -248,7 +257,8 @@ def test_remove_multiedge(self):
1: {0: {0: {}}, 2: {0: {}}},
2: {0: {0: {}}, 1: {0: {}}}})
G.remove_edge(0, 1)
assert_equal(G.adj, {0: {2: {0: {}}}, 1: {2: {0: {}}}, 2: {0: {0: {}}, 1: {0: {}}}})
kd = {0: {}}
assert_equal(G.adj, {0: {2: kd}, 1: {2: kd}, 2: {0: kd, 1: kd}})
assert_raises((KeyError, nx.NetworkXError), G.remove_edge, -1, 0)


Expand Down

0 comments on commit 7a138a1

Please sign in to comment.