访问列表中的项目并形成图形

Accessing items in a list, and forming graphs

我有一个二维 numpy 数组列表:

linelist = [[[0,0],[1,0]],[[0,0],[0,1]],[[1,0],[1,1]],[[0,1],[1,1]],[[1,2],[3,1]],[[1,2],[2,2]],[[3,1],[3,2]],[[2,2],[3,2]]]

linelist中的每一行都是连接边的顶点数组。 这些元素是形成两个正方形的线:

-----
|   |
-----

    -----
    |   |
    -----

我想形成两个图形,每个方格一个。为此,我使用了一个 for 循环。如果图中两个顶点都不存在,则我们形成一个新图。如果一个顶点存在于线列表中,那么它会被添加到当前图形中。为了连接两条线,它们需要共享一个共同的顶点。但是,我在编码时遇到了问题。 这是我目前所拥有的:

    graphs = [[]]
    i=0
    for elements in linelist:
        for graph in graphs:
            if elements[0] not in graph[i] and elements[1] not in graph[i]:
                graphs.append([])
                graphs[i].append(elements)
                i=i+1
            else:
                graphs[i].append(elements)

我的方法涉及 2 遍列表。在第一遍中,我将查看顶点并为每个 (1, 2, ...) 分配一个图形编号。如果两个顶点都没有看到,我将分配一个新的图形编号。否则,将其分配给现有的。

在第二遍中,我遍历列表并将属于同一图编号的边分组在一起。这是代码:

import collections
import itertools
import pprint


linelist = [[[0,0],[1,0]],[[0,0],[0,1]],[[1,0],[1,1]],[[0,1],[1,1]],[[1,2],[3,1]],[[1,2],[2,2]],[[3,1],[3,2]],[[2,2],[3,2]]]


# First pass: Look at the vertices and figure out which graph they
# belong to
vertices = {}
graph_numbers = itertools.count(1)
for v1, v2 in linelist:
    v1 = tuple(v1)
    v2 = tuple(v2)
    graph_number = vertices.get(v1) or vertices.get(v2) or next(graph_numbers)
    vertices[v1] = graph_number
    vertices[v2] = graph_number

print('Vertices:')
pprint.pprint(vertices)


# Second pass: Sort edges
graphs = collections.defaultdict(list)
for v1, v2 in linelist:
    graph_number = vertices[tuple(v1)]
    graphs[graph_number].append([v1, v2])

print('Graphs:')
pprint.pprint(graphs)

输出:

Vertices:
{(0, 0): 1,
 (0, 1): 1,
 (1, 0): 1,
 (1, 1): 1,
 (1, 2): 2,
 (2, 2): 2,
 (3, 1): 2,
 (3, 2): 2}
Graphs:
defaultdict(<type 'list'>, {1: [[[0, 0], [1, 0]], [[0, 0], [0, 1]], [[1, 0], [1, 1]], [[0, 1], [1, 1]]], 2: [[[1, 2], [3, 1]], [[1, 2], [2, 2]], [[3, 1], [3, 2]], [[2, 2], [3, 2]]]})

备注

  • 我必须将每个顶点从列表转换为元组,因为列表不能是字典的键。
  • graphs 表现得像一本字典,键是图形编号 (1, 2, ...),值是边列表

行的一点解释

graph_number = vertices.get(v1) or vertices.get(v2) or next(graph_numbers)

该行大致等于:

number1 = vertices.get(v1)
number2 = vertices.get(v2)
if number1 is None and number2 is None:
    graph_number = next(graph_numbers)
elif number1 is not None:
    graph_number = number1
else:
    graph_number = number2

表示:如果 v1 和 v2 都不在顶点中,则生成一个新数字(即 next(graph_numbers))。否则,将 graph_number 分配给不是 None 的任何值。

不仅那一行很简洁,它还利用了Python的短路特性:解释器首先计算vertices.get(v1)。如果这个 return 是一个数字 (1, 2, ...) 那么解释器将 return 这个数字并跳过计算 vertices.get(v2) or next(graph_numbers) 部分。

如果vertices.get(v1) returns None,也就是Python中的False,那么解释器会计算[=23]的下一段=]:vertices.get(v2)。同样,如果此 return 是一个非零数字,则计算停止并且该数字为 return。如果 vertices.get(v2) returns None,则解释器计算最后一段,next(graph_numbers) 和 returns 那个值。

我建议对图进行 'diffusion-like' 处理以找到不相交的子图。想到的一种算法是breadth-first search;它通过查找可以从起始节点到达哪些节点来工作。

linelist = [[[0,0],[1,0]],[[0,0],[0,1]],[[1,0],[1,1]],[[0,1],[1,1]],[[1,2],[3,1]],[[1,2],[2,2]],[[3,1],[3,2]],[[2,2],[3,2]]] 

# edge list usually reads v1 -> v2
graph = {}
# however these are lines so symmetry is assumed
for l in linelist:
    v1, v2 = map(tuple, l)
    graph[v1] = graph.get(v1, ()) + (v2,)      
    graph[v2] = graph.get(v2, ()) + (v1,)

def BFS(graph):
    """
    Implement breadth-first search
    """
    # get nodes
    nodes = list(graph.keys())
    graphs = []
    # check all nodes 
    while nodes:
        # initialize BFS
        toCheck = [nodes[0]]
        discovered = []
        # run bfs
        while toCheck:
            startNode = toCheck.pop()
            for neighbor in graph.get(startNode):
                if neighbor not in discovered:
                    discovered.append(neighbor)
                    toCheck.append(neighbor)
                    nodes.remove(neighbor)
        # add discovered graphs
        graphs.append(discovered)
    return graphs
print(BFS(graph))
for idx, graph in enumerate(BFS(graph)):
    print(f"This is {idx} graph with nodes {graph}")

输出

This is 0 graph with nodes [(1, 0), (0, 1), (0, 0), (1, 1)]
This is 1 graph with nodes [(3, 1), (2, 2), (1, 2), (3, 2)]

您可能对用于分析图表的包 networkx 感兴趣。例如,找到不相交的子图非常简单:

import networkx as nx
tmp = [tuple(tuple(j) for j in i) for i in linelist]
graph = nx.Graph(tmp);
for idx, graph in enumerate(nx.connected_components(graph)):
    print(idx, graph)