Elegancki sposób na usuwanie elementów z sekwencji w Pythonie? [duplikat]

To pytanie ma już odpowiedź tutaj:

Kiedy piszę kod w Pythonie, często muszę usuwać elementy z listy lub innego typu sekwencji w oparciu o pewne kryteria. Nie znalazłem rozwiązania, które jest eleganckie i skuteczne, ponieważ usuwanie elementów z listy, które obecnie iteracja jest zła. Na przykład nie możesz tego zrobić:

for name in names:
    if name[-5:] == 'Smith':
        names.remove(name)

Zwykle kończę robiąc coś takiego:

toremove = []
for name in names:
    if name[-5:] == 'Smith':
        toremove.append(name)
for name in toremove:
    names.remove(name)
del toremove

To jest nieefektywne, dość brzydkie i prawdopodobnie buggy (jak radzi sobie z wieloma wpisami "John Smith"?). Czy ktoś ma bardziej eleganckie rozwiązanie, a przynajmniej wydajniejsze?

Może taki, który działa ze słownikami?

Author: Andy Lester, 2008-08-20

14 answers

Dwa proste sposoby, aby wykonać tylko filtrowanie to:

  1. Using filter:

    names = filter(lambda name: name[-5:] != "Smith", names)

  2. Używanie składni list:

    names = [name for name in names if name[-5:] != "Smith"]

Zauważ, że oba przypadki zachowują wartości, dla których funkcja predykatu ocenia na True, więc musisz odwrócić logikę (tzn. mówisz "keep the people who don' t have the last name Smith "zamiast" remove the people who have the last name Smith").

Edit Śmieszne... dwa ludzie indywidualnie opublikowali obie odpowiedzi, które zaproponowałem, gdy publikowałem swoje.

 53
Author: John,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-10 05:32:20

Możesz także iterować wstecz nad listą:

for name in reversed(names):
    if name[-5:] == 'Smith':
        names.remove(name)

Ma to tę zaletę, że nie tworzy nowej Listy (jak filter lub zrozumienie listy) i używa iteratora zamiast kopii listy (jak [:]).

Zauważ, że chociaż usuwanie elementów podczas iteracji do tyłu jest bezpieczne, wstawianie ich jest nieco trudniejsze.

 37
Author: Xavier Martinez-Hidalgo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-08 01:24:09

Oczywistą odpowiedzią jest ta, którą dał John i kilka innych osób, a mianowicie:

>>> names = [name for name in names if name[-5:] != "Smith"]       # <-- slower

Ale to ma tę wadę, że tworzy nowy obiekt list, zamiast używać go ponownie. Zrobiłem kilka profili i eksperymentowałem, a najskuteczniejszą metodą, jaką wymyśliłem, jest: {]}

>>> names[:] = (name for name in names if name[-5:] != "Smith")    # <-- faster

Przypisanie do "names [:]" oznacza w zasadzie "zastąp zawartość listy nazw następującą wartością". Różni się od samego przypisywania imion, tym, że nie utwórz nowy obiekt listy. Prawa strona przypisania jest wyrażeniem generatora (zwróć uwagę na użycie nawiasów zamiast nawiasów kwadratowych). Spowoduje to iterację Pythona na całej liście.

Niektóre szybkie profilowanie sugeruje, że jest to o około 30% szybsze niż podejście do rozumienia list i o około 40% szybsze niż podejście do filtrowania.

Caveat: chociaż to rozwiązanie jest szybsze niż oczywiste rozwiązanie, jest bardziej niejasne i opiera się na bardziej zaawansowanych Techniki Pythona. Jeśli go używasz, polecam dołączenie do niego komentarza. Prawdopodobnie warto go używać tylko w przypadkach, w których naprawdę zależy ci na wydajności tej konkretnej operacji (która jest dość szybka bez względu na wszystko). (W przypadku, gdy użyłem tego, robiłem wyszukiwanie wiązki* i używałem tego do usuwania punktów Wyszukiwania Z Wiązki wyszukiwania.)

 28
Author: Edward Loper,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-09 14:41:40

Using a list comprehension

list = [x for x in list if x[-5:] != "smith"]
 10
Author: Corey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-20 17:49:29

Są chwile, kiedy filtrowanie (albo za pomocą filtra lub rozumienia listy) nie działa. Dzieje się tak, gdy jakiś inny obiekt trzyma odniesienie do listy, którą modyfikujesz i musisz zmodyfikować listę na swoim miejscu.

for name in names[:]:
    if name[-5:] == 'Smith':
        names.remove(name)

Jedyną różnicą od oryginalnego kodu jest użycie names[:] zamiast names W pętli for. W ten sposób Kod iteruje nad (płytką) kopią listy i usuwanie działa zgodnie z oczekiwaniami. Ponieważ kopiowanie listy jest płytkie, jest to dość szybkie.

 4
Author: gooli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-05 11:48:45

Filtr byłby świetny do tego. Prosty przykład:

names = ['mike', 'dave', 'jim']
filter(lambda x: x != 'mike', names)
['dave', 'jim']

Edit: Lista Coreya też jest super.

 3
Author: mk.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-20 17:49:10
names = filter(lambda x: x[-5:] != "Smith", names);
 2
Author: pottedmeat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-20 17:48:56

Oba rozwiązania, Filtr i zrozumienie wymaga zbudowania nowej listy. Nie znam wystarczająco dużo języków Pythona, aby być pewnym, ale myślę, że bardziej tradycyjne (ale mniej eleganckie) podejście może być bardziej efektywne: {]}

names = ['Jones', 'Vai', 'Smith', 'Perez']

