【问题标题】:Fastest way to update a dictionary in python在python中更新字典的最快方法
【发布时间】:2011-05-08 13:40:07
【问题描述】:

我有一本字典 A,还有一个可能的条目 foo。我知道 A[foo] 应该等于 x,但我不知道 A[foo] 是否已经定义。在任何情况下,如果 A[foo] 已被定义,则意味着它已经具有正确的值。

执行速度更快:

if foo not in A.keys(): 
   A[foo]=x 

或者干脆更新

A[foo]=x 

因为当计算机找到 foo 条目时,它也可以更新它。如果不是,我将不得不调用哈希表两次?

谢谢。

【问题讨论】:

  • 你怎么会有这个问题?通常你会知道你之前设置了哪些键,或者只是一次构建最终的字典。
  • 我正在计算代数中的所有元素(和关系)。我必须用我知道的来找出我不知道的。有些计算比较难,所以我把它们放在最后。希望在我去计算它们的时候,我可以免费使用其他的来推导它们。很快我就不再知道我已经发现了哪些关系以及我没有发现哪些关系。由于元素很多,关系也很多,所以我需要快速。
  • 从问题的描述来看,字典存储似乎不会成为您程序中的主要瓶颈。只需编写您可以编写的最清晰的程序,如果它太慢,请对其进行分析并在需要的地方进行优化。根据我的经验,我几乎从不去分析和优化步骤。
  • 当您使用timeit 时,您学到了什么?请发布结果。
  • @S.Lott 我刚刚使用timeit 发布了一个答案。

标签: python dictionary performance hashtable


【解决方案1】:

使用内置的 update() 函数更快。我稍微调整了上面 Steven Rumbalski 的示例,它显示了 update() 是如何最快的。至少有两种使用方法(使用元组列表或使用另一个字典)。前者(下面显示为 update_method1)是最快的。请注意,我还更改了有关 Steven Rumbalski 示例的其他一些内容。我的字典每个都有正好 100,000 个键,但新值有 10% 的机会不需要更新。这种冗余的机会将取决于您更新字典所用数据的性质。在我机器上的所有情况下,我的 update_method1 都是最快的。

import timeit

setup = """
import random
random.seed(0)
item_count = 100000
existing_dict = dict([(str(i), random.randint(1, 10)) for i in xrange(item_count)])
items = [(str(i), random.randint(1, 10)) for i in xrange(item_count)]
items_dict = dict(items)
"""
in_dict = """
for k, v in items:
    if k not in existing_dict:
        existing_dict[k] = v
"""
set_default = """
for k, v in items:
    existing_dict.setdefault(k, v)
"""
straight_add = """
for k, v in items:
    existing_dict[k] = v
"""
update_method1 = """
existing_dict.update(items)
"""
update_method2 = """
existing_dict.update(items_dict)
"""
print 'in_dict        ', timeit.Timer(in_dict, setup).timeit(1000)
print 'set_default    ', timeit.Timer(set_default, setup).timeit(1000)
print 'straight_add   ', timeit.Timer(straight_add, setup).timeit(1000)
print 'update_method1 ', timeit.Timer(update_method1, setup).timeit(1000)
print 'update_method2 ', timeit.Timer(update_method2, setup).timeit(1000)

这段代码产生了以下结果:

in_dict         10.6597309113
set_default     19.3389420509
straight_add    11.5891621113
update_method1  7.52693581581
update_method2  9.10132408142

