【问题标题】:Improve conversion speed of list of tuples to dict提高元组列表到字典的转换速度
【发布时间】:2018-04-26 20:39:41
【问题描述】:

我有一个列表l,由长度为 5 的元组组成。前四个条目是字符串,最后一个是整数。 创建此类列表的虚拟函数可能如下所示:

import numpy as np
import uuid
def get_dummy_data(n=10000):
    l = []
    for i in range(n):
        name = np.random.choice(["Cat", "Dog", "Duck"], 1)[0]
        c_id = uuid.uuid4().hex
        t_id = uuid.uuid4().hex
        l.append((c_id, t_id, name, "canFly", 1))
        if np.random.random() < 0.8:
            l.append((c_id, t_id, name, "isHungry", 0))
    return l

现在这个列表l 包含前三个元素相同但后两个元素不同的元组。这通过以 80% 的机会再次附加相同的元组但更改最后两个元素来举例说明。

目标是将这个长度为 5 的元组列表转换为一个字典,其中键是元组的第一个条目 (c_id),值的结构如下 (t_id, (name, {"isHungry": 0})) 或这个:(t_id, (name, {"canFly":1, "isHungry":0}))。

这可以通过以下循环来实现:

res = {}
for y in l:
    if y[0] not in res:
        res[y[0]] = (y[1], (y[2], {y[3]: y[4]}))
    else:
        res[y[0]][1][1].update({y[3]: y[4]}) 

现在的问题是:我可以让它更快吗?列表l 中可能有两个以上具有相同c_id 的元组(与get_dummy_data 函数相反),我们不能假设l 中的任何顺序。 在执行显式 for 循环来填充 dict 时,我总是有一种不好的感觉,所以我敢打赌,有一个好方法可以让这更快。

【问题讨论】:

  • “在执行显式 for 循环来填充字典时,我总是有一种不好的感觉”而不是......?无论如何,您实际上是否遇到了性能问题?这几乎是即时运行的,比 get_dummy_data 函数快。
  • 没有关于您的目标和背景或您的背景的更多信息。除了 juanpa 提供的东西之外,很难优化任何东西
  • @juanpa.arrivillaga 在这种特殊情况下,我只是想知道是否有比我更快的解决方案。所以让我们称之为“学术兴趣”

标签: python python-3.x


【解决方案1】:

您可以进行基本的微优化,从而使您的代码更具可读性。一个大的不是使用some_dict.update({x:y}) 而不是some_dict[x] = y。但这里有一些时间差异:

In [12]: %%timeit
    ...: res = {}
    ...: for y in data:
    ...:     if y[0] not in res:
    ...:         res[y[0]] = (y[1], (y[2], {y[3]: y[4]}))
    ...:     else:
    ...:         res[y[0]][1][1].update({y[3]: y[4]})
    ...:
100 loops, best of 3: 15.3 ms per loop

In [13]: %%timeit
    ...: res = {}
    ...: for a,b,c,d,e in data:
    ...:     if a not in res:
    ...:         res[a] = (b, (c, {d: e}))
    ...:     else:
    ...:         res[a][1][1][d] = e
    ...:
100 loops, best of 3: 11 ms per loop

这里是.update。请注意,每个y[...] 都是一个方法调用,这会减慢速度。但节省时间的最大部分是避免使用.update({...}。请注意,这种方法无缘无故地需要创建一个完整的 dict 对象:

In [18]: %%timeit
    ...: res = {}
    ...: for a,b,c,d,e in data:
    ...:     if a not in res:
    ...:         res[a] = (b, (c, {d: e}))
    ...:     else:
    ...:         res[a][1][1].update({d:e})
    ...:
100 loops, best of 3: 13.8 ms per loop

【讨论】:

  • 微优化优于no优化。
  • @Jean-FrançoisFabre 对我来说最大的问题是可读性。
  • 我喜欢你解释了为什么这运行得更快!
  • @juanpa.arrivillaga 检查我的更新;我从您的答案中借用了解包和字典更新,我的解决方案现在比您的解决方案快 20% :)
【解决方案2】:

这种循环一般比较慢:

res = {}
for y in l:
    if y[0] not in res:
        res[y[0]] = (y[1], (y[2], {y[3]: y[4]}))
    else:
        res[y[0]][1][1].update({y[3]: y[4]}) 

因为您要测试该键是否属于字典两次并且有 if/else 语句。

我会在lambda 和解包中使用变量的绑定属性(借用自 juanpa 的答案):

import collections
res = collections.defaultdict(lambda : (b, (c, {d: e})))

for a,b,c,d,e in l:
    res[a][1][1][d] = e

如果键不在字典中 defaultdict 使用 a 的当前值创建键,b ...,(感谢 lambda 在执行时评估值,而不是在声明时)保存每次测试并创建正确的密钥。现在update 部分有点多余,但它仍然应该更快,因为没有if/then 测试。

这个解决方案比我机器上的 juanpa(已经很好)答案更快(0.23 秒对 0.27 秒)。由于我的第一个版本较慢,因此我认为这是一次很好的协作。

【讨论】:

  • 实际上,我的测试变得变慢。摆弄它的最大时间是不必要的字典初始化。但这是对闭包的巧妙使用,尽管有点厚颜无耻。
  • 在我的机器上,使用 defaultdict 的版本比我尝试的运行速度稍慢。
  • 已编辑:将 juanpa 的好主意与闭包默认 dict hack 相结合,我的答案甚至更快。
  • 即使在您编辑之后,这似乎也不比@juanpa.arrivillaga 的版本快。
  • 令人惊讶,在我的机器上并非如此。你不想在import collections 上使用time 虽然...
猜你喜欢
  • 1970-01-01
  • 2013-04-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-20
  • 2017-06-07
相关资源
最近更新 更多