Как удалить элементы из списка во время итерации?

934

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

for tup in somelist:
    if determine(tup):
         code_to_remove_tup

Что я должен использовать вместо code_to_remove_tup? Я не могу понять, как удалить предмет таким способом.

lfaraone
источник
Большинство ответов на этой странице на самом деле не объясняют, почему удаление элементов во время итерации по списку приводит к странным результатам, но принятый ответ в этом вопросе дает , и, вероятно, это лучший обман для начинающих, которые сталкиваются с этой проблемой впервые.
Ггорлен

Ответы:

828

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

somelist = [x for x in somelist if not determine(x)]

Или, назначив фрагменту somelist[:], вы можете изменить существующий список, чтобы он содержал только те элементы, которые вы хотите:

somelist[:] = [x for x in somelist if not determine(x)]

Этот подход может быть полезен, если есть другие ссылки, somelistкоторые должны отражать изменения.

Вместо понимания вы также можете использовать itertools. В Python 2:

from itertools import ifilterfalse
somelist[:] = ifilterfalse(determine, somelist)

Или в Python 3:

from itertools import filterfalse
somelist[:] = filterfalse(determine, somelist)

Для ясности и для тех, кто находит использование [:]обозначений хакерским или нечетким, вот более явная альтернатива. Теоретически, он должен выполнять то же самое в отношении пространства и времени, чем указанные выше строки.

temp = []
while somelist:
    x = somelist.pop()
    if not determine(x):
        temp.append(x)
while temp:
    somelist.append(templist.pop())

Он также работает на других языках, которые могут не иметь возможности замены элементов в списках Python, с минимальными изменениями. Например, не все языки приводят пустые списки к a, Falseкак это делает Python. Вы можете заменить while somelist:что-то более явное, как while len(somelist) > 0:.

Дэвид Разник
источник
4
Можете ли вы сделать это быстрее, если вы знаете, что будут удалены только некоторые из них, т. Е. Удаляете только те и оставляете другие на месте, а не переписываете их?
highBandWidth
20
Что если мой список огромен и не может позволить себе сделать копию?
jpcgt
15
@jpcgt Вы должны использовать somelist[:] = (x for x in somelist if determine(x))это, чтобы создать генератор, который не может создавать ненужные копии.
Ростислав Кондратенко
8
@RostislavKondratenko: list_ass_slice()функция, которая реализует somelist[:]=вызовы PySequence_Fast()внутри. Эта функция всегда возвращает список, т. Е. Решение @Alex Martelli, которое уже использует список вместо генератора, скорее всего, более эффективно
jfs
6
Не могли бы вы объяснить, в чем разница между назначением списка и списком клонов, пожалуйста? Разве первоначальный список не somelistбудет видоизменяться в обоих методах?
Боуэн Лю
589

Ответы, предполагающие понимание списка, почти верны - за исключением того, что они строят совершенно новый список и затем дают ему то же имя, что и старый список, они НЕ изменяют старый список на месте. Это отличается от того, что вы делаете при выборочном удалении, как в предложении @ Lennart - это быстрее, но если к вашему списку обращаются по нескольким ссылкам, тот факт, что вы просто повторно устанавливаете одну из ссылок и НЕ изменяете объект списка само по себе может привести к тонким, катастрофическим ошибкам.

К счастью, очень легко получить как скорость понимания списка, так и требуемую семантику изменения на месте - просто код:

somelist[:] = [tup for tup in somelist if determine(tup)]

Обратите внимание на небольшую разницу с другими ответами: этот НЕ присваивается пустому имени - он присваивает фрагменту списка, который просто является целым списком, тем самым заменяя содержимое списка в том же объекте списка Python , а не просто перезаписывая одну ссылку (из предыдущего объекта списка в новый объект списка), как и другие ответы.