【讨论】:

    【解决方案2】:

    只需将项目添加到字典而不检查它们的存在。我使用 3 种不同的方法将 100,000 个项目添加到字典中,并使用 timeit 模块对其进行计时。

    1. if k not in d: d[k] = v
    2. d.setdefault(k, v)
    3. d[k] = v

    选项 3 是最快的,但不是很多。

    [ 实际上,我也尝试过if k not in d.keys(): d[k] = v,但速度慢了 300 倍(每次迭代都会构建一个键列表并执行线性搜索)。这让我的测试变得如此缓慢,以至于我把它放在这里了。 ]

    这是我的代码:

    import timeit
    
    setup = """
    import random
    random.seed(0)
    item_count = 100000
    # divide key range by 5 to ensure lots of duplicates 
    items = [(random.randint(0, item_count/5), 0) for i in xrange(item_count)]
    """
    in_dict = """
    d = {}
    for k, v in items:
        if k not in d:
            d[k] = v
    """
    set_default = """
    d = {}
    for k, v in items:
        d.setdefault(k, v)
    """
    straight_add = """
    d = {}
    for k, v in items:
        d[k] = v
    """
    print 'in_dict      ', timeit.Timer(in_dict, setup).timeit(1000)
    print 'set_default  ', timeit.Timer(set_default, setup).timeit(1000)
    print 'straight_add ', timeit.Timer(straight_add, setup).timeit(1000)
    

    结果:

    in_dict       13.090878085
    set_default   21.1309413091
    straight_add  11.4781760635
    

    注意:这一切都毫无意义。我们每天都会收到很多关于在 Python 中执行 x 或 y 的最快方法是什么的问题。在大多数情况下,很明显问题是在遇到任何性能问题之前提出的。我的建议?专注于编写您可以编写的最清晰的程序,如果它太慢,请对其进行分析并在需要的地方进行优化。以我的经验,我几乎从来没有去分析和优化步骤。从问题的描述来看,字典存储似乎不会成为您程序中的主要瓶颈。

    【讨论】:

    • 感谢您的测试。现在我们知道了。是的,当然,如果我“只是”对这个程序的速度感兴趣,我应该去进行分析。但我不是。我不认识你,但对我来说,我经常处于需要决定是重写字典条目还是之前检查的情况。只知道哪个更好,这是一个心理清洁问题。而且两个数量级很多!
    【解决方案3】:

    如果你“知道”A[foo]“应该”等于 x,那么我会这样做:

    assert(A[foo]==x)
    

    它会告诉你你的假设是否错误!

    【讨论】:

    • 虽然如果foo not in A 会出现KeyError 失败。但事实上,如果程序开始给出错误结果,请使用if foo in A: assert A[foo] == x
    • 谢谢,这行不通。 foo 可能没有定义,这根本不是一个错误。只有定义了它,我才知道它等于 x。如果我检查一下,我可能会编写更健壮的代码(事实上我现在确实有这些断言)但速度较慢。最终代码必须在没有这些断言的情况下工作。
    【解决方案4】:
    foo not in A.keys()
    

    将在 Python 2 中使用键创建一个新列表,然后对其执行线性搜索。这保证会更慢(尽管我主要反对它,因为有更快更优雅/惯用的替代方法。

    A[foo] = x
    

    if foo not in A:
        A[foo] = x
    

    如果A[foo] 已经存在但is not x 则不同。但由于您的“知道”A[foo] 将是 x,所以 语义 并不重要。无论如何,两者在性能方面都很好(如果不进行基准测试就很难判断,尽管直觉上我会说if 比复制指针花费更多的时间)。

    所以答案还是很清楚的:选择一个大大在代码方面更短且同样清晰的那个(第一个)。

    【讨论】:

      【解决方案5】:

      肯定有比您的第一个示例更快的方法。但我怀疑直接更新会比任何测试都快。

      【讨论】:

        【解决方案6】:

        A.setdefault(foo, x) 但我不确定它是否比if not A.has_key(foo): A[foo] = x 快。应该进行测试。

        【讨论】:

        • 我也在考虑 setdefault,但我怀疑它比 A[foo] = x
        • 不是更快,但是A[foo]=x 并没有做到原作者想要的。根据 sn-p foo:x 仅在字典没有键 foo 时添加。
        • 感谢 khachik,操作员(我)只需要确保最后 A[foo]=x。如果它已经定义并且 a[foo] 已经等于 x 我可以重新分配它,如果它更快。
        • @Pietro 确保:我刚刚测试过,A[foo]=xA.setdefault(foo, x) 更快(1/1.5)。因此,如果可以覆盖旧值,您可以重新分配。
        【解决方案7】:
        if foo not in A.keys(): 
            A[foo] = x 
        

        非常慢,因为A.keys() 创建了一个列表,必须在 O(N) 中解析。

        if foo not in A: 
            A[foo] = x 
        

        更快,因为它需要 O(1) 来检查 foo 是否存在于 A 中。

        A[foo] = x 
        

        更好,因为您已经拥有对象x,并且您只需添加(如果它不存在)指向它的指针到A

        【讨论】:

        • 我错了吗:-/?我认为问题是如何将一个项目设置为 dict 如果它不存在...
        • 他的问题写得有点奇怪,但他说'如果值已经设置,则设置正确',所以是的,在这种情况下用完全相同的值覆盖是可以的。
        • 嗨,Thomas,对不起,如果我用词搞笑的话。随意编辑更正它。但在我看来,你完全明白我的意思:-)
        猜你喜欢
        • 2011-08-04
        • 2022-11-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-01-25
        • 2010-11-21
        • 2016-02-11
        相关资源
        最近更新 更多