簡體   English   中英

從列表中生成字典的最快方法,其中鍵 == 值

[英]Fastest way to generate a dict from list where key == value

我有一個清單,說:

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()這樣的方法比這個特定用例有更廣泛的應用,因為:

dict.fromitems(keys, values)

原則上可以同時替代兩者:

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

和:

dict(zip(keys, values))

不,沒有更快的方法可用於字典。

那是因為性能成本全部在於處理迭代器中的每個項目、計算其散列並將鍵插入字典數據散列表結構中(包括動態增長這些結構)。 相比之下,執行字典理解字節碼真的微不足道。

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

這是每項技術的每項成本表,從 100 到 1000 萬項。 隨着哈希表結構增長的額外成本的累積,時間會增加。

當然, dict.fromkeys()可以處理項目得快一點,但它不是一個數量級比其他進程更快。 它的(小)速度優勢並非來自能夠在 C 中迭代; 區別純粹在於不必每次迭代更新值指針; 所有鍵都指向單個值引用。

zip()速度較慢,因為它構建了額外的對象(為每個鍵值對創建一個 2 項元組不是一項免費操作),並且它增加了該過程中涉及的迭代器數量,您從單個迭代器開始對於字典理解和dict.fromkeys() ,到 3 個迭代器( dict()迭代通過zip()委托給兩個單獨的鍵和值迭代器)。

dict類中添加一個單獨的方法來在 C 中處理這個問題是沒有意義的,因為

  1. 無論如何都不是一個足夠常見的用例(創建一個鍵和值相等的映射不是一個常見的需求)
  2. 無論如何,在 C 中不會比使用字典理解快得多。

這是禪宗的答案。 字典理解循環本身很快,這不是瓶頸。 正如 Martijn Pieters 所說,時間是用來花的:

  1. 訪問密鑰和;
  2. 計算密鑰的__hash__
  3. 可能你的__hash__不好,你有很多沖突,導致字典插入很昂貴。

不要擔心循環。 如果構建字典需要很長時間,那是因為這些操作很慢。

使用這里答案的結果,我們創建一個新類,該類將 defaultdict 子類化,並覆蓋其缺失的屬性以允許將密鑰傳遞給 default_factory:

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% 的原始值時,這兩者變得具有可比性。 如果您需要更少,則更好:

僅訪問原始值的一部分

子類化defaultdict

%%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)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM