Eliminazione di più elementi da un elenco


160

È possibile eliminare più elementi da un elenco contemporaneamente? Se voglio eliminare elementi all'indice 0 e 2 e provare qualcosa di simile del somelist[0], seguito da del somelist[2], la seconda istruzione verrà effettivamente eliminata somelist[3].

Suppongo che potrei sempre eliminare prima gli elementi con un numero più alto, ma spero che ci sia un modo migliore.

Risposte:


110

È possibile utilizzare enumeratee rimuovere i valori il cui indice corrisponde agli indici che si desidera rimuovere:

indices = 0, 2
somelist = [i for j, i in enumerate(somelist) if j not in indices]

2
Quasi, solo se elimini l'intero elenco. sarà len (indici) * len (somelist). Crea anche una copia, che può o non può essere desiderata
Richard Levasseur,

se stai cercando un valore in un elenco, lo è. l'operatore 'in' lavora sui valori di un elenco, mentre funziona sulle chiavi di un dict. Se sbaglio, ti prego di indicarmi il riferimento / riferimento
Richard Levasseur,

5
il motivo per cui ho scelto la tupla per gli indici era solo la semplicità del disco. sarebbe un lavoro perfetto per set () dando O (n)
SilentGhost il

18
Questo non sta eliminando affatto gli articoli dall'elenco, ma piuttosto creando un nuovo elenco. Se qualcosa contiene un riferimento all'elenco originale, conterrà comunque tutti gli elementi.
Tom Future

2
@SilentGhost Non è necessario per effettuare un'enumerazione. Che ne dici di questo somelist = [ lst[i] for i in xrange(len(lst)) if i not in set(indices) ]:?
ToolmakerSteve

183

Per qualche ragione non mi piace nessuna delle risposte qui. Sì, funzionano, ma a rigor di termini la maggior parte di loro non sta eliminando elementi in un elenco, vero? (Ma facendo una copia e poi sostituendo quella originale con la copia modificata).

Perché non eliminare prima solo l'indice più alto?

C'è una ragione per questo? Vorrei solo fare:

for i in sorted(indices, reverse=True):
    del somelist[i]

Se davvero non vuoi eliminare gli elementi all'indietro, suppongo che dovresti semplicemente deincrementare i valori degli indici che sono maggiori dell'ultimo indice eliminato (non puoi davvero usare lo stesso indice poiché hai un elenco diverso) o usare una copia dell'elenco (che non "eliminerebbe" ma sostituisce l'originale con una copia modificata).

Mi sto perdendo qualcosa qui, qualche motivo per NON cancellare in ordine inverso?


1
Non so perché questa non sia stata scelta come risposta accettata !. Grazie per questo.
swathis,

4
Ci sono due ragioni. (a) Per un elenco, la complessità temporale sarebbe superiore al metodo "crea una copia" (usando un insieme di indici) in media (assumendo indici casuali) perché alcuni elementi devono essere spostati in avanti più volte. (b) Almeno per me, è difficile da leggere, perché esiste una funzione di ordinamento che non corrisponde a nessuna logica di programma reale ed esiste solo per motivi tecnici. Anche se ormai ho già capire a fondo la logica, io ancora sento che sarebbe difficile da leggere.
Imperishable Night,

1
@ImperishableNightcould elaborate (a)? Non capisco "alcuni elementi devono essere spostati". Per (b) potresti semplicemente definire una funzione se hai bisogno di leggere la chiarezza.
tglaria,

109

