"""图的视图:子图、反向、有向、无向。
在某些算法中,临时改变图以排除某些节点或边很方便。通过视图而不是移除再重新添加会更好。在其他算法中,临时改变图以反转有向边,或将有向图视为无向图等也很方便。本模块提供了这些图视图。
生成的视图本质上是只读图,从原始图对象报告数据。我们提供了一个属性 G._graph,指向底层图对象。
注意:由于图视图看起来像图,可能会形成视图链。要注意链,因为它们在嵌套约15层视图时会变得非常慢。对于从图类创建的节点诱导子图的常见简单情况,我们通过直接返回原始图的子图而不是子图的子图来缩短链。我们小心地不破坏中间子图中的任何边过滤器。一般来说,确定如何缩短链是棘手的,而且比诱导子图更难处理受限视图。
通常,使用 .copy() 来避免链是最简单的。
"""
import networkx as nx
from networkx.classes.coreviews import (
FilterAdjacency,
FilterAtlas,
FilterMultiAdjacency,
UnionAdjacency,
UnionMultiAdjacency,
)
from networkx.classes.filters import no_filter
from networkx.exception import NetworkXError
from networkx.utils import deprecate_positional_args, not_implemented_for
__all__ = ["generic_graph_view", "subgraph_view", "reverse_view"]
[docs]
def generic_graph_view(G, create_using=None):
"""返回 `G` 的只读视图。
图 `G` 及其属性不会被复制,而是通过与 `G` 相同类(或 `create_using` 中指定的类)的新图对象进行查看。
Parameters
----------
G : 图
一个有向/无向图/多重图。
create_using : NetworkX 图构造函数, 可选 (默认=None)
要创建的图类型。如果是图实例,则在填充前清空。
如果为 `None` ,则根据 `G` 推断适当的图类型。
Returns
-------
newG : 图
通过 `create_using` 类查看的输入图 `G` 及其属性的视图。
Raises
------
NetworkXError
如果 `G` 是多重图(或多重有向图)但 `create_using` 不是,或者反之。
Notes
-----
返回的图视图是只读的(不能修改图)。
然而,视图会反映 `G` 中的任何更改。目的是模仿字典视图。
Examples
--------
>>> G = nx.Graph()
>>> G.add_edge(1, 2, weight=0.3)
>>> G.add_edge(2, 3, weight=0.5)
>>> G.edges(data=True)
EdgeDataView([(1, 2, {'weight': 0.3}), (2, 3, {'weight': 0.5})])
视图展示了原始图的属性。
>>> viewG = nx.graphviews.generic_graph_view(G)
>>> viewG.edges(data=True)
EdgeDataView([(1, 2, {'weight': 0.3}), (2, 3, {'weight': 0.5})])
对 `G` 的更改会反映在 `viewG` 中。
>>> G.remove_edge(2, 3)
>>> G.edges(data=True)
EdgeDataView([(1, 2, {'weight': 0.3})])
>>> viewG.edges(data=True)
EdgeDataView([(1, 2, {'weight': 0.3})])
我们可以通过 `create_using` 参数更改图类型。
>>> type(G)
<class 'networkx.classes.graph.Graph'>
>>> viewDG = nx.graphviews.generic_graph_view(G, create_using=nx.DiGraph)
>>> type(viewDG)
<class 'networkx.classes.digraph.DiGraph'>
"""
if create_using is None:
newG = G.__class__()
else:
newG = nx.empty_graph(0, create_using)
if G.is_multigraph() != newG.is_multigraph():
raise NetworkXError("Multigraph for G must agree with create_using")
newG = nx.freeze(newG)
# create view by assigning attributes from G
newG._graph = G
newG.graph = G.graph
newG._node = G._node
if newG.is_directed():
if G.is_directed():
newG._succ = G._succ
newG._pred = G._pred
# newG._adj is synced with _succ
else:
newG._succ = G._adj
newG._pred = G._adj
# newG._adj is synced with _succ
elif G.is_directed():
if G.is_multigraph():
newG._adj = UnionMultiAdjacency(G._succ, G._pred)
else:
newG._adj = UnionAdjacency(G._succ, G._pred)
else:
newG._adj = G._adj
return newG
[docs]
@deprecate_positional_args(version="3.4")
def subgraph_view(G, *, filter_node=no_filter, filter_edge=no_filter):
""" `G` 应用节点和边过滤器的视图。
`subgraph_view` 提供了一个输入图的只读视图,该视图根据两个过滤函数 `filter_node` 和 `filter_edge` 的结果排除节点和边。
`filter_node` 函数接受一个参数 --- 节点 --- 并返回 `True` 如果该节点应包含在子图中,返回 `False` 则不包含。
`filter_edge` 函数接受两个(如果是多图则三个)参数 --- 描述一条边的节点,如果可能存在平行边则加上边键 --- 并返回 `True` 如果该边应包含在子图中,返回 `False` 则不包含。
节点和边过滤函数在查询图元素时被调用,这意味着创建视图没有前期成本。
Parameters
----------
G : networkx.Graph
有向/无向图/多图
filter_node : callable, 可选
一个接受节点作为输入的函数,返回 `True` 如果该节点应出现在视图中。
filter_edge : callable, 可选
一个接受两个节点描述一条边(如果 `G` 是多图则加上边键)作为输入的函数,返回 `True` 如果该边应出现在视图中。
Returns
-------
graph : networkx.Graph
输入图的只读图视图。
Examples
--------
>>> G = nx.path_graph(6)
过滤函数对节点进行操作,并返回 `True` 如果该节点应出现在视图中:
>>> def filter_node(n1):
... return n1 != 5
>>> view = nx.subgraph_view(G, filter_node=filter_node)
>>> view.nodes()
NodeView((0, 1, 2, 3, 4))
我们可以使用闭包模式根据附加数据过滤图元素 --- 例如,基于附加到图的边数据进行过滤:
>>> G[3][4]["cross_me"] = False
>>> def filter_edge(n1, n2):
... return G[n1][n2].get("cross_me", True)
>>> view = nx.subgraph_view(G, filter_edge=filter_edge)
>>> view.edges()
EdgeView([(0, 1), (1, 2), (2, 3), (4, 5)])
>>> view = nx.subgraph_view(
... G,
... filter_node=filter_node,
... filter_edge=filter_edge,
... )
>>> view.nodes()
NodeView((0, 1, 2, 3, 4))
>>> view.edges()
EdgeView([(0, 1), (1, 2), (2, 3)])
"""
newG = nx.freeze(G.__class__())
newG._NODE_OK = filter_node
newG._EDGE_OK = filter_edge
# create view by assigning attributes from G
newG._graph = G
newG.graph = G.graph
newG._node = FilterAtlas(G._node, filter_node)
if G.is_multigraph():
Adj = FilterMultiAdjacency
def reverse_edge(u, v, k=None):
return filter_edge(v, u, k)
else:
Adj = FilterAdjacency
def reverse_edge(u, v, k=None):
return filter_edge(v, u)
if G.is_directed():
newG._succ = Adj(G._succ, filter_node, filter_edge)
newG._pred = Adj(G._pred, filter_node, reverse_edge)
# newG._adj is synced with _succ
else:
newG._adj = Adj(G._adj, filter_node, filter_edge)
return newG
[docs]
@not_implemented_for("undirected")
def reverse_view(G):
""" `G` 的边方向反转视图
`reverse_view` 返回输入图的一个只读视图,其中边方向被反转。
等同于 digraph.reverse(copy=False)
Parameters
----------
G : networkx.DiGraph
Returns
-------
graph : networkx.DiGraph
Examples
--------
>>> G = nx.DiGraph()
>>> G.add_edge(1, 2)
>>> G.add_edge(2, 3)
>>> G.edges()
OutEdgeView([(1, 2), (2, 3)])
>>> view = nx.reverse_view(G)
>>> view.edges()
OutEdgeView([(2, 1), (3, 2)])
"""
newG = generic_graph_view(G)
newG._succ, newG._pred = G._pred, G._succ
# newG._adj is synced with _succ
return newG