Usuwanie wielu elementów z listy

160

Czy można jednocześnie usunąć wiele elementów z listy? Jeśli chcę usunąć elementy o indeksie 0 i 2 i spróbować czegoś takiego jak del somelist[0], a następnie del somelist[2], druga instrukcja faktycznie usunie somelist[3].

Przypuszczam, że zawsze mógłbym najpierw usunąć elementy o wyższym numerze, ale mam nadzieję, że jest lepszy sposób.

Løiten
źródło

Odpowiedzi:

110

Prawdopodobnie nie jest to najlepsze rozwiązanie tego problemu:

indices = 0, 2
somelist = [i for j, i in enumerate(somelist) if j not in indices]
SilentGhost
źródło
2
Prawie, tylko jeśli usuniesz całą listę. będzie to len (indeksy) * len (lista). Tworzy również kopię, która może być pożądana lub nie
Richard Levasseur
jeśli sprawdzasz wartość na liście, tak jest. operator „in” działa na wartościach z listy, podczas gdy działa na klawiszach dyktu. Jeśli się mylę, wskaż mi informację zwrotną
Richard Levasseur
5
powodem, dla którego wybrałem krotkę dla indeksów, była tylko prostota zapisu. to byłaby idealna praca dla set () dając O (n)
SilentGhost
18
Nie polega to wcale na usuwaniu elementów z jakiejś listy, ale raczej na utworzeniu zupełnie nowej listy. Jeśli cokolwiek zawiera odniesienie do oryginalnej listy, nadal będzie zawierało wszystkie elementy.
Tom Future
2
@SilentGhost Nie jest konieczne do wyliczenia. A co powiesz na to somelist = [ lst[i] for i in xrange(len(lst)) if i not in set(indices) ]:?
ToolmakerSteve,
183

Z jakiegoś powodu nie podoba mi się żadna z odpowiedzi tutaj. Tak, działają, ale ściśle mówiąc, większość z nich nie usuwa elementów z listy, prawda? (Ale wykonanie kopii, a następnie zastąpienie oryginału kopią edytowaną).

Dlaczego po prostu nie usunąć najpierw wyższego indeksu?

Czy jest tego powód? Po prostu zrobiłbym:

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

Jeśli naprawdę nie chcesz usuwać elementów do tyłu, myślę, że powinieneś po prostu odliczyć wartości indeksów, które są większe niż ostatnio usunięty indeks (nie możesz naprawdę użyć tego samego indeksu, ponieważ masz inną listę) lub użyć kopię listy (co nie byłoby „usuwaniem”, ale zastąpieniem oryginału edytowaną kopią).

Czy czegoś tu brakuje, czy jest jakiś powód, aby NIE usuwać w odwrotnej kolejności?

tglaria
źródło
1
Nie wiem, dlaczego ta odpowiedź nie została wybrana jako zaakceptowana !. Dzięki za to.
swathis
4
Są dwa powody. (a) W przypadku listy złożoność czasowa byłaby wyższa niż w przypadku metody „tworzenia kopii” (przy użyciu zestawu wskaźników) (przy założeniu losowych indeksów), ponieważ niektóre elementy wymagają wielokrotnego przesuwania do przodu. (b) Przynajmniej dla mnie jest to trudne do odczytania, ponieważ istnieje funkcja sortowania, która nie odpowiada żadnej rzeczywistej logice programu i istnieje wyłącznie z powodów technicznych. Chociaż do tej pory już dobrze rozumiem logikę, nadal czuję, że byłoby to trudne do odczytania.
Imperishable Night
1
@ImperishableNight, czy mógłbyś rozwinąć (a)? Nie rozumiem, że „niektóre elementy wymagają przesunięcia”. Dla (b) możesz po prostu zdefiniować funkcję, jeśli potrzebujesz przejrzystości czytania.
tglaria
109

Jeśli usuwasz wiele nieprzylegających elementów, to, co opisujesz, jest najlepszym sposobem (i tak, pamiętaj, aby zacząć od najwyższego indeksu).

Jeśli elementy sąsiadują ze sobą, możesz użyć składni przypisywania plasterków:

a[2:10] = []
Greg Hewgill
źródło
95
Możesz też powiedzieć del a[2:10]z tym samym efektem.
sth
8
@sth Co ciekawe, del jest trochę szybszy niż przypisywanie.
thefourtheye
24

Możesz użyć numpy.deletew następujący sposób:

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