Алекс Мартелли
источник
1
Как мне сделать то же самое нарезанное задание с помощью dict? В Python 2.6?
PaulMcG
11
@Paul: Поскольку диктовки неупорядочены, ломтики не имеют смысла для диктовок. Если вы хотите заменить содержимое dict aсодержимым dict b, используйте a.clear(); a.update(b).
Свен Марнах
1
Почему можно «переустановить» одну из ссылок, заменив то, что переменная ссылается на ошибки? Похоже, что это будет потенциальной проблемой только в многопоточных приложениях, а не в однопоточных.
Дерек Дамер
59
@Derek x = ['foo','bar','baz']; y = x; x = [item for item in x if determine(item)];Это переназначает xрезультат понимания списка, но yвсе еще ссылается на исходный список ['foo','bar','baz']. Если вы ожидали xи yсослаться на тот же список, возможно, вы ввели ошибки. Вы это предотвратить путем присвоения ломтика всего списка, как Алекс показывает, и я показываю здесь: x = ["foo","bar","baz"]; y = x; x[:] = [item for item in x if determine(item)];. Список изменен на месте. обеспечение того, чтобы все ссылки на список (как xи yздесь) ссылались на новый список.
Стивен Т. Снайдер
на самом деле, использование filterфункции тоже создает новый список, не изменяет элементы на месте ... толькоolist[:] = [i for i in olist if not dislike(i)]
Джон Строуд
304

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

Например (зависит от того, какой тип списка):

for tup in somelist[:]:
    etc....

Пример:

>>> somelist = range(10)
>>> for x in somelist:
...     somelist.remove(x)
>>> somelist
[1, 3, 5, 7, 9]

>>> somelist = range(10)
>>> for x in somelist[:]:
...     somelist.remove(x)
>>> somelist
[]
Леннарт Регебро
источник
13
@Zen Потому что второй перебирает копию списка. Поэтому, когда вы изменяете исходный список, вы не изменяете копию, которую вы повторяете.
Леннарт Регебро
3
Что лучше сделать в somelist [:] по сравнению со списком (somelist)?
Мариуш Джамро
3
list(somelist)преобразует итерируемый в список. somelist[:]делает копию объекта, который поддерживает нарезку. Таким образом, они не обязательно делают то же самое. В этом случае я хочу сделать копию somelistобъекта, поэтому я использую[:]
Lennart Regebro
33
Примечание для тех, кто читает это, это ОЧЕНЬ медленно для списков. remove()должен пройти через ВЕСЬ список для каждой итерации, так что это займет вечность.
vitiral
7
Большое время не имеет значения при работе со списками только из дюжины предметов. Зачастую понятные и простые для понимания будущие программисты гораздо ценнее производительности.
Стив
128
for i in range(len(somelist) - 1, -1, -1):
    if some_condition(somelist, i):
        del somelist[i]

Тебе нужно идти задом наперед, иначе это похоже на спиливание ветки дерева, на котором ты сидишь :-)

Пользователи Python 2: замените range, xrangeчтобы избежать создания жестко закодированного списка

Джон Мачин
источник
13
В последних версиях Python вы можете сделать это еще более аккуратно с помощью reversed()встроенной функции
ncoghlan,
16
reversed () не создает новый список, он создает обратный итератор для предоставленной последовательности. Как и enumerate (), вы должны заключить его в list (), чтобы фактически получить список из него. Вы можете думать о отсортирован (), который делает создать новый список каждый раз , когда (он должен, так что он может сортировать его).
ncoghlan
1
@Mauris, потому что enumerateвозвращает итератор и reversedожидает последовательность. Я думаю, вы могли бы сделать, reversed(list(enumerate(somelist)))если вы не против создать дополнительный список в памяти.
drevicko
2
Это O (N * M) для массивов, это очень медленно, если вы удаляете много элементов из большого списка. Так что не рекомендуется.
Сэм Уоткинс
2
@ SamWatkins Да, этот ответ для того, когда вы удаляете пару элементов из очень большого массива. Меньше использования памяти, но это может быть в mразы медленнее.
Навин
52

Официальное руководство по Python 2 4.2. "для заявлений"

https://docs.python.org/2/tutorial/controlflow.html#for-statements

Эта часть документов дает понять, что:

  • вам нужно сделать копию итеративного списка, чтобы изменить его
  • Один из способов сделать это с помощью обозначения среза [:]

Если вам нужно изменить последовательность, которую вы повторяете внутри цикла (например, для дублирования выбранных элементов), рекомендуется сначала сделать копию. Итерация по последовательности неявно делает копию. Обозначение среза делает это особенно удобным:

>>> words = ['cat', 'window', 'defenestrate']
>>> for w in words[:]:  # Loop over a slice copy of the entire list.
...     if len(w) > 6:
...         words.insert(0, w)
...
>>> words
['defenestrate', 'cat', 'window', 'defenestrate']

