web-dev-qa-db-fra.com

Opération de soustraction de liste Python

Je veux faire quelque chose de semblable à 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 supporté par les listes python Quel est le meilleur moyen de le faire?

169
daydreamer

Utilisez une compréhension de liste:

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

Si vous voulez utiliser la syntaxe - infix, 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 absolument pas besoin des propriétés de liste (par exemple, la commande), utilisez simplement des ensembles comme le recommandent les autres réponses.

241
aaronasterling

Utilisez set difference

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

Ou bien vous pouvez simplement avoir x et y des ensembles pour que vous n'ayez aucune conversion.

190
quantumSoup

C'est 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

Sortie:

>>> print x - y
set([0, 8, 2, 4, 6])
34
Santa

si les articles en double et en commande 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]
31
nguyên

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

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

Ceci est un hybride entre la réponse de aaronasterling et la réponse de quantumSoup .

la version de aaronasterling effectue des comparaisons d'élément len(y) pour chaque élément de x, de sorte que le processus prend du temps quadratique. La version de quantumSoup utilise des ensembles, elle effectue donc une seule recherche d'ensemble constant pour chaque élément dans x—, mais, comme elle convertit bothx et y en ensembles, elle perd l'ordre de vos éléments.

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


Cependant, la version de quantumSoup a toujours un problème: elle nécessite que vos éléments soient hashable. Cela fait plutôt partie de la nature des jeux. ** Si vous essayez, par exemple, de soustraire une liste de dict d'une autre liste de dict, mais que la liste à soustraire est longue, que faites-vous?

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

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 utilisez souvent des valeurs compatibles JSON, qui peuvent être hachées, ou des listes ou des bases 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 fatiguable.


Si vos éléments ne sont pas et ne peuvent pas être définis, mais qu'ils sont comparables, vous pouvez au moins obtenir un temps log-linéaire (O(N*log M), qui est bien meilleur que le temps O(N*M) de la liste, comme l’heure O(N+M) de la solution définie) 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 comparables ni comparables, alors vous êtes pris avec la solution quadratique.


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

** La raison pour laquelle les recherches dans les ensembles sont constantes dans le temps 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.

13
abarnert

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

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

Je pense que cela sera légèrement meilleur que:

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

Les deux préservent l'ordre des listes.

7
rudolfbyker

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
>>>
2
user3435376

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}
1
Eds_k

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))
1
Joao Nicolau

La réponse fournie par @aaronasterling semble bonne, mais elle n’est pas compatible avec l’interface par défaut de list: 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 avec la liste 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
1
Hamid Zafar

Je pense que le moyen le 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]
0
Loochie

Vous pouvez utiliser Counter à partir de collections:

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