简单图模板 Graph

仿写 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


原文地址:https://www.cnblogs.com/wgwyanfs/p/6941818.html