Элегантный способ удаления элементов из последовательности в 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
это неэффективно, довольно уродливо и, возможно, багги (как он обрабатывает несколько записей "Джон Смит"?). У кого-нибудь есть более элегантное решение, или, по крайней мере, более эффективную?
Как насчет одного, который работает со словарями?
14 ответов:
два простых способа выполнить только фильтрацию:
используя
filter:
names = filter(lambda name: name[-5:] != "Smith", names)использовать список осмысленностей:
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: понимание списка кори тоже потрясающе.
оба решения, фильтр и понимание требуется создание нового списка. Я не знаю достаточно внутренних частей 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