Python trouve des éléments dans une liste qui ne sont pas dans l’autre [dupliquer]

137

J'ai besoin de comparer deux listes afin de créer une nouvelle liste d'éléments spécifiques trouvés dans une liste mais pas dans l'autre. Par exemple:

main_list=[]
list_1=["a", "b", "c", "d", "e"]
list_2=["a", "f", "c", "m"] 

Je veux parcourir list_1 et ajouter à main_list tous les éléments de list_2 qui ne se trouvent pas dans list_1.

Le résultat devrait être:

main_list=["f", "m"]

Comment puis-je le faire avec python?

CosimoCD
la source
2
Recherchez-vous des éléments dans list_2qui n'apparaissent nulle part dans list_1ou des éléments dans list_2qui ne sont pas présents dans le même index dans list_1?
Patrick Haugh

Réponses:

98

TL; DR:
SOLUTION (1)

import numpy as np
main_list = np.setdiff1d(list_2,list_1)
# yields the elements in `list_2` that are NOT in `list_1`

SOLUTION (2) Vous voulez une liste triée

def setdiff_sorted(array1,array2,assume_unique=False):
    ans = np.setdiff1d(array1,array2,assume_unique).tolist()
    if assume_unique:
        return sorted(ans)
    return ans
main_list = setdiff_sorted(list_2,list_1)




EXPLICATION:
(1) Vous pouvez utiliser de NumPy setdiff1d( array1, array2, assume_unique= False).

assume_uniquedemande à l'utilisateur SI les tableaux SONT DÉJÀ UNIQUES.
Si False, alors les éléments uniques sont déterminés en premier.
Si True, la fonction supposera que les éléments sont déjà uniques ET la fonction ignorera la détermination des éléments uniques.

Cela donne les valeurs uniques dans array1qui ne sont pas dans array2. assume_uniqueest Falsepar défaut.

Si vous êtes concerné par les éléments uniques (en fonction de la réponse de Chinny84 ), utilisez simplement (où assume_unique=False=> la valeur par défaut):

import numpy as np
list_1 = ["a", "b", "c", "d", "e"]
list_2 = ["a", "f", "c", "m"] 
main_list = np.setdiff1d(list_2,list_1)
# yields the elements in `list_2` that are NOT in `list_1`


(2) Pour ceux qui souhaitent que les réponses soient triées, j'ai créé une fonction personnalisée:

import numpy as np
def setdiff_sorted(array1,array2,assume_unique=False):
    ans = np.setdiff1d(array1,array2,assume_unique).tolist()
    if assume_unique:
        return sorted(ans)
    return ans

Pour obtenir la réponse, exécutez:

main_list = setdiff_sorted(list_2,list_1)

REMARQUES SUPPLÉMENTAIRES:
(a) La solution 2 (fonction personnalisée setdiff_sorted) renvoie une liste (comparée à un tableau dans la solution 1).

(b) Si vous n'êtes pas sûr que les éléments soient uniques, utilisez simplement le paramètre par défaut de NumPy's setdiff1ddans les deux solutions A et B. Qu'est-ce qui peut être un exemple de complication? Voir note (c).

(c) Les choses seront différentes si l'une des deux listes n'est pas unique.
Say list_2est pas unique: list2 = ["a", "f", "c", "m", "m"]. Conserver list1tel quel : list_1 = ["a", "b", "c", "d", "e"]
Définition de la valeur par défaut des assume_uniquerendements ["f", "m"](dans les deux solutions). CEPENDANT, si vous définissez assume_unique=True, les deux solutions donnent ["f", "m", "m"]. Pourquoi? C'est parce que l'utilisateur a supposé que les éléments sont uniques). Par conséquent, il vaut mieux garderassume_uniqueà sa valeur par défaut. Notez que les deux réponses sont triées.

jcoderepo
la source
Si vos listes sont déjà ordonnées, cela renverra également une liste ordonnée. La solution native de conversion en ensembles puis d'obtenir la différence (solutions présentées ci-dessous) renvoie une liste non ordonnée qui peut rendre plus difficile l'examen visuel de vos résultats.
Doubledown
1
Salut, @Doubledown! Votre préoccupation a été traitée dans le message modifié. J'espère que cela t'aides!
jcoderepo
183

Vous pouvez utiliser des ensembles:

main_list = list(set(list_2) - set(list_1))

Production:

>>> list_1=["a", "b", "c", "d", "e"]
>>> list_2=["a", "f", "c", "m"]
>>> set(list_2) - set(list_1)
set(['m', 'f'])
>>> list(set(list_2) - set(list_1))
['m', 'f']

