Сведения о вопросе

Drake

07:59, 2nd August, 2020

Теги

python   optimization   set   series    

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

Просмотров: 530   Ответов: 14

Когда я пишу код в 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

Это неэффективно, довольно уродливо и, возможно, глючно (как он обрабатывает несколько записей 'John Smith'?). Есть ли у кого-нибудь более элегантное решение или, по крайней мере, более эффективное?

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



  Сведения об ответе

KOMP

12:21, 4th August, 2020

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

  1. Использование filter :

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

  2. Используя списочные включения :

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

Обратите внимание , что оба случая сохраняют значения, для которых функция предиката вычисляет значение True, поэтому вам нужно изменить логику (т. е. вы говорите "keep the people who do not have the last name Smith" вместо "удалить людей, у которых есть фамилия Смит").

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


  Сведения об ответе

P_S_S

16:24, 17th August, 2020

Кроме того, вы можете выполнять обратные итерации по списку:

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

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

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


  Сведения об ответе

P_S_S

08:11, 20th August, 2020

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

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

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

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

Присвоение "names[:]" в основном означает "replace the contents of the names list with the following value". Он отличается от простого присвоения имен тем, что не создает новый объект списка. Правая сторона присваивания является генераторным выражением (обратите внимание на использование круглых скобок, а не квадратных скобок). Это приведет к тому, что Python будет повторяться по всему списку.

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

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


  Сведения об ответе

SEEYOU

12:34, 7th August, 2020

Используя список осмысления

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


  Сведения об ответе

dump

11:12, 23rd August, 2020

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

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

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


  Сведения об ответе

PIRLO

08:06, 17th August, 2020

фильтр был бы потрясающим для этого. Простой пример:

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

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


  Сведения об ответе

dump

11:46, 16th August, 2020

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


  Сведения об ответе

baggs

17:19, 13th August, 2020

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

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


  Сведения об ответе

SILA

07:05, 21st August, 2020

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

Редактировать: На самом деле, решение на уровне 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)]


  Сведения об ответе

SEEYOU

12:00, 4th August, 2020

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


  Сведения об ответе

PIRLO

14:57, 15th August, 2020

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

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


  Сведения об ответе

lats

08:22, 13th August, 2020

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


  Сведения об ответе

lool

19:07, 2nd August, 2020

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

  • Они делают копию вашего списка и возвращают новый, и это будет неэффективно, когда исходный список действительно большой
  • Они могут быть действительно громоздкими, когда критерии выбора элементов (в вашем случае, если 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

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


  Сведения об ответе

lesha

03:58, 5th August, 2020

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

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

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


Ответить на вопрос

Чтобы ответить на вопрос вам нужно войти в систему или зарегистрироваться