Operazione di sottrazione dell'elenco Python


227

Voglio fare qualcosa di simile a questo:

>>> 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])

Ma questo non è supportato dalle liste di Python Qual è il modo migliore per farlo?


@ezdazuzena questa non è sottrazione. Questa è la differenza tra due elenchi. La tua condivisione non è una pubblicazione di questa domanda.
— Celik

1
Cosa dovrebbe restituire [2, 2] - [2]? []? [2]?
— McKay,

@McKay [2,2] - [2] dovrebbe restituire [2]. [2,2] - [1,2,2,3] dovrebbe tornare []
— Robino il

Questa domanda riguarda la sottrazione dell'elenco ma la risposta accettata è più vicina all'impostazione della sottrazione.
— Robino,

2
Cosa dovrebbe tornare [2, 1, 2, 3, 2, 4, 2] - [2, 3, 2] e perché? Dovrebbe trovare il 232 nel mezzo e restituire il 2142? o dovrebbe trovare il primo ogni volta e restituire 1242? O qualcos'altro? Quello che sto dicendo è che queste non sono risposte ovvie e dipendono dalle necessità.
— McKay,

Risposte:


330

Usa una comprensione dell'elenco:

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

Se vuoi usare la -sintassi infix, puoi semplicemente fare:

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])

puoi quindi usarlo come:

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

Ma se non hai assolutamente bisogno delle proprietà dell'elenco (ad esempio, l'ordinamento), usa solo i set come le altre risposte raccomandano.


10
@admica, non utilizzare listper i nomi delle variabili in quanto ombreggia il listcostruttore. Se usi 'list', ti preghiamo di precederlo con un trattino basso. Inoltre, rilasciando *, hai rotto il mio codice ...
— aaronasterling

19
Se lo fai [1,1,2,2] - [1,2]otterrai un elenco vuoto. [1,1,2,2] - [2]dà [1,1]Quindi non è in realtà una lista di sottrazione, è più simile a "Elenco da Elenco X senza elementi dall'insieme Y " .
— Alfred Zien,

@AlfredZien quello che ha detto
— RetroCode

Il metodo di comprensione dell'elenco è molto più lento (nel mio esempio) rispetto al metodo della differenza impostata.
— Redfiloux,

1
@BarnabasSzabolcs: Questo non salverà nulla, perché si convertirà yin un setprima di ogni controllo (che è un costo simile al lavoro originale). Avresti bisogno di fare yset = set(y)al di fuori di listcomp, quindi testare if item not in yset, o come un trucco egregio, [item for yset in [set(y)] for item in x if item not in yset]che abusa di listcom annidati per memorizzare nella cache ysetcome una riga . Una soluzione unilinea leggermente meno brutta che si comporti adeguatamente sarebbe da usare list(itertools.filterfalse(set(y).__contains__, x))perché l'argomento a filterfalseè costruito solo una volta.
— ShadowRanger,

259

Usa la differenza impostata

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

Oppure potresti avere solo xey in modo da non dover fare alcuna conversione.


50
questo perderà qualsiasi ordine. Ciò può o meno avere importanza a seconda del contesto.
— aaronasterling

63
Ciò perderà anche eventuali duplicati che potrebbero essere necessari / desiderati da mantenere.
— Opale

RicevoTypeError: unhashable type: 'dict'
— Havnar il

Questo è molto più veloce nei casi in cui le liste confrontate sono grandi
— JqueryToAddNumbers

2
Se l'ordinazione e i duplicati degli articoli nell'elenco non sono importanti per il contesto, questa è un'ottima risposta e molto leggibile.
— Watt Iamsuri,

37

Questa è un'operazione "imposta sottrazione". Utilizzare la struttura di dati impostata per questo.

In Python 2.7:

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

Produzione:

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

1
list (set ([1,2,3,4,5]) - set ([1,2,3])) = [4, 5] in modo che sia ciascuno di essi da impostare per primo, quindi sottrarre (o diff ) e torna all'elenco.
— gseattle,

2
Non va bene se ti piace mantenere l'ordine degli oggetti originali dell'insieme x.
— Zahran,

34

se gli articoli duplicati e ordinati sono un problema:

[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]