Jeśli nie masz nic przeciwko kończeniu na numpytablicy na końcu, możesz pominąć rozszerzenie .tolist(). Powinieneś także zobaczyć całkiem duże ulepszenia szybkości, dzięki czemu jest to bardziej skalowalne rozwiązanie. Nie testowałem tego, ale numpyoperacje są skompilowanym kodem napisanym w języku C lub Fortran.

filE
źródło
1
ogólne rozwiązanie, gdy elementy nie następują po sobie +1
noɥʇʎԀʎzɐɹƆ
1
pytanie tutaj, co z usuwaniem ['a', 42].
evanhutomo
OGROMNE punkty bonusowe za to rozwiązanie, w porównaniu do innych, za szybkość. Mogę powiedzieć, że w przypadku bardzo dużego zestawu danych zajęło mi kilka minut, aby osiągnąć coś, co zajęło zaledwie kilka sekund przy starym dobrym numpy.
legel
18

Jako specjalizacja odpowiedzi Grega możesz nawet użyć rozszerzonej składni plasterków. na przykład. Jeśli chcesz usunąć pozycje 0 i 2:

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

Oczywiście nie obejmuje to żadnego arbitralnego wyboru, ale z pewnością może działać w przypadku usunięcia dowolnych dwóch elementów.

bobince
źródło
16

Jako funkcja:

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

Działa w czasie n log (n) , co powinno uczynić z tego najszybszego poprawnego rozwiązania.

Nikhil Chelliah
źródło
1
Wersja z args.sort (). Reverse () jest zdecydowanie lepsza. Zdarza się również, że działa z dyktami zamiast rzucać lub, co gorsza, po cichu korumpować.
sort () nie jest zdefiniowany dla krotki, musisz najpierw przekonwertować na listę. sort () zwraca None, więc nie możesz użyć na niej reverse ().
SilentGhost
@ R. Pate: Z tego powodu usunąłem pierwszą wersję. Dzięki. @ SilentGhost: Naprawiono to.
Nikhil Chelliah
@Nikhil: nie, nie zrobiłeś;) args = list (args) args.sort () args.reverse (), ale lepszą opcją byłoby: args =
sort
2
n log n? Naprawdę? Myślę, że nie del list[index]jest O (1).
user202729
12

Więc zasadniczo chcesz usunąć wiele elementów za jednym przejściem? W takim przypadku pozycja następnego elementu do usunięcia zostanie przesunięta o tyle, ile usunięto wcześniej.

Naszym celem jest usunięcie wszystkich samogłosek, które są wstępnie obliczane jako indeksy 1, 4 i 7. Zauważ, że ważne jest, aby indeksy to_delete były w porządku rosnącym, w przeciwnym razie nie zadziała.

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

Byłoby bardziej skomplikowane, gdybyś chciał usunąć elementy w dowolnej kolejności. IMO, sortowanie to_deletemoże być łatwiejsze niż ustalenie, kiedy należy lub nie należy odejmować index.

Richard Levasseur
źródło
8

Jestem całkowitym początkującym w Pythonie, a moje programowanie w tej chwili jest co najmniej prymitywne i brudne, ale moim rozwiązaniem było użycie kombinacji podstawowych poleceń, których nauczyłem się we wczesnych samouczkach:

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

Oczywiście ze względu na konieczność wybrania znaku „znak do usunięcia” ma to swoje ograniczenia.

Jeśli chodzi o wydajność, jak skaluje się rozmiar listy, jestem pewien, że moje rozwiązanie jest nieoptymalne. Jest to jednak proste, co mam nadzieję, że przemówi do innych początkujących i będzie działać w prostych przypadkach, w których some_listma dobrze znany format, np. Zawsze numeryczny ...

Paweł
źródło
2
zamiast używać '!' jako swój znak specjalny użyj Brak. Dzięki temu każda postać jest ważna i uwalnia twoje możliwości
portforwardpodcast
5

Oto alternatywa, która nie używa enumerate () do tworzenia krotek (tak jak w oryginalnej odpowiedzi SilentGhost).

Wydaje mi się to bardziej czytelne. (Może czułbym się inaczej, gdybym miał w zwyczaju wyliczać). PRZESTROGA: Nie testowałem wydajności tych dwóch podejść.

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

UWAGA: składnia Pythona 2.7. W przypadku Pythona 3 xrange=> range.

Stosowanie:

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

lista:

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

--- BONUS ---

Usuń wiele wartości z listy. Oznacza to, że mamy wartości, które chcemy usunąć:

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

Stosowanie:

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

lista:

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

To ta sama odpowiedź co poprzednio, ale tym razem dostarczyliśmy WARTOŚCI do usunięcia [0, 44, 55].