Документация по Python 2 7.3. "За заявление"

https://docs.python.org/2/reference/compound_stmts.html#for

Эта часть документов еще раз говорит о том, что вы должны сделать копию, и приводит фактический пример удаления:

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

for x in a[:]:
    if x < 0: a.remove(x)

Однако я не согласен с этой реализацией, поскольку .remove()приходится перебирать весь список, чтобы найти значение.

Лучшие обходные пути

Или:

  • начать новый массив с нуля и .append()вернуться в конец: https://stackoverflow.com/a/1207460/895245

    Это экономит время, но экономит меньше места, поскольку сохраняет копию массива во время итерации.

  • использовать delс индексом: https://stackoverflow.com/a/1207485/895245

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

    Это означает, что удаление предмета требует сдвига всех следующих предметов назад на один, что является O (N).

Как правило, вы просто хотите .append()выбрать более быстрый вариант по умолчанию, если только память не представляет большой проблемы.

Мог ли Python сделать это лучше?

Похоже, что этот конкретный Python API может быть улучшен. Сравните это, например, с:

  • Java ListIterator :: удалить какие документы "Этот вызов может быть сделан только один раз за вызов следующего или предыдущего"
  • C ++, std::vector::eraseкоторый возвращает действительный интегратор элемента после того, как тот удален

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

Возможно, основное обоснование заключается в том, что списки Python предполагаются с поддержкой динамического массива, и, следовательно, любой тип удаления будет неэффективным по времени в любом случае, в то время как Java имеет более приятную иерархию интерфейса с обеими реализациями ArrayListи LinkedListреализациями ListIterator.

Похоже, в Python stdlib нет явного связанного типа списка: Python Linked List

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
источник
48

Лучшим подходом для такого примера будет понимание списка

somelist = [tup for tup in somelist if determine(tup)]

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

newlist = []
for tup in somelist:
    # lots of code here, possibly setting things up for calling determine
    if determine(tup):
        newlist.append(tup)
somelist = newlist

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

for tup in somelist[:]:
    # lots of code here, possibly setting things up for calling determine
    if determine(tup):
        newlist.append(tup)
Эли Кортрайт
источник
37

Для тех, кто любит функциональное программирование:

somelist[:] = filter(lambda tup: not determine(tup), somelist)

или

from itertools import ifilterfalse
somelist[:] = list(ifilterfalse(determine, somelist))
Cide
источник
1. Понимание списка и выражения генератора заимствованы из Хаскелла, чисто функционального языка; они так же функциональны, как filterи Pythonic. 2. Если вам нужно lambdaиспользовать mapили filter, список comp или genexpr всегда лучший вариант; mapи filterможет быть немного быстрее, когда функция преобразования / предиката является встроенной в C Python, реализованной в C, и итерация не является ничтожно малой, но они всегда медленнее, когда вам нужно, lambdaчто listcomp / genexpr может избежать.
ShadowRanger
13

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

array = [lots of stuff]
arraySize = len(array)
i = 0
while i < arraySize:
    if someTest(array[i]):
        del array[i]
        arraySize -= 1
    else:
        i += 1

Что я не знаю, так это то, насколько эффективна пара удалений по сравнению с копированием большого списка. Пожалуйста, прокомментируйте, если у вас есть понимание.

Майкл
источник
В моем случае мне нужно переместить эти «нежелательные» элементы в другой список. Есть ли у вас какие-либо новые комментарии об этом решении? Я также думаю, что лучше использовать некоторые удаления, а не дублировать список.
gustavovelascoh
Это правильный ответ, если производительность является проблемой (хотя и @Alexey). Тем не менее, выбор listструктуры данных в первую очередь должен быть тщательно продуман, поскольку удаление из середины списка занимает линейное время по длине списка. Если вам действительно не нужен произвольный доступ к k-му последовательному элементу, возможно, подумайте OrderedDict?
максимум
@GVelascoh почему бы не создать newlist = [], а потом newlist.append(array[i])просто раньше del array[i]?
максимум
2
Обратите внимание, что это, вероятно, неэффективно по времени: если list()это связанный список, произвольный доступ дорог, если list()массив, удаление дорого, поскольку они требуют перемещения всех следующих элементов вперед. Приличный итератор может помочь в реализации связанного списка. Это, однако, может быть эффективным с точки зрения пространства.
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
10

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

