Je veux vérifier si aucun des éléments d'une liste sont présents dans une autre liste. Je peux le faire simplement avec le code ci-dessous, mais je soupçonne qu’il pourrait y avoir une fonction de bibliothèque pour le faire. Sinon, existe-t-il une méthode plus Pythonic pour obtenir le même résultat?.
In [78]: a = [1, 2, 3, 4, 5]
In [79]: b = [8, 7, 6]
In [80]: c = [8, 7, 6, 5]
In [81]: def lists_overlap(a, b):
....: for i in a:
....: if i in b:
....: return True
....: return False
....:
In [82]: lists_overlap(a, b)
Out[82]: False
In [83]: lists_overlap(a, c)
Out[83]: True
In [84]: def lists_overlap2(a, b):
....: return len(set(a).intersection(set(b))) > 0
....:
Réponse courte : utilisez not set(a).isdisjoint(b)
, c'est généralement le plus rapide.
Il existe quatre méthodes courantes pour vérifier si deux listes a
et b
partagent des éléments. La première option consiste à convertir les deux en ensembles et à vérifier leur intersection, comme suit:
bool(set(a) & set(b))
Comme les ensembles sont stockés à l'aide d'une table de hachage en Python, leur recherche est O(1)
(voir ici pour plus d'informations sur la complexité des opérateurs en Python). Théoriquement, il s'agit de O(n+m)
en moyenne pour n
et m
objets dans les listes a
et b
. Mais 1) il doit d’abord créer des ensembles à partir des listes, ce qui peut prendre un temps non négligeable, et 2) cela suppose que les collisions de hachage soient rares parmi vos données.
La deuxième façon de faire consiste à utiliser une expression génératrice effectuant une itération sur les listes, telle que:
any(i in a for i in b)
Cela permet de rechercher sur place, de sorte qu'aucune nouvelle mémoire n'est allouée pour les variables intermédiaires. Il sauve également sur la première découverte. Mais l'opérateur in
est toujours O(n)
sur les listes (voir ici ).
Une autre option proposée est un hybride pour parcourir l'une des listes, convertir l'autre en un ensemble et tester l'appartenance à cet ensemble, comme suit:
a = set(a); any(i in a for i in b)
Une quatrième approche consiste à tirer parti de la méthode isdisjoint()
des ensembles (figés) (voir here ), par exemple:
not set(a).isdisjoint(b)
Si les éléments que vous recherchez se trouvent au début d'un tableau (par exemple, il est trié), l'expression du générateur est privilégiée, car la méthode d'intersection d'ensembles doit allouer une nouvelle mémoire pour les variables intermédiaires:
from timeit import timeit
>>> timeit('bool(set(a) & set(b))', setup="a=list(range(1000));b=list(range(1000))", number=100000)
26.077727576019242
>>> timeit('any(i in a for i in b)', setup="a=list(range(1000));b=list(range(1000))", number=100000)
0.16220548999262974
Voici un graphique du temps d'exécution de cet exemple en fonction de la taille de la liste:
Notez que les deux axes sont logarithmiques. Ceci représente le meilleur cas pour l'expression du générateur. Comme on peut le constater, la méthode isdisjoint()
est préférable pour les très petites tailles de liste, alors que l'expression du générateur est préférable pour les plus grandes.
Par contre, comme la recherche commence au début de l'expression hybride et génératrice, si l'élément partagé se trouve systématiquement à la fin du tableau (ou que les deux listes ne partagent aucune valeur), les approches d'intersection disjoint et définie sont alors beaucoup plus rapide que l'expression du générateur et l'approche hybride.
>>> timeit('any(i in a for i in b)', setup="a=list(range(1000));b=[x+998 for x in range(999,0,-1)]", number=1000))
13.739536046981812
>>> timeit('bool(set(a) & set(b))', setup="a=list(range(1000));b=[x+998 for x in range(999,0,-1)]", number=1000))
0.08102107048034668
Il est intéressant de noter que l'expression du générateur est beaucoup plus lente pour les listes de grande taille. Ce n'est que pour 1000 répétitions, au lieu de 100000 pour la figure précédente. Cette configuration convient également bien lorsqu'aucun élément n'est partagé et constitue le meilleur cas pour les approches d'intersection disjointes et définies.
Voici deux analyses utilisant des nombres aléatoires (au lieu de modifier la configuration pour privilégier une technique ou une autre):
Forte chance de partage: les éléments sont tirés au hasard dans [1, 2*len(a)]
. Faible chance de partage: les éléments sont tirés au hasard dans [1, 1000*len(a)]
.
Jusqu'à présent, cette analyse supposait que les deux listes avaient la même taille. Dans le cas de deux listes de tailles différentes, par exemple, a
est beaucoup plus petit, isdisjoint()
est toujours plus rapide:
Assurez-vous que la liste a
est la plus petite, sinon les performances diminuent. Dans cette expérience, la taille de la liste a
a été définie constante sur 5
.
En résumé:
not set(a).isdisjoint(b)
est toujours le plus rapide.any(i in a for i in b)
est la plus rapide des grandes tailles de liste;not set(a).isdisjoint(b)
, qui est toujours plus rapide que bool(set(a) & set(b))
.a = set(a); any(i in a for i in b)
est généralement plus lent que les autres méthodes.Dans la plupart des cas, l'utilisation de la méthode isdisjoint()
est la meilleure approche, car l'expression du générateur prend beaucoup plus de temps à s'exécuter, car elle est très inefficace lorsqu'aucun élément n'est partagé.
def lists_overlap3(a, b):
return bool(set(a) & set(b))
Remarque: ce qui précède suppose que vous voulez un booléen comme réponse. Si tout ce dont vous avez besoin est une expression à utiliser dans une instruction if
, utilisez simplement if set(a) & set(b):
def lists_overlap(a, b):
sb = set(b)
return any(el in sb for el in a)
Ceci est asymptotiquement optimal (pire des cas, O (n + m)), et pourrait être meilleur que l’approche par intersection en raison du court-circuit de any
.
Par exemple.:
lists_overlap([3,4,5], [1,2,3])
retournera True dès qu'il arrivera à 3 in sb
EDIT: Autre variante (merci à Dave Kirby):
def lists_overlap(a, b):
sb = set(b)
return any(itertools.imap(sb.__contains__, a))
Cela repose sur l'itérateur de imap
, qui est implémenté en C plutôt que par un générateur de compréhension. Il utilise également sb.__contains__
comme fonction de mappage. Je ne sais pas quelle différence de performance cela fait. Il sera toujours en court-circuit.
Vous pouvez aussi utiliser any
avec une compréhension de liste:
any([item in a for item in b])
Dans python 2.6 ou version ultérieure, vous pouvez effectuer les opérations suivantes:
return not frozenset(a).isdisjoint(frozenset(b))
Vous pouvez utiliser la fonction quelconque any/w une expression génératrice:
def list_overlap(a,b):
return any(i for i in a if i in b)
Comme John et Lie l'ont souligné, cela donne des résultats incorrects lorsque, pour chaque i partagé par les deux listes, bool (i) == False. CA devrait etre:
return any(i in b for i in a)
J'en jetterai un autre avec un style de programmation fonctionnel:
any(map(lambda x: x in a, b))
Explication:
map(lambda x: x in a, b)
retourne une liste de booléens où des éléments de b
se trouvent dans a
. Cette liste est ensuite transmise à any
, qui renvoie simplement True
si des éléments sont True
.
Cette question est assez ancienne, mais j'ai remarqué que pendant que les gens discutaient entre ensembles et listes, personne ne songeait à les utiliser ensemble. Suivant l'exemple de Soravux,
Le pire des cas pour les listes:
>>> timeit('bool(set(a) & set(b))', setup="a=list(range(10000)); b=[x+9999 for x in range(10000)]", number=100000)
100.91506409645081
>>> timeit('any(i in a for i in b)', setup="a=list(range(10000)); b=[x+9999 for x in range(10000)]", number=100000)
19.746716022491455
>>> timeit('any(i in a for i in b)', setup="a= set(range(10000)); b=[x+9999 for x in range(10000)]", number=100000)
0.092626094818115234
Et le meilleur des cas pour les listes:
>>> timeit('bool(set(a) & set(b))', setup="a=list(range(10000)); b=list(range(10000))", number=100000)
154.69790101051331
>>> timeit('any(i in a for i in b)', setup="a=list(range(10000)); b=list(range(10000))", number=100000)
0.082653045654296875
>>> timeit('any(i in a for i in b)', setup="a= set(range(10000)); b=list(range(10000))", number=100000)
0.08434605598449707
Donc, même plus rapidement que parcourir deux listes est de parcourir une liste pour voir si elle est dans un ensemble, ce qui est logique dans la mesure où vérifier si un nombre est dans un ensemble prend un temps constant la liste.
Donc, ma conclusion est que parcourir une liste et vérifier si elle est dans un ensemble.
si vous ne vous souciez pas de ce que l'élément chevauchant pourrait être, vous pouvez simplement vérifier le len
de la liste combinée par rapport aux listes combinées en tant qu'ensemble. S'il y a des éléments qui se chevauchent, l'ensemble sera plus court:
len(set(a+b+c))==len(a+b+c)
renvoie True, s'il n'y a pas de chevauchement.