从被比较的两个词典之一的键创建一个新词典,Python

时间:2015-08-24 18:10:42

标签: python dictionary

我有两个带坐标的词典:

vertex_coordinates = {0: [x0,y0,z0], 1: [x1,y1,z1], 2: [x2,y2,z2] ...}
element_coordinates = {0: [X0,Y0,Z0], 2: [X2,Y2,Z2], 7: [X3,Y3,Z3] ...}

第一个字典的键只是0:N,而第二个字典的键是排序的,但不一定是结果。第二个字典实际上比第一个字典大得多,因此一个特殊情况是

len(vertex_coordinates) = 729
len(element_coordinates) = 58752

我想要的是一个字典,其中键表示第一个字典的键,并且与该键相关联的值是来自第二个字典的键列表,使得坐标相等。 例如,让

vertex_coordinates = {0: [1.0,1.0,1.0], 1: [0.0,0.0,0.0], 2: [3.0,4.0,5.0], 3: [3.0, 6.0, 7.0]}
element_coordinates = {0: [0.0,0.0,0.0], 1: [3.0,4.0,5.0], 3: [3.0,6.0,7.0], \
   4: [1.0,1.0,1.0], 6: [0.0,0.0,0.0], 7: [3.0,4.0,5.0], 8:[1.0,1.0,1.0] \
   10: [3.0,6.0,7.0]}

然后,所需的字典是

element_to_vertex = {0: [4,8], 1: [0,6], 2: [1,7], 3: [3,10]}

它可能有也可能不重要但是我的数据结构是这样的,在这个过程的最后没有字典2中没有键,它们都将在结果字典中结束,即dict2的值集合等于dict1的设定值。

我实施它的方式是:

for vertex in vertex_coordinates:
  temp = []
  for elem in element_coordinates:
    if(near(element_coordinates[elem][0], vertex_coordinates[vertex][0])):
      if(near(element_coordinates[elem][1], vertex_coordinates[vertex][1])):
        if(near(element_coordinates[elem][2], vertex_coordinates[vertex][2])):
          temp.append(elem)

  element_to_vertex[vertex] = temp

虽然这很好用,但速度很慢:在字典长度为729和58752的示例中,运行大约需要25秒,这些长度并不是我感兴趣的最大长度。你能否告诉我是否有可能加快速度,或者我是否应该考虑另一种解决这个问题的方法? 谢谢。

3 个答案:

答案 0 :(得分:3)

目前,您正在为element_coordinates中的每个条目重复vertex_coordinates。如你所见,这很慢。

为什么不制作一个与element_coordinates{(1.0,1.0,1.0):[4, 8], ...}相反的新词典。这样你只需迭代一次然后快速查看。

有一个问题(感谢@Lukas Graf)。浮点数并不总是正确比较,这可能不起作用。如果计算坐标,则可能存在舍入误差,并且查找将无法按预期工作。这就是您在问题中使用near方法的原因。您可以查看bigdecimal以获取可能的修复方法。如果数据相对干净或已设置,则应该没有问题。

这样做只会迭代每个字典一次。而不是O(n^2)它变为O(n)。这种方式使用更多内存,但您必须选择其中一种。

你会做这样的事情:

from collections import defaultdict
vertex_coordinates = {0: [1.0,1.0,1.0], 1: [0.0,0.0,0.0], 2: [3.0,4.0,5.0], 3: [3.0, 6.0, 7.0]}
element_coordinates = {0: [0.0,0.0,0.0], 1: [3.0,4.0,5.0], 3: [3.0,6.0,7.0], 4: [1.0,1.0,1.0], 6: [0.0,0.0,0.0], 7: [3.0,4.0,5.0], 8:[1.0,1.0,1.0], 10: [3.0,6.0,7.0]}

inv_el_coords = defaultdict(list)

for k, v in element_coordinates.items():
    inv_el_coords[tuple(v)].append(k)

element_to_vertex = {k:inv_el_coords[tuple(v)] for k,v in vertex_coordinates.items()}

print(element_to_vertex)

另一方面,如果最初可以将数据存储在元组中,这有助于提高速度,因为不需要将它们转换为元组。从我可以看到这不应该是一个问题,因为值列表总是3项长。如果你必须在一个中更改一个值,只需替换整个元组。

答案 1 :(得分:1)

您可能希望重新考虑如何存储数据。您可以使用numpy数组来存储顶点坐标和scipy稀疏矩阵来存储元素坐标。您将保持空间效率,但也可以获得有效的方法来操纵您的数据。

from scipy.sparse import coo_matrix
from itertools import chain
import numpy as np

# input as specified
vertex_coordinates = {0: [1.0,1.0,1.0], 1: [0.0,0.0,0.0], 2: [3.0,4.0,5.0], 3: [3.0, 6.0, 7.0]}
element_coordinates = {0: [0.0,0.0,0.00000001], 1: [3.0,4.0,5.0], 3: [3.0,6.0,7.0], \
   4: [1.0,1.0,1.0], 6: [0.0,0.0,0.0], 7: [3.0,4.0,5.0], 8:[1.0,1.0,1.0], \
   10: [3.0,6.0,7.0]}

# conversion to numpy array and sparse array
vertex_coordinates = np.array(list(vertex_coordinates.values()), dtype=float)
rows = list(chain.from_iterable([i] * 3 for i in element_coordinates))
cols = list(range(3)) * len(element_coordinates)
data = list(chain.from_iterable(element_coordinates.values()))
element_coordinates = coo_matrix((data, (rows, cols)))
del rows, cols, data

# create output
num_cols = vertex_coordinates.shape[1] # 3
num_rows = len(element_coordinates.row) // num_cols # 8 in this case
shape = num_rows, num_cols

element_to_vertex = {}
# data and row are flat arrays, reshape array to have 3 columns
data_view = element_coordinates.data.reshape(shape)
row_indices = element_coordinates.row[::num_cols]
for i, row in enumerate(vertex_coordinates):
    # compare each row in element_coordinates to see if there is any match
    matches = np.isclose(row, data_view)
    # keep only the rows that completely matched
    row_matches = matches.all(axis=1)
    if row_matches.any():
        # if at least one row matched then get their indices 
        indices = row_indices[row_matches]
        element_to_vertex[i] = indices.tolist()

print(element_to_vertex)
# prints {0: [4, 8], 1: [0, 6], 2: [1, 7], 3: [3, 10]}

这应该加快你的程序,但是如果不能知道你的数据的完整结构,我可能做出了不一定正确的假设。

答案 2 :(得分:0)

我没有你的数据所以我无法测试自己的表现,但是一个大的邪恶列表理解呢?像这样的东西?

element_to_vertex = {}
for vertex in vertex_coordinates:
    temp = []
    element_to_vertex[vertex] = [elem for elem in element_coordinates if(near(element_coordinates[elem][0], vertex_coordinates[vertex][0])) and if(near(element_coordinates[elem][1], vertex_coordinates[vertex][1])) and if(near(element_coordinates[elem][2], vertex_coordinates[vertex][2]))]

你可能没有注意到巨大的速度提升,但也许有些因为它不必每次都查找append()方法。为了获得更好的性能,请考虑进入C。