仿写 networkx 的功能
# -*- coding: cp936 -*- ''' 简单图 Graph: 要求: 关于节点: 功能1.add_node: 通过 add_node 一次性加一个节点 字符串,数字,任何可以被哈希的 python 对象都可以当做节点 包括自定义的类型或者另一个图( 构成超图 ) 格式: >>> G.add_node( 1 ) 功能2.add_nodes_from: 通过其他容器加入节点,比如list, dict,set,文件或者另一个图 格式: >>> G.add_nodes_from( [2, 3] ) >>> H = Graph() >>> H.add_path( range( 10 ) ) >>> G.add_nodes_from( H ) # 构成超图 关于边: 功能1.add_edge: 加入一条边 格式: >>> G.add_edge( 1, 2 ) # 在节点 1 和节点 2 之间加一条边 功能2.add_edges_from: 加入边集 格式: >>> G.add_edges_from( [ ( 2, 4 ), ( 1, 3 ) ] ) # 通过边集来加边 >>> G.add_edges_from( H.edges() ) # 通过另一个容器来加边 注: 当加边操作的两个节点不存在时,会自动创建出来 关于属性: 每个图,边,节点都可以拥有 '键值对' 属性 初始时都为空,但是允许被动态添加和修改属性 可以通过 add_node, add_edge 操作,或者直接对节点,边,图的字典进行修改 功能1.节点属性: >>> G = Graph( name = 'scheme' ) >>> G.graph { 'name': 'scheme' } >>> G.add_node( 1, city = 'Nanjing' ) >>> G.nodes[1] { 'city': 'Nanjing' } >>> G.node { 1: {'city': 'Nanjing'} } >>> G.add_node( 1 ) # 但是再加入相同的节点时,属性不会消失掉 >>> G.node {1: {'city': 'Nanjing'}} >>> G.add_node( 1, nowtime = '8:00' ) # 加入相同的节点,带上新的属性时,原来的属性会被更新 >>> G.node {1: { 'city': 'Nanjing', 'nowtime': '8:00' } } >>> G.add_node( 1, nowtime = '8:10' ) >>> G.node {1: { 'city': 'Nanjing', 'nowtime': '8:10' } } >>> G.nodes[1]['nowtime'] = '10:00' # 但是当节点 1 不存在时,这样写属性需要报错 >>> del G.nodes[1]['nowtime'] >>> G.add_nodes_from( range( 7, 9 ), city = 'shanghai' ) 注: pass 功能2.边的属性: >>> G.add_edge( 1, 2 weight = 100 ) >>> G.edge { 1: { 2: { 'weight': 100 } }, 2: { 1: { 'weight': 100 } } } >>> G.add_edges_from( [ ( 1, 3 ), ( 2, 3 ) ], weight = 111 ) >>> G.edge { 1: { 2: { 'weight': 100 }, 3: { 'weight': 111 } }, 2: { 1: { 'weight': 100 }, 3: { 'weight': 111 } }, 3: { 1: { 'weight': 111 }, 2: { 'weight': 111 } } } >>> G.add_edges_from( [ ( 1, 3, { 'weight': 1 } ), ( 3, 4, { 'weight': 2 } ) ] ) >>> G.edge { 1: { 2: { 'weight': 100 }, 3: { 'weight': 1 } }, 2: { 1: { 'weight': 100 }, 3: { 'weight': 111 } }, 3: { 1: { 'weight': 1 }, 2: { 'weight': 111 }, 4: { 'weight': 2 } }, 4: { 3: { 'weight': 2 } } } >>> G.edge[1][2]['weight'] = 111111 # 允许直接操作 或者 >>> G[1][2]['weight'] = 1111 利用 python 的特性提供快捷操作: 比如: >>> 1 in G True >>> [ n for n in G if n < 3 ] # 节点迭代 [1, 2] >>> len(G) # 节点个数 5 >>> G[1] # 与该点相邻的所有点的属性 { 2: { 'weight': 100 }, 3: { 'weight': 1 } } 提供 adjacency_iter 来遍历边: >>> for node, nbrsdict in G.adjacency_iter(): for nbr, attr in nbrsdict.items(): if 'weight' in attr: ( node, nbr, attr['weight'] ) (1, 2, 100) (1, 3, 1) (2, 1, 100) (2, 3, 111) (3, 1, 1) (3, 2, 111) (3, 4, 2) (4, 3, 2) >>> [ ( start, end, attr['weight']) for start, end, attr in G.edges( data = True ) if 'weight' in attr ] [(1, 2, 100), (1, 3, 1), (2, 3, 111), (3, 4, 2)] 其他一些功能: pass ''' class Graph( object ): def __init__( self, data = None, **attr ): self.graph = {} self.node = {} self.adj = {} if data is not None: pass self.graph.update( attr ) self.edge = self.adj @property def name( self ): return self.graph.get( 'name', '' ) @name.setter def name( self, newname ): self.graph['name'] = newname def __str__( self ): return self.name def __iter__( self ): return iter( self.node ) def __contains__( self, node ): try: return node in self.node except TypeError: return False def __len__( self ): return len( self.node ) def __getitem__( self, node ): return self.adj[node] def add_node( self, node, attr_dict = None, **attr ): if attr_dict is None: attr_dict = attr else: try: attr_dict.update( attr ) except AttributeError: raise Exception( "The attr_dict argument must be a dictionary." ) if node not in self.node: self.adj[node] = {} self.node[node] = attr_dict else: self.node[node].update( attr_dict ) def add_nodes_from( self, nodes, **attr ): for node in nodes: try: newnode = node not in self.node except TypeError: node_, node_dict = node if node_ not in self.node: self.adj[node_] = {} newdict = attr.copy() newdict.update( node_dict ) self.node[node_] = newdict else: olddict = self.node[node_] olddict.update( attr ) olddict.update( node_dict ) continue if newnode: self.adj[node] = {} self.node[node] = attr.copy() else: self.node[node].update( attr ) def remove_node( self, start ): try: nbrs = list( adj[start].keys() ) del self.node[start] except KeyError: raise Exception( "The node %s is not in the graph."%( start ) ) for end in nbrs: del self.adj[start][end] del self.adj[start] def remove_nodes_from( self, nodes ): for start in nodes: try: del self.node[start] for end in list( self.adj[start].keys() ): del self.adj[end][start] del self.adj[start] except KeyError: pass def nodes( self, show_info = False ): def nodes_iter( show_info = False ): if show_info: return iter( self.node.item() ) return iter( self.node ) return list( nodes_iter( show_info = show_info ) ) def number_of_nodes( self ): return len( self.node ) def order( self ): return len( self.node ) def has_node( self, node ): try: return node in self.node except TypeError: return False def add_edge( self, start, end, attr_dict = None, **attr ): if attr_dict is None: attr_dict = attr else: try: attr_dict.update( attr ) except AttributeError: raise Exception( "The attr_dict argument must be a dictionary." ) if start not in self.node: self.adj[start] = {} self.node[start] = {} if end not in self.node: self.adj[end] = {} self.node[end] = {} data_dict = self.adj[start].get( end, {} ) data_dict.update( attr_dict ) self.adj[start][end] = data_dict self.adj[end][start] = data_dict def add_edges_from( self, edges, attr_dict = None, **attr ): if attr_dict is None: attr_dict = attr else: try: attr_dict.update( attr ) except AttributeError: raise Exception( "The attr_dict argument must be a dictionary." ) for edge in edges: elem_num = len( edge ) if elem_num == 3: start, end, start_end_dict = edge elif elem_num == 2: start, end = edge else: raise Exception( "Edge tuple %s must be a 2-tuple or 3-tuple."%( edge ) ) attr_dict.update( start_end_dict ) self.add_edge( start, end, attr_dict ) def remove_edge( self, start, end ): try: del self.adj[start][end] if start != end: del adj[end][start] except KeyError: raise Exception( "The edge %s-%s is not in the graph"%( start,end ) ) def remove_edges_from( self, edges ): for edge in edges: start, end = edge[:2] if start in self.adj and end in self.adj[start]: del self.adj[start][end] if start != end: del self.adj[end][start] def has_edge( self, start, end ): try: return end in self.adj[start] except KeyError: return False def neighbors( self, node ): try: return list( self.adj[node] ) except KeyError: raise Exception( "The node %s is not in the graph."%( node,) ) def neighbors_iter( self, node ): try: return iter( self.adj[n] ) except KeyError: raise Exception( "The node %s is not in the graph."%( node,) ) def edges( self, nodes = None, show_info = False ): def edges_iter( nodes, show_info = False ): seen = {} if nodes is None: nodes_nbrs = self.adj.items() else: nodes_nbrs = ( ( node, self.adj[node] ) for node in self.nodes_container( nodes ) ) if show_info: for node, nbrs in nodes_nbrs: for nbr, data in nbrs.items(): if nbr not in seen: yield ( node, nbr, data ) seen[node] = 1 else: for node, nbrs in nodes_nbrs: for nbr, data in nbrs.items(): if nbr not in seen: yield ( node, nbr ) seen[node] = 1 del seen return list( edges_iter( nodes, show_info = show_info ) ) def get_edge_data( self, start, end, default = None ): try: return self.adj[start][end] except KeyError: return default def adjacency_list( self ): return list( map( list, iter( self.adj.values() ) ) ) def adjacency_iter( self ): return iter( self.adj.items() ) def degree( self, nodes_container = None, weight = None ): ''' 功能: 返回一个节点或者一系列节点的度( degree ) 参数: nodes_container: 可以被迭代的容器,可选,默认值为所有节点 weight: 字符串或者为空,可选,默认为空, 若是为None,则所有的边的权重都设为 1, degree 则是所有与节点相邻的 weight 权重的边的个数之和 返回值: 字典或者数字 字典: 一系列节点与它们的键值对 数字: 一个指定节点的度 例如: >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc >>> G.add_path( [ 0, 1, 2, 3 ] ) >>> G.degree(0) 1 >>> G.degree( [ 0, 1 ] ) { 0: 1, 1: 2 } >>> list( G.degree( [ 0, 1 ] ).values() ) [ 1, 2 ] ''' if nodes_container in self: return next( self.degree_iter( nodes_container, weight ) )[1] else: return dict( self.degree_iter( nodes_container, weight ) ) def degree_iter( self, nodes_container = None, weight = None ): ''' 功能: 返回一个( node, degree ) 的迭代对象 参数: nodes_container: 可以被迭代的容器,可选,默认值为所有节点 weight: 字符串或者为空,可选,默认为空, 若是为None,则所有的边的权重都设为 1, degree 则是所有与节点相邻的 weight 权重的边的个数之和 返回值: 返回一个( node, degree ) 的迭代对象 ''' if nodes_container is None: nodes_nbrs = self.adj.items() else: nodes_nbrs = ( ( node, self.adj[node] ) for node in self.nodes_container_iter( nodes_container ) ) if weight is None: for node, nbrs in nodes_nbrs: yield ( node, len( nbrs ) + ( node in nbrs ) ) else: for node, nbrs in nodes_nbrs: yield (node, sum( ( nbrs[nbr].get( weight, 1 ) for nbr in nbrs ) ) + ( node in nbrs and nbrs[node].get( weight, 1 ) ) ) def clear( self ): self.name = '' self.adj.clear() self.node.clear() self.graph.clear() def copy( self ): from copy import deepcopy return deepcopy( self ) def is_multigraph( self ): return False def is_directed( self ): return False def subgraph( self, nodes ): from copy import deepcopy nodes = self.nodes_container_iter( nodes ) H = self.__class__() H.graph = deepcopy( self.graph ) for node in nodes: H.node[node] = deepcopy( self.node[node] ) for node in H.node: H_nbrs = {} H.adj[node] = H_nbrs for nbr, attr in self.adj[node].items(): if nbr in H.adj: H_nbrs[nbr] = attr H.adj[nbr][node] = attr return H def nodes_with_selfloops( self ): return [ node for node, nbrs in self.adj.items() if node in nbrs ] def selfloop_edges( self, show_info = False ): if show_info: return [ ( node, node, nbrs[node] ) for node, nbrs in self.adj.items() if node in nbrs ] else: return [ ( node, node ) for node, nbrs in self.adj.items() if node in nbrs ] def number_of_selfloops( self ): return len( self.selfloop_edges() ) def size( self, weight = None ): s = sum( self.degree( weight = weight ).values() ) / 2 if weight is None: return int( s ) else: return float( s ) def number_of_edges( self, start = None, end = None ): if start is None: return int( self.size() ) if end in self.adj[start]: return 1 else: return 0 def add_path( self, nodes, **attr ): node_list = list( nodes ) edges = zip( node_list[:-1], node_list[1:] ) self.add_edges_from( edges, **attr ) def add_cycle( self, nodes, **attr ): node_list = list( nodes ) edges = zip( node_list, node_list[1:] + [node_list[0]] ) self.add_edges_from( edges, **attr ) def nodes_container_iter( self, nodes_container = None ): ''' 功能: 返回存在图中且在 nodes_container 中的节点的迭代对象 参数: nodes_container: 可以被迭代的容器,可选,默认值为所有节点 返回值: nodes_iter: iterator ''' if nodes_container is None: container = iter( self.adj.keys() ) elif nodes_container in self: container = iter( [nodes_container] ) else: def container_iter( node_list, adj ): try: for node in node_list: if node in adj: yield node except TypeError as e: message = e.args[0] import sys sys.stdout.write( message ) if 'iter' in message: raise Exception( "nodes_container is not a node or a sequence of nodes." ) elif 'hashable' in message: raise Exception( "Node %s in the sequence nbunch is not a valid node."%n ) else: raise container = container_iter( nodes_container, self.adj ) return container
简单图模板 Graph
时间: 2024-11-14 11:28:10