Opération de soustraction de liste Python

227

Je veux faire quelque chose de similaire à ceci:

>>> x = [1,2,3,4,5,6,7,8,9,0]  
>>> x  
[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]  
>>> y = [1,3,5,7,9]  
>>> y  
[1, 3, 5, 7, 9]  
>>> y - x   # (should return [2,4,6,8,0])

Mais cela n'est pas pris en charge par les listes python Quelle est la meilleure façon de le faire?

rêveur
la source
@ezdazuzena ce n'est pas une soustraction. C'est la différence entre deux listes. Votre partage n'est pas une duplication de cette question.
Celik
1
Que doit renvoyer [2, 2] - [2]? []? [2]?
McKay
@McKay [2,2] - [2] devrait renvoyer [2]. [2,2] - [1,2,2,3] devrait revenir []
Robino
Cette question concerne la soustraction de liste mais la réponse acceptée est plus proche de la soustraction d'ensemble.
Robino
2
Que doit [2, 1, 2, 3, 2, 4, 2] - [2, 3, 2] retourner et pourquoi? Doit-il trouver le 232 au milieu et retourner 2142? ou doit-il trouver le premier à chaque fois et retourner 1242? Ou autre chose? Ce que je dis, c'est que ce ne sont pas des réponses évidentes et dépendent des besoins.
McKay

Réponses:

330

Utilisez une compréhension de liste:

[item for item in x if item not in y]

Si vous souhaitez utiliser la -syntaxe infixe, vous pouvez simplement faire:

class MyList(list):
    def __init__(self, *args):
        super(MyList, self).__init__(args)

    def __sub__(self, other):
        return self.__class__(*[item for item in self if item not in other])

vous pouvez ensuite l'utiliser comme:

x = MyList(1, 2, 3, 4)
y = MyList(2, 5, 2)
z = x - y   

Mais si vous n'avez pas absolument besoin des propriétés de liste (par exemple, la commande), utilisez simplement des ensembles comme les autres réponses le recommandent.

aaronasterling
la source
10
@admica, ne l'utilisez pas listpour les noms de variables car il masque le listconstructeur. Si vous utilisez «liste», veuillez le faire précéder d'un trait de soulignement. De plus, en supprimant le *, vous avez cassé mon code ...
aaronasterling
19
Si vous le faites, [1,1,2,2] - [1,2]vous obtiendrez une liste vide. [1,1,2,2] - [2]donne [1,1]Donc ce n'est pas vraiment une soustraction de liste, c'est plus comme "Liste de la liste X sans éléments de l'ensemble Y " .
Alfred Zien
@AlfredZien ce qu'il a dit
RetroCode
La méthode de compréhension de liste est beaucoup plus lente (dans mon exemple) que la méthode de différence définie.
redfiloux
1
@BarnabasSzabolcs: Cela ne sauvera rien, car il se convertira yen un setavant chaque vérification (ce qui est un coût similaire au travail original). Vous devez soit faire en yset = set(y)dehors de listcomp, puis tester if item not in yset, ou, en tant que hack flagrant, faire [item for yset in [set(y)] for item in x if item not in yset]qui abuse des listcomps imbriqués pour le mettre en cache ysetcomme une ligne. Une solution un-liner un peu moins laide qui fonctionne correctement serait à utiliser list(itertools.filterfalse(set(y).__contains__, x))car l'argument to filterfalsen'est construit qu'une seule fois.
ShadowRanger
259

Utiliser la différence définie

>>> z = list(set(x) - set(y))
>>> z
[0, 8, 2, 4, 6]

Ou vous pouvez simplement avoir des ensembles x et y afin de ne pas avoir à faire de conversions.

quantumSoup
la source
50
cela perdra toute commande. Cela peut ou non être important selon le contexte.
aaronasterling
63
Cela perdra également tous les doublons possibles qui peuvent avoir besoin / vouloir être maintenus.
Opal
Je reçoisTypeError: unhashable type: 'dict'
Havnar
C'est beaucoup plus rapide dans les cas où les listes comparées sont grandes
JqueryToAddNumbers
2
Si la commande et les doublons d'articles dans la liste ne sont pas importants pour le contexte, c'est une excellente réponse et elle est très lisible.
Watt Iamsuri
37

