【问题标题】:Make a new dictionary from keys of one of the two dictionaries being compared, Python从被比较的两个字典之一的键中创建一个新字典,Python
【发布时间】:2015-08-24 18:10:42
【问题描述】:

我有两个带坐标的字典:

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]}

这可能很重要,也可能不重要,但我的数据结构是这样的等于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 秒,而这些长度并不是我感兴趣的最大长度。您能否告诉我是否可以加快速度,或者我是否应该考虑另一种解决此问题的方法? 谢谢。

【问题讨论】:

  • dof 来自哪里?
  • 我的错,编辑它。它应该是'elem'

标签: python dictionary


【解决方案1】:

目前您正在为vertex_coordinates 中的每个条目迭代element_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 个项目长。如果您必须将一个值更改为一个,只需替换整个元组即可。

【讨论】:

  • 虽然查找表通常是解决此类问题的理想方法,但此解决方案存在一个问题:OP 正在处理 浮点数(3D 空间中的坐标) .不幸的是,由于表示问题和舍入错误,浮点数并不总是与相等性进行比较,即使它们在所有意图和目的上都是相等的。这就是为什么它们通常是 compared using a tolerance epsilon - 我假设这就是 OP 的 near() 函数所做的。
  • 因此,除非有某种保证,任何一组中的坐标都不是某种算术运算的结果,否则inv_el_coords[tuple(v)] 查找可能会失败。例如。 hash(0.2 + 0.1) != hash(0.3)
  • 你是对的。我不应该假设所有的数字都会看起来那么完美。
【解决方案2】:

您可能希望重新考虑如何存储数据。您可以使用一个 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]}

这应该会加快您的程序,但由于无法了解您的数据的完整结构,我可能做出了不一定正确的假设。

【讨论】:

    【解决方案3】:

    我没有你的数据,所以我不能自己测试性能,但是一个大的邪恶列表理解呢?像这样?

    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。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-03
      • 1970-01-01
      • 2017-04-14
      • 2022-12-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多