Элегантный способ удалить элементы из последовательности в 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

Это неэффективно, довольно некрасиво и, возможно, содержит ошибки (как он обрабатывает несколько записей «Джон Смит»?). Есть ли у кого-нибудь более элегантное решение или хотя бы более эффективное?

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

Ваш код удаляет несколько Смитов или вы его редактировали?

Geoffrey 20.07.2010 16:24
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
56
1
41 522
14
Перейти к ответу Данный вопрос помечен как решенный

Ответы 14

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

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

редактировать: можно понять, что он просил «эффективности» ... все эти предложенные методы просто перебирают список, что совпадает с тем, что он предложил.

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

Edward Loper 09.01.2011 17:44

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

filter было бы здорово для этого. Простой пример:

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

Редактировать: Понимание списка Кори тоже потрясающе.

Использование понимание списка

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

На самом деле, похоже, не работает для целых чисел. temprevengelist = "0-12354-6876" temprevengelist = temprevengelist.split ('-') list = [x for x in temprevengelist if x [-5:]! = 6876]

Fahim Akhter 28.01.2010 10:48

@FahimAkhter: Это потому, что вы сравниваете целое число со строкой: в Python 6876 (int) и "6876" (строка) - это два разных значения, и они не равны. Попробуйте заменить x[-5:] != 6876 на x[-5:] != "6876" или int(x[-5:]) != 6876.

Edward Loper 20.04.2012 23:33
Ответ принят как подходящий

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

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

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

  2. Использование списков:

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

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

Редактировать Забавно ... два человека по отдельности опубликовали оба ответа, которые я предложил, когда я писал свой.

not name.endswith("Smith") выглядит намного лучше :-)

Jochen Ritzel 07.12.2009 07:13

конечно, если вам нравится читаемость или что-то в этом роде.

John 08.12.2009 01:31

Может кто-нибудь объяснить мне [-5:]. Что произойдет, если вы захотите проверить весь список?

Robert Johnstone 14.09.2011 19:45

@Sevenearths: «[-5:]» берет последние пять символов имени, так как мы хотим знать, заканчивается ли имя на «Smith». Как предположил Йохен, выражение «name [: - 5]! = 'Smith'» можно было бы более читабельно записать как «not name.endswith ('Smith')».

Edward Loper 14.11.2011 18:57

Не забудьте упомянуть об увеличении производительности за счет использования name.endswith("Smith") вместо [-5:].

notbad.jpeg 03.07.2014 05:24

@ notbad.jpeg: Микрооптимизация не важна, но есть производительность decrease при использовании name.endswith("Smith") по сравнению с индексированием. Протестируйте его (я сделал)!

Gerrat 23.04.2015 18:21

@Gerrat Спасибо за исправление. Я думал, что endswith() будет иметь лучшую производительность, поскольку это более питонический способ сравнения, и он может использовать преимущества оптимизации, потому что ему не нужно делать субкопию строки.

notbad.jpeg 29.04.2015 19:05

Оба решения, фильтр и понимание, требуют создания нового списка. Я не знаю достаточно о внутреннем устройстве 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

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

Я думаю, что names.remove (name) может быть операцией O (n), которая сделает этот алгоритм O (n ^ 2).

postfuturist 04.10.2008 07:28

Я бы лично написал свое выражение while как item

Miquella 08.10.2008 04:31

Вероятно, более эффективно использовать del names [элемент] или names.pop (элемент), чем names.remove (имя). Это гораздо менее вероятно, чем O (n), хотя я не знаю, как это работает на самом деле.

rjmunro 05.11.2008 16:11

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

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

Ваше исходное решение на самом деле более эффективно для очень больших списков, даже если мы можем согласиться с его уродливостью. Но если вы беспокоитесь, что у вас может быть несколько 'John 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

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

Это не работает должным образом, если у вас есть более одной записи Smith, потому что дополнительные экземпляры для удаления были смещены из-за удаления более ранних экземпляров. По той же причине этот алгоритм вызывает исключение, если в конец списка добавляется вторая запись «Смит».

Miquella 08.10.2008 04:37

@Miquella: вы правы, мой исходный пост не удался для нескольких Смитов, я исправил его, выполняя удаление в обратном порядке. Спасибо.

Ricardo Reyes 10.10.2008 22:12

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

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

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

Чтобы ответить на ваш вопрос о работе со словарями, обратите внимание, что 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'])

вам не нужно помещать () вокруг выражений генератора, если они не являются единственным аргументом, а [] заставляет выражение генератора материализовать список, что делает dict([(k,v) for k,v in d.items()]) намного медленнее, чем dict(((k,v) for k,v in d.items()))

Dan D. 04.03.2011 13:40

Вы также можете перебирать список в обратном направлении:

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

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

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

Это действительно инновационное и питоническое решение. Я люблю это!

richo 07.12.2009 07:13

Работает ли это, если в списке есть дубликаты (соответствующие предикату)?

Jon-Eric 04.09.2012 20:58

@ Джон-Эрик: да, работает. Если есть дубликат, то первый удаляется, список сокращается, и reversed() возвращает тот же name во второй раз. Это алгоритм O (n ** 2), в отличие от принятый ответ, который использует алгоритм O (n).

jfs 24.05.2014 01:18

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

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

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

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

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

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

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

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

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

Действительно интересное открытие производительности. Не могли бы вы рассказать подробнее о своей среде профилирования и методах оценки?

Drake Guan 02.03.2012 05:39

Бьюсь об заклад, вы могли бы сделать это еще быстрее, используя not name.endswith('Smith') вместо создания среза на каждой итерации. В любом случае, это ценная информация, которую я, вероятно, никогда бы не нашел, если бы не ваш ответ, спасибо.

notbad.jpeg 03.07.2014 05:27

предложение names[:] было особенно полезно для использования с os.walk для фильтрации имен каталогов для прохождения

wowest 18.06.2015 05:49

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

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

для удаления конечных элементов: del original_list[new_list_size:]

jfs 03.03.2013 10:23

Вот моя реализация 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

Другие вопросы по теме