так:

for item in originalList:
   if (item != badValue):
        newList.append(item)

и чтобы избежать необходимости перекодировать весь проект с новым именем списков:

originalList[:] = newList

обратите внимание, из документации Python:

copy.copy (x) Возвращает мелкую копию x.

copy.deepcopy (x) Возвращает глубокую копию x.

ntk4
источник
3
Это не добавляет никакой новой информации, которой не было в принятом ответе годами ранее.
Марк Амери
2
Это просто и просто еще один способ взглянуть на проблему @MarkAmery. Это менее сжато для тех людей, которые не любят синтаксис сжатого кодирования.
ntk4
9

Этот ответ был первоначально написан в ответ на вопрос, который был помечен как дубликат: удаление координат из списка на python

В вашем коде есть две проблемы:

1) При использовании remove () вы пытаетесь удалить целые числа, тогда как вам нужно удалить кортеж.

2) Цикл for пропустит элементы в вашем списке.

Давайте разберемся, что происходит, когда мы выполняем ваш код:

>>> L1 = [(1,2), (5,6), (-1,-2), (1,-2)]
>>> for (a,b) in L1:
...   if a < 0 or b < 0:
...     L1.remove(a,b)
... 
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
TypeError: remove() takes exactly one argument (2 given)

Первая проблема заключается в том, что вы передаете оба «a» и «b» для remove (), но remove () принимает только один аргумент. Итак, как мы можем заставить remove () правильно работать с вашим списком? Нам нужно выяснить, что представляет собой каждый элемент вашего списка. В этом случае каждый из них является кортежем. Чтобы увидеть это, давайте перейдем к одному элементу списка (индексация начинается с 0):

>>> L1[1]
(5, 6)
>>> type(L1[1])
<type 'tuple'>

Ага! Каждый элемент L1 на самом деле является кортежем. Так вот что мы должны передать, чтобы удалить (). Кортежи в python очень просты, они просто создаются путем заключения значений в скобки. «a, b» не является кортежем, но «(a, b)» является кортежем. Поэтому мы модифицируем ваш код и запускаем его снова:

# The remove line now includes an extra "()" to make a tuple out of "a,b"
L1.remove((a,b))

Этот код выполняется без ошибок, но давайте посмотрим на список, который он выводит:

L1 is now: [(1, 2), (5, 6), (1, -2)]

Почему (1, -2) все еще в вашем списке? Оказывается, изменение списка при использовании цикла для его перебора - очень плохая идея без особой осторожности. Причина того, что (1, -2) остается в списке, заключается в том, что местоположения каждого элемента в списке менялись между итерациями цикла for. Давайте посмотрим, что произойдет, если мы добавим приведенный выше код в более длинный список:

L1 = [(1,2),(5,6),(-1,-2),(1,-2),(3,4),(5,7),(-4,4),(2,1),(-3,-3),(5,-1),(0,6)]
### Outputs:
L1 is now: [(1, 2), (5, 6), (1, -2), (3, 4), (5, 7), (2, 1), (5, -1), (0, 6)]

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

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

L2 = L1
for (a,b) in L1:
    if a < 0 or b < 0 :
        L2.remove((a,b))
# Now, remove the original copy of L1 and replace with L2
print L2 is L1
del L1
L1 = L2; del L2
print ("L1 is now: ", L1)

Однако выходные данные будут идентичны предыдущим:

'L1 is now: ', [(1, 2), (5, 6), (1, -2), (3, 4), (5, 7), (2, 1), (5, -1), (0, 6)]

Это потому, что когда мы создали L2, python фактически не создавал новый объект. Вместо этого он просто ссылался на L2 на тот же объект, что и L1. Мы можем проверить это с помощью «is», которое отличается от просто «равно» (==).

>>> L2=L1
>>> L1 is L2
True

Мы можем сделать настоящую копию, используя copy.copy (). Тогда все работает как положено:

import copy
L1 = [(1,2), (5,6),(-1,-2), (1,-2),(3,4),(5,7),(-4,4),(2,1),(-3,-3),(5,-1),(0,6)]
L2 = copy.copy(L1)
for (a,b) in L1:
    if a < 0 or b < 0 :
        L2.remove((a,b))
# Now, remove the original copy of L1 and replace with L2
del L1
L1 = L2; del L2
>>> L1 is now: [(1, 2), (5, 6), (3, 4), (5, 7), (2, 1), (0, 6)]

