![](/img/trans.png)
[英]Is there a way to make a dictionary from a single list where the key and value for the dict are taken from a specific index
[英]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()
類的東西)。
像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 中處理這個問題是沒有意義的,因為
這是禪宗的答案。 字典理解循環本身很快,這不是瓶頸。 正如 Martijn Pieters 所說,時間是用來花的:
__hash__
。__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.