Элегантный способ удаления элементов из последовательности в Python? [дубликат]



этот вопрос уже есть ответ здесь:



когда я пишу код на Python, мне часто нужно удалять элементы из списка или другого типа последовательности на основе некоторых критериев. Я не нашел решение, которое является элегантным и эффективным, как удаление элементов из списка, который вы в настоящее время перебираете, плохо. Например, вы не можете сделать это:



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


Я обычно в конечном итоге делает что-то вроде этого:



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


это неэффективно, довольно уродливо и, возможно, багги (как он обрабатывает несколько записей "Джон Смит"?). У кого-нибудь есть более элегантное решение, или, по крайней мере, более эффективную?



Как насчет одного, который работает со словарями?

825   14  

14 ответов:

два простых способа выполнить только фильтрацию:

  1. используя filter:

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

  2. использовать список осмысленностей:

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

обратите внимание, что оба случая сохраняют значения, для которых функция предиката вычисляет True, поэтому вы должны изменить логику (т. е. вы говорите "сохранить людей, у которых нет фамилии Смит" вместо "удалить людей, у которых есть фамилия Смит").

Edit смешно... два человека индивидуально разместили оба ответа, которые я предложил, когда я размещал свой.

вы также можете пройти назад по списку:

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

Это имеет то преимущество, что он не создает новый список (например,filter или понимание списка) и использует итератор вместо копии списка (например,[:]).

обратите внимание, что хотя удаление элементов при повторении назад безопасно, вставка их несколько сложнее.

очевидный ответ-Это тот, который дал Джон и еще несколько человек, а именно:

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

но это имеет тот недостаток, что он создает новый объект списка, а не повторно использовать исходный объект. Я сделал некоторые профилирования и эксперименты, и самый эффективный метод, который я придумал:

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

присвоение " имен [:] "в основном означает"заменить содержимое списка имен следующим значением". Это отличается от просто присвоения имен, в что он не создает новый объект списка. Правая часть присваивания является генераторным выражением (обратите внимание на использование скобок, а не квадратных скобок). Это приведет к тому, что Python будет повторяться по всему списку.

некоторые быстрые профилирования показывают, что это примерно на 30% быстрее, чем подход к пониманию списка, и примерно на 40% быстрее, чем подход к фильтру.

будьте осторожны: хотя это решение быстрее, чем очевидное решение, это больше неясно, и полагается на более продвинутые методы Python. Если вы используете его, я рекомендую сопровождать его комментарием. Вероятно, это стоит использовать только в тех случаях, когда вы действительно заботитесь о производительности этой конкретной операции (что довольно быстро, несмотря ни на что). (В случае, когда я использовал это, я выполнял поиск* beam и использовал это для удаления точек поиска из поискового луча.)

С помощью осознание

list = [x for x in list if x[-5:] != "smith"]

бывают случаи, когда фильтрация (либо с помощью фильтра или понимания списка) не работает. Это происходит, когда какой-либо другой объект содержит ссылку на список, который вы изменяете, и вам нужно изменить список на месте.

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

единственное отличие от исходного кода-это использование names[:] вместо names в цикле for. Таким образом, код повторяется над (мелкой) копией списка, и удаления работают так, как ожидалось. Поскольку копирование списка неглубоко, это довольно быстрый.

фильтр был бы удивительным для этого. Простой пример:

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

Edit: понимание списка кори тоже потрясающе.

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

оба решения, фильтр и понимание требуется создание нового списка. Я не знаю достаточно внутренних частей Python, чтобы быть уверенным, но я думаю что более традиционные (но менее элегантный) подход может быть более эффективным:

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

в любом случае, для коротких списков я придерживаюсь любого из двух решений, предложенных ранее.

чтобы ответить на ваш вопрос о работе со словарями, следует отметить, что Python 3.0 будет включать в себя дикт пониманий:

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

В то же время, вы можете сделать квази-дикт понимания таким образом:

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

или как более прямой ответ:

dict([(key, name) for key, name in some_dictionary.iteritems if name[-5:] != 'Smith'])

Если список должен быть отфильтрован на месте и размер списка довольно большой, то алгоритмы, упомянутые в предыдущих ответах, которые основаны на списке.remove (), может быть непригодным, потому что их вычислительная сложность составляет O(n^2). В этом случае вы можете использовать следующую функцию No-so pythonic:

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

изменить: На самом деле, решение в https://stackoverflow.com/a/4639748/274937 превосходит мое решение. Он более питонический и работает быстрее. Так вот новая реализация 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)]

фильтр и список понимания в порядке для вашего примера, но у них есть несколько проблем:

  • они делают копию вашего списка и возвращает новый, и это будет неэффективно, когда исходный список действительно большой
  • они могут быть действительно громоздкими, когда критерии для выбора элементов (в вашем случае, если имя[-5:] == 'Smith') более сложны или имеют несколько условий.

ваше оригинальное решение на самом деле более эффективно очень большие списки, даже если мы можем согласиться, что это уродливее. Но если вы беспокоитесь, что у вас может быть несколько "Джон Смит", его можно исправить, удалив на основе позиции, а не на значение:

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

мы не можем выбрать решение без учета размера списка, но для больших списков я бы предпочел ваше 2-проходное решение вместо фильтра или списков понимания

в случае набора.

toRemove = set([])  
for item in mySet:  
    if item is unwelcome:  
        toRemove.add(item)  
mySets = mySet - toRemove 

вот мой filter_inplace реализация, которая может быть использована для фильтрации элементов из списка на месте, я придумал это самостоятельно, прежде чем найти эту страницу. Это тот же алгоритм, что и опубликованный PabloG, просто более общий, поэтому вы можете использовать его для фильтрации списков на месте, он также может удалить из списка на основе comparisonFunc если установлено обратное True; своего рода обратный фильтр, если хотите.

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

Ну, это явно проблема со структурой данных, которую вы используете. Например, используйте хэш-таблицу. Некоторые реализации поддерживают несколько записей на ключ, поэтому можно либо удалить самый новый элемент, либо удалить их все.

но это, и то, что вы собираетесь найти решение, элегантность через различные структуры данных, а не алгоритм. Может быть, вы можете сделать лучше, если он отсортирован или что-то еще, но итерация в списке - это ваш единственный метод здесь.

edit: один понимает, что он просит "эффективности"... все эти предлагаемые методы просто повторяют список, который совпадает с тем, что он предложил.

Comments

    Ничего не найдено.