【问题标题】:Efficiency: 2D-list to dictionary in python效率:python 中字典的二维列表
【发布时间】:2018-07-20 04:23:57
【问题描述】:

我有一个二维列表。

l = [[100, 1], [43, 2], [201, 1], [5, 7], ...]

我想将列表转换为第二个元素作为键的字典。每个键的值应该是所有第一个元素的列表,其中键作为第二个元素。 此示例列表的字典应如下所示:

{
    1: [100, 201],
    2: [43],
    7: [5],
    ...
}

对于这种转换,我有两种解决方案。其中哪一个更有效,为什么?另外:还有其他更有效的解决方案吗?

解决方案1:

d = {}
for elem in l:
    if elem[1] in d:
        d[elem[1]].append(elem[0])
    else:
        d[elem[1]] = [elem[0]]

解决方案2:

d = {}

for elem in l:
    d[elem[1]] = []

for elem in l:
    d[elem[1]].append(elem[0])

【问题讨论】:

    标签: python python-3.x performance list dictionary


    【解决方案1】:

    第一种解决方案效率更高,因为它需要 O(n) 时间才能完成: if 语句是 O(1),因为字典查找是 O(1)。

    第二个解决方案也是 O(n),但它运行了两次,所以 O(n+n)。

    在第一种解决方案中,您可以跳过 if 语句并直接附加 elem[0],并添加一个 try except 块。

    【讨论】:

    • 但是第一个循环在每次迭代中执行两个操作,所以也是 O(2n)。
    • @tobias_k 它确实做了两个操作。但我怀疑iffor 循环快吗?
    • @VikasDamodar 是的,第一个版本可能更快。循环本身可能有一些(恒定的?)开销,并且您只根据需要初始化尽可能多的列表,但仍然:说“它循环两次,所以它必须比只循环一次的另一个慢”充其量是误导。
    • @tobias_k 我知道了,那么我的解决方案呢,需要更多时间吗?
    • @VikasDamodar,您应该检查 setdefault 的复杂性,它可能比 if 更快,但可能不会胜过异常处理。
    【解决方案2】:

    你的两个解决方案都没有问题,它们很好而且高效:

    1. 解决方案 1 迭代列表一次,但执行字典查找两次。因为这大约是 O(1),而你有 2,我会说这是 2xN。
    2. 解决方案 2 迭代列表两次,每次查找一次 - 再次 2xN。

    从理论的角度来看,它们是相同的。运行时间我敢打赌它们也非常接近。一种更 Pythonic 的方式(请求宽恕而不是许可,感谢@tobias_k)可能会改进第一个解决方案:

    d = {}
    for elem in l:
        try:
            d[elem[1]].append(elem[0])
        except KeyError:
            d[elem[1]] = [elem[0]]
    

    如果您有许多重复键,这会更好,因为异常的开销将远小于所有 if(并且只有一次查找),因此这将取决于实际存在的列表。如果您选择此解决方案,您可能需要阅读defaultdict

    一些新信息

    我猜错了!即使理论上它们是相同的,并且操作量似乎相同,但还是有区别的。我猜这与 Python 优化 if 语句的能力有关。

    使用timeit 模块标记您的第一个方法a、您的第二个方法b 和我提供的c,我将这些方法用于两个列表:

    l1=[(x,y) for x,y in zip(range(1000),range(1000,2000)]
    l1=[(x,2) for x,y in range(1000)]
    

    l1 的结果:

    方法a 最快。慢 30% 是 b,再慢 30% 是 c

    l2 的结果:

    ab 方法几乎相同(仍然快一点),c 比两者都快一点(这是我们预期的)。

    出于实用目的,我会说第一个版本比第二个更好,但如果你有很多重复键,3d 会是最好的。归根结底,理论一切都很好,但是实际上最好的方法是依赖于列表的。

    【讨论】:

    • @tobias_k 很公平,谢谢。在 Python 中,我(错误地)将 try-except 与鸭子类型同义。我想你可以说“把它当作一个列表,如果没有就抓住它”,但我同意这是一个延伸。
    • 这仍然不正确。问题不在于它不是一个列表——这会引发 TypeError 或 AttributeError。问题是它根本不存在。我认为您正在寻找的概念是“请求宽恕而不是许可”或类似的东西。 (该表达式通常与鸭子类型结合使用,例如here,在这种情况下不应该。)
    • @pogothebear 为了未来读者的利益,请接受您选择的答案。
    • @kabanus 我想等待其他解决方案会更好。
    • @user32185 也许。无论如何,如果有答案,就应该接受一个解决方案——如果出现更好的解决方案,它总是可以改变的。我只向代表很少的人制作这些 cmets,因为他们经常将投票与接受混淆,或者忘记接受(根据经验)。
    【解决方案3】:

    我认为solution 1solution 2更高效,因为它只有一个循环。我认为您可以尝试下面的代码,它可能比 solution 1

    更有效
    l = [[100, 1], [43, 2], [201, 1], [5, 7]]
    d = {}
    for k, v in l:
        d.setdefault(v, []).append(k)
    print(d)
    

    它给出这样的o/p:

    {1: [100, 201], 2: [43], 7: [5]}
    

    【讨论】:

    • 在我看来,这个解决方案始终比 OP solution1 慢。
    • @user32185 在 OP 的解决方案 1 中,它通过 if:..else,因此当它使用设置默认值执行时,时间可能会减少,因为它在每个循环中执行一次
    【解决方案4】:

    您的两个解决方案将具有相同的 O(n)。但是你可以使用collections.defaultdict:

    from collections import defaultdict
    
    l = [[100, 1], [43, 2], [201, 1]]
    
    d = defaultdict(list)
    
    for v, k in l:
        d[k].append(v)
    
    # defaultdict(list, {1: [100, 201], 2: [43]})
    

    一些基准测试:

    from collections import defaultdict
    import numpy as np
    
    l = np.random.randint(1, 100, [int(1e7), 2])
    
    def dd(l):
        d = defaultdict(list)
    
        for i in l:
            d[i[1]].append(i[0])
    
        return d
    
    def op(l):
        d = {}
        for elem in l:
            if elem[1] in d:
                d[elem[1]].append(elem[0])
            else:
                d[elem[1]] = [elem[0]]    
    
        return d
    
    %timeit dd(l)  # 1 loop, best of 3: 8.22 s per loop
    %timeit op(l)  # 1 loop, best of 3: 9.47 s per loop
    

    【讨论】:

    • 在我看来这比 OP 解决方案慢 2 倍。
    • 我不确定这是一个很好的基准测试列表。我用过这个l = np.random.randint(1, 100, [int(1e7), 2])
    • @user32185,已根据您的输入进行了更新并进行了细微的改进。
    【解决方案5】:
    l = [[100, 1], [43, 2], [201, 1], [5, 7]]
    d = dict(l)
    print(d)
    

    它会给你输出为 {100: 1, 43: 2, 201: 1, 5: 7}

    【讨论】:

    • 只有当一个键最多有1个值时才有效
    猜你喜欢
    • 2012-04-28
    • 1970-01-01
    • 2018-04-21
    • 2012-09-14
    • 1970-01-01
    • 1970-01-01
    • 2014-10-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多