Наконец, есть одно более чистое решение, чем создание совершенно новой копии L1. Обратный () функция:

L1 = [(1,2), (5,6),(-1,-2), (1,-2),(3,4),(5,7),(-4,4),(2,1),(-3,-3),(5,-1),(0,6)]
for (a,b) in reversed(L1):
    if a < 0 or b < 0 :
        L1.remove((a,b))
print ("L1 is now: ", L1)
>>> L1 is now: [(1, 2), (5, 6), (3, 4), (5, 7), (2, 1), (0, 6)]

К сожалению, я не могу адекватно описать, как работает reversed (). Он возвращает объект 'listreverseiterator', когда ему передается список. В практических целях вы можете думать об этом как о создании обратной копии аргумента. Это решение, которое я рекомендую.

Cinghiale
источник
4

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

inlist = [{'field1':10, 'field2':20}, {'field1':30, 'field2':15}]    
for idx, i in enumerate(inlist):
    do some stuff with i['field1']
    if somecondition:
        xlist.append(idx)
for i in reversed(xlist): del inlist[i]

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

fantabolous
источник
Почему получение индекса более актуально в случае, когда у вас есть список диктов, чем в случае любого другого вида списка? Насколько я могу судить, это не имеет смысла.
Марк Амери
4

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

Прежде всего вам нужно заменить цикл foreach на цикл while ,

i = 0
while i < len(somelist):
    if determine(somelist[i]):
         del somelist[i]
    else:
        i += 1

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

Муджиб
источник
3

Вы можете попробовать выполнить цикл в обратном порядке, поэтому для some_list вы будете делать что-то вроде:

list_len = len(some_list)
for i in range(list_len):
    reverse_i = list_len - 1 - i
    cur = some_list[reverse_i]

    # some logic with cur element

    if some_condition:
        some_list.pop(reverse_i)

Таким образом, индекс выравнивается и не страдает от обновления списка (независимо от того, используете ли вы элемент cur или нет).

Queequeg
источник
Зацикливание reversed(list(enumerate(some_list)))будет проще, чем вычисление индексов самостоятельно.
Марк Амери
@MarkAmery не думаю, что вы можете изменить список таким образом.
Queequeg
3

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

alist = ['good', 'bad', 'good', 'bad', 'good']
i = 0
for x in alist[:]:
    if x == 'bad':
        alist.pop(i)
        i -= 1
    # do something cool with x or just print x
    print(x)
    i += 1
Алексей
источник
Вы действительно должны просто использовать понимания. Их гораздо проще понять.
Beefster
Что если я захочу что-то удалить bad, что-то с этим сделать, а также что-то сделать goodв одном цикле?
Алексей
1
На самом деле, я понял, что здесь есть некоторая хитрость в том, что вы делаете копию списка с открытым срезом ( alist[:]). И поскольку вы, возможно, делаете что-то необычное, у него действительно есть сценарий использования. Хороший пересмотр - это хорошо. Возьми мой голос.
Beefster
2

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

`` `

k = range(5)
v = ['a','b','c','d','e']
d = {key:val for key,val in zip(k, v)}

print d
for i in range(5):
    print d[i]
    d.pop(i)
print d

`` `

Рафа
источник
2

TLDR:

Я написал библиотеку, которая позволяет вам сделать это:

from fluidIter import FluidIterable
fSomeList = FluidIterable(someList)  
for tup in fSomeList:
    if determine(tup):
        # remove 'tup' without "breaking" the iteration
        fSomeList.remove(tup)
        # tup has also been removed from 'someList'
        # as well as 'fSomeList'

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

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


Полный ответ:

Редактировать: последний пример кода в этом ответе дает пример использования того, почему вы можете захотеть изменить список на месте, а не использовать его для понимания. Первая часть ответов служит руководством к тому, как можно изменить массив на месте.

Решение следует из этого ответа (для связанного вопроса) от senderle. Что объясняет, как индекс массива обновляется при переборе списка, который был изменен. Приведенное ниже решение предназначено для правильного отслеживания индекса массива, даже если список изменен.

Скачать fluidIter.pyс здесь https://github.com/alanbacon/FluidIterator , это просто один файл поэтому нет необходимости устанавливать мерзавца. Установщика не существует, поэтому вам нужно убедиться, что файл находится в пути python. Код был написан для Python 3 и не тестировался на Python 2.