Se stai eliminando più elementi non adiacenti, quello che descrivi è il modo migliore (e sì, assicurati di iniziare dall'indice più alto).

Se i tuoi articoli sono adiacenti, puoi utilizzare la sintassi dell'assegnazione delle sezioni:

a[2:10] = []

95
Puoi anche dire del a[2:10]con lo stesso effetto.
qc

8
@sth È interessante notare che del è leggermente più veloce dell'assegnazione.
Thefourtheye

24

Puoi usare numpy.deletecome segue:

import numpy as np
a = ['a', 'l', 3.14, 42, 'u']
I = [0, 2]
np.delete(a, I).tolist()
# Returns: ['l', '42', 'u']

Se non ti dispiace finire con un numpyarray alla fine, puoi tralasciare il file .tolist(). Dovresti vedere anche alcuni miglioramenti della velocità piuttosto importanti, che rendono questa soluzione più scalabile. Non l'ho confrontato, ma le numpyoperazioni sono codice compilato scritto in C o Fortran.


1
soluzione generale quando gli elementi non sono consecutivi +1
noɥʇʎԀʎzɐɹƆ

1
domanda qui, che ne dici di cancellare ['a', 42].
evanhutomo,

Enormi punti bonus per questa soluzione, rispetto agli altri, per la velocità. Quello che posso dire è che per un set di dati molto grande, mi ci sono voluti diversi minuti per ottenere qualcosa che impiegava solo pochi secondi con un buon vecchio intorpidimento.
legel

18

Come specializzazione della risposta di Greg, puoi persino usare la sintassi della sezione estesa. per esempio. Se si desidera eliminare gli elementi 0 e 2:

>>> a= [0, 1, 2, 3, 4]
>>> del a[0:3:2]
>>> a
[1, 3, 4]

Questo non copre alcuna selezione arbitraria, ovviamente, ma può sicuramente funzionare per eliminare due elementi.


16

Come una funzione:

def multi_delete(list_, *args):
    indexes = sorted(list(args), reverse=True)
    for index in indexes:
        del list_[index]
    return list_

Viene eseguito in n log (n) time, il che dovrebbe renderlo la soluzione corretta più veloce di sempre.


1
La versione con args.sort (). Reverse () è decisamente migliore. Capita anche di lavorare con dadi invece di lanciare o, peggio, corrompere silenziosamente.

sort () non è definito per la tupla, dovresti prima convertirlo in elenco. sort () restituisce None, quindi non è possibile utilizzare reverse () su di esso.
SilentGhost,

@ R. Pate: ho rimosso la prima versione per quel motivo. Grazie. @ SilentGhost: risolto.
Nikhil Chelliah,

@Nikhil: no non l'hai fatto;) args = list (args) args.sort () args.reverse () ma l'opzione migliore sarebbe: args = ordinato (args, reverse = True)
SilentGhost

2
n log n? Veramente? Non penso del list[index]sia O (1).
user202729

12

Quindi, essenzialmente vuoi eliminare più elementi in un passaggio? In tal caso, la posizione dell'elemento successivo da eliminare sarà sfalsata, tuttavia molti sono stati eliminati in precedenza.

Il nostro obiettivo è eliminare tutte le vocali, che sono precompilate come indici 1, 4 e 7. Nota che è importante che gli indici to_delete siano in ordine crescente, altrimenti non funzionerà.

to_delete = [1, 4, 7]
target = list("hello world")
for offset, index in enumerate(to_delete):
  index -= offset
  del target[index]

Sarebbe più complicato se si volessero eliminare gli elementi in qualsiasi ordine. IMO, l'ordinamento to_deletepotrebbe essere più facile che capire quando dovresti o non dovresti sottrarre index.


8

Sono un principiante assoluto in Python, e la mia programmazione al momento è rozza e sporca per non dire altro, ma la mia soluzione è stata quella di utilizzare una combinazione dei comandi di base che ho imparato nei primi tutorial:

some_list = [1,2,3,4,5,6,7,8,10]
rem = [0,5,7]

for i in rem:
    some_list[i] = '!' # mark for deletion

for i in range(0, some_list.count('!')):
    some_list.remove('!') # remove
print some_list

Ovviamente, a causa della necessità di scegliere un carattere "contrassegno per l'eliminazione", questo ha i suoi limiti.

Per quanto riguarda le prestazioni in base alle dimensioni dell'elenco, sono sicuro che la mia soluzione non è ottimale. Tuttavia, è semplice, che spero piaccia agli altri principianti, e funzionerà in casi semplici in cui some_listè di un formato ben noto, ad esempio, sempre numerico ...


2
invece di usare '!' come personaggio speciale, usa Nessuno. Ciò mantiene valido ogni personaggio e libera le tue possibilità
portforwardpodcast il

5

Ecco un'alternativa, che non usa enumerate () per creare tuple (come nella risposta originale di SilentGhost).

Questo mi sembra più leggibile. (Forse mi sentirei diversamente se avessi l'abitudine di usare enumerate.) CAVEAT: Non ho testato le prestazioni dei due approcci.

# Returns a new list. "lst" is not modified.
def delete_by_indices(lst, indices):
    indices_as_set = set(indices)
    return [ lst[i] for i in xrange(len(lst)) if i not in indices_as_set ]

NOTA: sintassi di Python 2.7. Per Python 3, xrange=> range.

Uso:

lst = [ 11*x for x in xrange(10) ]
somelist = delete_by_indices( lst, [0, 4, 5])

somelist:

[11, 22, 33, 66, 77, 88, 99]

--- BONUS ---

Elimina più valori da un elenco. Cioè, abbiamo i valori che vogliamo eliminare:

# Returns a new list. "lst" is not modified.
def delete__by_values(lst, values):
    values_as_set = set(values)
    return [ x for x in lst if x not in values_as_set ]

Uso:

somelist = delete__by_values( lst, [0, 44, 55] )

somelist:

[11, 22, 33, 66, 77, 88, 99]

Questa è la stessa risposta di prima, ma questa volta abbiamo fornito i VALORI da eliminare [0, 44, 55].


Ho deciso che @ SilentGhost era difficile da leggere, a causa dei nomi delle variabili non descrittivi utilizzati per il risultato dell'enumerato. Inoltre, i genitori avrebbero reso più facile la lettura. Così qui è come vorrei parola la sua soluzione (con "set", ha aggiunto, per le prestazioni): [ value for (i, value) in enumerate(lst) if i not in set(indices) ]. Ma lascerò la mia risposta qui, perché mostro anche come eliminare in base ai valori. È un caso più semplice, ma potrebbe aiutare qualcuno.
ToolmakerSteve

@ Veedrac- grazie; Ho riscritto per costruire prima il set. Cosa ne pensi: una soluzione più veloce ora di quella di SilentGhost? (Non mi considero abbastanza importante da tempo in realtà, solo chiedendo la tua opinione.) Allo stesso modo, vorrei riscrivere la versione di SilentGhost come indices_as_set = set(indices), [ value for (i, value) in enumerate(lst) if i not in indices_as_set ]per accelerarlo.
ToolmakerSteve

C'è una ragione di stile per il doppio trattino basso in delete__by_values()?
Tom,

5

Un metodo di comprensione dell'elenco alternativo che utilizza i valori dell'indice dell'elenco:

stuff = ['a', 'b', 'c', 'd', 'e', 'f', 'woof']
index = [0, 3, 6]
new = [i for i in stuff if stuff.index(i) not in index]

Questo ritorna:

['b', 'c', 'e', 'f']

buona risposta, ma nominare l'elenco di indici come indexfuorviante poiché nell'elenco iteratore viene utilizzato il metodoindex()
Joe

4

ecco un altro metodo che rimuove gli elementi in atto. anche se la tua lista è davvero lunga, è più veloce.

>>> a = range(10)
>>> remove = [0,4,5]
>>> from collections import deque
>>> deque((list.pop(a, i) for i in sorted(remove, reverse=True)), maxlen=0)

>>> timeit.timeit('[i for j, i in enumerate(a) if j not in remove]', setup='import random;remove=[random.randrange(100000) for i in range(100)]; a = range(100000)', number=1)
0.1704120635986328

>>> timeit.timeit('deque((list.pop(a, i) for i in sorted(remove, reverse=True)), maxlen=0)', setup='from collections import deque;import random;remove=[random.randrange(100000) for i in range(100)]; a = range(100000)', number=1)
0.004853963851928711

+1: uso interessante di deque per eseguire un'azione for come parte di un'espressione, piuttosto che richiedere un blocco "for ..:". Tuttavia, per questo semplice caso, trovo Nikhil per il blocco più leggibile.
ToolmakerSteve

4

Questo è stato menzionato, ma in qualche modo nessuno è riuscito a farlo davvero bene.

Sulla O(n)soluzione sarebbe:

indices = {0, 2}
somelist = [i for j, i in enumerate(somelist) if j not in indices]

Questo è molto vicino alla versione di SilentGhost , ma aggiunge due parentesi graffe.


Questo non è O(n)se si contano le ricerche necessarie log(len(indices))per ogni iterazione.
Fisico pazzo,

@MadPhysicist j not in indicesè O(1).
Veedrac,

Non sono sicuro di come ottieni quel numero. Poiché gli indici sono un insieme, j not in indicesrichiede ancora la ricerca, che è O(log(len(indices))). Mentre sono d'accordo che una ricerca in un set di 2 elementi si qualifica come O(1), nel caso generale lo sarà O(log(N)). Ad ogni modo O(N log(N))batte ancora O(N^2).
Fisico pazzo,


E cosa hanno fatto esattamente due parentesi graffe?
Nucleare03020704,

4
l = ['a','b','a','c','a','d']
to_remove = [1, 3]
[l[i] for i in range(0, len(l)) if i not in to_remove])

È praticamente la stessa della risposta più votata, solo un modo diverso di scriverla. Si noti che l'utilizzo di l.index () non è una buona idea, perché non può gestire elementi duplicati in un elenco.


2

Il metodo Remove provoca molti spostamenti degli elementi dell'elenco. Penso che sia meglio fare una copia:

...
new_list = []
for el in obj.my_list:
   if condition_is_true(el):
      new_list.append(el)
del obj.my_list
obj.my_list = new_list
...

2

tecnicamente, la risposta è NO, non è possibile cancellare due oggetti ALLO STESSO TEMPO. Tuttavia, è possibile eliminare due oggetti in una riga di bellissimo pitone.

del (foo['bar'],foo['baz'])

verrà eliminato in modo recusivo foo['bar'], quindifoo['baz']


Questo si elimina da un oggetto dict, non da un elenco, ma sto ancora + 1 perché è dannatamente carino!
Ulf Aslak,

Si applica anche all'elenco, con la sintassi appropriata. Tuttavia, l'affermazione è che non è possibile eliminare due oggetti contemporaneamente è falso; vedi risposta di @bobince
Pedro Gimeno

2

possiamo farlo usando un ciclo for che scorre ripetutamente sugli indici dopo aver ordinato l'elenco degli indici in ordine decrescente

mylist=[66.25, 333, 1, 4, 6, 7, 8, 56, 8769, 65]
indexes = 4,6
indexes = sorted(indexes, reverse=True)
for i in index:
    mylist.pop(i)
print mylist

2

Per gli indici 0 e 2 dall'elenco A:

for x in (2,0): listA.pop(x)

Per alcuni indici casuali da rimuovere dall'elenco A:

indices=(5,3,2,7,0) 
for x in sorted(indices)[::-1]: listA.pop(x)

2

Volevo un modo per confrontare le diverse soluzioni che rendevano facile girare le manopole.

Per prima cosa ho generato i miei dati:

import random

N = 16 * 1024
x = range(N)
random.shuffle(x)
y = random.sample(range(N), N / 10)

Quindi ho definito le mie funzioni:

def list_set(value_list, index_list):
    index_list = set(index_list)
    result = [value for index, value in enumerate(value_list) if index not in index_list]
    return result

def list_del(value_list, index_list):
    for index in sorted(index_list, reverse=True):
        del(value_list[index])

def list_pop(value_list, index_list):
    for index in sorted(index_list, reverse=True):
        value_list.pop(index)

Quindi ho usato timeitper confrontare le soluzioni:

import timeit
from collections import OrderedDict

M = 1000
setup = 'from __main__ import x, y, list_set, list_del, list_pop'
statement_dict = OrderedDict([
    ('overhead',  'a = x[:]'),
    ('set', 'a = x[:]; list_set(a, y)'),
    ('del', 'a = x[:]; list_del(a, y)'),
    ('pop', 'a = x[:]; list_pop(a, y)'),
])

overhead = None
result_dict = OrderedDict()
for name, statement in statement_dict.iteritems():
    result = timeit.timeit(statement, number=M, setup=setup)
    if overhead is None:
        overhead = result
    else:
        result = result - overhead
        result_dict[name] = result

for name, result in result_dict.iteritems():
    print "%s = %7.3f" % (name, result)

Produzione

set =   1.711
del =   3.450
pop =   3.618

Quindi il generatore con gli indici in a è setstato il vincitore. Ed delè leggermente più veloce allora pop.


Grazie per questo confronto, questo mi ha portato a fare i miei test (in realtà ho appena preso in prestito il tuo codice) e per un piccolo numero di elementi da rimuovere, l'overhead per la creazione di un SET lo rende la soluzione peggiore (usa 10, 100, 500 per il lunghezza di "y" e vedrai). Come il più delle volte, questo dipende dall'applicazione.
tglaria,

2

Puoi usare questa logica:

my_list = ['word','yes','no','nice']

c=[b for i,b in enumerate(my_list) if not i in (0,2,3)]

print c

2

Un'altra implementazione dell'idea di rimuovere dall'indice più alto.

for i in range(len(yourlist)-1, -1, -1):
    del yourlist(i)

1

Posso davvero pensare a due modi per farlo:

  1. tagliare la lista come (questo elimina il 1o, 3o e 8o elemento)

    somelist = somelist [1: 2] + somelist [3: 7] + somelist [8:]

  2. fallo sul posto, ma uno alla volta:

    somelist.pop (2) somelist.pop (0)


1

Puoi farlo in un dict, non in un elenco. In un elenco gli elementi sono in sequenza. In un dict dipendono solo dall'indice.

Codice semplice solo per spiegarlo facendo :

>>> lst = ['a','b','c']
>>> dct = {0: 'a', 1: 'b', 2:'c'}
>>> lst[0]
'a'
>>> dct[0]
'a'
>>> del lst[0]
>>> del dct[0]
>>> lst[0]
'b'
>>> dct[0]
Traceback (most recent call last):
  File "<pyshell#19>", line 1, in <module>
    dct[0]
KeyError: 0
>>> dct[1]
'b'
>>> lst[1]
'c'

Un modo per "convertire" un elenco in un dict è:

>>> dct = {}
>>> for i in xrange(0,len(lst)): dct[i] = lst[i]

L'inverso è:

lst = [dct[i] for i in sorted(dct.keys())] 

Comunque penso che sia meglio iniziare a cancellare dall'indice più alto come hai detto.


Python garantisce [dct [i] per i in dct] utilizzerà sempre valori crescenti di i? In tal caso, list (dct.values ​​()) è sicuramente migliore.

Non ci stavo pensando. Hai ragione. Non ho alcuna garanzia mentre leggo [qui] [1] che gli articoli saranno raccolti in ordine, o almeno nell'ordine previsto. Ho modificato. [1]: docs.python.org/library/stdtypes.html#dict.items
Andrea Ambu,

2
Questa risposta parla dei dizionari in un modo fondamentalmente sbagliato. Un dizionario ha CHIAVI (non INDICI). Sì, le coppie chiave / valore sono indipendenti l'una dall'altra. No, non importa in quale ordine si eliminano le voci. La conversione in un dizionario solo per eliminare alcuni elementi da un elenco sarebbe eccessiva.
ToolmakerSteve

1

Per generalizzare il commento da @sth . L'eliminazione degli oggetti in qualsiasi classe, che implementa abc.MutableSequence , e listin particolare, avviene tramite il __delitem__metodo magico. Questo metodo funziona in modo simile a __getitem__, nel senso che può accettare un numero intero o una sezione. Ecco un esempio:

class MyList(list):
    def __delitem__(self, item):
        if isinstance(item, slice):
            for i in range(*item.indices(len(self))):
                self[i] = 'null'
        else:
            self[item] = 'null'


l = MyList(range(10))
print(l)
del l[5:8]
print(l)

Questo produrrà

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 'null', 'null', 'null', 8, 9]