ToolmakerSteve
źródło
Zdecydowałem, że @ SilentGhost jest tylko trudny do odczytania z powodu nieopisowych nazw zmiennych użytych w wyniku wyliczenia. Ponadto pareny ułatwiłyby czytanie. Więc tutaj jest jak bym słowo jego rozwiązanie (z dodatkiem „SET”, do wykonania) [ value for (i, value) in enumerate(lst) if i not in set(indices) ]. Ale zostawię tutaj swoją odpowiedź, ponieważ pokazuję również, jak usuwać według wartości. Co jest prostszym przypadkiem, ale może komuś pomóc.
ToolmakerSteve
@ Veedrac- dziękuję; Napisałem ponownie, aby najpierw zbudować zestaw. Jak myślisz - teraz szybsze rozwiązanie niż SilentGhost? (Nie uważam, że ważne, aby rzeczywiście dosyć czasu, po prostu prosząc swoją opinię.) Podobnie, bym ponownie napisać wersja SilentGhost jako indices_as_set = set(indices), [ value for (i, value) in enumerate(lst) if i not in indices_as_set ], aby ją przyspieszyć.
ToolmakerSteve
Czy jest jakiś stylowy powód dla podwójnego podkreślenia w delete__by_values()?
Tomek
5

Alternatywna metoda rozumienia listy, która używa wartości indeksu list:

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]

To zwraca:

['b', 'c', 'e', 'f']
Miauczeć
źródło
dobra odpowiedź, ale nazwanie listy indeksów indexjest mylące, ponieważ w liście zastosowano iteratorindex()
Joe
4

oto kolejna metoda, która usuwa elementy na miejscu. jeśli twoja lista jest naprawdę długa, jest szybsza.

>>> 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
user545424
źródło
+1: Ciekawe użycie deque do wykonania akcji jako części wyrażenia, zamiast wymagać bloku „for ..:”. Jednak w tym prostym przypadku uważam, że Nikhil for block jest bardziej czytelny.
ToolmakerSteve
4

Wspomniano o tym, ale jakoś nikt nie zdołał tego zrobić dobrze.

Na O(n)rozwiązanie byłoby:

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

Jest to bardzo zbliżone do wersji SilentGhost , ale dodaje dwa nawiasy klamrowe.

Veedrac
źródło
Nie O(n)dzieje się tak, jeśli policzysz wyszukiwania log(len(indices))dla każdej iteracji.
Mad Physicist
@MadPhysicist j not in indicesjest O(1).
Veedrac
Nie jestem pewien, skąd masz ten numer. Ponieważ indeksy są zbiorem, j not in indicesnadal wymaga wyszukiwania, czyli O(log(len(indices))). Chociaż zgadzam się, że wyszukiwanie w zestawie 2-elementowym kwalifikuje się tak O(1), jak w ogólnym przypadku tak będzie O(log(N)). Tak czy inaczej O(N log(N))nadal bije O(N^2).
Mad Physicist
A co dokładnie zrobiły dwa aparaty ortodontyczne?
Nuclear03020704
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])

Zasadniczo jest to to samo, co odpowiedź najczęściej głosowana, tylko inny sposób jej zapisania. Zwróć uwagę, że użycie l.index () nie jest dobrym pomysłem, ponieważ nie obsługuje ona zduplikowanych elementów na liście.

cynk
źródło
2

Metoda Remove spowoduje spore przesunięcia elementów listy. Myślę, że lepiej zrobić kopię:

...
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
...
luca
źródło
2

technicznie odpowiedź brzmi NIE, nie można usunąć dwóch obiektów JEDNOCZEŚNIE. Jednak jest możliwe usunięcie dwóch obiektów w jednym wierszu pięknego Pythona.

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

usunie foo['bar']wówczas ponowniefoo['baz']

David Brilliant
źródło
To usuwa obiekt dyktowania, a nie listę, ale nadal daję mu +1, bo jest cholernie ładny!
Ulf Aslak,
Dotyczy to również listy, z odpowiednią składnią. Jednak twierdzenie, że nie jest możliwe jednoczesne usunięcie dwóch obiektów, jest fałszywe; zobacz odpowiedź @bobince
Pedro Gimeno
2

możemy to zrobić używając pętli for iterującej po indeksach po posortowaniu listy indeksów w porządku malejącym

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
Gourav Singla
źródło
2

Dla indeksów 0 i 2 z listy A:

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

Dla niektórych losowych indeksów do usunięcia z listy A:

indices=(5,3,2,7,0) 
for x in sorted(indices)[::-1]: listA.pop(x)
dżem
źródło
2

