【问题标题】:Speed up finding matches between two dictionaries (Python)加快查找两个字典之间的匹配项 (Python)
【发布时间】:2015-02-27 06:50:45
【问题描述】:

我正在使用 Python 2.7 解决空间分析问题。我有一个字典 edges 表示图中的边,其中键是 edgeID,值是起点/终点:

{e1: [(12.8254, 55.3880), (12.8343, 55.3920)], 
e2: [(12.8254, 55.3880), (12.8235, 55.3857)], 
e3: [(12.2432, 57.1120), (12.2426, 57.1122)]}

我还有另一个字典nodes,其中键是节点ID,值是节点坐标:

{n14: (12.8254, 55.3880), 
n15: (12.8340, 55.3883), 
n16: (12.8235, 55.3857), 
n17: (12.8343, 55.3920)}

我需要得到一个看起来像这样的列表(键中的“n”和“e”只是为了说明这个问题,我在那里有整数):

[(e1,n14,n17),(e2,n14,n16)..]

也就是说,我遍历边缘字典,获取每个键,找到存在于nodes 字典中的值并添加到元组中。这就是我现在的做法:

edgesList = []
for featureId in edges:
        edgeFeatureId = [k for k, v in edges.iteritems() if k == featureId][0]
        edgeStartPoint = [k for k, v in nodes.iteritems() if v == edges[featureId][0]][0]#start point
        edgeEndPoint = [k for k, v in nodes.iteritems() if v == edges[featureId][1]][0]#end point
        edgesList.append((edgeFeatureId,edgeStartPoint,edgeEndPoint))

这是可行的,但在处理大型数据集时非常慢(100K 边和 90K 节点需要大约 10 分钟)。

我已经弄清楚如何在获取每个元组的项目时使用列表推导,但是是否可以将我的 3 个列表推导合二为一,以避免使用 for 循环迭代边缘(如果这会加快事情了)?

有没有其他方法可以更快地建立这样的列表?

更新

正如 Martin 所建议的,我已经反转了我的节点字典:

nodesDict = dict((v,k) for k,v in oldnodesDict.iteritems())

以节点坐标元组作为键,以节点ID 作为值。不幸的是,它并没有加快查找过程(这里是更新的代码 - 我将kv 翻转为edgeStartPointedgeEndPoint):

edgesList = []
for featureId in edges:
        edgeFeatureId = [k for k, v in edges.iteritems() if k == featureId][0]
        edgeStartPoint = [v for k, v in nodes.iteritems() if k == edges[featureId][0]][0]#start point
        edgeEndPoint = [v for k, v in nodes.iteritems() if k == edges[featureId][1]][0]#end point
        edgesList.append((edgeFeatureId,edgeStartPoint,edgeEndPoint))

【问题讨论】:

  • 你能创建一个代表边缘的对象吗?您可以重载 equal 函数或其他函数以简化使用此数据结构的工作吗?
  • @Marcin,你的意思是为边缘对象创建一个类吗?我希望能够进行某种智能字典匹配,但可以找到任何合适的东西。
  • 是的,一个 Edge 类。看起来它可以简单地让你的代码和操作在边缘。
  • 好的,感谢您的提示。我试图加速的操作是我将在 Python 中执行的最后一个操作,之前发生的所有数据处理都在几秒钟内发生,这只是最后一部分需要花费大量时间。我想避免为 Edge 创建类,因为我不会将它用于其他任何事情......
  • 你为什么到处使用iteritems(),字典的重点是基于键的查找,iteritems() 每次查找都有 O(N) 的性能。要么重新排列您的数据以允许按键查找,要么切换到至少会给出 Olog(n) 的二叉搜索树类型结构。

标签: python list dictionary list-comprehension


【解决方案1】:

由于您是根据坐标进行匹配,因此您的节点字典应该倒置。

也就是说,它应该是这样的:

{(12.8254, 55.3880): n14, 
(12.8340, 55.3883): n15, 
(12.8235, 55.3857): n16, 
(12.8343, 55.3920): n17}

