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

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

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

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

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

 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) 

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

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

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

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

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

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

 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 [] 
 for i in xrange(len(somelist) - 1, -1, -1): if some_condition(somelist, i): del somelist[i] 

Вам нужно идти назад, иначе это немного похоже на распиливание ветки дерева, на которой вы сидите 🙂

Ваш лучший подход к такому примеру – это понимание списка

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

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

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

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

или

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

Официальный учебник Python 2 4.2. «Заявления» гласит :

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

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

что было предложено по адресу: https://stackoverflow.com/a/1207427/895245

Документация Python 2 7.3. «Заявление» дает тот же совет :

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

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

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

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

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

так:

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

и во избежание повторного кодирования всего проекта с новым именем списка:

 originalList[:] = newList 

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

copy.copy (x) Вернуть мелкую копию x.

copy.deepcopy (x) Верните глубокую копию x.

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

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

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

Этот ответ был первоначально написан в ответ на вопрос, который с тех пор был отмечен как дубликат: удаление координат из списка на 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 "", line 3, in  TypeError: remove() takes exactly one argument (2 given) 

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

 >>> L1[1] (5, 6) >>> type(L1[1])  

Ага! Каждый элемент L1 на самом деле является кортежем. Так вот что нам нужно передать remove (). Кортежи в 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. Функция reverse ():

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

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

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

 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 , индексы, которые вы собираетесь удалить позже, не изменяются на вас.

Вы можете использовать filter() ansible как встроенный.

Подробнее здесь.

Вы можете попробовать for-looping в обратном порядке, поэтому для 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) 

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

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

“ `

 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 

“ `

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 , это всего лишь один файл, поэтому не нужно устанавливать git. Нет инсталлятора, поэтому вам нужно убедиться, что файл находится на пути 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 для объекта списка текучей среды. Также применяются другие обычные итерационные методы, такие как del fluidL[i] , .remove , .insert , .append , .extend . Список также можно изменить с помощью срезов (методы sort и reverse копирования не реализованы).

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

т.е.

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

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

 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 просто предоставляет оболочку для исходного объекта списка. К исходному объекту можно обращаться как к свойству жидкого объекта:

 originalList = fluidArr.fixedIterable 

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

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


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

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

т.е.

 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] 

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

 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 

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

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

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

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

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

 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) 

That should be significantly faster than anything else.

Right away you want to create a copy of the list so you can have that as a reference when you are iterating through and deleting tuples in that list that meet a certain criteria.

Then it depends on what type of list you want for the output whether that be a list of the removed tuples or a list of the tuples that are not removed.

As David pointed out, I recommend list comprehension to keep the elements you don’t want to remove.

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

Готово ли Mono в прайм-тайм?

Где находится файловая система подсистемы Linux, расположенная в Windows 10?

Случайный массив с использованием LINQ и C #

Получить процентное использование процессора

Обновление Force GUI из UI Thread

«Недопустимый примитив JSON» в обработке Ajax

Как вы реализуете повторную попытку?

Что означает основной конец ассоциации в соотношении 1: 1 в структуре Entity

Как немедленно завершить блокировку streamа при операции ввода-вывода сокета?

Анимированный gif не работает в наложении MKMapView с использованием MKOverlayRenderer

Состав и группа LINQ по периодам времени

Слияние MS Word 2007 с ошибками происходит в почтовых кодах с ведущими нулями (например, 01234)

как я могу добавить файл helpl в студию Android (из примера выставления счетов в приложении)

Удалите защиту от записи с флэш-диска USB

Как конвертировать или записывать файл изображения .DMG в Windows?

Давайте будем гением компьютера.