2
Funziona, sebbene sia O(m * n)runtime (e io rabbrividisco ogni volta che un listcomp include effetti collaterali); puoi migliorarlo usandocollections.Counter per ottenere il O(m + n)runtime.
— ShadowRanger,

Sto facendo fatica a capirlo, qualcuno può spiegare?
— Anushka,

20

Per molti casi d'uso, la risposta che desideri è:

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

Questo è un ibrido tra la risposta di aaronasterling e la risposta di quantumSoup .

La versione di aaronasterling len(y)confronta gli elementi per ogni elemento in x, quindi richiede un tempo quadratico. La versione di quantumSoup utilizza set, quindi esegue una singola ricerca di set a tempo costante per ogni elemento in x—ma, poiché converte entrambi x e yin set, perde l'ordine dei tuoi elementi.

Convertendo solo yin un set e ripetendo xin ordine, ottieni il meglio da entrambi i mondi: tempo lineare e conservazione dell'ordine. *


Tuttavia, questo ha ancora un problema dalla versione di quantumSoup: richiede che i tuoi elementi siano hash. È praticamente integrato nella natura dei set. ** Se stai provando, ad esempio, a sottrarre un elenco di dadi da un altro elenco di dadi, ma l'elenco da sottrarre è grande, cosa fai?

Se riesci a decorare i tuoi valori in qualche modo che sono hash, questo risolve il problema. Ad esempio, con un dizionario semplice i cui valori sono essi stessi cancellabili:

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

Se i tuoi tipi sono un po 'più complicati (ad esempio, spesso hai a che fare con valori compatibili con JSON, che sono hash, o elenchi o dicts i cui valori sono ricorsivamente dello stesso tipo), puoi comunque usare questa soluzione. Ma alcuni tipi non possono essere convertiti in nulla di hash.


Se i tuoi articoli non sono, e non possono essere realizzati, hash, ma sono comparabili, puoi almeno ottenere un tempo log-linear ( O(N*log M)che è molto meglio del O(N*M)tempo della soluzione dell'elenco, ma non buono come il O(N+M)tempo della soluzione impostata) ordinando e utilizzando 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)]

Se i tuoi articoli non sono né lavabili né confrontabili, allora sei bloccato con la soluzione quadratica.


* Nota che puoi anche farlo usando una coppia di OrderedSetoggetti, per i quali puoi trovare ricette e moduli di terze parti. Ma penso che sia più semplice.

** Il motivo per cui le ricerche impostate sono a tempo costante è che tutto ciò che deve fare è hash il valore e vedere se c'è una voce per quell'hash. Se non è possibile eseguire l'hashing del valore, questo non funzionerà.


7

Cercare i valori negli insiemi è più veloce che cercarli negli elenchi:

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

Credo che questo ridimensionerà leggermente meglio di:

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

Entrambi preservano l'ordine delle liste.


Memorizzerà nella cache set(y)e non si convertirà yin un nuovo set su ogni ciclo? In caso contrario, avresti risposto bisogno di abarnert: ys = set(y); [i for i in x if i not in ys].
— Jacktose,

2
Alcuni test approssimativi suggeriscono che il if i not in set(y)25% di tempo in più rispetto a if i not in y(dove si ytrova un elenco). La pre-conversione del set richiede il 55% di tempo in meno. Testato con piuttosto breve xe y, ma le differenze dovrebbero essere più pronunciate con la lunghezza, se non altro.
— Jacktose,

1
@Jacktose: Sì, questa soluzione funziona di più, perché deve iterare e hash ogni elemento yper ogni elemento di x; a meno che il confronto di uguaglianza non sia veramente costoso rispetto al calcolo dell'hash, questo perderà sempre in chiaro item not in y.
— ShadowRanger,

@ShadowRanger che ha un senso. Se impostare la conversione fosse un modo affidabile più veloce per fare quel controllo, penseresti che il compilatore farebbe sempre il controllo in quel modo.
— Jacktose,

5

Se gli elenchi consentono elementi duplicati, puoi utilizzare Contatore dalle raccolte:

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

Se è necessario preservare l'ordine degli elementi da x:

result = [ v for c in [Counter(y)] for v in x if not c[v] or c.subtract([v]) ]

Questo è buono, anche se perde l'ordinamento; la correzione è un po 'più complicata .
— ShadowRanger,

@ShadowRanger, lo è davvero. ma solo un po '.
— Alain T.

Non ti preoccupare, vado solo a rabbrividire alle liste con la cache e gli effetti collaterali (anche se suppongo che la combinazione dei due rimuova gli effetti collaterali visibili esternamente?). :-)
— ShadowRanger