Il s'agit d'une opération de "soustraction d'ensemble". Utilisez la structure de données définie pour cela.

En Python 2.7:

x = {1,2,3,4,5,6,7,8,9,0}
y = {1,3,5,7,9}
print x - y

Production:

>>> print x - y
set([0, 8, 2, 4, 6])
Père Noël
la source
1
list (set ([1,2,3,4,5]) - set ([1,2,3])) = [4, 5] de sorte que ce soit les listes à définir en premier, puis soustrayez (ou diff à sens unique) ) et retour à la liste.
gseattle
2
Pas bon si vous souhaitez conserver l'ordre d'origine des articles de l'ensemble x.
Zahran
34

si les articles en double et les commandes posent problème:

[i for i in a if not i in b or b.remove(i)]

a = [1,2,3,3,3,3,4]
b = [1,3]
result: [2, 3, 3, 3, 4]
nguyên
la source
2
Cela fonctionne, bien que ce soit à l' O(m * n)exécution (et je grince des dents chaque fois qu'un listcomp inclut des effets secondaires); vous pouvez l'améliorer en utilisantcollections.Counter pour obtenir l' O(m + n)exécution.
ShadowRanger
J'ai du mal à comprendre cela, quelqu'un peut-il expliquer?
anushka
20

Pour de nombreux cas d'utilisation, la réponse que vous souhaitez est:

ys = set(y)
[item for item in x if item not in ys]

Il s'agit d'un hybride entre la réponse d' Aaronasterling et la réponse de QuantumSoup .

La version de aaronasterling fait des len(y)comparaisons d'éléments pour chaque élément dans x, donc cela prend du temps quadratique. La version de quantumSoup utilise des ensembles, elle effectue donc une seule recherche d'ensemble à temps constant pour chaque élément dans x- mais, comme elle convertit les deux x et yen ensembles, elle perd l'ordre de vos éléments.

En convertissant uniquement yen un ensemble et en itérant xdans l'ordre, vous obtenez le meilleur des deux mondes: le temps linéaire et la préservation de l'ordre. *


Cependant, cela a toujours un problème avec la version de quantumSoup: cela nécessite que vos éléments soient lavables. C'est à peu près intégré dans la nature des ensembles. ** Si vous essayez, par exemple, de soustraire une liste de dict d'une autre liste de dict, mais que la liste à soustraire est grande, que faites-vous?

Si vous pouvez décorer vos valeurs de manière à ce qu'elles soient lavables, cela résout le problème. Par exemple, avec un dictionnaire plat dont les valeurs sont elles-mêmes hachables:

ys = {tuple(item.items()) for item in y}
[item for item in x if tuple(item.items()) not in ys]

Si vos types sont un peu plus compliqués (par exemple, vous avez souvent affaire à des valeurs compatibles JSON, qui sont hachables, ou à des listes ou des codes dont les valeurs sont récursivement du même type), vous pouvez toujours utiliser cette solution. Mais certains types ne peuvent tout simplement pas être convertis en quelque chose de lavable.


Si vos articles ne sont pas, et ne peuvent pas être, lavables, mais ils sont comparables, vous pouvez au moins obtenir un temps log-linéaire ( O(N*log M), ce qui est beaucoup mieux que le O(N*M)temps de la solution de liste, mais pas aussi bon que le O(N+M)temps de la solution réglée) en triant et en utilisant bisect:

ys = sorted(y)
def bisect_contains(seq, item):
    index = bisect.bisect(seq, item)
    return index < len(seq) and seq[index] == item
[item for item in x if bisect_contains(ys, item)]

Si vos articles ne sont ni lavables ni comparables, alors vous êtes coincé avec la solution quadratique.


* Notez que vous pouvez également le faire en utilisant une paire d' OrderedSetobjets, pour laquelle vous pouvez trouver des recettes et des modules tiers. Mais je pense que c'est plus simple.

** La raison pour laquelle les recherches définies sont à temps constant est qu'il suffit de hacher la valeur et de voir s'il existe une entrée pour ce hachage. S'il ne peut pas hacher la valeur, cela ne fonctionnera pas.

abarnert
la source
7