Chciałem znaleźć sposób na porównanie różnych rozwiązań, które ułatwiają kręcenie gałkami.

Najpierw wygenerowałem moje dane:

import random

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

Następnie zdefiniowałem swoje funkcje:

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)

Następnie timeitporównywałem rozwiązania:

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)

Wynik

set =   1.711
del =   3.450
pop =   3.618

Wygrał więc generator z indeksami w a set. I deljest nieznacznie szybszy pop.

David Cullen
źródło
Dziękuję za to porównanie, doprowadziło mnie to do zrobienia własnych testów (właściwie właśnie pożyczyłem twój kod) i za niewielką liczbę elementów do usunięcia, narzut związany z utworzeniem SET-a sprawia, że ​​jest to najgorsze rozwiązanie (użyj 10, 100, 500 dla długość `` y '', a zobaczysz). Jak w większości przypadków, zależy to od aplikacji.
tglaria
2

Możesz użyć tej logiki:

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

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

print c
raghu
źródło
2

Kolejna realizacja pomysłu usunięcia z najwyższego indeksu.

for i in range(len(yourlist)-1, -1, -1):
    del yourlist(i)
ipramusinto
źródło
1

Właściwie mogę wymyślić dwa sposoby, aby to zrobić:

  1. pokrój listę w taki sposób (spowoduje to usunięcie pierwszego, trzeciego i ósmego elementu)

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

  2. zrób to na miejscu, ale pojedynczo:

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

Bartosz Radaczyński
źródło
1

Możesz to zrobić na dyktandzie, a nie na liście. Na liście elementy są w kolejności. W dyktandzie zależą tylko od indeksu.

Prosty kod, aby to wyjaśnić, wykonując :

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

Sposób „konwersji” listy w dyktandzie to:

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

Odwrotność to:

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

W każdym razie myślę, że lepiej jest rozpocząć usuwanie z wyższego indeksu, jak powiedziałeś.

Andrea Ambu
źródło
Czy Python gwarantuje, że [dct [i] for i in dct] zawsze będzie używać rosnących wartości i? Jeśli tak, lista (dct.values ​​()) jest z pewnością lepsza.
Nie myślałem o tym. Masz rację. Nie ma gwarancji, ponieważ przeczytałem [tutaj] [1], że towary zostaną odebrane w kolejności, a przynajmniej w oczekiwanej kolejności. Edytowałem. [1]: docs.python.org/library/stdtypes.html#dict.items
Andrea Ambu
2
Ta odpowiedź mówi o słownikach w zasadniczo błędny sposób. Słownik ma KLUCZE (nie WSKAŹNIKI). Tak, pary klucz / wartość są od siebie niezależne. Nie, nie ma znaczenia, w jakiej kolejności usuwasz wpisy. Konwersja do słownika tylko w celu usunięcia niektórych elementów z listy byłaby przesadą.
ToolmakerSteve,
1

Aby uogólnić komentarz z @sth . Usuwanie pozycji w dowolnej klasie, która implementuje abc.MutableSequence , listaw szczególności, odbywa się za pomocą __delitem__metody magic. Ta metoda działa podobnie __getitem__, co oznacza, że ​​może akceptować liczbę całkowitą lub wycinek. Oto przykład:

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)

To wyjdzie

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 'null', 'null', 'null', 8, 9]
Alexander Zhukov
źródło
1

Importowanie go tylko z tego powodu może być przesadą, ale jeśli i tak używasz pandas, rozwiązanie jest proste i proste:

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']
Lorinc Nyitrai
źródło
1
some_list.remove(some_list[max(i, j)])

Pozwala uniknąć kosztów sortowania i jawnego kopiowania listy.

Chester
źródło
0

A co z jednym z nich (jestem bardzo nowy w Pythonie, ale wydają się w porządku):

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)

[„Atlantyk”, „Pacyfik”, „Indie”]

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)

[„Atlantyk”, „Pacyfik”, „Indie”]

user12001090
źródło
0

Żadna z odpowiedzi udzielonych do tej pory nie powoduje usunięcia w miejscu O (n) na długości listy dla dowolnej liczby indeksów do usunięcia, więc oto moja wersja:

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]
Pedro Gimeno
źródło
0

Możesz też użyć usuwania.

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)
Jiwon Kim
źródło
0

Umieściłem to razem w list_difffunkcji, która po prostu przyjmuje dwie listy jako dane wejściowe i zwraca ich różnicę, zachowując oryginalną kolejność pierwszej listy.

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)

Przykładowe użycie:

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']
mirekphd
źródło