Réponse courte : utilisation not set(a).isdisjoint(b)
, c'est généralement le plus rapide.
Il existe quatre méthodes courantes pour tester si deux listes a
et b
partager des éléments. La première option consiste à convertir les deux en ensembles et à vérifier leur intersection, en tant que telle:
bool(set(a) & set(b))
Étant donné que les ensembles sont stockés à l'aide d'une table de hachage en Python, leur recherche estO(1)
(voir ici pour plus d'informations sur la complexité des opérateurs en Python). Théoriquement, il s'agit O(n+m)
en moyenne de pour n
et d' 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) il suppose que les collisions de hachage sont rares parmi vos données.
La deuxième façon de le faire consiste à utiliser une expression de générateur effectuant une itération sur les listes, telle que:
any(i in a for i in b)
Cela permet de rechercher sur place, donc aucune nouvelle mémoire n'est allouée pour les variables intermédiaires. Il renonce également à la première découverte. Mais l' in
opérateur est toujours O(n)
sur des listes (voir ici ).
Une autre option proposée est un hybride pour parcourir l'une des listes, convertir l'autre dans un ensemble et tester l'appartenance à cet ensemble, comme ceci:
a = set(a); any(i in a for i in b)
Une quatrième approche consiste à tirer parti de la isdisjoint()
méthode des ensembles (figés) (voir ici ), par exemple:
not set(a).isdisjoint(b)
Si les éléments que vous recherchez sont proches du 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 des 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. Cela représente le meilleur cas pour l'expression du générateur. Comme on peut le voir, la isdisjoint()
méthode est meilleure pour les très petites tailles de liste, tandis que l'expression du générateur est meilleure pour les plus grandes tailles de liste.
Par contre, comme la recherche commence par le début de l'expression hybride et génératrice, si l'élément partagé est systématiquement à la fin du tableau (ou les deux listes ne partagent aucune valeur), les approches d'intersection disjointes et d'ensemble 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 plus grande taille. Ce n'est que pour 1000 répétitions, au lieu des 100000 pour la figure précédente. Cette configuration se rapproche également bien lorsque aucun élément n'est partagé, et est le meilleur cas pour les approches d'intersection disjointes et définies.
Voici deux analyses utilisant des nombres aléatoires (au lieu de truquer la configuration pour favoriser une technique ou une autre):
Forte chance de partage: les éléments sont tirés au hasard [1, 2*len(a)]
. Faible chance de partage: les éléments sont tirés au hasard [1, 1000*len(a)]
.
Jusqu'à présent, cette analyse supposait que les deux listes étaient de la même taille. Dans le cas de deux listes de tailles différentes, par exemple a
est beaucoup plus petite, isdisjoint()
c'est toujours plus rapide:
Assurez-vous que la a
liste est la plus petite, sinon les performances diminuent. Dans cette expérience, la a
taille de la liste a été définie constante sur 5
.
En résumé:
- Si les listes sont très petites (<10 éléments),
not set(a).isdisjoint(b)
c'est toujours le plus rapide.
- Si les éléments des listes sont triés ou ont une structure régulière dont vous pouvez tirer parti, l'expression du générateur
any(i in a for i in b)
est la plus rapide sur les grandes tailles de liste;
- Testez l'intersection définie avec
not set(a).isdisjoint(b)
, qui est toujours plus rapide que bool(set(a) & set(b))
.
- L'hybride "itérer dans la liste, tester sur le plateau"
a = set(a); any(i in a for i in b)
est généralement plus lent que les autres méthodes.
- L'expression du générateur et l'hybride sont beaucoup plus lents que les deux autres approches lorsqu'il s'agit de listes sans partage d'éléments.
Dans la plupart des cas, l'utilisation de la isdisjoint()
méthode est la meilleure approche car l'expression du générateur prendra beaucoup plus de temps à s'exécuter, car elle est très inefficace lorsqu'aucun élément n'est partagé.
len(...) > 0
carbool(set([]))
donne False. Et bien sûr, si vous gardiez vos listes sous forme d'ensembles pour commencer, vous auriez une surcharge de création de sauvegarde.