代码之家  ›  专栏  ›  技术社区  ›  norok2

从key==value的列表生成dict的最快方法

  •  1
  • norok2  · 技术社区  · 7 年前

    我有一张单子,上面写着:

    NUM = 100
    my_list = list(range(NUM))
    

    我想生成一个 dict

    my_dict = {item: item for item in my_list}
    

    或:

    my_dict = dict(zip(my_list, my_list))
    

    我已经运行了一些微基准测试,看起来它们的速度差不多,但是我希望第二个会快得多,因为循环应该在C中发生。

    my_dict = {key: SOMETHING for key in keys}
    

    转化为更快的:

    my_dict = dict.fromkeys(k, SOMETHING)
    

    所以,我的问题是:有没有类似的结构 {x: x for x in my_list} ?


    编辑

    我查过了 dir(dict) 在这个方向上似乎什么都没有(我希望它被称为类似的东西) dict.fromitems() ).


    编辑2

    会有比这个特定用例更广泛的应用,因为:

    dict.fromitems(keys, values)
    

    {k, v for k, v in zip(keys, values)}
    

    以及:

    dict(zip(keys, values))
    
    2 回复  |  直到 7 年前
        1
  •  7
  •   Martijn Pieters    7 年前

    不,没有更快的方法可用于词典。

    这是因为性能成本都是在处理迭代器中的每个项、计算其哈希值并将键插入字典数据哈希表结构(包括动态增长这些结构)中。相比之下,执行字典理解字节码实在是微不足道。

    dict(zip(it, it)) , {k: k for k in it} dict.fromkeys(it) 速度都很接近:

    >>> from timeit import Timer
    >>> tests = {
    ...     'dictcomp': '{k: k for k in it}',
    ...     'dictzip': 'dict(zip(it, it))',
    ...     'fromkeys': 'dict.fromkeys(it)',
    ... }
    >>> timings = {n: [] for n in tests}
    >>> for magnitude in range(2, 8):
    ...     it = range(10 ** magnitude)
    ...     for name, test in tests.items():
    ...         peritemtimes = []
    ...         for repetition in range(3):
    ...             count, total = Timer(test, 'from __main__ import it').autorange()
    ...             peritemtimes.append(total / count / (10 ** magnitude))
    ...         timings[name].append(min(peritemtimes))  # best of 3
    ...
    >>> for name, times in timings.items():
    ...     print(f'{name:>8}', *(f'{t * 10 ** 9:5.1f} ns' for t in times), sep=' | ')
    ...
    dictcomp |  46.5 ns |  47.5 ns |  50.0 ns |  79.0 ns | 101.1 ns | 111.7 ns
     dictzip |  49.3 ns |  56.3 ns |  71.6 ns | 109.7 ns | 132.9 ns | 145.8 ns
    fromkeys |  33.9 ns |  37.2 ns |  37.4 ns |  62.7 ns |  87.6 ns |  95.7 ns
    

    dict.fromkeys() 可以处理项目a 小的 稍微快一点,但它并不比其他过程快一个数量级。它的(小)速度优势并不来自于能够在这里用C进行迭代;区别仅仅在于不必每次迭代都更新值指针;所有键都指向单值引用。

    zip() 速度较慢,因为它构建了其他对象(为 每个键值对 不是无成本操作), 它增加了过程中涉及的迭代器的数量,您可以从单个迭代器获得对字典的理解和理解 字典fromkeys() ,到3个迭代器( dict() zip() ,到2 分离 键和值的迭代器)。

    将单独的方法添加到 dict 类来处理此问题,因为

    1. 无论如何都不是一个足够常见的用例(创建键和值相等的映射不是一个常见的需求)
    2. C语言的速度不会明显快于字典理解 无论如何 .
        2
  •  0
  •   satnhak    6 年前

    1. 取用钥匙和钥匙;
    2. 计算 __hash__ 钥匙的钥匙。
    3. __散列__

    别担心回路。如果要花很长时间来建立字典,那是因为这些操作很慢。

        3
  •  -3
  •   PMende    7 年前

    使用答案的结果 here 丢失的

    from collections import defaultdict
    class keydefaultdict(defaultdict):
        def __missing__(self, key):
            if self.default_factory is None:
                raise KeyError(key)
            else:
                ret = self[key] = self.default_factory(key)
                return ret
    

    现在,您可以通过执行以下操作来创建要查找的词典:

    my_dict = keydefaultdict(lambda x: x)
    

    那些 价值观。

    defaultdict :

    %%timeit
    my_dict = keydefaultdict(lambda x: x)
    for num in some_numbers: my_dict[num] == num
    

    结果:

    4.46 s ± 71.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    

    听写理解

    %%timeit
    my_dict = {x: x for x in some_numbers}
    for num in some_numbers: my_dict[num] == num
    

    结果:

    1.19 s ± 20.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    

    当您最终需要访问大约17%的原始值时,这两个值就具有可比性。如果你需要的更少,效果更好:

    仅访问原始值的一部分

    子类别化 默认dict

    %%timeit
    frac = 0.17
    my_dict = keydefaultdict(lambda x: x)
    for num in some_numbers[:int(len(some_numbers)*frac)]: my_dict[num] == num
    

    结果:

    770 ms ± 4.69 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    

    听写理解

    %%timeit
    frac = 0.175
    my_dict = {x: x for x in some_numbers}
    for num in some_numbers[:int(len(some_numbers)*frac)]: my_dict[num] == num
    

    781 ms ± 4.03 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)