123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431 |
- """
- 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 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)
- # EdgeDataViews
- class OutEdgeDataView:
- """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, keys=False, default=None):
- 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):
- """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
- return self._adjdict[u][v]
- # 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)
- 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)
- # 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, keys=False, default=None):
- if nbunch is None and data is False and keys is True:
- return self
- return self.dataview(self, nbunch, data, keys, default)
- def data(self, data=True, keys=False, default=None, nbunch=None):
- if nbunch is None and data is False and keys is True:
- return self
- return self.dataview(self, nbunch, data, keys, default)
- 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]
|