from fluidIter import FluidIterable
l = [0,1,2,3,4,5,6,7,8]  
fluidL = FluidIterable(l)                       
for i in fluidL:
    print('initial state of list on this iteration: ' + str(fluidL)) 
    print('current iteration value: ' + str(i))
    print('popped value: ' + str(fluidL.pop(2)))
    print(' ')

print('Final List Value: ' + str(l))

Это даст следующий результат:

initial state of list on this iteration: [0, 1, 2, 3, 4, 5, 6, 7, 8]
current iteration value: 0
popped value: 2

initial state of list on this iteration: [0, 1, 3, 4, 5, 6, 7, 8]
current iteration value: 1
popped value: 3

initial state of list on this iteration: [0, 1, 4, 5, 6, 7, 8]
current iteration value: 4
popped value: 4

initial state of list on this iteration: [0, 1, 5, 6, 7, 8]
current iteration value: 5
popped value: 5

initial state of list on this iteration: [0, 1, 6, 7, 8]
current iteration value: 6
popped value: 6

initial state of list on this iteration: [0, 1, 7, 8]
current iteration value: 7
popped value: 7

initial state of list on this iteration: [0, 1, 8]
current iteration value: 8
popped value: 8

Final List Value: [0, 1]

Выше мы использовали popметод объекта списка флюидов. Другие общие Iterable также реализованы методы , такие как del fluidL[i], .remove, .insert, .append, .extend. Список также можно изменить с помощью срезов ( sortиreverse методы не реализованы).

Единственным условием является то, что вы должны только изменить список на месте, если в какой-то момент fluidLили если он lбыл переназначен на другой объект списка, код не будет работать. Исходный fluidLобъект по-прежнему будет использоваться циклом for, но мы не сможем его изменить.

т.е.

fluidL[2] = 'a'   # is OK
fluidL = [0, 1, 'a', 3, 4, 5, 6, 7, 8]  # is not OK

Если мы хотим получить доступ к текущему значению индекса списка, мы не можем использовать перечисление, поскольку это только подсчитывает, сколько раз цикл for выполнялся. Вместо этого мы будем использовать объект итератора напрямую.

fluidArr = FluidIterable([0,1,2,3])
# get iterator first so can query the current index
fluidArrIter = fluidArr.__iter__()
for i, v in enumerate(fluidArrIter):
    print('enum: ', i)
    print('current val: ', v)
    print('current ind: ', fluidArrIter.currentIndex)
    print(fluidArr)
    fluidArr.insert(0,'a')
    print(' ')

print('Final List Value: ' + str(fluidArr))

Это выведет следующее:

enum:  0
current val:  0
current ind:  0
[0, 1, 2, 3]

enum:  1
current val:  1
current ind:  2
['a', 0, 1, 2, 3]

enum:  2
current val:  2
current ind:  4
['a', 'a', 0, 1, 2, 3]

enum:  3
current val:  3
current ind:  6
['a', 'a', 'a', 0, 1, 2, 3]

Final List Value: ['a', 'a', 'a', 'a', 0, 1, 2, 3]

FluidIterableКласс просто предоставляет обертку для исходного объекта списка. Доступ к исходному объекту можно получить как свойство объекта Fluid, например:

originalList = fluidArr.fixedIterable

Больше примеров / тестов можно найти в if __name__ is "__main__":разделе внизу fluidIter.py. На них стоит посмотреть, потому что они объясняют, что происходит в различных ситуациях. Например: замена больших разделов списка с использованием фрагмента. Или используя (и модифицируя) ту же итерацию во вложенных циклах for.

Как я уже сказал, для начала: это сложное решение, которое ухудшит читабельность вашего кода и затруднит его отладку. Поэтому другие решения , такие как списковые упомянутый в Дэвиде Raznick в ответе следует считать первым. При этом я нашел случаи, когда этот класс был полезен для меня и его было проще использовать, чем отслеживать индексы элементов, которые необходимо удалить.


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

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

т.е.

newList = [i for i in oldList if testFunc(i)]

Но что, если результат testFuncзависит от уже добавленных элементов newList? Или элементы все еще вoldList этом, могут быть добавлены далее? Может все же быть способ использовать понимание списка, но он начнет терять свою элегантность, и для меня легче изменить список на месте.

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