这样,当您迭代边缘时,您可以非常快速地查找相应的节点:

edgesList = []
for featureId in edges:
    coordinates = edges[featureId]
    c0, c1 = coordinates

    n0 = nodes[c0]
    n1 = nodes[c1]

    edgesList.append((featureId, n0, n1))

请记住,字典可以非常快速地找到任何给定键的对应值。如此之快,在一般情况下,在字典大小为 1 或 100 万的情况下,查找 should barely change 的速度。

【讨论】:

  • Martin,坐标变量是什么?我不明白edgeFeatureId, coordinates = e 的工作原理。
  • x, y = [1, 2] 表示 x 将变为 1,y 将变为 2。您需要使用我提供的代码,而不仅仅是反转字典。
  • 我对您的原始代码略有误解。更新了我的答案 - 现在应该更清楚了。
  • 'coordinates = edges[featureId] 是有道理的。我刚刚意识到我正在使用== 搜索值,而不是使用dict[key] 引用基于键的值。感谢您指出这一点,它工作正常且速度非常快。
【解决方案2】:

在您的 cmets 中发现,问题是最后一次操作 edgesList.append((id,start,end))

这似乎是一个数据类型问题:大字典在设计上会减慢速度。看看here

但很高兴您可以改用双端队列(deque)。 deque documentation:“双端队列支持线程安全、内存高效的从双端队列两侧追加和弹出,在任一方向上的 O(1) 性能大致相同。”

在代码中这意味着你初始化一个双端队列并以高性能附加到它。

edgesList = deque() 
for featureId in edges:
        edgeFeatureId = [k for k, v in edges.iteritems() if k == featureId][0]
        edgeStartPoint = [v for k, v in nodes.iteritems() if k == edges[featureId][0]][0]#start point
        edgeEndPoint = [v for k, v in nodes.iteritems() if k == edges[featureId][1]][0]#end point
        edgesList.append((edgeFeatureId,edgeStartPoint,edgeEndPoint))

【讨论】:

  • 感谢您的提示。我应该为哪个 dict 使用 set 代替?搜索节点时如何引用设置的“键”?任何代码sn-p?
  • 抱歉,集合仅使用值。今天早上我有点累了:我再想一想
【解决方案3】:

根据您的示例数据,这是一个我认为可能有效的示例:

edges = {
    1: [(12.8254, 55.3880), (12.8343, 55.3920)],
    2: [(12.8254, 55.3880), (12.8235, 55.3857)],
    3: [(12.2432, 57.1120), (12.2426, 57.1122)]}
nodes = {
    14: (12.8254, 55.3880),
    15: (12.8340, 55.3883),
    16: (12.8235, 55.3857),
    17: (12.8343, 55.3920)}
reverseNodes=dict((v,k) for k, v in nodes.iteritems())
edgesList=[]
for k,v in edges.items():
    edgesList.append( 
            (k,
             reverseNodes.get(v[0], -1),
             reverseNodes.get(v[1], -1)))

也许我对您构建edgesList 的过程有些不理解,但我认为这看起来更简单、更快。

再次根据您的示例代码,这是消耗您的 CPU 时间的原因:

edgeFeatureId = [k for k, v in edges.iteritems() if k == featureId][0]
edgeStartPoint = [v for k, v in nodes.iteritems() if k == edges[featureId][0]][0]#start point
edgeEndPoint = [v for k, v in nodes.iteritems() if k == edges[featureId][1]][0]#end point

这存在于一个 for 循环中,因此对于每个边,您:

  • 在边列表上多迭代一次(以找到您已经拥有的边 ID)
  • 遍历节点列表两次以查找起点和终点(您不再需要这个,因为我们已经知道如何使用 reverseNodes-dict 进行直接查找)。

因此,根据您的数据大小,您应该得到大约 100000*(100000+90000+90000) 或 O(n^2) 次操作,这不仅仅是一次通过边缘(100000 或 O(n))

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-19
    相关资源
    最近更新 更多