Notes supplémentaires basées sur les réponses de georg, NPE, Scott et Havok.
J'essayais d'effectuer cette action sur des collections de 2 ou plus de dictionnaires et je voulais voir le temps que cela prenait pour chacun. Comme je voulais le faire sur un nombre quelconque de dictionnaires, j'ai dû modifier un peu certaines des réponses. Si quelqu'un a de meilleures suggestions pour elles, n'hésitez pas à éditer.
Voici ma méthode de test. Je l'ai récemment mise à jour pour inclure des tests avec des dictionnaires BEAUCOUP plus grands, et de nouveau pour inclure les méthodes plus récentes de Havok et Scott:
Tout d'abord, j'ai utilisé les données suivantes:
import random
x = {'xy1': 1, 'xy2': 2, 'xyz': 3, 'only_x': 100}
y = {'xy1': 10, 'xy2': 20, 'xyz': 30, 'only_y': 200}
z = {'xyz': 300, 'only_z': 300}
small_tests = [x, y, z]
# 200,000 clés aléatoires de 8 lettres
keys = [''.join(random.choice("abcdefghijklmnopqrstuvwxyz") for _ in range(8)) for _ in range(200000)]
a, b, c = {}, {}, {}
# 50/50 de chances qu'une valeur soit assignée à chaque dictionnaire, certaines clés seront manquées mais bon
for key in keys:
if random.getrandbits(1):
a[key] = random.randint(0, 1000)
if random.getrandbits(1):
b[key] = random.randint(0, 1000)
if random.getrandbits(1):
c[key] = random.randint(0, 1000)
large_tests = [a, b, c]
print("a:", len(a), "b:", len(b), "c:", len(c))
#: a: 100069 b: 100385 c: 99989
Maintenant chacune des méthodes:
from collections import defaultdict, Counter
from functools import reduce
def georg_method(tests):
return {k: sum(t.get(k, 0) for t in tests) for k in set.union(*[set(t) for t in tests])}
def georg_method_nosum(tests):
# Si vous savez que vous aurez exactement 3 dictionnaires
return {k: tests[0].get(k, 0) + tests[1].get(k, 0) + tests[2].get(k, 0) for k in set.union(*[set(t) for t in tests])}
def npe_method(tests):
ret = defaultdict(int)
for d in tests:
for k, v in d.items():
ret[k] += v
return dict(ret)
# Remarque: Il y a un bug avec la méthode de Scott. Voir ci-dessous pour les détails.
# Scott a inclus une version similaire utilisant des compteurs qui est corrigée
# Voir la méthode de mise à jour scott ci-dessous
def scott_method(tests):
return dict(sum((Counter(t) for t in tests), Counter()))
def scott_method_nosum(tests):
# Si vous savez que vous aurez exactement 3 dictionnaires
return dict(Counter(tests[0]) + Counter(tests[1]) + Counter(tests[2]))
def scott_update_method(tests):
ret = Counter()
for test in tests:
ret.update(test)
return dict(ret)
def scott_update_method_static(tests):
# Si vous savez que vous aurez exactement 3 dictionnaires
xx = Counter(tests[0])
yy = Counter(tests[1])
zz = Counter(tests[2])
xx.update(yy)
xx.update(zz)
return dict(xx)
def havok_method(tests):
def reducer(accumulator, element):
for key, value in element.items():
accumulator[key] = accumulator.get(key, 0) + value
return accumulator
return reduce(reducer, tests, {})
methods = {
"georg_method": georg_method, "georg_method_nosum": georg_method_nosum,
"npe_method": npe_method,
"scott_method": scott_method, "scott_method_nosum": scott_method_nosum,
"scott_update_method": scott_update_method, "scott_update_method_static": scott_update_method_static,
"havok_method": havok_method
}
J'ai également écrit une fonction rapide pour trouver toutes les différences entre les listes. Malheureusement, c'est là que j'ai trouvé le problème dans la méthode de Scott, à savoir, si vous avez des dictionnaires qui totalisent 0, le dictionnaire ne sera pas du tout inclus en raison de la façon dont Counter()
se comporte lors de l'ajout.
Configuration de test:
- MacBook Pro (15 pouces, fin 2016), Intel Core i7 2,9 GHz, 16 Go de RAM LPDDR3 2133 MHz, exécutant macOS Mojave Version 10.14.5
- Python 3.6.5 via IPython 6.1.0
Enfin, les résultats:
Résultats: Petits Tests
for name, method in methods.items():
print("Méthode:", name)
%timeit -n10000 method(small_tests)
#: Méthode: georg_method
#: 7.81 µs ± 321 ns par boucle (moyenne ± écart type de 7 répétitions, 10000 boucles chacune)
#: Méthode: georg_method_nosum
#: 4.6 µs ± 48.8 ns par boucle (moyenne ± écart type de 7 répétitions, 10000 boucles chacune)
#: Méthode: npe_method
#: 3.2 µs ± 24.7 ns par boucle (moyenne ± écart type de 7 répétitions, 10000 boucles chacune)
#: Méthode: scott_method
#: 24.9 µs ± 326 ns par boucle (moyenne ± écart type de 7 répétitions, 10000 boucles chacune)
#: Méthode: scott_method_nosum
#: 18.9 µs ± 64.8 ns par boucle (moyenne ± écart type de 7 répétitions, 10000 boucles chacune)
#: Méthode: scott_update_method
#: 9.1 µs ± 90.7 ns par boucle (moyenne ± écart type de 7 répétitions, 10000 boucles chacune)
#: Méthode: scott_update_method_static
#: 14.4 µs ± 122 ns par boucle (moyenne ± écart type de 7 répétitions, 10000 boucles chacune)
#: Méthode: havok_method
#: 3.09 µs ± 47.9 ns par boucle (moyenne ± écart type de 7 répétitions, 10000 boucles chacune)
Résultats: Grands Tests
Naturellement, je n'ai pas pu exécuter autant de boucles
for name, method in methods.items():
print("Méthode:", name)
%timeit -n10 method(large_tests)
#: Méthode: georg_method
#: 347 ms ± 20 ms par boucle (moyenne ± écart type de 7 répétitions, 10 boucles chacune)
#: Méthode: georg_method_nosum
#: 280 ms ± 4.97 ms par boucle (moyenne ± écart type de 7 répétitions, 10 boucles chacune)
#: Méthode: npe_method
#: 119 ms ± 11 ms par boucle (moyenne ± écart type de 7 répétitions, 10 boucles chacune)
#: Méthode: scott_method
#: 324 ms ± 16.8 ms par boucle (moyenne ± écart type de 7 répétitions, 10 boucles chacune)
#: Méthode: scott_method_nosum
#: 289 ms ± 14.3 ms par boucle (moyenne ± écart type de 7 répétitions, 10 boucles chacune)
#: Méthode: scott_update_method
#: 123 ms ± 1.94 ms par boucle (moyenne ± écart type de 7 répétitions, 10 boucles chacune)
#: Méthode: scott_update_method_static
#: 136 ms ± 3.19 ms par boucle (moyenne ± écart type de 7 répétitions, 10 boucles chacune)
#: Méthode: havok_method
#: 103 ms ± 1.31 ms par boucle (moyenne ± écart type de 7 répétitions, 10 boucles chacune)
Conclusion
╔═══════════════════════════╦═══════╦═════════════════════════════╗
║ ║ ║ Meilleur Temps par Boucle ║
║ Algorithme ║ Par ╠══════════════╦══════════════╣
║ ║ ║ small_tests ║ large_tests ║
╠═══════════════════════════╬═══════╬══════════════╬══════════════╣
║ functools reduce ║ Havok ║ 3.1 µs ║ 103,000 µs ║
║ defaultdict sum ║ NPE ║ 3.2 µs ║ 119,000 µs ║
║ Boucle Counter().update ║ Scott ║ 9.1 µs ║ 123,000 µs ║
║ Counter().update statique ║ Scott ║ 14.4 µs ║ 136,000 µs ║
║ set unions sans sum() ║ georg ║ 4.6 µs ║ 280,000 µs ║
║ set unions avec sum() ║ georg ║ 7.8 µs ║ 347,000 µs ║
║ Counter() sans sum() ║ Scott ║ 18.9 µs ║ 289,000 µs ║
║ Counter() avec sum() ║ Scott ║ 24.9 µs ║ 324,000 µs ║
╚═══════════════════════════╩═══════╩══════════════╩══════════════╝
Important. Les résultats peuvent varier selon votre machine.