randInts = [70, 20, 61, 80, 54, 18, 7, 18, 55, 9]
fRandInts = FluidIterable(randInts)
fRandIntsIter = fRandInts.__iter__()
# for each value in the list (outer loop)
# test against every other value in the list (inner loop)
for i in fRandIntsIter:
    print(' ')
    print('outer val: ', i)
    innerIntsIter = fRandInts.__iter__()
    for j in innerIntsIter:
        innerIndex = innerIntsIter.currentIndex
        # skip the element that the outloop is currently on
        # because we don't want to test a value against itself
        if not innerIndex == fRandIntsIter.currentIndex:
            # if the test element, j, is a multiple 
            # of the reference element, i, then remove 'j'
            if j%i == 0:
                print('remove val: ', j)
                # remove element in place, without breaking the
                # iteration of either loop
                del fRandInts[innerIndex]
            # end if multiple, then remove
        # end if not the same value as outer loop
    # end inner loop
# end outerloop

print('')
print('final list: ', randInts)

Результат и окончательный сокращенный список показаны ниже

outer val:  70

outer val:  20
remove val:  80

outer val:  61

outer val:  54

outer val:  18
remove val:  54
remove val:  18

outer val:  7
remove val:  70

outer val:  55

outer val:  9
remove val:  18

final list:  [20, 61, 7, 55, 9]
Резонанс
источник
Трудно сказать, чрезмерно ли это спроектировано, потому что неясно, какую проблему он пытается решить; что достигается удалением элементов с использованием этого подхода, чего some_list[:] = [x for x in some_list if not some_condition(x)]не достигается? Без ответа на этот вопрос, почему кто-то должен верить, что загрузка и использование вашей 600-строчной библиотеки с опечатками и закомментированным кодом является лучшим решением их проблемы, чем однострочная? -1.
Марк Амери
@MarkAmery. Основной вариант использования, когда это происходит при попытке определить, должен ли элемент быть удален (или добавлен или перемещен), основываясь не только на самом элементе, но и на состоянии другого элемента в списке или на состоянии списка как все. Например, с помощью списочных представлений невозможно написать что-то вроде, some_list[:] = [x for x in some_list if not some_condition(y)]где yнаходится другой элемент списка x. И не было бы возможности написать some_list[:] = [x for x in some_list if not some_condition(intermediateStateOf_some_list)].
Резонанс
2

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

Filterполучает функцию и последовательность. Filterприменяет переданную функцию к каждому элементу по очереди, а затем решает, следует ли сохранить или отбросить элемент в зависимости от того, является ли возвращаемое значение функции Trueили False.

Вот пример (получите шансы в кортеже):

list(filter(lambda x:x%2==1, (1, 2, 4, 5, 6, 9, 10, 15)))  
# result: [1, 5, 9, 15]

Внимание: вы также не можете обрабатывать итераторы. Итераторы иногда лучше, чем последовательности.

chseng
источник
2

цикл for будет проходить через индекс.

считай, у тебя есть список,

[5, 7, 13, 29, 65, 91]

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

ваша переменная

lis = [5, 7, 13, 29, 35, 65, 91]
       0  1   2   3   4   5   6

во время 5-й итерации,

Ваш номер 35 не был простым, поэтому вы удалили его из списка.

lis.remove(y)

а затем следующее значение (65) перейти к предыдущему индексу.

lis = [5, 7, 13, 29, 65, 91]
       0  1   2   3   4   5

так что 4-я итерация сделала указатель переместился на 5-ую ..

вот почему ваш цикл не покрывает 65, так как он переместился в предыдущий индекс.

поэтому вы не должны ссылаться на список в другой переменной, которая все еще ссылается на оригинал, а не на копию.

ite = lis #dont do it will reference instead copy

поэтому сделайте копию списка, используя list[::]

теперь тебе это даст,

[5, 7, 13, 29]

Проблема в том, что вы удалили значение из списка во время итерации, после чего индекс вашего списка свернется.

так что вы можете попробовать понимание вместо этого.

который поддерживает все повторяемые как, список, кортеж, dict, строка и т. д.

Мохидин бен Мухаммед
источник
Это помогло мне понять, почему мой код не работал.
Вахид Садик
2

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

Пример:

i = 0
length = len(list1)