Inoltre, questo codice non funzionerà come scritto; Counter.subtractnon rimuove gli elementi a valore zero ( -e lo -=fanno, ma non subtract), quindi non smetteresti mai di rimuoverli. Si desidera sostituire not v in ccon not c[v](che restituisce zero per gli elementi inesistenti, in modo da poter testare in sicurezza il ritorno per "zeroiness" tramite not).
— ShadowRanger,

@ShadowRanger, Buona cattura! Risolto il problema ora.
— Alain T.,

3

Penso che il modo più semplice per raggiungere questo obiettivo sia usando 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]

3

Le altre soluzioni presentano uno dei pochi problemi:

  1. Non preservano l'ordine, o
  2. Non rimuovono un conteggio preciso di elementi, ad esempio per x = [1, 2, 2, 2]e y = [2, 2]si convertono yin a set, o rimuovono tutti gli elementi corrispondenti ( [1]solo lasciando ) o rimuovono uno di ciascun elemento unico (lasciando [1, 2, 2]), quando il comportamento corretto sarebbe rimuovere 2due volte, in partenza[1, 2] , o
  3. Funzionano O(m * n), dove una soluzione ottimale può fare il O(m + n)lavoro

Alain era sulla buona stradaCounter per risolvere il n. 2 e il n. 3, ma quella soluzione perderà gli ordini. La soluzione che conserva l'ordine (rimuovendo le prime ncopie di ciascun valore per le nripetizioni listdei valori da rimuovere) è:

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.

Provalo online!

Per farlo rimuovere le ultime copie di ogni elemento, basta cambiare il forciclo in for val in reversed(x):e aggiungere out.reverse()immediatamente dopo essere usciti dal forciclo.

Costruendo il valore Counterè O(n)in termini di ylunghezza, l'iterazione xè O(n)in termini di xlunghezza e i Countertest di appartenenza e la mutazione sono O(1), mentre list.appendsono ammortizzati O(1)(un dato appendpuò essere O(n), ma per molti appendsecondi, le medie generali di O maggiore O(1)da sempre meno di questi richiede una riallocazione), quindi il lavoro complessivo svolto è O(m + n).

Puoi anche provare per determinare se c'erano elementi da ycui non sono stati rimossi dal xtest:

remaining = +remaining  # Removes all keys with zero counts from Counter
if remaining:
    # remaining contained elements with non-zero counts

Nota: Questo non richiede i valori per essere hashable, ma qualsiasi soluzione che non richiede oggetti hashabili o non è di uso generale (es può contare ints nella matrice di lunghezza fissa) o ha a che fare più di O(m + n)lavoro (ad esempio la migliore grande prossimo -O sarebbe fare una sorta listdi coppie valore / conteggi univoci, cambiando le O(1) dictricerche in O(log n)ricerche binarie; avresti bisogno di valori univoci con i loro conteggi, non solo valori non unici ordinati, perché altrimenti pagheresti i O(n)costi per rimuovere il elementi dal ordinati list).
— ShadowRanger,

2

Prova questo.

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

1

La risposta fornita da @aaronasterling sembra buono, tuttavia, non è compatibile con l'interfaccia predefinita di lista: x = MyList(1, 2, 3, 4)vs x = MyList([1, 2, 3, 4]). Pertanto, il codice seguente può essere utilizzato come un più amichevole elenco 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])

Esempio:

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

0

Penso che questo sia più veloce:

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}

Questa non è sottrazione. In effetti, questa è la differenza simmetrica tra due elenchi.
— Parth Chauhan,

Inoltre, questo funziona solo per gli oggetti hash all'interno degli elenchi
— zhukovgreen,

-1

Questo esempio sottrae due elenchi:

# 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))

8
Evita questo, è O (N ^ 2)
— Alexander - Ripristina Monica
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.