Как удалить элементы из списка во время итерации? - PullRequest
853 голосов
/ 30 июля 2009

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

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

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

Ответы [ 26 ]

4 голосов
/ 18 августа 2014

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

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

4 голосов
/ 09 января 2011

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

Для более подробной информации проверьте здесь

3 голосов
/ 10 июля 2015

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

3 голосов
/ 02 сентября 2016

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

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
3 голосов
/ 23 октября 2018

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

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

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

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

2 голосов
/ 16 декабря 2015

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

`` `

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 голосов
/ 17 мая 2016

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

1052 * т.е. *

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

originalList = fluidArr.fixedIterable

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

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


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

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

* * Тысяча семьдесят-семь т.е.
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]
1 голос
/ 30 апреля 2019

цикл 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]

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

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

, который поддерживает все повторяемые значения, такие как list, tuple, dict, string и т. Д.

1 голос
/ 03 декабря 2018

Я могу придумать три подхода для решения вашей проблемы. В качестве примера я создам случайный список кортежей 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. Я предпочитаю метод2 . Для вышеупомянутого примера, time(method1) : time(method2) : time(method3) = 1 : 1 : 1.7

1 голос
/ 10 ноября 2018

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

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

...