while i < length:
    if condition:
        list1.remove(list1[i])
        i -= 1
        length -= 1

    i += 1
Без имени
источник
1

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

Однако есть один случай, когда безопасно удалить элементы из последовательности, которую вы повторяете: если вы удаляете только один элемент во время итерации. Это может быть обеспечено с помощью returnили break. Например:

for i, item in enumerate(lst):
    if item % 4 == 0:
        foo(item)
        del lst[i]
        break

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

Beefster
источник
1

Я могу придумать три подхода для решения вашей проблемы. В качестве примера я создам случайный список кортежей somelist = [(1,2,3), (4,5,6), (3,6,6), (7,8,9), (15,0,0), (10,11,12)]. Условие, которое я выбираю, таково sum of elements of a tuple = 15. В финальном списке у нас будут только те кортежи, чья сумма не равна 15.

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

Метод 1.> Используйте предложенный вами фреймворк (где заполняется код внутри цикла for). Я использую небольшой код delдля удаления кортежа, который удовлетворяет указанному условию. Однако этот метод пропускает кортеж (который удовлетворяет указанному условию), если два последовательно размещенных кортежа удовлетворяют данному условию.

for tup in somelist:
    if ( sum(tup)==15 ): 
        del somelist[somelist.index(tup)]

print somelist
>>> [(1, 2, 3), (3, 6, 6), (7, 8, 9), (10, 11, 12)]

Метод 2.> Создайте новый список, который содержит элементы (кортежи), где данное условие не выполняется (это то же самое, что удаление элементов списка, где выполняется данное условие). Ниже приведен код для этого:

newlist1 = [somelist[tup] for tup in range(len(somelist)) if(sum(somelist[tup])!=15)]

print newlist1
>>>[(1, 2, 3), (7, 8, 9), (10, 11, 12)]

Метод 3.> Найдите индексы, в которых выполняется данное условие, а затем используйте элементы удаления (кортежи), соответствующие этим индексам. Ниже приведен код для этого.

indices = [i for i in range(len(somelist)) if(sum(somelist[i])==15)]
newlist2 = [tup for j, tup in enumerate(somelist) if j not in indices]

print newlist2
>>>[(1, 2, 3), (7, 8, 9), (10, 11, 12)]

Метод 1 и метод 2 быстрее, чем метод 3 . Метод2 и метод3 более эффективны, чем метод1. Я предпочитаю method2 . Для вышеупомянутого примераtime(method1) : time(method2) : time(method3) = 1 : 1 : 1.7

Сиддхарт Сатпатия
источник
0

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

import numpy as np

orig_list = np.array([1, 2, 3, 4, 5, 100, 8, 13])

remove_me = [100, 1]

cleaned = np.delete(orig_list, remove_me)
print(cleaned)

Это должно быть значительно быстрее, чем все остальное.

CENTURION
источник
Из того, что я измерил, NumPy начинает работать быстрее для списков из более чем 20 элементов и достигает> 12-кратной более быстрой фильтрации для больших списков из 1000 элементов и более.
Георгий
0

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

Вот пример, где предварительное копирование списка некорректно, обратная итерация невозможна, и понимание списка также не вариант.

""" Sieve of Eratosthenes """

def generate_primes(n):
    """ Generates all primes less than n. """
    primes = list(range(2,n))
    idx = 0
    while idx < len(primes):
        p = primes[idx]
        for multiple in range(p+p, n, p):
            try:
                primes.remove(multiple)
            except ValueError:
                pass #EAFP
        idx += 1
        yield p
CodeKid
источник
0

Если вы будете использовать новый список позже, вы можете просто установить элемент в None, а затем судить о нем в последующем цикле, как это

for i in li:
    i = None

for elem in li:
    if elem is None:
        continue

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

Джон Чжан
источник
-1

открыть список чисел, и вы хотите удалить все не делятся на 3,

list_number =[i for i in range(100)]

используя list comprehension, это создаст новый список и создаст новое пространство памяти

new_list =[i for i in list_number if i%3!=0]

используя lambda filterфункцию, это создаст результирующий новый список и займет место памяти

new_list = list(filter(lambda x:x%3!=0, list_number))

без использования места в памяти для нового списка и изменения существующего списка

for index, value in enumerate(list_number):
    if list_number[index]%3==0:
        list_number.remove(value)
sahasrara62
источник