1

L'importazione solo per questo motivo potrebbe essere eccessiva, ma se ti capita di utilizzare pandascomunque, la soluzione è semplice e diretta:

import pandas as pd
stuff = pd.Series(['a','b','a','c','a','d'])
less_stuff = stuff[stuff != 'a']  # define any condition here
# results ['b','c','d']

1
some_list.remove(some_list[max(i, j)])

Evita i costi di ordinamento e deve copiare esplicitamente l'elenco.


0

Che ne dici di uno di questi (sono molto nuovo in Python, ma sembrano ok):

ocean_basin = ['a', 'Atlantic', 'Pacific', 'Indian', 'a', 'a', 'a']
for i in range(1, (ocean_basin.count('a') + 1)):
    ocean_basin.remove('a')
print(ocean_basin)

['Atlantico', 'Pacifico', 'Indiano']

ob = ['a', 'b', 4, 5,'Atlantic', 'Pacific', 'Indian', 'a', 'a', 4, 'a']
remove = ('a', 'b', 4, 5)
ob = [i for i in ob if i not in (remove)]
print(ob)

['Atlantico', 'Pacifico', 'Indiano']


0

Nessuna delle risposte offerte finora esegue la cancellazione in atto in O (n) sulla lunghezza della lista per un numero arbitrario di indici da eliminare, quindi ecco la mia versione:

def multi_delete(the_list, indices):
    assert type(indices) in {set, frozenset}, "indices must be a set or frozenset"
    offset = 0
    for i in range(len(the_list)):
        if i in indices:
            offset += 1
        elif offset:
            the_list[i - offset] = the_list[i]
    if offset:
        del the_list[-offset:]

# Example:
a = [0, 1, 2, 3, 4, 5, 6, 7]
multi_delete(a, {1, 2, 4, 6, 7})
print(a)  # prints [0, 3, 5]

0

Puoi anche usare Rimuovi.

delete_from_somelist = []
for i in [int(0), int(2)]:
     delete_from_somelist.append(somelist[i])
for j in delete_from_somelist:
     newlist = somelist.remove(j)

0

Ho messo tutto insieme in una list_difffunzione che prende semplicemente due liste come input e restituisce la loro differenza, preservando l'ordine originale della prima lista.

def list_diff(list_a, list_b, verbose=False):

    # returns a difference of list_a and list_b,
    # preserving the original order, unlike set-based solutions

    # get indices of elements to be excluded from list_a
    excl_ind = [i for i, x in enumerate(list_a) if x in list_b]
    if verbose:
        print(excl_ind)

    # filter out the excluded indices, producing a new list 
    new_list = [i for i in list_a if list_a.index(i) not in excl_ind]
    if verbose:
        print(new_list)

    return(new_list)

Esempio di utilizzo:

my_list = ['a', 'b', 'c', 'd', 'e', 'f', 'woof']
# index = [0, 3, 6]

# define excluded names list
excl_names_list = ['woof', 'c']

list_diff(my_list, excl_names_list)
>> ['a', 'b', 'd', 'e', 'f']
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.