Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/networkx/classes/reportviews.py: 25%
613 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-10-20 07:00 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-10-20 07:00 +0000
1"""
2View Classes provide node, edge and degree "views" of a graph.
4Views for nodes, edges and degree are provided for all base graph classes.
5A view means a read-only object that is quick to create, automatically
6updated when the graph changes, and provides basic access like `n in V`,
7`for n in V`, `V[n]` and sometimes set operations.
9The views are read-only iterable containers that are updated as the
10graph is updated. As with dicts, the graph should not be updated
11while iterating through the view. Views can be iterated multiple times.
13Edge and Node views also allow data attribute lookup.
14The resulting attribute dict is writable as `G.edges[3, 4]['color']='red'`
15Degree views allow lookup of degree values for single nodes.
16Weighted degree is supported with the `weight` argument.
18NodeView
19========
21 `V = G.nodes` (or `V = G.nodes()`) allows `len(V)`, `n in V`, set
22 operations e.g. "G.nodes & H.nodes", and `dd = G.nodes[n]`, where
23 `dd` is the node data dict. Iteration is over the nodes by default.
25NodeDataView
26============
28 To iterate over (node, data) pairs, use arguments to `G.nodes()`
29 to create a DataView e.g. `DV = G.nodes(data='color', default='red')`.
30 The DataView iterates as `for n, color in DV` and allows
31 `(n, 'red') in DV`. Using `DV = G.nodes(data=True)`, the DataViews
32 use the full datadict in writeable form also allowing contain testing as
33 `(n, {'color': 'red'}) in VD`. DataViews allow set operations when
34 data attributes are hashable.
36DegreeView
37==========
39 `V = G.degree` allows iteration over (node, degree) pairs as well
40 as lookup: `deg=V[n]`. There are many flavors of DegreeView
41 for In/Out/Directed/Multi. For Directed Graphs, `G.degree`
42 counts both in and out going edges. `G.out_degree` and
43 `G.in_degree` count only specific directions.
44 Weighted degree using edge data attributes is provide via
45 `V = G.degree(weight='attr_name')` where any string with the
46 attribute name can be used. `weight=None` is the default.
47 No set operations are implemented for degrees, use NodeView.
49 The argument `nbunch` restricts iteration to nodes in nbunch.
50 The DegreeView can still lookup any node even if nbunch is specified.
52EdgeView
53========
55 `V = G.edges` or `V = G.edges()` allows iteration over edges as well as
56 `e in V`, set operations and edge data lookup `dd = G.edges[2, 3]`.
57 Iteration is over 2-tuples `(u, v)` for Graph/DiGraph. For multigraphs
58 edges 3-tuples `(u, v, key)` are the default but 2-tuples can be obtained
59 via `V = G.edges(keys=False)`.
61 Set operations for directed graphs treat the edges as a set of 2-tuples.
62 For undirected graphs, 2-tuples are not a unique representation of edges.
63 So long as the set being compared to contains unique representations
64 of its edges, the set operations will act as expected. If the other
65 set contains both `(0, 1)` and `(1, 0)` however, the result of set
66 operations may contain both representations of the same edge.
68EdgeDataView
69============
71 Edge data can be reported using an EdgeDataView typically created
72 by calling an EdgeView: `DV = G.edges(data='weight', default=1)`.
73 The EdgeDataView allows iteration over edge tuples, membership checking
74 but no set operations.
76 Iteration depends on `data` and `default` and for multigraph `keys`
77 If `data is False` (the default) then iterate over 2-tuples `(u, v)`.
78 If `data is True` iterate over 3-tuples `(u, v, datadict)`.
79 Otherwise iterate over `(u, v, datadict.get(data, default))`.
80 For Multigraphs, if `keys is True`, replace `u, v` with `u, v, key`
81 to create 3-tuples and 4-tuples.
83 The argument `nbunch` restricts edges to those incident to nodes in nbunch.
84"""
85from collections.abc import Mapping, Set
87import networkx as nx
89__all__ = [
90 "NodeView",
91 "NodeDataView",
92 "EdgeView",
93 "OutEdgeView",
94 "InEdgeView",
95 "EdgeDataView",
96 "OutEdgeDataView",
97 "InEdgeDataView",
98 "MultiEdgeView",
99 "OutMultiEdgeView",
100 "InMultiEdgeView",
101 "MultiEdgeDataView",
102 "OutMultiEdgeDataView",
103 "InMultiEdgeDataView",
104 "DegreeView",
105 "DiDegreeView",
106 "InDegreeView",
107 "OutDegreeView",
108 "MultiDegreeView",
109 "DiMultiDegreeView",
110 "InMultiDegreeView",
111 "OutMultiDegreeView",
112]
115# NodeViews
116class NodeView(Mapping, Set):
117 """A NodeView class to act as G.nodes for a NetworkX Graph
119 Set operations act on the nodes without considering data.
120 Iteration is over nodes. Node data can be looked up like a dict.
121 Use NodeDataView to iterate over node data or to specify a data
122 attribute for lookup. NodeDataView is created by calling the NodeView.
124 Parameters
125 ----------
126 graph : NetworkX graph-like class
128 Examples
129 --------
130 >>> G = nx.path_graph(3)
131 >>> NV = G.nodes()
132 >>> 2 in NV
133 True
134 >>> for n in NV:
135 ... print(n)
136 0
137 1
138 2
139 >>> assert NV & {1, 2, 3} == {1, 2}
141 >>> G.add_node(2, color="blue")
142 >>> NV[2]
143 {'color': 'blue'}
144 >>> G.add_node(8, color="red")
145 >>> NDV = G.nodes(data=True)
146 >>> (2, NV[2]) in NDV
147 True
148 >>> for n, dd in NDV:
149 ... print((n, dd.get("color", "aqua")))
150 (0, 'aqua')
151 (1, 'aqua')
152 (2, 'blue')
153 (8, 'red')
154 >>> NDV[2] == NV[2]
155 True
157 >>> NVdata = G.nodes(data="color", default="aqua")
158 >>> (2, NVdata[2]) in NVdata
159 True
160 >>> for n, dd in NVdata:
161 ... print((n, dd))
162 (0, 'aqua')
163 (1, 'aqua')
164 (2, 'blue')
165 (8, 'red')
166 >>> NVdata[2] == NV[2] # NVdata gets 'color', NV gets datadict
167 False
168 """
170 __slots__ = ("_nodes",)
172 def __getstate__(self):
173 return {"_nodes": self._nodes}
175 def __setstate__(self, state):
176 self._nodes = state["_nodes"]
178 def __init__(self, graph):
179 self._nodes = graph._node
181 # Mapping methods
182 def __len__(self):
183 return len(self._nodes)
185 def __iter__(self):
186 return iter(self._nodes)
188 def __getitem__(self, n):
189 if isinstance(n, slice):
190 raise nx.NetworkXError(
191 f"{type(self).__name__} does not support slicing, "
192 f"try list(G.nodes)[{n.start}:{n.stop}:{n.step}]"
193 )
194 return self._nodes[n]
196 # Set methods
197 def __contains__(self, n):
198 return n in self._nodes
200 @classmethod
201 def _from_iterable(cls, it):
202 return set(it)
204 # DataView method
205 def __call__(self, data=False, default=None):
206 if data is False:
207 return self
208 return NodeDataView(self._nodes, data, default)
210 def data(self, data=True, default=None):
211 """
212 Return a read-only view of node data.
214 Parameters
215 ----------
216 data : bool or node data key, default=True
217 If ``data=True`` (the default), return a `NodeDataView` object that
218 maps each node to *all* of its attributes. `data` may also be an
219 arbitrary key, in which case the `NodeDataView` maps each node to
220 the value for the keyed attribute. In this case, if a node does
221 not have the `data` attribute, the `default` value is used.
222 default : object, default=None
223 The value used when a node does not have a specific attribute.
225 Returns
226 -------
227 NodeDataView
228 The layout of the returned NodeDataView depends on the value of the
229 `data` parameter.
231 Notes
232 -----
233 If ``data=False``, returns a `NodeView` object without data.
235 See Also
236 --------
237 NodeDataView
239 Examples
240 --------
241 >>> G = nx.Graph()
242 >>> G.add_nodes_from([
243 ... (0, {"color": "red", "weight": 10}),
244 ... (1, {"color": "blue"}),
245 ... (2, {"color": "yellow", "weight": 2})
246 ... ])
248 Accessing node data with ``data=True`` (the default) returns a
249 NodeDataView mapping each node to all of its attributes:
251 >>> G.nodes.data()
252 NodeDataView({0: {'color': 'red', 'weight': 10}, 1: {'color': 'blue'}, 2: {'color': 'yellow', 'weight': 2}})
254 If `data` represents a key in the node attribute dict, a NodeDataView mapping
255 the nodes to the value for that specific key is returned:
257 >>> G.nodes.data("color")
258 NodeDataView({0: 'red', 1: 'blue', 2: 'yellow'}, data='color')
260 If a specific key is not found in an attribute dict, the value specified
261 by `default` is returned:
263 >>> G.nodes.data("weight", default=-999)
264 NodeDataView({0: 10, 1: -999, 2: 2}, data='weight')
266 Note that there is no check that the `data` key is in any of the
267 node attribute dictionaries:
269 >>> G.nodes.data("height")
270 NodeDataView({0: None, 1: None, 2: None}, data='height')
271 """
272 if data is False:
273 return self
274 return NodeDataView(self._nodes, data, default)
276 def __str__(self):
277 return str(list(self))
279 def __repr__(self):
280 return f"{self.__class__.__name__}({tuple(self)})"
283class NodeDataView(Set):
284 """A DataView class for nodes of a NetworkX Graph
286 The main use for this class is to iterate through node-data pairs.
287 The data can be the entire data-dictionary for each node, or it
288 can be a specific attribute (with default) for each node.
289 Set operations are enabled with NodeDataView, but don't work in
290 cases where the data is not hashable. Use with caution.
291 Typically, set operations on nodes use NodeView, not NodeDataView.
292 That is, they use `G.nodes` instead of `G.nodes(data='foo')`.
294 Parameters
295 ==========
296 graph : NetworkX graph-like class
297 data : bool or string (default=False)
298 default : object (default=None)
299 """
301 __slots__ = ("_nodes", "_data", "_default")
303 def __getstate__(self):
304 return {"_nodes": self._nodes, "_data": self._data, "_default": self._default}
306 def __setstate__(self, state):
307 self._nodes = state["_nodes"]
308 self._data = state["_data"]
309 self._default = state["_default"]
311 def __init__(self, nodedict, data=False, default=None):
312 self._nodes = nodedict
313 self._data = data
314 self._default = default
316 @classmethod
317 def _from_iterable(cls, it):
318 try:
319 return set(it)
320 except TypeError as err:
321 if "unhashable" in str(err):
322 msg = " : Could be b/c data=True or your values are unhashable"
323 raise TypeError(str(err) + msg) from err
324 raise
326 def __len__(self):
327 return len(self._nodes)
329 def __iter__(self):
330 data = self._data
331 if data is False:
332 return iter(self._nodes)
333 if data is True:
334 return iter(self._nodes.items())
335 return (
336 (n, dd[data] if data in dd else self._default)
337 for n, dd in self._nodes.items()
338 )
340 def __contains__(self, n):
341 try:
342 node_in = n in self._nodes
343 except TypeError:
344 n, d = n
345 return n in self._nodes and self[n] == d
346 if node_in is True:
347 return node_in
348 try:
349 n, d = n
350 except (TypeError, ValueError):
351 return False
352 return n in self._nodes and self[n] == d
354 def __getitem__(self, n):
355 if isinstance(n, slice):
356 raise nx.NetworkXError(
357 f"{type(self).__name__} does not support slicing, "
358 f"try list(G.nodes.data())[{n.start}:{n.stop}:{n.step}]"
359 )
360 ddict = self._nodes[n]
361 data = self._data
362 if data is False or data is True:
363 return ddict
364 return ddict[data] if data in ddict else self._default
366 def __str__(self):
367 return str(list(self))
369 def __repr__(self):
370 name = self.__class__.__name__
371 if self._data is False:
372 return f"{name}({tuple(self)})"
373 if self._data is True:
374 return f"{name}({dict(self)})"
375 return f"{name}({dict(self)}, data={self._data!r})"
378# DegreeViews
379class DiDegreeView:
380 """A View class for degree of nodes in a NetworkX Graph
382 The functionality is like dict.items() with (node, degree) pairs.
383 Additional functionality includes read-only lookup of node degree,
384 and calling with optional features nbunch (for only a subset of nodes)
385 and weight (use edge weights to compute degree).
387 Parameters
388 ==========
389 graph : NetworkX graph-like class
390 nbunch : node, container of nodes, or None meaning all nodes (default=None)
391 weight : bool or string (default=None)
393 Notes
394 -----
395 DegreeView can still lookup any node even if nbunch is specified.
397 Examples
398 --------
399 >>> G = nx.path_graph(3)
400 >>> DV = G.degree()
401 >>> assert DV[2] == 1
402 >>> assert sum(deg for n, deg in DV) == 4
404 >>> DVweight = G.degree(weight="span")
405 >>> G.add_edge(1, 2, span=34)
406 >>> DVweight[2]
407 34
408 >>> DVweight[0] # default edge weight is 1
409 1
410 >>> sum(span for n, span in DVweight) # sum weighted degrees
411 70
413 >>> DVnbunch = G.degree(nbunch=(1, 2))
414 >>> assert len(list(DVnbunch)) == 2 # iteration over nbunch only
415 """
417 def __init__(self, G, nbunch=None, weight=None):
418 self._graph = G
419 self._succ = G._succ if hasattr(G, "_succ") else G._adj
420 self._pred = G._pred if hasattr(G, "_pred") else G._adj
421 self._nodes = self._succ if nbunch is None else list(G.nbunch_iter(nbunch))
422 self._weight = weight
424 def __call__(self, nbunch=None, weight=None):
425 if nbunch is None:
426 if weight == self._weight:
427 return self
428 return self.__class__(self._graph, None, weight)
429 try:
430 if nbunch in self._nodes:
431 if weight == self._weight:
432 return self[nbunch]
433 return self.__class__(self._graph, None, weight)[nbunch]
434 except TypeError:
435 pass
436 return self.__class__(self._graph, nbunch, weight)
438 def __getitem__(self, n):
439 weight = self._weight
440 succs = self._succ[n]
441 preds = self._pred[n]
442 if weight is None:
443 return len(succs) + len(preds)
444 return sum(dd.get(weight, 1) for dd in succs.values()) + sum(
445 dd.get(weight, 1) for dd in preds.values()
446 )
448 def __iter__(self):
449 weight = self._weight
450 if weight is None:
451 for n in self._nodes:
452 succs = self._succ[n]
453 preds = self._pred[n]
454 yield (n, len(succs) + len(preds))
455 else:
456 for n in self._nodes:
457 succs = self._succ[n]
458 preds = self._pred[n]
459 deg = sum(dd.get(weight, 1) for dd in succs.values()) + sum(
460 dd.get(weight, 1) for dd in preds.values()
461 )
462 yield (n, deg)
464 def __len__(self):
465 return len(self._nodes)
467 def __str__(self):
468 return str(list(self))
470 def __repr__(self):
471 return f"{self.__class__.__name__}({dict(self)})"
474class DegreeView(DiDegreeView):
475 """A DegreeView class to act as G.degree for a NetworkX Graph
477 Typical usage focuses on iteration over `(node, degree)` pairs.
478 The degree is by default the number of edges incident to the node.
479 Optional argument `weight` enables weighted degree using the edge
480 attribute named in the `weight` argument. Reporting and iteration
481 can also be restricted to a subset of nodes using `nbunch`.
483 Additional functionality include node lookup so that `G.degree[n]`
484 reported the (possibly weighted) degree of node `n`. Calling the
485 view creates a view with different arguments `nbunch` or `weight`.
487 Parameters
488 ==========
489 graph : NetworkX graph-like class
490 nbunch : node, container of nodes, or None meaning all nodes (default=None)
491 weight : string or None (default=None)
493 Notes
494 -----
495 DegreeView can still lookup any node even if nbunch is specified.
497 Examples
498 --------
499 >>> G = nx.path_graph(3)
500 >>> DV = G.degree()
501 >>> assert DV[2] == 1
502 >>> assert G.degree[2] == 1
503 >>> assert sum(deg for n, deg in DV) == 4
505 >>> DVweight = G.degree(weight="span")
506 >>> G.add_edge(1, 2, span=34)
507 >>> DVweight[2]
508 34
509 >>> DVweight[0] # default edge weight is 1
510 1
511 >>> sum(span for n, span in DVweight) # sum weighted degrees
512 70
514 >>> DVnbunch = G.degree(nbunch=(1, 2))
515 >>> assert len(list(DVnbunch)) == 2 # iteration over nbunch only
516 """
518 def __getitem__(self, n):
519 weight = self._weight
520 nbrs = self._succ[n]
521 if weight is None:
522 return len(nbrs) + (n in nbrs)
523 return sum(dd.get(weight, 1) for dd in nbrs.values()) + (
524 n in nbrs and nbrs[n].get(weight, 1)
525 )
527 def __iter__(self):
528 weight = self._weight
529 if weight is None:
530 for n in self._nodes:
531 nbrs = self._succ[n]
532 yield (n, len(nbrs) + (n in nbrs))
533 else:
534 for n in self._nodes:
535 nbrs = self._succ[n]
536 deg = sum(dd.get(weight, 1) for dd in nbrs.values()) + (
537 n in nbrs and nbrs[n].get(weight, 1)
538 )
539 yield (n, deg)
542class OutDegreeView(DiDegreeView):
543 """A DegreeView class to report out_degree for a DiGraph; See DegreeView"""
545 def __getitem__(self, n):
546 weight = self._weight
547 nbrs = self._succ[n]
548 if self._weight is None:
549 return len(nbrs)
550 return sum(dd.get(self._weight, 1) for dd in nbrs.values())
552 def __iter__(self):
553 weight = self._weight
554 if weight is None:
555 for n in self._nodes:
556 succs = self._succ[n]
557 yield (n, len(succs))
558 else:
559 for n in self._nodes:
560 succs = self._succ[n]
561 deg = sum(dd.get(weight, 1) for dd in succs.values())
562 yield (n, deg)
565class InDegreeView(DiDegreeView):
566 """A DegreeView class to report in_degree for a DiGraph; See DegreeView"""
568 def __getitem__(self, n):
569 weight = self._weight
570 nbrs = self._pred[n]
571 if weight is None:
572 return len(nbrs)
573 return sum(dd.get(weight, 1) for dd in nbrs.values())
575 def __iter__(self):
576 weight = self._weight
577 if weight is None:
578 for n in self._nodes:
579 preds = self._pred[n]
580 yield (n, len(preds))
581 else:
582 for n in self._nodes:
583 preds = self._pred[n]
584 deg = sum(dd.get(weight, 1) for dd in preds.values())
585 yield (n, deg)
588class MultiDegreeView(DiDegreeView):
589 """A DegreeView class for undirected multigraphs; See DegreeView"""
591 def __getitem__(self, n):
592 weight = self._weight
593 nbrs = self._succ[n]
594 if weight is None:
595 return sum(len(keys) for keys in nbrs.values()) + (
596 n in nbrs and len(nbrs[n])
597 )
598 # edge weighted graph - degree is sum of nbr edge weights
599 deg = sum(
600 d.get(weight, 1) for key_dict in nbrs.values() for d in key_dict.values()
601 )
602 if n in nbrs:
603 deg += sum(d.get(weight, 1) for d in nbrs[n].values())
604 return deg
606 def __iter__(self):
607 weight = self._weight
608 if weight is None:
609 for n in self._nodes:
610 nbrs = self._succ[n]
611 deg = sum(len(keys) for keys in nbrs.values()) + (
612 n in nbrs and len(nbrs[n])
613 )
614 yield (n, deg)
615 else:
616 for n in self._nodes:
617 nbrs = self._succ[n]
618 deg = sum(
619 d.get(weight, 1)
620 for key_dict in nbrs.values()
621 for d in key_dict.values()
622 )
623 if n in nbrs:
624 deg += sum(d.get(weight, 1) for d in nbrs[n].values())
625 yield (n, deg)
628class DiMultiDegreeView(DiDegreeView):
629 """A DegreeView class for MultiDiGraph; See DegreeView"""
631 def __getitem__(self, n):
632 weight = self._weight
633 succs = self._succ[n]
634 preds = self._pred[n]
635 if weight is None:
636 return sum(len(keys) for keys in succs.values()) + sum(
637 len(keys) for keys in preds.values()
638 )
639 # edge weighted graph - degree is sum of nbr edge weights
640 deg = sum(
641 d.get(weight, 1) for key_dict in succs.values() for d in key_dict.values()
642 ) + sum(
643 d.get(weight, 1) for key_dict in preds.values() for d in key_dict.values()
644 )
645 return deg
647 def __iter__(self):
648 weight = self._weight
649 if weight is None:
650 for n in self._nodes:
651 succs = self._succ[n]
652 preds = self._pred[n]
653 deg = sum(len(keys) for keys in succs.values()) + sum(
654 len(keys) for keys in preds.values()
655 )
656 yield (n, deg)
657 else:
658 for n in self._nodes:
659 succs = self._succ[n]
660 preds = self._pred[n]
661 deg = sum(
662 d.get(weight, 1)
663 for key_dict in succs.values()
664 for d in key_dict.values()
665 ) + sum(
666 d.get(weight, 1)
667 for key_dict in preds.values()
668 for d in key_dict.values()
669 )
670 yield (n, deg)
673class InMultiDegreeView(DiDegreeView):
674 """A DegreeView class for inward degree of MultiDiGraph; See DegreeView"""
676 def __getitem__(self, n):
677 weight = self._weight
678 nbrs = self._pred[n]
679 if weight is None:
680 return sum(len(data) for data in nbrs.values())
681 # edge weighted graph - degree is sum of nbr edge weights
682 return sum(
683 d.get(weight, 1) for key_dict in nbrs.values() for d in key_dict.values()
684 )
686 def __iter__(self):
687 weight = self._weight
688 if weight is None:
689 for n in self._nodes:
690 nbrs = self._pred[n]
691 deg = sum(len(data) for data in nbrs.values())
692 yield (n, deg)
693 else:
694 for n in self._nodes:
695 nbrs = self._pred[n]
696 deg = sum(
697 d.get(weight, 1)
698 for key_dict in nbrs.values()
699 for d in key_dict.values()
700 )
701 yield (n, deg)
704class OutMultiDegreeView(DiDegreeView):
705 """A DegreeView class for outward degree of MultiDiGraph; See DegreeView"""
707 def __getitem__(self, n):
708 weight = self._weight
709 nbrs = self._succ[n]
710 if weight is None:
711 return sum(len(data) for data in nbrs.values())
712 # edge weighted graph - degree is sum of nbr edge weights
713 return sum(
714 d.get(weight, 1) for key_dict in nbrs.values() for d in key_dict.values()
715 )
717 def __iter__(self):
718 weight = self._weight
719 if weight is None:
720 for n in self._nodes:
721 nbrs = self._succ[n]
722 deg = sum(len(data) for data in nbrs.values())
723 yield (n, deg)
724 else:
725 for n in self._nodes:
726 nbrs = self._succ[n]
727 deg = sum(
728 d.get(weight, 1)
729 for key_dict in nbrs.values()
730 for d in key_dict.values()
731 )
732 yield (n, deg)
735# EdgeDataViews
736class OutEdgeDataView:
737 """EdgeDataView for outward edges of DiGraph; See EdgeDataView"""
739 __slots__ = (
740 "_viewer",
741 "_nbunch",
742 "_data",
743 "_default",
744 "_adjdict",
745 "_nodes_nbrs",
746 "_report",
747 )
749 def __getstate__(self):
750 return {
751 "viewer": self._viewer,
752 "nbunch": self._nbunch,
753 "data": self._data,
754 "default": self._default,
755 }
757 def __setstate__(self, state):
758 self.__init__(**state)
760 def __init__(self, viewer, nbunch=None, data=False, *, default=None):
761 self._viewer = viewer
762 adjdict = self._adjdict = viewer._adjdict
763 if nbunch is None:
764 self._nodes_nbrs = adjdict.items
765 else:
766 # dict retains order of nodes but acts like a set
767 nbunch = dict.fromkeys(viewer._graph.nbunch_iter(nbunch))
768 self._nodes_nbrs = lambda: [(n, adjdict[n]) for n in nbunch]
769 self._nbunch = nbunch
770 self._data = data
771 self._default = default
772 # Set _report based on data and default
773 if data is True:
774 self._report = lambda n, nbr, dd: (n, nbr, dd)
775 elif data is False:
776 self._report = lambda n, nbr, dd: (n, nbr)
777 else: # data is attribute name
778 self._report = (
779 lambda n, nbr, dd: (n, nbr, dd[data])
780 if data in dd
781 else (n, nbr, default)
782 )
784 def __len__(self):
785 return sum(len(nbrs) for n, nbrs in self._nodes_nbrs())
787 def __iter__(self):
788 return (
789 self._report(n, nbr, dd)
790 for n, nbrs in self._nodes_nbrs()
791 for nbr, dd in nbrs.items()
792 )
794 def __contains__(self, e):
795 u, v = e[:2]
796 if self._nbunch is not None and u not in self._nbunch:
797 return False # this edge doesn't start in nbunch
798 try:
799 ddict = self._adjdict[u][v]
800 except KeyError:
801 return False
802 return e == self._report(u, v, ddict)
804 def __str__(self):
805 return str(list(self))
807 def __repr__(self):
808 return f"{self.__class__.__name__}({list(self)})"
811class EdgeDataView(OutEdgeDataView):
812 """A EdgeDataView class for edges of Graph
814 This view is primarily used to iterate over the edges reporting
815 edges as node-tuples with edge data optionally reported. The
816 argument `nbunch` allows restriction to edges incident to nodes
817 in that container/singleton. The default (nbunch=None)
818 reports all edges. The arguments `data` and `default` control
819 what edge data is reported. The default `data is False` reports
820 only node-tuples for each edge. If `data is True` the entire edge
821 data dict is returned. Otherwise `data` is assumed to hold the name
822 of the edge attribute to report with default `default` if that
823 edge attribute is not present.
825 Parameters
826 ----------
827 nbunch : container of nodes, node or None (default None)
828 data : False, True or string (default False)
829 default : default value (default None)
831 Examples
832 --------
833 >>> G = nx.path_graph(3)
834 >>> G.add_edge(1, 2, foo="bar")
835 >>> list(G.edges(data="foo", default="biz"))
836 [(0, 1, 'biz'), (1, 2, 'bar')]
837 >>> assert (0, 1, "biz") in G.edges(data="foo", default="biz")
838 """
840 __slots__ = ()
842 def __len__(self):
843 return sum(1 for e in self)
845 def __iter__(self):
846 seen = {}
847 for n, nbrs in self._nodes_nbrs():
848 for nbr, dd in nbrs.items():
849 if nbr not in seen:
850 yield self._report(n, nbr, dd)
851 seen[n] = 1
852 del seen
854 def __contains__(self, e):
855 u, v = e[:2]
856 if self._nbunch is not None and u not in self._nbunch and v not in self._nbunch:
857 return False # this edge doesn't start and it doesn't end in nbunch
858 try:
859 ddict = self._adjdict[u][v]
860 except KeyError:
861 return False
862 return e == self._report(u, v, ddict)
865class InEdgeDataView(OutEdgeDataView):
866 """An EdgeDataView class for outward edges of DiGraph; See EdgeDataView"""
868 __slots__ = ()
870 def __iter__(self):
871 return (
872 self._report(nbr, n, dd)
873 for n, nbrs in self._nodes_nbrs()
874 for nbr, dd in nbrs.items()
875 )
877 def __contains__(self, e):
878 u, v = e[:2]
879 if self._nbunch is not None and v not in self._nbunch:
880 return False # this edge doesn't end in nbunch
881 try:
882 ddict = self._adjdict[v][u]
883 except KeyError:
884 return False
885 return e == self._report(u, v, ddict)
888class OutMultiEdgeDataView(OutEdgeDataView):
889 """An EdgeDataView for outward edges of MultiDiGraph; See EdgeDataView"""
891 __slots__ = ("keys",)
893 def __getstate__(self):
894 return {
895 "viewer": self._viewer,
896 "nbunch": self._nbunch,
897 "keys": self.keys,
898 "data": self._data,
899 "default": self._default,
900 }
902 def __setstate__(self, state):
903 self.__init__(**state)
905 def __init__(self, viewer, nbunch=None, data=False, *, default=None, keys=False):
906 self._viewer = viewer
907 adjdict = self._adjdict = viewer._adjdict
908 self.keys = keys
909 if nbunch is None:
910 self._nodes_nbrs = adjdict.items
911 else:
912 # dict retains order of nodes but acts like a set
913 nbunch = dict.fromkeys(viewer._graph.nbunch_iter(nbunch))
914 self._nodes_nbrs = lambda: [(n, adjdict[n]) for n in nbunch]
915 self._nbunch = nbunch
916 self._data = data
917 self._default = default
918 # Set _report based on data and default
919 if data is True:
920 if keys is True:
921 self._report = lambda n, nbr, k, dd: (n, nbr, k, dd)
922 else:
923 self._report = lambda n, nbr, k, dd: (n, nbr, dd)
924 elif data is False:
925 if keys is True:
926 self._report = lambda n, nbr, k, dd: (n, nbr, k)
927 else:
928 self._report = lambda n, nbr, k, dd: (n, nbr)
929 else: # data is attribute name
930 if keys is True:
931 self._report = (
932 lambda n, nbr, k, dd: (n, nbr, k, dd[data])
933 if data in dd
934 else (n, nbr, k, default)
935 )
936 else:
937 self._report = (
938 lambda n, nbr, k, dd: (n, nbr, dd[data])
939 if data in dd
940 else (n, nbr, default)
941 )
943 def __len__(self):
944 return sum(1 for e in self)
946 def __iter__(self):
947 return (
948 self._report(n, nbr, k, dd)
949 for n, nbrs in self._nodes_nbrs()
950 for nbr, kd in nbrs.items()
951 for k, dd in kd.items()
952 )
954 def __contains__(self, e):
955 u, v = e[:2]
956 if self._nbunch is not None and u not in self._nbunch:
957 return False # this edge doesn't start in nbunch
958 try:
959 kdict = self._adjdict[u][v]
960 except KeyError:
961 return False
962 if self.keys is True:
963 k = e[2]
964 try:
965 dd = kdict[k]
966 except KeyError:
967 return False
968 return e == self._report(u, v, k, dd)
969 return any(e == self._report(u, v, k, dd) for k, dd in kdict.items())
972class MultiEdgeDataView(OutMultiEdgeDataView):
973 """An EdgeDataView class for edges of MultiGraph; See EdgeDataView"""
975 __slots__ = ()
977 def __iter__(self):
978 seen = {}
979 for n, nbrs in self._nodes_nbrs():
980 for nbr, kd in nbrs.items():
981 if nbr not in seen:
982 for k, dd in kd.items():
983 yield self._report(n, nbr, k, dd)
984 seen[n] = 1
985 del seen
987 def __contains__(self, e):
988 u, v = e[:2]
989 if self._nbunch is not None and u not in self._nbunch and v not in self._nbunch:
990 return False # this edge doesn't start and doesn't end in nbunch
991 try:
992 kdict = self._adjdict[u][v]
993 except KeyError:
994 try:
995 kdict = self._adjdict[v][u]
996 except KeyError:
997 return False
998 if self.keys is True:
999 k = e[2]
1000 try:
1001 dd = kdict[k]
1002 except KeyError:
1003 return False
1004 return e == self._report(u, v, k, dd)
1005 return any(e == self._report(u, v, k, dd) for k, dd in kdict.items())
1008class InMultiEdgeDataView(OutMultiEdgeDataView):
1009 """An EdgeDataView for inward edges of MultiDiGraph; See EdgeDataView"""
1011 __slots__ = ()
1013 def __iter__(self):
1014 return (
1015 self._report(nbr, n, k, dd)
1016 for n, nbrs in self._nodes_nbrs()
1017 for nbr, kd in nbrs.items()
1018 for k, dd in kd.items()
1019 )
1021 def __contains__(self, e):
1022 u, v = e[:2]
1023 if self._nbunch is not None and v not in self._nbunch:
1024 return False # this edge doesn't end in nbunch
1025 try:
1026 kdict = self._adjdict[v][u]
1027 except KeyError:
1028 return False
1029 if self.keys is True:
1030 k = e[2]
1031 dd = kdict[k]
1032 return e == self._report(u, v, k, dd)
1033 return any(e == self._report(u, v, k, dd) for k, dd in kdict.items())
1036# EdgeViews have set operations and no data reported
1037class OutEdgeView(Set, Mapping):
1038 """A EdgeView class for outward edges of a DiGraph"""
1040 __slots__ = ("_adjdict", "_graph", "_nodes_nbrs")
1042 def __getstate__(self):
1043 return {"_graph": self._graph, "_adjdict": self._adjdict}
1045 def __setstate__(self, state):
1046 self._graph = state["_graph"]
1047 self._adjdict = state["_adjdict"]
1048 self._nodes_nbrs = self._adjdict.items
1050 @classmethod
1051 def _from_iterable(cls, it):
1052 return set(it)
1054 dataview = OutEdgeDataView
1056 def __init__(self, G):
1057 self._graph = G
1058 self._adjdict = G._succ if hasattr(G, "succ") else G._adj
1059 self._nodes_nbrs = self._adjdict.items
1061 # Set methods
1062 def __len__(self):
1063 return sum(len(nbrs) for n, nbrs in self._nodes_nbrs())
1065 def __iter__(self):
1066 for n, nbrs in self._nodes_nbrs():
1067 for nbr in nbrs:
1068 yield (n, nbr)
1070 def __contains__(self, e):
1071 try:
1072 u, v = e
1073 return v in self._adjdict[u]
1074 except KeyError:
1075 return False
1077 # Mapping Methods
1078 def __getitem__(self, e):
1079 if isinstance(e, slice):
1080 raise nx.NetworkXError(
1081 f"{type(self).__name__} does not support slicing, "
1082 f"try list(G.edges)[{e.start}:{e.stop}:{e.step}]"
1083 )
1084 u, v = e
1085 return self._adjdict[u][v]
1087 # EdgeDataView methods
1088 def __call__(self, nbunch=None, data=False, *, default=None):
1089 if nbunch is None and data is False:
1090 return self
1091 return self.dataview(self, nbunch, data, default=default)
1093 def data(self, data=True, default=None, nbunch=None):
1094 """
1095 Return a read-only view of edge data.
1097 Parameters
1098 ----------
1099 data : bool or edge attribute key
1100 If ``data=True``, then the data view maps each edge to a dictionary
1101 containing all of its attributes. If `data` is a key in the edge
1102 dictionary, then the data view maps each edge to its value for
1103 the keyed attribute. In this case, if the edge doesn't have the
1104 attribute, the `default` value is returned.
1105 default : object, default=None
1106 The value used when an edge does not have a specific attribute
1107 nbunch : container of nodes, optional (default=None)
1108 Allows restriction to edges only involving certain nodes. All edges
1109 are considered by default.
1111 Returns
1112 -------
1113 dataview
1114 Returns an `EdgeDataView` for undirected Graphs, `OutEdgeDataView`
1115 for DiGraphs, `MultiEdgeDataView` for MultiGraphs and
1116 `OutMultiEdgeDataView` for MultiDiGraphs.
1118 Notes
1119 -----
1120 If ``data=False``, returns an `EdgeView` without any edge data.
1122 See Also
1123 --------
1124 EdgeDataView
1125 OutEdgeDataView
1126 MultiEdgeDataView
1127 OutMultiEdgeDataView
1129 Examples
1130 --------
1131 >>> G = nx.Graph()
1132 >>> G.add_edges_from([
1133 ... (0, 1, {"dist": 3, "capacity": 20}),
1134 ... (1, 2, {"dist": 4}),
1135 ... (2, 0, {"dist": 5})
1136 ... ])
1138 Accessing edge data with ``data=True`` (the default) returns an
1139 edge data view object listing each edge with all of its attributes:
1141 >>> G.edges.data()
1142 EdgeDataView([(0, 1, {'dist': 3, 'capacity': 20}), (0, 2, {'dist': 5}), (1, 2, {'dist': 4})])
1144 If `data` represents a key in the edge attribute dict, a dataview listing
1145 each edge with its value for that specific key is returned:
1147 >>> G.edges.data("dist")
1148 EdgeDataView([(0, 1, 3), (0, 2, 5), (1, 2, 4)])
1150 `nbunch` can be used to limit the edges:
1152 >>> G.edges.data("dist", nbunch=[0])
1153 EdgeDataView([(0, 1, 3), (0, 2, 5)])
1155 If a specific key is not found in an edge attribute dict, the value
1156 specified by `default` is used:
1158 >>> G.edges.data("capacity")
1159 EdgeDataView([(0, 1, 20), (0, 2, None), (1, 2, None)])
1161 Note that there is no check that the `data` key is present in any of
1162 the edge attribute dictionaries:
1164 >>> G.edges.data("speed")
1165 EdgeDataView([(0, 1, None), (0, 2, None), (1, 2, None)])
1166 """
1167 if nbunch is None and data is False:
1168 return self
1169 return self.dataview(self, nbunch, data, default=default)
1171 # String Methods
1172 def __str__(self):
1173 return str(list(self))
1175 def __repr__(self):
1176 return f"{self.__class__.__name__}({list(self)})"
1179class EdgeView(OutEdgeView):
1180 """A EdgeView class for edges of a Graph
1182 This densely packed View allows iteration over edges, data lookup
1183 like a dict and set operations on edges represented by node-tuples.
1184 In addition, edge data can be controlled by calling this object
1185 possibly creating an EdgeDataView. Typically edges are iterated over
1186 and reported as `(u, v)` node tuples or `(u, v, key)` node/key tuples
1187 for multigraphs. Those edge representations can also be using to
1188 lookup the data dict for any edge. Set operations also are available
1189 where those tuples are the elements of the set.
1190 Calling this object with optional arguments `data`, `default` and `keys`
1191 controls the form of the tuple (see EdgeDataView). Optional argument
1192 `nbunch` allows restriction to edges only involving certain nodes.
1194 If `data is False` (the default) then iterate over 2-tuples `(u, v)`.
1195 If `data is True` iterate over 3-tuples `(u, v, datadict)`.
1196 Otherwise iterate over `(u, v, datadict.get(data, default))`.
1197 For Multigraphs, if `keys is True`, replace `u, v` with `u, v, key` above.
1199 Parameters
1200 ==========
1201 graph : NetworkX graph-like class
1202 nbunch : (default= all nodes in graph) only report edges with these nodes
1203 keys : (only for MultiGraph. default=False) report edge key in tuple
1204 data : bool or string (default=False) see above
1205 default : object (default=None)
1207 Examples
1208 ========
1209 >>> G = nx.path_graph(4)
1210 >>> EV = G.edges()
1211 >>> (2, 3) in EV
1212 True
1213 >>> for u, v in EV:
1214 ... print((u, v))
1215 (0, 1)
1216 (1, 2)
1217 (2, 3)
1218 >>> assert EV & {(1, 2), (3, 4)} == {(1, 2)}
1220 >>> EVdata = G.edges(data="color", default="aqua")
1221 >>> G.add_edge(2, 3, color="blue")
1222 >>> assert (2, 3, "blue") in EVdata
1223 >>> for u, v, c in EVdata:
1224 ... print(f"({u}, {v}) has color: {c}")
1225 (0, 1) has color: aqua
1226 (1, 2) has color: aqua
1227 (2, 3) has color: blue
1229 >>> EVnbunch = G.edges(nbunch=2)
1230 >>> assert (2, 3) in EVnbunch
1231 >>> assert (0, 1) not in EVnbunch
1232 >>> for u, v in EVnbunch:
1233 ... assert u == 2 or v == 2
1235 >>> MG = nx.path_graph(4, create_using=nx.MultiGraph)
1236 >>> EVmulti = MG.edges(keys=True)
1237 >>> (2, 3, 0) in EVmulti
1238 True
1239 >>> (2, 3) in EVmulti # 2-tuples work even when keys is True
1240 True
1241 >>> key = MG.add_edge(2, 3)
1242 >>> for u, v, k in EVmulti:
1243 ... print((u, v, k))
1244 (0, 1, 0)
1245 (1, 2, 0)
1246 (2, 3, 0)
1247 (2, 3, 1)
1248 """
1250 __slots__ = ()
1252 dataview = EdgeDataView
1254 def __len__(self):
1255 num_nbrs = (len(nbrs) + (n in nbrs) for n, nbrs in self._nodes_nbrs())
1256 return sum(num_nbrs) // 2
1258 def __iter__(self):
1259 seen = {}
1260 for n, nbrs in self._nodes_nbrs():
1261 for nbr in list(nbrs):
1262 if nbr not in seen:
1263 yield (n, nbr)
1264 seen[n] = 1
1265 del seen
1267 def __contains__(self, e):
1268 try:
1269 u, v = e[:2]
1270 return v in self._adjdict[u] or u in self._adjdict[v]
1271 except (KeyError, ValueError):
1272 return False
1275class InEdgeView(OutEdgeView):
1276 """A EdgeView class for inward edges of a DiGraph"""
1278 __slots__ = ()
1280 def __setstate__(self, state):
1281 self._graph = state["_graph"]
1282 self._adjdict = state["_adjdict"]
1283 self._nodes_nbrs = self._adjdict.items
1285 dataview = InEdgeDataView
1287 def __init__(self, G):
1288 self._graph = G
1289 self._adjdict = G._pred if hasattr(G, "pred") else G._adj
1290 self._nodes_nbrs = self._adjdict.items
1292 def __iter__(self):
1293 for n, nbrs in self._nodes_nbrs():
1294 for nbr in nbrs:
1295 yield (nbr, n)
1297 def __contains__(self, e):
1298 try:
1299 u, v = e
1300 return u in self._adjdict[v]
1301 except KeyError:
1302 return False
1304 def __getitem__(self, e):
1305 if isinstance(e, slice):
1306 raise nx.NetworkXError(
1307 f"{type(self).__name__} does not support slicing, "
1308 f"try list(G.in_edges)[{e.start}:{e.stop}:{e.step}]"
1309 )
1310 u, v = e
1311 return self._adjdict[v][u]
1314class OutMultiEdgeView(OutEdgeView):
1315 """A EdgeView class for outward edges of a MultiDiGraph"""
1317 __slots__ = ()
1319 dataview = OutMultiEdgeDataView
1321 def __len__(self):
1322 return sum(
1323 len(kdict) for n, nbrs in self._nodes_nbrs() for nbr, kdict in nbrs.items()
1324 )
1326 def __iter__(self):
1327 for n, nbrs in self._nodes_nbrs():
1328 for nbr, kdict in nbrs.items():
1329 for key in kdict:
1330 yield (n, nbr, key)
1332 def __contains__(self, e):
1333 N = len(e)
1334 if N == 3:
1335 u, v, k = e
1336 elif N == 2:
1337 u, v = e
1338 k = 0
1339 else:
1340 raise ValueError("MultiEdge must have length 2 or 3")
1341 try:
1342 return k in self._adjdict[u][v]
1343 except KeyError:
1344 return False
1346 def __getitem__(self, e):
1347 if isinstance(e, slice):
1348 raise nx.NetworkXError(
1349 f"{type(self).__name__} does not support slicing, "
1350 f"try list(G.edges)[{e.start}:{e.stop}:{e.step}]"
1351 )
1352 u, v, k = e
1353 return self._adjdict[u][v][k]
1355 def __call__(self, nbunch=None, data=False, *, default=None, keys=False):
1356 if nbunch is None and data is False and keys is True:
1357 return self
1358 return self.dataview(self, nbunch, data, default=default, keys=keys)
1360 def data(self, data=True, default=None, nbunch=None, keys=False):
1361 if nbunch is None and data is False and keys is True:
1362 return self
1363 return self.dataview(self, nbunch, data, default=default, keys=keys)
1366class MultiEdgeView(OutMultiEdgeView):
1367 """A EdgeView class for edges of a MultiGraph"""
1369 __slots__ = ()
1371 dataview = MultiEdgeDataView
1373 def __len__(self):
1374 return sum(1 for e in self)
1376 def __iter__(self):
1377 seen = {}
1378 for n, nbrs in self._nodes_nbrs():
1379 for nbr, kd in nbrs.items():
1380 if nbr not in seen:
1381 for k, dd in kd.items():
1382 yield (n, nbr, k)
1383 seen[n] = 1
1384 del seen
1387class InMultiEdgeView(OutMultiEdgeView):
1388 """A EdgeView class for inward edges of a MultiDiGraph"""
1390 __slots__ = ()
1392 def __setstate__(self, state):
1393 self._graph = state["_graph"]
1394 self._adjdict = state["_adjdict"]
1395 self._nodes_nbrs = self._adjdict.items
1397 dataview = InMultiEdgeDataView
1399 def __init__(self, G):
1400 self._graph = G
1401 self._adjdict = G._pred if hasattr(G, "pred") else G._adj
1402 self._nodes_nbrs = self._adjdict.items
1404 def __iter__(self):
1405 for n, nbrs in self._nodes_nbrs():
1406 for nbr, kdict in nbrs.items():
1407 for key in kdict:
1408 yield (nbr, n, key)
1410 def __contains__(self, e):
1411 N = len(e)
1412 if N == 3:
1413 u, v, k = e
1414 elif N == 2:
1415 u, v = e
1416 k = 0
1417 else:
1418 raise ValueError("MultiEdge must have length 2 or 3")
1419 try:
1420 return k in self._adjdict[v][u]
1421 except KeyError:
1422 return False
1424 def __getitem__(self, e):
1425 if isinstance(e, slice):
1426 raise nx.NetworkXError(
1427 f"{type(self).__name__} does not support slicing, "
1428 f"try list(G.in_edges)[{e.start}:{e.stop}:{e.step}]"
1429 )
1430 u, v, k = e
1431 return self._adjdict[v][u][k]