item = 0
while item <> len(names):
    name = names [item]
    if name=='Smith':
        names.remove(name)
    else:
        item += 1

print names

W każdym razie, dla krótkich list, trzymam się jednego z dwóch rozwiązań zaproponowanych wcześniej.

 2
Author: PabloG,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-20 18:20:33

Aby odpowiedzieć na twoje pytanie dotyczące pracy ze słownikami, powinieneś zauważyć, że Python 3.0 będzie zawierał składanie dict :

>>> {i : chr(65+i) for i in range(4)}

W międzyczasie możesz zrobić quasi-dict w ten sposób:

>>> dict([(i, chr(65+i)) for i in range(4)])

Lub jako bardziej bezpośrednia odpowiedź:

dict([(key, name) for key, name in some_dictionary.iteritems if name[-5:] != 'Smith'])
 2
Author: Jason Baker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-08 00:17:06

Jeśli lista powinna być filtrowana na miejscu, a rozmiar listy jest dość duży, to algorytmy wymienione w poprzednich odpowiedziach, które są oparte na liście.remove (), może być nieodpowiednie, ponieważ ich złożoność obliczeniowa wynosi O (n^2). W tym przypadku można użyć następującej funkcji pythonicznej no-so:

def filter_inplace(func, original_list):
  """ Filters the original_list in-place.

  Removes elements from the original_list for which func() returns False.

  Algrithm's computational complexity is O(N), where N is the size
  of the original_list.
  """

  # Compact the list in-place.
  new_list_size = 0
  for item in original_list:
    if func(item):
      original_list[new_list_size] = item
      new_list_size += 1

  # Remove trailing items from the list.
  tail_size = len(original_list) - new_list_size
  while tail_size:
    original_list.pop()
    tail_size -= 1


a = [1, 2, 3, 4, 5, 6, 7]

# Remove even numbers from a in-place.
filter_inplace(lambda x: x & 1, a)

# Prints [1, 3, 5, 7]
print a

Edytuj: Rozwiązanie w https://stackoverflow.com/a/4639748/274937 jest lepszy od mojego rozwiązania. Jest bardziej pythoniczny i działa szybciej. Oto nowa implementacja filter_inplace ():

def filter_inplace(func, original_list):
  """ Filters the original_list inplace.

  Removes elements from the original_list for which function returns False.

  Algrithm's computational complexity is O(N), where N is the size
  of the original_list.
  """
  original_list[:] = [item for item in original_list if func(item)]
 2
Author: valyala,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:54:44

Składanie filtrów i list jest w porządku dla Twojego przykładu, ale mają kilka problemów:

  • robią kopię Twojej listy i zwracają nową, a to będzie nieefektywne, gdy oryginalna lista jest naprawdę duża
  • mogą być naprawdę uciążliwe, gdy kryteria wyboru przedmiotów (w Twoim przypadku, jeśli nazwa[-5:] == 'Smith') są bardziej skomplikowane lub mają kilka warunków.

Twoje oryginalne rozwiązanie jest w rzeczywistości bardziej wydajne dla bardzo dużych list, nawet jeśli zgadzam się, że jest brzydszy. Ale jeśli martwisz się, że możesz mieć wiele 'John Smith', można to naprawić usuwając na podstawie pozycji, a nie na wartości:

names = ['Jones', 'Vai', 'Smith', 'Perez', 'Smith']

toremove = []
for pos, name in enumerate(names):
    if name[-5:] == 'Smith':
        toremove.append(pos)
for pos in sorted(toremove, reverse=True):
    del(names[pos])

print names

Nie możemy wybrać rozwiązania bez uwzględnienia rozmiaru listy, ale dla dużych list wolałbym Twoje rozwiązanie 2-przebiegowe zamiast filtra lub listy składów

 1
Author: Ricardo Reyes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-10 18:11:15

W przypadku zbioru.

toRemove = set([])  
for item in mySet:  
    if item is unwelcome:  
        toRemove.add(item)  
mySets = mySet - toRemove 
 1
Author: CashMonkey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-07 04:08:57

Oto moja implementacja filter_inplace, która może być używana do filtrowania elementów z listy w miejscu, wymyśliłem to samodzielnie przed znalezieniem tej strony. Jest to ten sam algorytm, co PabloG, po prostu bardziej ogólny, dzięki czemu można go używać do filtrowania list w miejscu, jest również w stanie usunąć z listy na podstawie comparisonFunc jeśli odwrócony jest ustawiony True; rodzaj odwróconego filtra, jeśli chcesz.

def filter_inplace(conditionFunc, list, reversed=False):
    index = 0
    while index < len(list):
        item = list[index]

        shouldRemove = not conditionFunc(item)
        if reversed: shouldRemove = not shouldRemove

        if shouldRemove:
            list.remove(item)
        else:
            index += 1
 1
Author: Cory Gross,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-15 14:12:56

Cóż, jest to ewidentny problem ze strukturą danych, której używasz. Użyj na przykład hashtable. Niektóre implementacje obsługują wiele wpisów na klucz, więc można albo wyłączyć najnowszy element, albo usunąć wszystkie z nich.

Ale to jest, i to, co masz zamiar znaleźć rozwiązanie jest, elegancja poprzez inną strukturę danych, nie algorytm. Może możesz zrobić lepiej, jeśli jest posortowane, czy coś, ale iteracja na liście jest jedyną metodą tutaj.

edit: one zdaje sobie sprawę, że prosił o "efektywność"... wszystkie te sugerowane metody po prostu powtarzają się na liście, która jest taka sama, jak sugerował.

 -2
Author: nlucaroni,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-21 20:33:22