【问题标题】:Fastest way to increment counters in dictionary在字典中增加计数器的最快方法
【发布时间】:2018-02-09 06:08:07
【问题描述】:

增加存储在字典中的计数器的最快方法是什么?

因为我必须执行数十万次相同的操作,所以我正在寻找比我下面的更有效的方法:

def funcA(a):
    keys = [x for x in range(1, 51)]
    adict = {key: 0 for key in keys}

    for k in adict.keys():  # this is the code I would like to improve
        if k <= a:
            adict[k] += 1
        else:
            break


import timeit
number = 100000

t1 = timeit.timeit(
        'funcA(5)',
        setup="from __main__ import funcA", number=number)
print(t1)

>>> 0.42629639082588255

尝试使用列表推导似乎会减慢一切,可能是因为它缺少 break 语句?

def funcB(a):
    # not working, invalid syntax
    keys = [x for x in range(1, 51)]
    adict = {key: 0 for key in keys}

    def _inc(x):
        x += 1
        return x

    [_inc(adict[k]) for k in adict.keys() if k <= a]

# Timing: 0.5831785711925477

注意:最初我有if float(k) &lt;= float(a):,但由于我只期待数字(整数或浮点数),删除float() 转换改进了代码。这个假设合理吗?

注意 2:正如几个 cmets 中所述,break 语句可能会在结果字典中给出意想不到的结果,所以最好这样做:

def funcA(a):
    keys = [x for x in range(1, 51)]
    adict = {key: 0 for key in keys}

    for k in adict:
        if k <= a:
            adict[k] += 1

# Timing: 0.5132114209700376

【问题讨论】:

  • 您可以将for k in adict.keys() 缩短为for k in adict
  • 为什么不直接做:adict = {key: 1 if key &lt;= a else 0 for key in keys}
  • @AshwiniChaudhary 我想增加密钥adict[k] += 1
  • 另请注意dict.keys() 返回一组键,这意味着它们有时可能不会按顺序排列。因此,如果a = 20 并且出现的第一个键是22,那么您将跳出循环并且什么都不会改变。
  • 为什么在键是整数时使用字典?为什么不做 for k in range(1,a): 而不是测试 k

标签: python performance loops dictionary counter


【解决方案1】:

在您的情况下,您可以使用布尔值(比较的结果)可以简单地转换为整数的事实。它可能不是最快的,但绝对是短且“相对”快的:

def funcA(a):
     adict = {key: int(key <= a) for key in range(1, 51)}

这是假设第二个函数实际上是您想要的,因为由于break,第一个函数可能会给出不同的结果。字典是无序的,因此它不能为小于或等于a 的键增加一些值。此外,它不会增加值,它只是将它们设置为 10,因为在这种情况下您实际上不需要添加。

但是,这不一定是最快的方法,因为它必须执行大量函数调用和int 查找。因此,我将按性能(从最快到最慢)的顺序介绍一些更等效的操作:

def cached_version():
    range_cache = range(1, 51)
    cache = dict.fromkeys(range_cache, 0)
    def inner(a):
        adict = cache.copy()
        for key in range_cache[:a]:  # requires a to be an integer!
            adict[key] = 1
        return adict
    return inner

func1 = cached_version()  # initialize cache

def func2(a):
    keys = range(1, 51)
    adict = dict.fromkeys(keys[:a], 1)   # requires a to be an integer!
    for key in keys[a:]:
        adict[key] = 0
    return adict

def func3(a):
    adict = {}
    for key in range(1, 51):
        if key <= a:
            adict[key] = 1
        else:
            adict[key] = 0
    return adict

def func4(a):
    return {key: 1 if key <= a else 0 for key in range(1, 51)}

def func5(a):
    keys = range(1, 51) 
    adict = dict.fromkeys(keys[:a], 1)  # requires a to be an integer!
    adict.update(dict.fromkeys(keys[a:], 0))
    return adict

def func6(a):
    return dict(zip(range(1, 51), [1]*a + [0]*(49-a)))  # requires a to be an integer!

from itertools import chain

def func7(a):
    return dict(zip(range(1, 51), chain([1]*a, [0]*(49-a))))  # requires a to be an integer!

def func8(a):  # the one I originally mentioned
     adict = {key: int(key <= a) for key in range(1, 51)}

时间是在 Python 3.5、Windows 10 上完成的,在其他机器和其他 Python 版本上可能存在差异。另请注意,如果您有更多的键,而不仅仅是range(1, 51),性能可能会完全不同。

【讨论】:

  • 没错,但即使没有break,这个解决方案似乎也比我拥有的任何一个功能都慢。你说得对,break 语句可能会弄乱结果。
  • @PedroA 好的,我做了一些计时并尝试了各种组合。我想我找到了一个非常快的(尽管它是一个带缓存的闭包)。 :)
  • 很好,但我很惊讶func3func4 快,认为理解会更快?
  • @Chris_Rands 我认为理解通常更快的原因是因为它们可以避免函数查找和调用,如.append 用于lists 或.add 用于set。但是,当为一个似乎并非如此的 dict 分配键时。不过速度并没有慢多少(我认为只有 5%)!
  • @PedroA 这是一个间接层,因此从长远来看更难维护(好的文档可以提供帮助)。它还增加了内存使用量,因为缓存的变量保存在内存中。如果性能是一个问题,那么值得尝试这样的优化。但是对于日常使用,我更喜欢像{key: int(key &lt;= a) for key in range(1, 51)} 理解这样的可读性更强的解决方案。这些更具可读性并且不会太慢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-05-08
  • 2016-02-11
  • 2010-11-21
  • 2015-10-01
  • 2019-03-11
  • 2011-07-12
  • 2019-08-08
相关资源
最近更新 更多