aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/networkx/classes/reportviews.py
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/networkx/classes/reportviews.py
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are hereHEADmaster
Diffstat (limited to '.venv/lib/python3.12/site-packages/networkx/classes/reportviews.py')
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/classes/reportviews.py1447
1 files changed, 1447 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/networkx/classes/reportviews.py b/.venv/lib/python3.12/site-packages/networkx/classes/reportviews.py
new file mode 100644
index 00000000..789662de
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/classes/reportviews.py
@@ -0,0 +1,1447 @@
+"""
+View Classes provide node, edge and degree "views" of a graph.
+
+Views for nodes, edges and degree are provided for all base graph classes.
+A view means a read-only object that is quick to create, automatically
+updated when the graph changes, and provides basic access like `n in V`,
+`for n in V`, `V[n]` and sometimes set operations.
+
+The views are read-only iterable containers that are updated as the
+graph is updated. As with dicts, the graph should not be updated
+while iterating through the view. Views can be iterated multiple times.
+
+Edge and Node views also allow data attribute lookup.
+The resulting attribute dict is writable as `G.edges[3, 4]['color']='red'`
+Degree views allow lookup of degree values for single nodes.
+Weighted degree is supported with the `weight` argument.
+
+NodeView
+========
+
+ `V = G.nodes` (or `V = G.nodes()`) allows `len(V)`, `n in V`, set
+ operations e.g. "G.nodes & H.nodes", and `dd = G.nodes[n]`, where
+ `dd` is the node data dict. Iteration is over the nodes by default.
+
+NodeDataView
+============
+
+ To iterate over (node, data) pairs, use arguments to `G.nodes()`
+ to create a DataView e.g. `DV = G.nodes(data='color', default='red')`.
+ The DataView iterates as `for n, color in DV` and allows
+ `(n, 'red') in DV`. Using `DV = G.nodes(data=True)`, the DataViews
+ use the full datadict in writeable form also allowing contain testing as
+ `(n, {'color': 'red'}) in VD`. DataViews allow set operations when
+ data attributes are hashable.
+
+DegreeView
+==========
+
+ `V = G.degree` allows iteration over (node, degree) pairs as well
+ as lookup: `deg=V[n]`. There are many flavors of DegreeView
+ for In/Out/Directed/Multi. For Directed Graphs, `G.degree`
+ counts both in and out going edges. `G.out_degree` and
+ `G.in_degree` count only specific directions.
+ Weighted degree using edge data attributes is provide via
+ `V = G.degree(weight='attr_name')` where any string with the
+ attribute name can be used. `weight=None` is the default.
+ No set operations are implemented for degrees, use NodeView.
+
+ The argument `nbunch` restricts iteration to nodes in nbunch.
+ The DegreeView can still lookup any node even if nbunch is specified.
+
+EdgeView
+========
+
+ `V = G.edges` or `V = G.edges()` allows iteration over edges as well as
+ `e in V`, set operations and edge data lookup `dd = G.edges[2, 3]`.
+ Iteration is over 2-tuples `(u, v)` for Graph/DiGraph. For multigraphs
+ edges 3-tuples `(u, v, key)` are the default but 2-tuples can be obtained
+ via `V = G.edges(keys=False)`.
+
+ Set operations for directed graphs treat the edges as a set of 2-tuples.
+ For undirected graphs, 2-tuples are not a unique representation of edges.
+ So long as the set being compared to contains unique representations
+ of its edges, the set operations will act as expected. If the other
+ set contains both `(0, 1)` and `(1, 0)` however, the result of set
+ operations may contain both representations of the same edge.
+
+EdgeDataView
+============
+
+ Edge data can be reported using an EdgeDataView typically created
+ by calling an EdgeView: `DV = G.edges(data='weight', default=1)`.
+ The EdgeDataView allows iteration over edge tuples, membership checking
+ but no set operations.
+
+ Iteration depends on `data` and `default` and for multigraph `keys`
+ If `data is False` (the default) then iterate over 2-tuples `(u, v)`.
+ If `data is True` iterate over 3-tuples `(u, v, datadict)`.
+ Otherwise iterate over `(u, v, datadict.get(data, default))`.
+ For Multigraphs, if `keys is True`, replace `u, v` with `u, v, key`
+ to create 3-tuples and 4-tuples.
+
+ The argument `nbunch` restricts edges to those incident to nodes in nbunch.
+"""
+
+from abc import ABC
+from collections.abc import Mapping, Set
+
+import networkx as nx
+
+__all__ = [
+ "NodeView",
+ "NodeDataView",
+ "EdgeView",
+ "OutEdgeView",
+ "InEdgeView",
+ "EdgeDataView",
+ "OutEdgeDataView",
+ "InEdgeDataView",
+ "MultiEdgeView",
+ "OutMultiEdgeView",
+ "InMultiEdgeView",
+ "MultiEdgeDataView",
+ "OutMultiEdgeDataView",
+ "InMultiEdgeDataView",
+ "DegreeView",
+ "DiDegreeView",
+ "InDegreeView",
+ "OutDegreeView",
+ "MultiDegreeView",
+ "DiMultiDegreeView",
+ "InMultiDegreeView",
+ "OutMultiDegreeView",
+]
+
+
+# NodeViews
+class NodeView(Mapping, Set):
+ """A NodeView class to act as G.nodes for a NetworkX Graph
+
+ Set operations act on the nodes without considering data.
+ Iteration is over nodes. Node data can be looked up like a dict.
+ Use NodeDataView to iterate over node data or to specify a data
+ attribute for lookup. NodeDataView is created by calling the NodeView.
+
+ Parameters
+ ----------
+ graph : NetworkX graph-like class
+
+ Examples
+ --------
+ >>> G = nx.path_graph(3)
+ >>> NV = G.nodes()
+ >>> 2 in NV
+ True
+ >>> for n in NV:
+ ... print(n)
+ 0
+ 1
+ 2
+ >>> assert NV & {1, 2, 3} == {1, 2}
+
+ >>> G.add_node(2, color="blue")
+ >>> NV[2]
+ {'color': 'blue'}
+ >>> G.add_node(8, color="red")
+ >>> NDV = G.nodes(data=True)
+ >>> (2, NV[2]) in NDV
+ True
+ >>> for n, dd in NDV:
+ ... print((n, dd.get("color", "aqua")))
+ (0, 'aqua')
+ (1, 'aqua')
+ (2, 'blue')
+ (8, 'red')
+ >>> NDV[2] == NV[2]
+ True
+
+ >>> NVdata = G.nodes(data="color", default="aqua")
+ >>> (2, NVdata[2]) in NVdata
+ True
+ >>> for n, dd in NVdata:
+ ... print((n, dd))
+ (0, 'aqua')
+ (1, 'aqua')
+ (2, 'blue')
+ (8, 'red')
+ >>> NVdata[2] == NV[2] # NVdata gets 'color', NV gets datadict
+ False
+ """
+
+ __slots__ = ("_nodes",)
+
+ def __getstate__(self):
+ return {"_nodes": self._nodes}
+
+ def __setstate__(self, state):
+ self._nodes = state["_nodes"]
+
+ def __init__(self, graph):
+ self._nodes = graph._node
+
+ # Mapping methods
+ def __len__(self):
+ return len(self._nodes)
+
+ def __iter__(self):
+ return iter(self._nodes)
+
+ def __getitem__(self, n):
+ if isinstance(n, slice):
+ raise nx.NetworkXError(
+ f"{type(self).__name__} does not support slicing, "
+ f"try list(G.nodes)[{n.start}:{n.stop}:{n.step}]"
+ )
+ return self._nodes[n]
+
+ # Set methods
+ def __contains__(self, n):
+ return n in self._nodes
+
+ @classmethod
+ def _from_iterable(cls, it):
+ return set(it)
+
+ # DataView method
+ def __call__(self, data=False, default=None):
+ if data is False:
+ return self
+ return NodeDataView(self._nodes, data, default)
+
+ def data(self, data=True, default=None):
+ """
+ Return a read-only view of node data.
+
+ Parameters
+ ----------
+ data : bool or node data key, default=True
+ If ``data=True`` (the default), return a `NodeDataView` object that
+ maps each node to *all* of its attributes. `data` may also be an
+ arbitrary key, in which case the `NodeDataView` maps each node to
+ the value for the keyed attribute. In this case, if a node does
+ not have the `data` attribute, the `default` value is used.
+ default : object, default=None
+ The value used when a node does not have a specific attribute.
+
+ Returns
+ -------
+ NodeDataView
+ The layout of the returned NodeDataView depends on the value of the
+ `data` parameter.
+
+ Notes
+ -----
+ If ``data=False``, returns a `NodeView` object without data.
+
+ See Also
+ --------
+ NodeDataView
+
+ Examples
+ --------
+ >>> G = nx.Graph()
+ >>> G.add_nodes_from(
+ ... [
+ ... (0, {"color": "red", "weight": 10}),
+ ... (1, {"color": "blue"}),
+ ... (2, {"color": "yellow", "weight": 2}),
+ ... ]
+ ... )
+
+ Accessing node data with ``data=True`` (the default) returns a
+ NodeDataView mapping each node to all of its attributes:
+
+ >>> G.nodes.data()
+ NodeDataView({0: {'color': 'red', 'weight': 10}, 1: {'color': 'blue'}, 2: {'color': 'yellow', 'weight': 2}})
+
+ If `data` represents a key in the node attribute dict, a NodeDataView mapping
+ the nodes to the value for that specific key is returned:
+
+ >>> G.nodes.data("color")
+ NodeDataView({0: 'red', 1: 'blue', 2: 'yellow'}, data='color')
+
+ If a specific key is not found in an attribute dict, the value specified
+ by `default` is returned:
+
+ >>> G.nodes.data("weight", default=-999)
+ NodeDataView({0: 10, 1: -999, 2: 2}, data='weight')
+
+ Note that there is no check that the `data` key is in any of the
+ node attribute dictionaries:
+
+ >>> G.nodes.data("height")
+ NodeDataView({0: None, 1: None, 2: None}, data='height')
+ """
+ if data is False:
+ return self
+ return NodeDataView(self._nodes, data, default)
+
+ def __str__(self):
+ return str(list(self))
+
+ def __repr__(self):
+ return f"{self.__class__.__name__}({tuple(self)})"
+
+
+class NodeDataView(Set):
+ """A DataView class for nodes of a NetworkX Graph
+
+ The main use for this class is to iterate through node-data pairs.
+ The data can be the entire data-dictionary for each node, or it
+ can be a specific attribute (with default) for each node.
+ Set operations are enabled with NodeDataView, but don't work in
+ cases where the data is not hashable. Use with caution.
+ Typically, set operations on nodes use NodeView, not NodeDataView.
+ That is, they use `G.nodes` instead of `G.nodes(data='foo')`.
+
+ Parameters
+ ==========
+ graph : NetworkX graph-like class
+ data : bool or string (default=False)
+ default : object (default=None)
+ """
+
+ __slots__ = ("_nodes", "_data", "_default")
+
+ def __getstate__(self):
+ return {"_nodes": self._nodes, "_data": self._data, "_default": self._default}
+
+ def __setstate__(self, state):
+ self._nodes = state["_nodes"]
+ self._data = state["_data"]
+ self._default = state["_default"]
+
+ def __init__(self, nodedict, data=False, default=None):
+ self._nodes = nodedict
+ self._data = data
+ self._default = default
+
+ @classmethod
+ def _from_iterable(cls, it):
+ try:
+ return set(it)
+ except TypeError as err:
+ if "unhashable" in str(err):
+ msg = " : Could be b/c data=True or your values are unhashable"
+ raise TypeError(str(err) + msg) from err
+ raise
+
+ def __len__(self):
+ return len(self._nodes)
+
+ def __iter__(self):
+ data = self._data
+ if data is False:
+ return iter(self._nodes)
+ if data is True:
+ return iter(self._nodes.items())
+ return (
+ (n, dd[data] if data in dd else self._default)
+ for n, dd in self._nodes.items()
+ )
+
+ def __contains__(self, n):
+ try:
+ node_in = n in self._nodes
+ except TypeError:
+ n, d = n
+ return n in self._nodes and self[n] == d
+ if node_in is True:
+ return node_in
+ try:
+ n, d = n
+ except (TypeError, ValueError):
+ return False
+ return n in self._nodes and self[n] == d
+
+ def __getitem__(self, n):
+ if isinstance(n, slice):
+ raise nx.NetworkXError(
+ f"{type(self).__name__} does not support slicing, "
+ f"try list(G.nodes.data())[{n.start}:{n.stop}:{n.step}]"
+ )
+ ddict = self._nodes[n]
+ data = self._data
+ if data is False or data is True:
+ return ddict
+ return ddict[data] if data in ddict else self._default
+
+ def __str__(self):
+ return str(list(self))
+
+ def __repr__(self):
+ name = self.__class__.__name__
+ if self._data is False:
+ return f"{name}({tuple(self)})"
+ if self._data is True:
+ return f"{name}({dict(self)})"
+ return f"{name}({dict(self)}, data={self._data!r})"
+
+
+# DegreeViews
+class DiDegreeView:
+ """A View class for degree of nodes in a NetworkX Graph
+
+ The functionality is like dict.items() with (node, degree) pairs.
+ Additional functionality includes read-only lookup of node degree,
+ and calling with optional features nbunch (for only a subset of nodes)
+ and weight (use edge weights to compute degree).
+
+ Parameters
+ ==========
+ graph : NetworkX graph-like class
+ nbunch : node, container of nodes, or None meaning all nodes (default=None)
+ weight : bool or string (default=None)
+
+ Notes
+ -----
+ DegreeView can still lookup any node even if nbunch is specified.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(3)
+ >>> DV = G.degree()
+ >>> assert DV[2] == 1
+ >>> assert sum(deg for n, deg in DV) == 4
+
+ >>> DVweight = G.degree(weight="span")
+ >>> G.add_edge(1, 2, span=34)
+ >>> DVweight[2]
+ 34
+ >>> DVweight[0] # default edge weight is 1
+ 1
+ >>> sum(span for n, span in DVweight) # sum weighted degrees
+ 70
+
+ >>> DVnbunch = G.degree(nbunch=(1, 2))
+ >>> assert len(list(DVnbunch)) == 2 # iteration over nbunch only
+ """
+
+ def __init__(self, G, nbunch=None, weight=None):
+ self._graph = G
+ self._succ = G._succ if hasattr(G, "_succ") else G._adj
+ self._pred = G._pred if hasattr(G, "_pred") else G._adj
+ self._nodes = self._succ if nbunch is None else list(G.nbunch_iter(nbunch))
+ self._weight = weight
+
+ def __call__(self, nbunch=None, weight=None):
+ if nbunch is None:
+ if weight == self._weight:
+ return self
+ return self.__class__(self._graph, None, weight)
+ try:
+ if nbunch in self._nodes:
+ if weight == self._weight:
+ return self[nbunch]
+ return self.__class__(self._graph, None, weight)[nbunch]
+ except TypeError:
+ pass
+ return self.__class__(self._graph, nbunch, weight)
+
+ def __getitem__(self, n):
+ weight = self._weight
+ succs = self._succ[n]
+ preds = self._pred[n]
+ if weight is None:
+ return len(succs) + len(preds)
+ return sum(dd.get(weight, 1) for dd in succs.values()) + sum(
+ dd.get(weight, 1) for dd in preds.values()
+ )
+
+ def __iter__(self):
+ weight = self._weight
+ if weight is None:
+ for n in self._nodes:
+ succs = self._succ[n]
+ preds = self._pred[n]
+ yield (n, len(succs) + len(preds))
+ else:
+ for n in self._nodes:
+ succs = self._succ[n]
+ preds = self._pred[n]
+ deg = sum(dd.get(weight, 1) for dd in succs.values()) + sum(
+ dd.get(weight, 1) for dd in preds.values()
+ )
+ yield (n, deg)
+
+ def __len__(self):
+ return len(self._nodes)
+
+ def __str__(self):
+ return str(list(self))
+
+ def __repr__(self):
+ return f"{self.__class__.__name__}({dict(self)})"
+
+
+class DegreeView(DiDegreeView):
+ """A DegreeView class to act as G.degree for a NetworkX Graph
+
+ Typical usage focuses on iteration over `(node, degree)` pairs.
+ The degree is by default the number of edges incident to the node.
+ Optional argument `weight` enables weighted degree using the edge
+ attribute named in the `weight` argument. Reporting and iteration
+ can also be restricted to a subset of nodes using `nbunch`.
+
+ Additional functionality include node lookup so that `G.degree[n]`
+ reported the (possibly weighted) degree of node `n`. Calling the
+ view creates a view with different arguments `nbunch` or `weight`.
+
+ Parameters
+ ==========
+ graph : NetworkX graph-like class
+ nbunch : node, container of nodes, or None meaning all nodes (default=None)
+ weight : string or None (default=None)
+
+ Notes
+ -----
+ DegreeView can still lookup any node even if nbunch is specified.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(3)
+ >>> DV = G.degree()
+ >>> assert DV[2] == 1
+ >>> assert G.degree[2] == 1
+ >>> assert sum(deg for n, deg in DV) == 4
+
+ >>> DVweight = G.degree(weight="span")
+ >>> G.add_edge(1, 2, span=34)
+ >>> DVweight[2]
+ 34
+ >>> DVweight[0] # default edge weight is 1
+ 1
+ >>> sum(span for n, span in DVweight) # sum weighted degrees
+ 70
+
+ >>> DVnbunch = G.degree(nbunch=(1, 2))
+ >>> assert len(list(DVnbunch)) == 2 # iteration over nbunch only
+ """
+
+ def __getitem__(self, n):
+ weight = self._weight
+ nbrs = self._succ[n]
+ if weight is None:
+ return len(nbrs) + (n in nbrs)
+ return sum(dd.get(weight, 1) for dd in nbrs.values()) + (
+ n in nbrs and nbrs[n].get(weight, 1)
+ )
+
+ def __iter__(self):
+ weight = self._weight
+ if weight is None:
+ for n in self._nodes:
+ nbrs = self._succ[n]
+ yield (n, len(nbrs) + (n in nbrs))
+ else:
+ for n in self._nodes:
+ nbrs = self._succ[n]
+ deg = sum(dd.get(weight, 1) for dd in nbrs.values()) + (
+ n in nbrs and nbrs[n].get(weight, 1)
+ )
+ yield (n, deg)
+
+
+class OutDegreeView(DiDegreeView):
+ """A DegreeView class to report out_degree for a DiGraph; See DegreeView"""
+
+ def __getitem__(self, n):
+ weight = self._weight
+ nbrs = self._succ[n]
+ if self._weight is None:
+ return len(nbrs)
+ return sum(dd.get(self._weight, 1) for dd in nbrs.values())
+
+ def __iter__(self):
+ weight = self._weight
+ if weight is None:
+ for n in self._nodes:
+ succs = self._succ[n]
+ yield (n, len(succs))
+ else:
+ for n in self._nodes:
+ succs = self._succ[n]
+ deg = sum(dd.get(weight, 1) for dd in succs.values())
+ yield (n, deg)
+
+
+class InDegreeView(DiDegreeView):
+ """A DegreeView class to report in_degree for a DiGraph; See DegreeView"""
+
+ def __getitem__(self, n):
+ weight = self._weight
+ nbrs = self._pred[n]
+ if weight is None:
+ return len(nbrs)
+ return sum(dd.get(weight, 1) for dd in nbrs.values())
+
+ def __iter__(self):
+ weight = self._weight
+ if weight is None:
+ for n in self._nodes:
+ preds = self._pred[n]
+ yield (n, len(preds))
+ else:
+ for n in self._nodes:
+ preds = self._pred[n]
+ deg = sum(dd.get(weight, 1) for dd in preds.values())
+ yield (n, deg)
+
+
+class MultiDegreeView(DiDegreeView):
+ """A DegreeView class for undirected multigraphs; See DegreeView"""
+
+ def __getitem__(self, n):
+ weight = self._weight
+ nbrs = self._succ[n]
+ if weight is None:
+ return sum(len(keys) for keys in nbrs.values()) + (
+ n in nbrs and len(nbrs[n])
+ )
+ # edge weighted graph - degree is sum of nbr edge weights
+ deg = sum(
+ d.get(weight, 1) for key_dict in nbrs.values() for d in key_dict.values()
+ )
+ if n in nbrs:
+ deg += sum(d.get(weight, 1) for d in nbrs[n].values())
+ return deg
+
+ def __iter__(self):
+ weight = self._weight
+ if weight is None:
+ for n in self._nodes:
+ nbrs = self._succ[n]
+ deg = sum(len(keys) for keys in nbrs.values()) + (
+ n in nbrs and len(nbrs[n])
+ )
+ yield (n, deg)
+ else:
+ for n in self._nodes:
+ nbrs = self._succ[n]
+ deg = sum(
+ d.get(weight, 1)
+ for key_dict in nbrs.values()
+ for d in key_dict.values()
+ )
+ if n in nbrs:
+ deg += sum(d.get(weight, 1) for d in nbrs[n].values())
+ yield (n, deg)
+
+
+class DiMultiDegreeView(DiDegreeView):
+ """A DegreeView class for MultiDiGraph; See DegreeView"""
+
+ def __getitem__(self, n):
+ weight = self._weight
+ succs = self._succ[n]
+ preds = self._pred[n]
+ if weight is None:
+ return sum(len(keys) for keys in succs.values()) + sum(
+ len(keys) for keys in preds.values()
+ )
+ # edge weighted graph - degree is sum of nbr edge weights
+ deg = sum(
+ d.get(weight, 1) for key_dict in succs.values() for d in key_dict.values()
+ ) + sum(
+ d.get(weight, 1) for key_dict in preds.values() for d in key_dict.values()
+ )
+ return deg
+
+ def __iter__(self):
+ weight = self._weight
+ if weight is None:
+ for n in self._nodes:
+ succs = self._succ[n]
+ preds = self._pred[n]
+ deg = sum(len(keys) for keys in succs.values()) + sum(
+ len(keys) for keys in preds.values()
+ )
+ yield (n, deg)
+ else:
+ for n in self._nodes:
+ succs = self._succ[n]
+ preds = self._pred[n]
+ deg = sum(
+ d.get(weight, 1)
+ for key_dict in succs.values()
+ for d in key_dict.values()
+ ) + sum(
+ d.get(weight, 1)
+ for key_dict in preds.values()
+ for d in key_dict.values()
+ )
+ yield (n, deg)
+
+
+class InMultiDegreeView(DiDegreeView):
+ """A DegreeView class for inward degree of MultiDiGraph; See DegreeView"""
+
+ def __getitem__(self, n):
+ weight = self._weight
+ nbrs = self._pred[n]
+ if weight is None:
+ return sum(len(data) for data in nbrs.values())
+ # edge weighted graph - degree is sum of nbr edge weights
+ return sum(
+ d.get(weight, 1) for key_dict in nbrs.values() for d in key_dict.values()
+ )
+
+ def __iter__(self):
+ weight = self._weight
+ if weight is None:
+ for n in self._nodes:
+ nbrs = self._pred[n]
+ deg = sum(len(data) for data in nbrs.values())
+ yield (n, deg)
+ else:
+ for n in self._nodes:
+ nbrs = self._pred[n]
+ deg = sum(
+ d.get(weight, 1)
+ for key_dict in nbrs.values()
+ for d in key_dict.values()
+ )
+ yield (n, deg)
+
+
+class OutMultiDegreeView(DiDegreeView):
+ """A DegreeView class for outward degree of MultiDiGraph; See DegreeView"""
+
+ def __getitem__(self, n):
+ weight = self._weight
+ nbrs = self._succ[n]
+ if weight is None:
+ return sum(len(data) for data in nbrs.values())
+ # edge weighted graph - degree is sum of nbr edge weights
+ return sum(
+ d.get(weight, 1) for key_dict in nbrs.values() for d in key_dict.values()
+ )
+
+ def __iter__(self):
+ weight = self._weight
+ if weight is None:
+ for n in self._nodes:
+ nbrs = self._succ[n]
+ deg = sum(len(data) for data in nbrs.values())
+ yield (n, deg)
+ else:
+ for n in self._nodes:
+ nbrs = self._succ[n]
+ deg = sum(
+ d.get(weight, 1)
+ for key_dict in nbrs.values()
+ for d in key_dict.values()
+ )
+ yield (n, deg)
+
+
+# A base class for all edge views. Ensures all edge view and edge data view
+# objects/classes are captured by `isinstance(obj, EdgeViewABC)` and
+# `issubclass(cls, EdgeViewABC)` respectively
+class EdgeViewABC(ABC):
+ pass
+
+
+# EdgeDataViews
+class OutEdgeDataView(EdgeViewABC):
+ """EdgeDataView for outward edges of DiGraph; See EdgeDataView"""
+
+ __slots__ = (
+ "_viewer",
+ "_nbunch",
+ "_data",
+ "_default",
+ "_adjdict",
+ "_nodes_nbrs",
+ "_report",
+ )
+
+ def __getstate__(self):
+ return {
+ "viewer": self._viewer,
+ "nbunch": self._nbunch,
+ "data": self._data,
+ "default": self._default,
+ }
+
+ def __setstate__(self, state):
+ self.__init__(**state)
+
+ def __init__(self, viewer, nbunch=None, data=False, *, default=None):
+ self._viewer = viewer
+ adjdict = self._adjdict = viewer._adjdict
+ if nbunch is None:
+ self._nodes_nbrs = adjdict.items
+ else:
+ # dict retains order of nodes but acts like a set
+ nbunch = dict.fromkeys(viewer._graph.nbunch_iter(nbunch))
+ self._nodes_nbrs = lambda: [(n, adjdict[n]) for n in nbunch]
+ self._nbunch = nbunch
+ self._data = data
+ self._default = default
+ # Set _report based on data and default
+ if data is True:
+ self._report = lambda n, nbr, dd: (n, nbr, dd)
+ elif data is False:
+ self._report = lambda n, nbr, dd: (n, nbr)
+ else: # data is attribute name
+ self._report = (
+ lambda n, nbr, dd: (n, nbr, dd[data])
+ if data in dd
+ else (n, nbr, default)
+ )
+
+ def __len__(self):
+ return sum(len(nbrs) for n, nbrs in self._nodes_nbrs())
+
+ def __iter__(self):
+ return (
+ self._report(n, nbr, dd)
+ for n, nbrs in self._nodes_nbrs()
+ for nbr, dd in nbrs.items()
+ )
+
+ def __contains__(self, e):
+ u, v = e[:2]
+ if self._nbunch is not None and u not in self._nbunch:
+ return False # this edge doesn't start in nbunch
+ try:
+ ddict = self._adjdict[u][v]
+ except KeyError:
+ return False
+ return e == self._report(u, v, ddict)
+
+ def __str__(self):
+ return str(list(self))
+
+ def __repr__(self):
+ return f"{self.__class__.__name__}({list(self)})"
+
+
+class EdgeDataView(OutEdgeDataView):
+ """A EdgeDataView class for edges of Graph
+
+ This view is primarily used to iterate over the edges reporting
+ edges as node-tuples with edge data optionally reported. The
+ argument `nbunch` allows restriction to edges incident to nodes
+ in that container/singleton. The default (nbunch=None)
+ reports all edges. The arguments `data` and `default` control
+ what edge data is reported. The default `data is False` reports
+ only node-tuples for each edge. If `data is True` the entire edge
+ data dict is returned. Otherwise `data` is assumed to hold the name
+ of the edge attribute to report with default `default` if that
+ edge attribute is not present.
+
+ Parameters
+ ----------
+ nbunch : container of nodes, node or None (default None)
+ data : False, True or string (default False)
+ default : default value (default None)
+
+ Examples
+ --------
+ >>> G = nx.path_graph(3)
+ >>> G.add_edge(1, 2, foo="bar")
+ >>> list(G.edges(data="foo", default="biz"))
+ [(0, 1, 'biz'), (1, 2, 'bar')]
+ >>> assert (0, 1, "biz") in G.edges(data="foo", default="biz")
+ """
+
+ __slots__ = ()
+
+ def __len__(self):
+ return sum(1 for e in self)
+
+ def __iter__(self):
+ seen = {}
+ for n, nbrs in self._nodes_nbrs():
+ for nbr, dd in nbrs.items():
+ if nbr not in seen:
+ yield self._report(n, nbr, dd)
+ seen[n] = 1
+ del seen
+
+ def __contains__(self, e):
+ u, v = e[:2]
+ if self._nbunch is not None and u not in self._nbunch and v not in self._nbunch:
+ return False # this edge doesn't start and it doesn't end in nbunch
+ try:
+ ddict = self._adjdict[u][v]
+ except KeyError:
+ return False
+ return e == self._report(u, v, ddict)
+
+
+class InEdgeDataView(OutEdgeDataView):
+ """An EdgeDataView class for outward edges of DiGraph; See EdgeDataView"""
+
+ __slots__ = ()
+
+ def __iter__(self):
+ return (
+ self._report(nbr, n, dd)
+ for n, nbrs in self._nodes_nbrs()
+ for nbr, dd in nbrs.items()
+ )
+
+ def __contains__(self, e):
+ u, v = e[:2]
+ if self._nbunch is not None and v not in self._nbunch:
+ return False # this edge doesn't end in nbunch
+ try:
+ ddict = self._adjdict[v][u]
+ except KeyError:
+ return False
+ return e == self._report(u, v, ddict)
+
+
+class OutMultiEdgeDataView(OutEdgeDataView):
+ """An EdgeDataView for outward edges of MultiDiGraph; See EdgeDataView"""
+
+ __slots__ = ("keys",)
+
+ def __getstate__(self):
+ return {
+ "viewer": self._viewer,
+ "nbunch": self._nbunch,
+ "keys": self.keys,
+ "data": self._data,
+ "default": self._default,
+ }
+
+ def __setstate__(self, state):
+ self.__init__(**state)
+
+ def __init__(self, viewer, nbunch=None, data=False, *, default=None, keys=False):
+ self._viewer = viewer
+ adjdict = self._adjdict = viewer._adjdict
+ self.keys = keys
+ if nbunch is None:
+ self._nodes_nbrs = adjdict.items
+ else:
+ # dict retains order of nodes but acts like a set
+ nbunch = dict.fromkeys(viewer._graph.nbunch_iter(nbunch))
+ self._nodes_nbrs = lambda: [(n, adjdict[n]) for n in nbunch]
+ self._nbunch = nbunch
+ self._data = data
+ self._default = default
+ # Set _report based on data and default
+ if data is True:
+ if keys is True:
+ self._report = lambda n, nbr, k, dd: (n, nbr, k, dd)
+ else:
+ self._report = lambda n, nbr, k, dd: (n, nbr, dd)
+ elif data is False:
+ if keys is True:
+ self._report = lambda n, nbr, k, dd: (n, nbr, k)
+ else:
+ self._report = lambda n, nbr, k, dd: (n, nbr)
+ else: # data is attribute name
+ if keys is True:
+ self._report = (
+ lambda n, nbr, k, dd: (n, nbr, k, dd[data])
+ if data in dd
+ else (n, nbr, k, default)
+ )
+ else:
+ self._report = (
+ lambda n, nbr, k, dd: (n, nbr, dd[data])
+ if data in dd
+ else (n, nbr, default)
+ )
+
+ def __len__(self):
+ return sum(1 for e in self)
+
+ def __iter__(self):
+ return (
+ self._report(n, nbr, k, dd)
+ for n, nbrs in self._nodes_nbrs()
+ for nbr, kd in nbrs.items()
+ for k, dd in kd.items()
+ )
+
+ def __contains__(self, e):
+ u, v = e[:2]
+ if self._nbunch is not None and u not in self._nbunch:
+ return False # this edge doesn't start in nbunch
+ try:
+ kdict = self._adjdict[u][v]
+ except KeyError:
+ return False
+ if self.keys is True:
+ k = e[2]
+ try:
+ dd = kdict[k]
+ except KeyError:
+ return False
+ return e == self._report(u, v, k, dd)
+ return any(e == self._report(u, v, k, dd) for k, dd in kdict.items())
+
+
+class MultiEdgeDataView(OutMultiEdgeDataView):
+ """An EdgeDataView class for edges of MultiGraph; See EdgeDataView"""
+
+ __slots__ = ()
+
+ def __iter__(self):
+ seen = {}
+ for n, nbrs in self._nodes_nbrs():
+ for nbr, kd in nbrs.items():
+ if nbr not in seen:
+ for k, dd in kd.items():
+ yield self._report(n, nbr, k, dd)
+ seen[n] = 1
+ del seen
+
+ def __contains__(self, e):
+ u, v = e[:2]
+ if self._nbunch is not None and u not in self._nbunch and v not in self._nbunch:
+ return False # this edge doesn't start and doesn't end in nbunch
+ try:
+ kdict = self._adjdict[u][v]
+ except KeyError:
+ try:
+ kdict = self._adjdict[v][u]
+ except KeyError:
+ return False
+ if self.keys is True:
+ k = e[2]
+ try:
+ dd = kdict[k]
+ except KeyError:
+ return False
+ return e == self._report(u, v, k, dd)
+ return any(e == self._report(u, v, k, dd) for k, dd in kdict.items())
+
+
+class InMultiEdgeDataView(OutMultiEdgeDataView):
+ """An EdgeDataView for inward edges of MultiDiGraph; See EdgeDataView"""
+
+ __slots__ = ()
+
+ def __iter__(self):
+ return (
+ self._report(nbr, n, k, dd)
+ for n, nbrs in self._nodes_nbrs()
+ for nbr, kd in nbrs.items()
+ for k, dd in kd.items()
+ )
+
+ def __contains__(self, e):
+ u, v = e[:2]
+ if self._nbunch is not None and v not in self._nbunch:
+ return False # this edge doesn't end in nbunch
+ try:
+ kdict = self._adjdict[v][u]
+ except KeyError:
+ return False
+ if self.keys is True:
+ k = e[2]
+ dd = kdict[k]
+ return e == self._report(u, v, k, dd)
+ return any(e == self._report(u, v, k, dd) for k, dd in kdict.items())
+
+
+# EdgeViews have set operations and no data reported
+class OutEdgeView(Set, Mapping, EdgeViewABC):
+ """A EdgeView class for outward edges of a DiGraph"""
+
+ __slots__ = ("_adjdict", "_graph", "_nodes_nbrs")
+
+ def __getstate__(self):
+ return {"_graph": self._graph, "_adjdict": self._adjdict}
+
+ def __setstate__(self, state):
+ self._graph = state["_graph"]
+ self._adjdict = state["_adjdict"]
+ self._nodes_nbrs = self._adjdict.items
+
+ @classmethod
+ def _from_iterable(cls, it):
+ return set(it)
+
+ dataview = OutEdgeDataView
+
+ def __init__(self, G):
+ self._graph = G
+ self._adjdict = G._succ if hasattr(G, "succ") else G._adj
+ self._nodes_nbrs = self._adjdict.items
+
+ # Set methods
+ def __len__(self):
+ return sum(len(nbrs) for n, nbrs in self._nodes_nbrs())
+
+ def __iter__(self):
+ for n, nbrs in self._nodes_nbrs():
+ for nbr in nbrs:
+ yield (n, nbr)
+
+ def __contains__(self, e):
+ try:
+ u, v = e
+ return v in self._adjdict[u]
+ except KeyError:
+ return False
+
+ # Mapping Methods
+ def __getitem__(self, e):
+ if isinstance(e, slice):
+ raise nx.NetworkXError(
+ f"{type(self).__name__} does not support slicing, "
+ f"try list(G.edges)[{e.start}:{e.stop}:{e.step}]"
+ )
+ u, v = e
+ try:
+ return self._adjdict[u][v]
+ except KeyError as ex: # Customize msg to indicate exception origin
+ raise KeyError(f"The edge {e} is not in the graph.")
+
+ # EdgeDataView methods
+ def __call__(self, nbunch=None, data=False, *, default=None):
+ if nbunch is None and data is False:
+ return self
+ return self.dataview(self, nbunch, data, default=default)
+
+ def data(self, data=True, default=None, nbunch=None):
+ """
+ Return a read-only view of edge data.
+
+ Parameters
+ ----------
+ data : bool or edge attribute key
+ If ``data=True``, then the data view maps each edge to a dictionary
+ containing all of its attributes. If `data` is a key in the edge
+ dictionary, then the data view maps each edge to its value for
+ the keyed attribute. In this case, if the edge doesn't have the
+ attribute, the `default` value is returned.
+ default : object, default=None
+ The value used when an edge does not have a specific attribute
+ nbunch : container of nodes, optional (default=None)
+ Allows restriction to edges only involving certain nodes. All edges
+ are considered by default.
+
+ Returns
+ -------
+ dataview
+ Returns an `EdgeDataView` for undirected Graphs, `OutEdgeDataView`
+ for DiGraphs, `MultiEdgeDataView` for MultiGraphs and
+ `OutMultiEdgeDataView` for MultiDiGraphs.
+
+ Notes
+ -----
+ If ``data=False``, returns an `EdgeView` without any edge data.
+
+ See Also
+ --------
+ EdgeDataView
+ OutEdgeDataView
+ MultiEdgeDataView
+ OutMultiEdgeDataView
+
+ Examples
+ --------
+ >>> G = nx.Graph()
+ >>> G.add_edges_from(
+ ... [
+ ... (0, 1, {"dist": 3, "capacity": 20}),
+ ... (1, 2, {"dist": 4}),
+ ... (2, 0, {"dist": 5}),
+ ... ]
+ ... )
+
+ Accessing edge data with ``data=True`` (the default) returns an
+ edge data view object listing each edge with all of its attributes:
+
+ >>> G.edges.data()
+ EdgeDataView([(0, 1, {'dist': 3, 'capacity': 20}), (0, 2, {'dist': 5}), (1, 2, {'dist': 4})])
+
+ If `data` represents a key in the edge attribute dict, a dataview listing
+ each edge with its value for that specific key is returned:
+
+ >>> G.edges.data("dist")
+ EdgeDataView([(0, 1, 3), (0, 2, 5), (1, 2, 4)])
+
+ `nbunch` can be used to limit the edges:
+
+ >>> G.edges.data("dist", nbunch=[0])
+ EdgeDataView([(0, 1, 3), (0, 2, 5)])
+
+ If a specific key is not found in an edge attribute dict, the value
+ specified by `default` is used:
+
+ >>> G.edges.data("capacity")
+ EdgeDataView([(0, 1, 20), (0, 2, None), (1, 2, None)])
+
+ Note that there is no check that the `data` key is present in any of
+ the edge attribute dictionaries:
+
+ >>> G.edges.data("speed")
+ EdgeDataView([(0, 1, None), (0, 2, None), (1, 2, None)])
+ """
+ if nbunch is None and data is False:
+ return self
+ return self.dataview(self, nbunch, data, default=default)
+
+ # String Methods
+ def __str__(self):
+ return str(list(self))
+
+ def __repr__(self):
+ return f"{self.__class__.__name__}({list(self)})"
+
+
+class EdgeView(OutEdgeView):
+ """A EdgeView class for edges of a Graph
+
+ This densely packed View allows iteration over edges, data lookup
+ like a dict and set operations on edges represented by node-tuples.
+ In addition, edge data can be controlled by calling this object
+ possibly creating an EdgeDataView. Typically edges are iterated over
+ and reported as `(u, v)` node tuples or `(u, v, key)` node/key tuples
+ for multigraphs. Those edge representations can also be using to
+ lookup the data dict for any edge. Set operations also are available
+ where those tuples are the elements of the set.
+ Calling this object with optional arguments `data`, `default` and `keys`
+ controls the form of the tuple (see EdgeDataView). Optional argument
+ `nbunch` allows restriction to edges only involving certain nodes.
+
+ If `data is False` (the default) then iterate over 2-tuples `(u, v)`.
+ If `data is True` iterate over 3-tuples `(u, v, datadict)`.
+ Otherwise iterate over `(u, v, datadict.get(data, default))`.
+ For Multigraphs, if `keys is True`, replace `u, v` with `u, v, key` above.
+
+ Parameters
+ ==========
+ graph : NetworkX graph-like class
+ nbunch : (default= all nodes in graph) only report edges with these nodes
+ keys : (only for MultiGraph. default=False) report edge key in tuple
+ data : bool or string (default=False) see above
+ default : object (default=None)
+
+ Examples
+ ========
+ >>> G = nx.path_graph(4)
+ >>> EV = G.edges()
+ >>> (2, 3) in EV
+ True
+ >>> for u, v in EV:
+ ... print((u, v))
+ (0, 1)
+ (1, 2)
+ (2, 3)
+ >>> assert EV & {(1, 2), (3, 4)} == {(1, 2)}
+
+ >>> EVdata = G.edges(data="color", default="aqua")
+ >>> G.add_edge(2, 3, color="blue")
+ >>> assert (2, 3, "blue") in EVdata
+ >>> for u, v, c in EVdata:
+ ... print(f"({u}, {v}) has color: {c}")
+ (0, 1) has color: aqua
+ (1, 2) has color: aqua
+ (2, 3) has color: blue
+
+ >>> EVnbunch = G.edges(nbunch=2)
+ >>> assert (2, 3) in EVnbunch
+ >>> assert (0, 1) not in EVnbunch
+ >>> for u, v in EVnbunch:
+ ... assert u == 2 or v == 2
+
+ >>> MG = nx.path_graph(4, create_using=nx.MultiGraph)
+ >>> EVmulti = MG.edges(keys=True)
+ >>> (2, 3, 0) in EVmulti
+ True
+ >>> (2, 3) in EVmulti # 2-tuples work even when keys is True
+ True
+ >>> key = MG.add_edge(2, 3)
+ >>> for u, v, k in EVmulti:
+ ... print((u, v, k))
+ (0, 1, 0)
+ (1, 2, 0)
+ (2, 3, 0)
+ (2, 3, 1)
+ """
+
+ __slots__ = ()
+
+ dataview = EdgeDataView
+
+ def __len__(self):
+ num_nbrs = (len(nbrs) + (n in nbrs) for n, nbrs in self._nodes_nbrs())
+ return sum(num_nbrs) // 2
+
+ def __iter__(self):
+ seen = {}
+ for n, nbrs in self._nodes_nbrs():
+ for nbr in list(nbrs):
+ if nbr not in seen:
+ yield (n, nbr)
+ seen[n] = 1
+ del seen
+
+ def __contains__(self, e):
+ try:
+ u, v = e[:2]
+ return v in self._adjdict[u] or u in self._adjdict[v]
+ except (KeyError, ValueError):
+ return False
+
+
+class InEdgeView(OutEdgeView):
+ """A EdgeView class for inward edges of a DiGraph"""
+
+ __slots__ = ()
+
+ def __setstate__(self, state):
+ self._graph = state["_graph"]
+ self._adjdict = state["_adjdict"]
+ self._nodes_nbrs = self._adjdict.items
+
+ dataview = InEdgeDataView
+
+ def __init__(self, G):
+ self._graph = G
+ self._adjdict = G._pred if hasattr(G, "pred") else G._adj
+ self._nodes_nbrs = self._adjdict.items
+
+ def __iter__(self):
+ for n, nbrs in self._nodes_nbrs():
+ for nbr in nbrs:
+ yield (nbr, n)
+
+ def __contains__(self, e):
+ try:
+ u, v = e
+ return u in self._adjdict[v]
+ except KeyError:
+ return False
+
+ def __getitem__(self, e):
+ if isinstance(e, slice):
+ raise nx.NetworkXError(
+ f"{type(self).__name__} does not support slicing, "
+ f"try list(G.in_edges)[{e.start}:{e.stop}:{e.step}]"
+ )
+ u, v = e
+ return self._adjdict[v][u]
+
+
+class OutMultiEdgeView(OutEdgeView):
+ """A EdgeView class for outward edges of a MultiDiGraph"""
+
+ __slots__ = ()
+
+ dataview = OutMultiEdgeDataView
+
+ def __len__(self):
+ return sum(
+ len(kdict) for n, nbrs in self._nodes_nbrs() for nbr, kdict in nbrs.items()
+ )
+
+ def __iter__(self):
+ for n, nbrs in self._nodes_nbrs():
+ for nbr, kdict in nbrs.items():
+ for key in kdict:
+ yield (n, nbr, key)
+
+ def __contains__(self, e):
+ N = len(e)
+ if N == 3:
+ u, v, k = e
+ elif N == 2:
+ u, v = e
+ k = 0
+ else:
+ raise ValueError("MultiEdge must have length 2 or 3")
+ try:
+ return k in self._adjdict[u][v]
+ except KeyError:
+ return False
+
+ def __getitem__(self, e):
+ if isinstance(e, slice):
+ raise nx.NetworkXError(
+ f"{type(self).__name__} does not support slicing, "
+ f"try list(G.edges)[{e.start}:{e.stop}:{e.step}]"
+ )
+ u, v, k = e
+ return self._adjdict[u][v][k]
+
+ def __call__(self, nbunch=None, data=False, *, default=None, keys=False):
+ if nbunch is None and data is False and keys is True:
+ return self
+ return self.dataview(self, nbunch, data, default=default, keys=keys)
+
+ def data(self, data=True, default=None, nbunch=None, keys=False):
+ if nbunch is None and data is False and keys is True:
+ return self
+ return self.dataview(self, nbunch, data, default=default, keys=keys)
+
+
+class MultiEdgeView(OutMultiEdgeView):
+ """A EdgeView class for edges of a MultiGraph"""
+
+ __slots__ = ()
+
+ dataview = MultiEdgeDataView
+
+ def __len__(self):
+ return sum(1 for e in self)
+
+ def __iter__(self):
+ seen = {}
+ for n, nbrs in self._nodes_nbrs():
+ for nbr, kd in nbrs.items():
+ if nbr not in seen:
+ for k, dd in kd.items():
+ yield (n, nbr, k)
+ seen[n] = 1
+ del seen
+
+
+class InMultiEdgeView(OutMultiEdgeView):
+ """A EdgeView class for inward edges of a MultiDiGraph"""
+
+ __slots__ = ()
+
+ def __setstate__(self, state):
+ self._graph = state["_graph"]
+ self._adjdict = state["_adjdict"]
+ self._nodes_nbrs = self._adjdict.items
+
+ dataview = InMultiEdgeDataView
+
+ def __init__(self, G):
+ self._graph = G
+ self._adjdict = G._pred if hasattr(G, "pred") else G._adj
+ self._nodes_nbrs = self._adjdict.items
+
+ def __iter__(self):
+ for n, nbrs in self._nodes_nbrs():
+ for nbr, kdict in nbrs.items():
+ for key in kdict:
+ yield (nbr, n, key)
+
+ def __contains__(self, e):
+ N = len(e)
+ if N == 3:
+ u, v, k = e
+ elif N == 2:
+ u, v = e
+ k = 0
+ else:
+ raise ValueError("MultiEdge must have length 2 or 3")
+ try:
+ return k in self._adjdict[v][u]
+ except KeyError:
+ return False
+
+ def __getitem__(self, e):
+ if isinstance(e, slice):
+ raise nx.NetworkXError(
+ f"{type(self).__name__} does not support slicing, "
+ f"try list(G.in_edges)[{e.start}:{e.stop}:{e.step}]"
+ )
+ u, v, k = e
+ return self._adjdict[v][u][k]