Supprimer tous les éléments qui apparaissent dans une liste d'une autre


367

Disons que j'ai deux listes, l1et l2. Je veux effectuer l1 - l2, qui renvoie tous les éléments de l1pas dans l2.

Je peux penser à une approche en boucle naïve pour faire cela, mais cela va être vraiment inefficace. Qu'est-ce qu'un moyen pythonique et efficace de le faire?

Par exemple, si je dois l1 = [1,2,6,8] and l2 = [2,3,5,8], l1 - l2devrait revenir[1,6]


12
Juste un conseil: PEP8 déclare que le "L" minuscule ne devrait pas être utilisé car il ressemble trop à un 1.
spelchekr

2
Je suis d'accord. J'ai lu toute cette question et les réponses en me demandant pourquoi les gens continuaient à utiliser onze et douze. Ce n'est que lorsque j'ai lu le commentaire de @spelchekr que cela avait du sens.
robline


@JimG. La trame de données et la liste ne sont pas la même chose.
réduction de l'activité du

Réponses:


492

Python a une fonctionnalité de langage appelée List Comprehensions qui est parfaitement adaptée pour rendre ce genre de chose extrêmement facile. L'instruction suivante fait exactement ce que vous voulez et stocke le résultat dans l3:

l3 = [x for x in l1 if x not in l2]

l3contiendra [1, 6].


8
Très pythonique; Je l'aime! Quelle est son efficacité?
fandom

2
Je pense que c'est assez efficace, et cela a l'avantage d'être extrêmement lisible et clair quant à ce que vous essayez d'accomplir. Je suis tombé sur un article de blog que vous pourriez trouver intéressant sur l'efficacité: blog.cdleary.com/2010/04/efficiency-of-list-comprehensions
Donut

6
@fandom: la compréhension de la liste elle-même est assez efficace (bien qu'une compréhension de générateur puisse être plus efficace en ne dupliquant pas les éléments en mémoire), mais l' inopérateur n'est pas aussi efficace sur une liste. insur une liste est O (n), tandis que insur un ensemble est O (1). Cependant, jusqu'à ce que vous atteigniez des milliers d'éléments ou plus, vous ne remarquerez probablement pas la différence.
Daniel Pryden

1
l3 = [x for x in l1 if x not in set(l2)]? Je suis sûr que set(l2)serait appelé plus d'une fois.
Danosaure

5
Vous pouvez également simplement définir l2s = set(l2)puis dire l3 = [x for x in l1 if x not in l2s]. Un peu plus facile.
spelchekr

149

Une façon consiste à utiliser des ensembles:

>>> set([1,2,6,8]) - set([2,3,5,8])
set([1, 6])

58
Cela supprimera également les doublons l1, ce qui peut être un effet secondaire indésirable.
kindall

37
..et perdre l'ordre des éléments (si l'ordre est important).
Danosaure

3
Je veux juste ajouter que je l' ai chronométré ce rapport la réponse acceptée et il a été plus performant par un facteur d'environ 3: timeit.timeit('a = [1,2,3,4]; b = [1,3]; c = [i for i in a if a not in b]', number=100000) -> 0.12061533199999985 timeit.timeit('a = {1,2,3,4}; b = {1,3}; c = a - b', number=100000) -> 0.04106225999998969. Donc, si les performances sont un facteur important, cette réponse peut être plus appropriée (et aussi si vous ne vous souciez pas des doublons ou de la commande)
wfgeo

38

Comme alternative, vous pouvez également utiliser filteravec l'expression lambda pour obtenir le résultat souhaité. Par exemple:

>>> l1 = [1,2,6,8]
>>> l2 = set([2,3,5,8])

#     v  `filter` returns the a iterator object. Here I'm type-casting 
#     v  it to `list` in order to display the resultant value
>>> list(filter(lambda x: x not in l2, l1))
[1, 6]

Comparaison des performances

Ici, je compare les performances de toutes les réponses mentionnées ici. Comme prévu, l' set opération basée sur Arkku est la plus rapide.

  • Arkku's Set Difference - First (0,124 usec par boucle)

    mquadri$ python -m timeit -s "l1 = set([1,2,6,8]); l2 = set([2,3,5,8]);" "l1 - l2"
    10000000 loops, best of 3: 0.124 usec per loop
  • Compréhension de liste de Daniel Pryden avec setrecherche - Deuxième (0,302 usec par boucle)

    mquadri$ python -m timeit -s "l1 = [1,2,6,8]; l2 = set([2,3,5,8]);" "[x for x in l1 if x not in l2]"
    1000000 loops, best of 3: 0.302 usec per loop
  • Compréhension de la liste de Donut sur liste simple - Troisième (0,552 usec par boucle)

    mquadri$ python -m timeit -s "l1 = [1,2,6,8]; l2 = [2,3,5,8];" "[x for x in l1 if x not in l2]"
    1000000 loops, best of 3: 0.552 usec per loop
  • Utilisation de Moinuddin Quadrifilter - Quatrième (0,972 usec par boucle)

    mquadri$ python -m timeit -s "l1 = [1,2,6,8]; l2 = set([2,3,5,8]);" "filter(lambda x: x not in l2, l1)"
    1000000 loops, best of 3: 0.972 usec per loop
  • Akshay Hazari utilise une combinaison de reduce+filter - Cinquième (3,97 usec par boucle)

    mquadri$ python -m timeit "l1 = [1,2,6,8]; l2 = [2,3,5,8];" "reduce(lambda x,y : filter(lambda z: z!=y,x) ,l1,l2)"
    100000 loops, best of 3: 3.97 usec per loop

