Aucune des solutions proposées jusqu'à présent ne résout le cas général de l'intersection de N dictionnaires.
Donc, si vous voulez gérer l'intersection de N
des dictionnaires arbitraires :
from functools import reduce
def dict_intersection(*dict_list):
return reduce(lambda a,b: dict(a.items() & b.items()), dict_list)
a = {k:k for k in range(0,5)} # {0: 0, 1: 1, 2: 2, 3: 3, 4: 4}
b = {k:k for k in range(2,7)} # {2: 2, 3: 3, 4: 4, 5: 5, 6: 6}
c = {k:k for k in range(3,8)} # {3: 3, 4: 4, 5: 5, 6: 6, 7: 7}
dict_intersection(a,b,c) # {3:3, 4:4}
# or if you have a list of dicts
dicts = [{k:k for k in range(0+n,5+n)} for n in (0,2,3)] # == [a,b,c]
dict_intersection(*dicts) # {3:3, 4:4}
Utilisation de functools.reduce
permet de réaliser l'opération en une seule itération sur la liste des dictionnaires au lieu des multiples boucles de certaines solutions. Elle n'exécute pas non plus d'instructions conditionnelles supplémentaires.
Compromis
Changer dict_intersection_v1
a dict_intersection_v2
nous pouvons voir qu'il fonctionne plus rapidement pour une plus grande liste de dictionnaires et/ou de dictionnaires (concevoir une expérience appropriée pour tester quel est le facteur le plus important est en dehors de la portée de cette solution). Ce gain de performance est dû à la réduction du nombre d'instanciations de dictionnaires.
def dict_intersection_v1(*dict_list):
return reduce(lambda a,b: dict(a.items() & b.items()), dict_list)
def dict_intersection_v2(*dict_list):
return dict(reduce(lambda a,b: a & b, (d.items() for d in dict_list)))
dict_lst1 = [{k:k for k in range(0+n,5+n)} for n in (0,2,3)] # = [a,b,c]
dict_lst2 = [{k:k for k in range(0,50,n)} for n in range(1,5)]]
dict_lst3 = [{k:k for k in range(0,500,n)} for n in range(40)]
dict_lst4 = [{k:k for k in range(0+n,500+n)} for n in range(400)]
liste de dictées
nombre de paires de kv
dict_intersection_v1
dict_intersection_v2
différence relative
1
15
808 ns ± 4,31 ns par boucle (moyenne ± écart-type de 7 exécutions, 1000000 boucles chacune)
821 ns ± 0,785 ns par boucle (moyenne ± écart-type de 7 exécutions, 1000000 boucles chacune)
+ 1.6%
2
105
3,14 µs ± 11,9 ns par boucle (moyenne ± écart-type de 7 exécutions, 100000 boucles chacune)
2,38 µs ± 5,76 ns par boucle (moyenne ± écart-type de 7 exécutions, 100000 boucles chacune)
-24.2%
3
2155
36,9 µs ± 61,9 ns par boucle (moyenne ± écart-type de 7 exécutions, 10000 boucles chacune)
25,1 µs ± 131 ns par boucle (moyenne ± écart-type de 7 exécutions, 10000 boucles chacune)
-32.0%
4
200_000
9,08 ms ± 22 µs par boucle (moyenne ± écart-type de 7 passages, 100 boucles chacun)
4,88 ms ± 5,31 µs par boucle (moyenne ± écart-type de 7 passages, 100 boucles chacun)
-46.3%
La régression pour le résultat dict_lst1
est principalement dû à la différence de surcharge entre la création d'un dictionnaire après chaque intersection et la surcharge due à dict.items()
dans le générateur (et le surcoût général des appels de fonctions de Python).
NB : J'ai fait un test en utilisant la liste pré-calculée de dict.items()
pour le pour un dictionnaire au lieu de la v2's qui construit le générateur à la volée.
J'ai testé les deux passages dans la liste pré-calculée en dehors des timings et dans les timings, et, bien que ce soit statistiquement significatif, c'est moins de 30 μs et 10 μs respectivement. Si vous essayez d'obtenir ces gains en regardant un langage différent ou Cython pourrait être mieux.