Par commentaire de @JonClements, voici une version plus ordonnée:

>>> list_1=["a", "b", "c", "d", "e"]
>>> list_2=["a", "f", "c", "m"]
>>> list(set(list_2).difference(list_1))
['m', 'f']
nrlakin
la source
2
C'est bien si nous ne nous soucions que des uniqueéléments, mais que se passe-t-il si nous en avons plusieurs, m'spar exemple, cela ne les prendrait pas.
Chinny84
C'est vrai. J'ai supposé que l'affiche recherchait des éléments uniques. Je suppose que cela dépend de ce qu'il entend par «spécifique».
nrlakin
En effet ps je n'ai pas voté contre votre réponse, surtout pour une question originale peu claire.
Chinny84
13
Vous pourriez écrire ceci comme list(set(list_2).difference(list_1))cela évite la setconversion explicite ...
Jon Clements
Pas de soucis! Merci @leaf pour l'aide au formatage.
nrlakin
61

Je ne sais pas pourquoi les explications ci-dessus sont si compliquées lorsque vous avez des méthodes natives disponibles:

main_list = list(set(list_2)-set(list_1))
A.Kot
la source
6
La préservation de l'ordre pourrait en être la raison
Keith
57

Utilisez une compréhension de liste comme celle-ci:

main_list = [item for item in list_2 if item not in list_1]

Production:

>>> list_1 = ["a", "b", "c", "d", "e"]
>>> list_2 = ["a", "f", "c", "m"] 
>>> 
>>> main_list = [item for item in list_2 if item not in list_1]
>>> main_list
['f', 'm']

Éditer:

Comme mentionné dans les commentaires ci-dessous, avec de grandes listes, ce qui précède n'est pas la solution idéale. Lorsque c'est le cas, une meilleure option serait de convertir list_1en une setpremière:

set_1 = set(list_1)  # this reduces the lookup time from O(n) to O(1)
main_list = [item for item in list_2 if item not in set_1]
ettanany
la source
3
Note: Pour plus list_1, vous voudriez preconvert à un set/ frozenset, par exemple set_1 = frozenset(list_1), puis main_list = [item for item in list_2 if item not in set_1], ce qui réduit le temps de vérification de O(n)par article à (environ) O(1).
ShadowRanger
@ettanany S'il vous plaît méfiez-vous si vous essayez la solution comme ettanany a publié. J'ai essayé la solution d'ettanany telle quelle et elle est en effet très lente pour une liste plus longue. Pouvez-vous mettre à jour la réponse pour incorporer la suggestion de shadowranger?
Doubledown le
Serait-il possible d'obtenir l'index au lieu de la chaîne?
JareBear le
@JareBear Vous pouvez utiliser enumerate()pour cela:[index for (index, item) in enumerate(list_2) if item not in list_1]
ettanany
Merci beaucoup @ ettanany !! Je vais mettre en œuvre cela dès que possible, je l'avais fait. Mais votre code semble tellement plus propre.
JareBear
6

Si vous voulez une solution à une ligne (en ignorant les importations) qui ne nécessite que du O(max(n, m))travail pour les entrées de longueur net m, pas du O(n * m)travail, vous pouvez le faire avec le itertoolsmodule :

from itertools import filterfalse

main_list = list(filterfalse(set(list_1).__contains__, list_2))

Cela tire parti des fonctions fonctionnelles prenant une fonction de rappel lors de la construction, lui permettant de créer le rappel une fois et de le réutiliser pour chaque élément sans avoir besoin de le stocker quelque part (car le filterfalsestocke en interne); les compréhensions de listes et les expressions génératrices peuvent le faire, mais c'est moche. †

Cela donne les mêmes résultats en une seule ligne que:

main_list = [x for x in list_2 if x not in list_1]

à la vitesse de:

set_1 = set(list_1)
main_list = [x for x in list_2 if x not in set_1]

Bien sûr, si les comparaisons sont destinées à être positionnelles, alors:

list_1 = [1, 2, 3]
list_2 = [2, 3, 4]

devrait produire:

main_list = [2, 3, 4]