PS: set ne conserve pas l'ordre et supprime les éléments en double de la liste. Par conséquent, n'utilisez pas la différence définie si vous en avez besoin.


32

En développant la réponse de Donut et les autres réponses ici, vous pouvez obtenir des résultats encore meilleurs en utilisant une compréhension de générateur au lieu d'une compréhension de liste et en utilisant une setstructure de données (puisque lein opérateur est O (n) sur une liste mais O (1) sur un ensemble).

Voici donc une fonction qui fonctionnerait pour vous:

def filter_list(full_list, excludes):
    s = set(excludes)
    return (x for x in full_list if x not in s)

Le résultat sera un itérable qui récupérera paresseusement la liste filtrée. Si vous avez besoin d'un véritable objet liste (par exemple, si vous devez faire un len()résultat), vous pouvez facilement créer une liste comme ceci:

filtered_list = list(filter_list(full_list, excludes))

29

Utilisez le type d'ensemble Python. Ce serait le plus pythonique. :)

De plus, comme il est natif, il devrait également être la méthode la plus optimisée.

Voir:

http://docs.python.org/library/stdtypes.html#set

http://docs.python.org/library/sets.htm (pour les anciens python)

# Using Python 2.7 set literal format.
# Otherwise, use: l1 = set([1,2,6,8])
#
l1 = {1,2,6,8}
l2 = {2,3,5,8}
l3 = l1 - l2

5
Lors de l'utilisation d'ensembles, il convient de noter que la sortie de est ordonnée, c'est-à-dire que {1,3,2} devient {1,2,3} et {"A", "C", "B"} devient {"A", "B", "C"} et vous ne voudrez peut-être pas avoir cela.
Pablo Reyes

2
cette méthode ne fonctionnera pas si la liste l1comprend des éléments répétés.
jdhao

10

utilisez Set Comprehensions {x pour x dans l2} ou set (l2) pour obtenir l'ensemble, puis utilisez List Comprehensions pour obtenir la liste

l2set = set(l2)
l3 = [x for x in l1 if x not in l2set]

code de test de référence:

import time

l1 = list(range(1000*10 * 3))
l2 = list(range(1000*10 * 2))

l2set = {x for x in l2}

tic = time.time()
l3 = [x for x in l1 if x not in l2set]
toc = time.time()
diffset = toc-tic
print(diffset)

tic = time.time()
l3 = [x for x in l1 if x not in l2]
toc = time.time()
difflist = toc-tic
print(difflist)

print("speedup %fx"%(difflist/diffset))

résultat du test de référence:

0.0015058517456054688
3.968189239501953
speedup 2635.179227x    

1
l2set = set( l2 )au lieu del2set = { x for x in l2 }
cz

1
Belle soultion! Mais il faut garder à l'esprit que cela ne fonctionne qu'avec des objets lavables.
Eerik Sven Puudist

7

Solution alternative:

reduce(lambda x,y : filter(lambda z: z!=y,x) ,[2,3,5,8],[1,2,6,8])

2
Y a-t-il un avantage à utiliser cette méthode? Il semble que ce soit plus complexe et plus difficile à lire sans grand bénéfice.
skrrgwasme

Cela peut sembler complexe. La réduction est très flexible et peut être utilisée à de nombreuses fins. Il est connu comme pli. réduire est en fait foldl. Supposons que vous souhaitiez y ajouter des éléments plus complexes, alors cela sera possible dans cette fonction, mais la compréhension de la liste, qui est la meilleure réponse sélectionnée, ne vous donnera qu'une sortie du même type, c'est-à-dire une liste et probablement de la même longueur avec des plis. modifiez également le type de sortie. en.wikipedia.org/wiki/Fold_%28higher-order_function%29 . Cette solution est n * m ou moins complexe. D'autres peuvent être meilleurs ou non.
Akshay Hazari

1
réduire (fonction, liste, accumulateur initial (qui peut être de tout type))
Akshay Hazari
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.