La recherche de valeurs dans des ensembles est plus rapide que leur recherche dans des listes:

[item for item in x if item not in set(y)]

Je pense que cela évoluera légèrement mieux que:

[item for item in x if item not in y]

Les deux préservent l'ordre des listes.

Rudolfbyker
la source
Va-t-il mettre en cache set(y)et ne pas se convertir yen un nouvel ensemble sur chaque boucle? Sinon, vous feriez la réponse de besoin abarnert: ys = set(y); [i for i in x if i not in ys].
Jacktose
2
Certains tests approximatifs suggèrent que cela if i not in set(y)prend 25% de plus que if i not in y(où yest une liste). La pré-conversion de l'ensemble prend 55% moins de temps. Testé avec assez court xet y, mais les différences devraient être plus prononcées avec la longueur, le cas échéant.
Jacktose
1
@Jacktose: Oui, cette solution fait plus de travail, car elle doit itérer et hacher chaque élément de ypour chaque élément de x; à moins que la comparaison d'égalité ne soit vraiment coûteuse par rapport au calcul de hachage, cela sera toujours perdu en clair item not in y.
ShadowRanger
@ShadowRanger qui a du sens. Si la conversion d'ensemble était un moyen fiable et plus rapide de faire cette vérification, vous penseriez que le compilateur ferait toujours la vérification de cette façon.
Jacktose
5

Si les listes autorisent les éléments en double, vous pouvez utiliser Counter à partir de collections:

from collections import Counter
result = list((Counter(x)-Counter(y)).elements())

Si vous devez conserver l'ordre des éléments de x:

result = [ v for c in [Counter(y)] for v in x if not c[v] or c.subtract([v]) ]
Alain T.
la source
C'est bien, même si cela perd de l'ordre; réparer c'est un peu plus compliqué .
ShadowRanger
@ShadowRanger, c'est en effet. mais juste un peu.
Alain T.
Ne me dérange pas, je vais juste frémir devant les listcomps avec la mise en cache et les effets secondaires (bien que je suppose que la combinaison des deux supprime les effets secondaires visibles de l'extérieur?). :-)
ShadowRanger
De plus, ce code ne fonctionnera pas tel qu'il est écrit; Counter.subtractne supprime pas les éléments à valeur nulle ( -et le -=fait, mais pas subtract), vous ne cesserez donc jamais de supprimer des éléments. Vous souhaitez remplacer not v in cpar not c[v](qui renvoie zéro pour les éléments inexistants, vous pouvez donc tester en toute sécurité le retour pour "zéro" via not).
ShadowRanger
@ShadowRanger, bonne prise! Fixé maintenant.
Alain T.
3

Je pense que la façon la plus simple d'y parvenir est d'utiliser set ().

>>> x = [1,2,3,4,5,6,7,8,9,0]  
>>> y = [1,3,5,7,9]  
>>> list(set(x)- set(y))
[0, 2, 4, 6, 8]
Loochie
la source
3

Les autres solutions ont l'un des problèmes suivants:

  1. Ils ne préservent pas l'ordre, ou
  2. Ils ne suppriment pas un nombre précis d'éléments, par exemple pour x = [1, 2, 2, 2]et y = [2, 2]ils se convertissent yen a set, et suppriment tous les éléments correspondants (en laissant [1]uniquement) ou en supprimant un de chaque élément unique (en partant [1, 2, 2]), lorsque le comportement correct consiste à supprimer 2deux fois, partir [1, 2], ou
  3. Ils O(m * n)fonctionnent, où une solution optimale peut O(m + n)fonctionner

Alain était sur la bonne voieCounter pour résoudre les problèmes n ° 2 et n ° 3, mais cette solution perdra son ordre. La solution qui préserve l'ordre (en supprimant les premières ncopies de chaque valeur pour les nrépétitions dans les listvaleurs à supprimer) est:

from collections import Counter

x = [1,2,3,4,3,2,1]  
y = [1,2,2]  
remaining = Counter(y)

out = []
for val in x:
    if remaining[val]:
        remaining[val] -= 1
    else:
        out.append(val)
# out is now [3, 4, 3, 1], having removed the first 1 and both 2s.

Essayez-le en ligne!

Pour lui faire supprimer les dernières copies de chaque élément, changez simplement la forboucle en for val in reversed(x):et ajoutez out.reverse()immédiatement après avoir quitté la forboucle.

La construction de l' Counterest O(n)en termes de y« lien de, itérer xest O(n)en termes de x» longueur s, et les Countertests d'adhésion et de mutation sont O(1), tout list.appendest amorti O(1)(une donnée appendpeut être O(n), mais pour beaucoup de appends, les moyennes Big-O global O(1)depuis moins en moins d'entre eux nécessitent une réaffectation), de sorte que le travail global effectué l'est O(m + n).

Vous pouvez également tester pour déterminer s'il y avait des éléments yqui n'ont pas été supprimés xen testant:

remaining = +remaining  # Removes all keys with zero counts from Counter
if remaining:
    # remaining contained elements with non-zero counts
ShadowRanger
la source
Note: Ceci ne nécessitent les valeurs à hashable, mais toute solution qui ne nécessite pas des objets HASHABLE soit ne sont pas d' usage général (par exemple , peut compter ints dans un tableau de longueur fixe) ou doit faire plus que le O(m + n)travail (par exemple , la meilleure grande -O serait de faire un tri listde paires valeur / nombre uniques, en transformant les O(1) dictrecherches en recherches O(log n)binaires; vous auriez besoin de valeurs uniques avec leurs nombres, pas seulement des valeurs non uniques triées, car sinon vous paieriez des O(n)frais pour supprimer le éléments du tri list).
ShadowRanger
2

Essaye ça.

def subtract_lists(a, b):
    """ Subtracts two lists. Throws ValueError if b contains items not in a """
    # Terminate if b is empty, otherwise remove b[0] from a and recurse
    return a if len(b) == 0 else [a[:i] + subtract_lists(a[i+1:], b[1:]) 
                                  for i in [a.index(b[0])]][0]

>>> x = [1,2,3,4,5,6,7,8,9,0]
>>> y = [1,3,5,7,9]
>>> subtract_lists(x,y)
[2, 4, 6, 8, 0]
>>> x = [1,2,3,4,5,6,7,8,9,0,9]
>>> subtract_lists(x,y)
[2, 4, 6, 8, 0, 9]     #9 is only deleted once
>>>
user3435376
la source
1

La réponse fournie par @aaronasterling semble bon, cependant, il est compatible avec l'interface par défaut de la liste: x = MyList(1, 2, 3, 4)vs x = MyList([1, 2, 3, 4]). Ainsi, le code ci-dessous peut être utilisé comme une liste plus conviviale pour python:

class MyList(list):
    def __init__(self, *args):
        super(MyList, self).__init__(*args)

    def __sub__(self, other):
        return self.__class__([item for item in self if item not in other])

Exemple:

x = MyList([1, 2, 3, 4])
y = MyList([2, 5, 2])
z = x - y
Hamid Zafar
la source
0

Je pense que c'est plus rapide:

In [1]: a = [1,2,3,4,5]

In [2]: b = [2,3,4,5]

In [3]: c = set(a) ^ set(b)

In [4]: c
Out[4]: {1}
Eds_k
la source
Ce n'est pas de la soustraction. En fait, c'est la différence symétrique entre deux listes.
Parth Chauhan
De plus, cela ne fonctionne que pour les objets lavables à l'intérieur des listes
zhukovgreen
-1

Cet exemple soustrait deux listes:

# List of pairs of points
list = []
list.append([(602, 336), (624, 365)])
list.append([(635, 336), (654, 365)])
list.append([(642, 342), (648, 358)])
list.append([(644, 344), (646, 356)])
list.append([(653, 337), (671, 365)])
list.append([(728, 13), (739, 32)])
list.append([(756, 59), (767, 79)])

itens_to_remove = []
itens_to_remove.append([(642, 342), (648, 358)])
itens_to_remove.append([(644, 344), (646, 356)])

print("Initial List Size: ", len(list))

for a in itens_to_remove:
    for b in list:
        if a == b :
            list.remove(b)

print("Final List Size: ", len(list))
Joao Nicolau
la source
8
Évitez cela, c'est O (N ^ 2)
Alexander - Rétablir Monica