(car aucune valeur dans list_2n'a une correspondance au même index dans list_1), vous devriez certainement aller avec la réponse de Patrick , qui n'implique aucun lists ou sets temporaire (même avec sets étant à peu près O(1), ils ont un facteur «constant» par vérification plus élevé que la simple égalité vérifie) et implique du O(min(n, m))travail, moins que toute autre réponse, et si votre problème est sensible à la position, est la seule solution correcte lorsque des éléments correspondants apparaissent à des décalages incompatibles.

†: La façon de faire la même chose avec une compréhension de liste qu'une seule ligne serait d'abuser de la boucle imbriquée pour créer et mettre en cache des valeurs dans la boucle "la plus externe", par exemple:

main_list = [x for set_1 in (set(list_1),) for x in list_2 if x not in set_1]

ce qui donne également un avantage mineur en termes de performances sur Python 3 (car il set_1est désormais défini localement dans le code de compréhension, plutôt que de rechercher à partir de la portée imbriquée pour chaque vérification; sur Python 2, cela n'a pas d'importance, car Python 2 n'utilise pas de fermetures pour list comprehensions; ils opèrent dans la même portée dans laquelle ils sont utilisés).

ShadowRanger
la source
4
main_list=[]
list_1=["a", "b", "c", "d", "e"]
list_2=["a", "f", "c", "m"]

for i in list_2:
    if i not in list_1:
        main_list.append(i)

print(main_list)

production:

['f', 'm']
Inconnu
la source
Comme la solution équivalente basée sur la compréhension de liste , elle sera lente si elle list_1est grande et list_2est de taille non triviale, car elle implique des len(list_2) O(n)analyses de list_1, la création O(n * m)(où net msont les longueurs de list_2et list_1respectivement). Si vous convertissez list_1en set/ frozensetà l'avant, les contrôles de contenu peuvent être effectués O(1), ce qui rend le travail total O(n)sur la longueur de list_2(techniquement O(max(n, m)), puisque vous O(m)travaillez pour faire le set).
ShadowRanger
1

Je voudrais ziples listes ensemble pour les comparer élément par élément.

main_list = [b for a, b in zip(list1, list2) if a!= b]
Patrick Haugh
la source
Si l'OP veut comparer élément par élément (ce n'est pas clair, l'exemple pourrait aller dans les deux sens), c'est beaucoup plus efficace que les autres réponses, car il s'agit d'un seul passage bon marché sur les deux listavec un seul nouveau en listcours de construction, pas de temporaires supplémentaires , pas de contrôles de confinement coûteux, etc.
ShadowRanger
1
@ShadowRanger cela ne fonctionnerait que pour la différence élémentaire, ce qui est un point clé
ford prefect
@fordprefect: Ouais. Ma propre réponse couvre les différences indépendantes de la position.
ShadowRanger
1

J'ai utilisé deux méthodes et j'ai trouvé une méthode plus utile qu'une autre. Voici ma réponse:

Mes données d'entrée:

crkmod_mpp = ['M13','M18','M19','M24']
testmod_mpp = ['M13','M14','M15','M16','M17','M18','M19','M20','M21','M22','M23','M24']

Méthode 1: np.setdiff1dJ'aime cette approche par rapport à une autre car elle préserve la position

test= list(np.setdiff1d(testmod_mpp,crkmod_mpp))
print(test)
['M15', 'M16', 'M22', 'M23', 'M20', 'M14', 'M17', 'M21']

Méthode 2: bien qu'elle donne la même réponse que dans la méthode 1 mais perturbe l'ordre

test = list(set(testmod_mpp).difference(set(crkmod_mpp)))
print(test)
['POA23', 'POA15', 'POA17', 'POA16', 'POA22', 'POA18', 'POA24', 'POA21']

Method1 np.setdiff1drépond parfaitement à mes exigences. Cette réponse pour information.

Msquare
la source
0

Si le nombre d'occurrences doit être pris en compte, vous devez probablement utiliser quelque chose comme collections.Counter:

list_1=["a", "b", "c", "d", "e"]
list_2=["a", "f", "c", "m"] 
from collections import Counter
cnt1 = Counter(list_1)
cnt2 = Counter(list_2)
final = [key for key, counts in cnt2.items() if cnt1.get(key, 0) != counts]

>>> final
['f', 'm']

Comme promis, cela peut également traiter un nombre différent d'occurrences comme une «différence»:

list_1=["a", "b", "c", "d", "e", 'a']
cnt1 = Counter(list_1)
cnt2 = Counter(list_2)
final = [key for key, counts in cnt2.items() if cnt1.get(key, 0) != counts]

>>> final
['a', 'f', 'm']
MSeifert
la source
-1

À partir de ser1, supprimez les éléments présents dans ser2.

Contribution

ser1 = pd.Series ([1, 2, 3, 4, 5]) ser2 = pd.Series ([4, 5, 6, 7, 8])

Solution

ser1 [~ ser1.isin (ser2)]

Adnan
la source
Bienvenue dans Stack Overflow. Cette question a huit autres réponses, dont l'une a été acceptée par l'affiche originale. Veuillez décrire comment votre réponse améliore ce qui a déjà été présenté.
chb