Як видалити елементи зі списку під час ітерації?


934

Я повторюю список кортежів у Python, і намагаюся видалити їх, якщо вони відповідають певним критеріям.

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

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


Більшість відповідей на цій сторінці насправді не пояснюють, чому видалення елементів під час повторення списку дає дивні результати, але прийнята відповідь у цьому питанні робить і, ймовірно, є кращою справою для початківців, які стикаються з цією проблемою вперше.
ggorlen

Відповіді:


827

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

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, з мінімальними модифікаціями. Наприклад, не всі мови видають порожні списки а, 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
Як я можу виконати те саме нарізане завдання з діктатом? У 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)]
Джон Strood

302

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

Наприклад (залежить від типу списку):

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
Що краще робити соліст [:] порівняно зі списком (сомеліст)?
Маріуш Джамро

3
list(somelist)перетворить ітерабельний в список. somelist[:]робить копію об'єкта, який підтримує нарізку. Тому вони не обов'язково роблять те саме. У цьому випадку я хочу зробити копію somelistоб'єкта, тому я використовую[:]
Lennart Regebro

33
Зауважте, хто це читає, це ДУЖЕ повільно для списків. remove()повинен перейти цілий список для кожної ітерації, тому це займе вічно.
витірал

7
Великий час O не має значення при роботі зі списками лише з десятка предметів. Майбутнім зрозумілим і простим для розуміння майбутніх програмістів є набагато цінніше, ніж продуктивність.
Стів

127
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 () не створює нового списку, він створює зворотний ітератор над наданою послідовністю. Як і перелічити (), вам доведеться загорнути його в список (), щоб фактично отримати список із нього. Ви можете думати про впорядкований (), який робить створити новий список кожен раз , коли (він повинен, так що він може сортувати його).
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 зробити це краще?

Схоже, саме цей API Python міг би бути покращений Порівняйте, наприклад, з:

  • Java ListIterator :: видаліть документи "Цей дзвінок можна здійснити лише один раз за виклик наступного чи попереднього"
  • C ++, std::vector::eraseякий повертає дійсний інтератор елементу після вилученого

і те, і інше дозволяє зрозуміти, що ви не можете змінювати ітераційний список, окрім самого ітератора, і дає вам ефективні способи зробити це без копіювання списку.

Можливо, основоположне обґрунтування полягає в тому, що списки Python вважаються підтримкою динамічного масиву, і тому будь-який тип видалення в будь-якому разі буде неефективним у той час, в той час як у Java є більш приємна ієрархія інтерфейсу з обома ArrayListі LinkedListреалізаціями ListIterator.

Здається, не існує явного зв'язаного типу списку в Python stdlib: Python Linked List


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

1. Зрозуміння списку та вирази генераторів запозичені з Haskell, чистої функціональної мови; вони точно такі ж функціональні, як filterі пітонічні. 2. Якщо вам потрібно lambdaвикористовувати mapабо filter, список comp або genexpr - завжди кращий варіант; mapі filterможе бути дещо швидшим, коли функція перетворення / предикат є вбудованою програмою Python, що реалізується на C, ітерабельна програма не є тривіально малою, але вони завжди повільніші, коли вам потрібно lambdaуникнути цього списку comp / 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()це масив, делети коштують дорого, оскільки їм потрібно переміщувати всі наступні елементи вперед. Гідний ітератор може зробити корисним для реалізації пов'язаного списку. Однак це може бути економічно простором.
Ciro Santilli 冠状 病毒 审查 六四 事件 法轮功

10

Було б розумно також просто створити новий список, якщо поточний елемент списку відповідає бажаним критеріям.

тому:

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

і щоб уникнути необхідності повторного кодування всього проекту з новою назвою списків:

originalList[:] = newList

Примітка, з документації Python:

copy.copy (x) Поверніть дрібну копію x.

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


3
Це додає нової інформації, яка не була у прийнятій відповіді роками раніше.
Марк Амері

2
Це простий і просто інший спосіб подивитися на проблему @MarkAmery. Він менш ущільнений для тих людей, які не люблять стислий синтаксис кодування.
ntk4

9

Ця відповідь була спочатку написана у відповідь на питання, яке з тих пір було позначене як дублікат: Видалення координат зі списку на python

У коді є дві проблеми:

1) Використовуючи delete (), ви намагаєтесь видалити цілі числа, тоді як вам потрібно видалити кортеж.

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", щоб видалити (), але delete () приймає лише один аргумент. Тож як ми можемо змусити delete () працювати належним чином зі своїм списком? Нам потрібно розібратися, що таке кожен ваш список. У цьому випадку кожен з них є кортежем. Щоб побачити це, давайте отримаємо доступ до одного елемента списку (індексація починається з 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)]

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

Найінтуїтивніше рішення - скопіювати список, потім повторити його над початковим списком і лише змінити копію. Ви можете спробувати зробити так:

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. Ми можемо перевірити це за допомогою "є", що відрізняється від просто "рівного" (==).

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

На жаль, я не можу адекватно описати, як працює реверс (). Він повертає об'єкт 'listreverseiterator', коли список передається йому. У практичних цілях ви можете вважати це створенням зворотної копії його аргументу. Це рішення, яке я рекомендую.


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


Чому отримання індексу є більш актуальним у випадку, коли у вас є список диктовок, ніж у випадку будь-якого іншого списку? Наскільки я не можу сказати, це не має сенсу.
Марк Амеррі


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 або ні).


Цикл циклу 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Клас просто надає обгортку для вихідного об'єкта списку. До оригінального об'єкта можна отримати доступ до властивості текучого об'єкта так:

originalList = fluidArr.fixedIterable

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

Як я зазначив для початку: це складне рішення, яке зашкодить читабельності вашого коду та ускладнить налагодження. Тому інші рішення , такі як спискові згаданий в Девіді 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]

Попередження: Ви також не можете обробляти ітератори. Ітератори іноді краще, ніж послідовності.


2

для циклу буде ітерація через індекс ..

вважаємо, що у вас є список,

[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

тому четверта ітерація, виконана вказівником, перемістилася на 5-ту.

ось чому ваш цикл не охоплює 65 з моменту переходу його в попередній індекс.

тому вам не слід посилатись на іншу змінну, яка все ще посилається на оригінал замість копії.

ite = lis #dont do it will reference instead copy

так само зробіть копію списку за допомогою list[::]

тепер ви це дасте,

[5, 7, 13, 29]

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

тож ви можете спробувати зрозуміти натомість.

який підтримує всі ітерабельні такі як, список, кортеж, диктант, рядок тощо


Це допомогло мені зрозуміти, чому мій код не працює.
Вахід Садік

2

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

Приклад:

i = 0
length = len(list1)

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

    i += 1

1

Інші відповіді вірні, що зазвичай неправильно видаляти зі списку, який ви повторюєте. Зворотне ітерація дозволяє уникнути підводних каменів, але набагато складніше слідувати коду, який це робить, тому зазвичай вам краще скористатися розумінням списку або filter.

Однак є один випадок, коли безпечно видаляти елементи з послідовності, яку ви повторюєте: якщо ви видаляєте лише один елемент під час ітерації. Це можна забезпечити, використовуючи a returnабо a break. Наприклад:

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

Це часто простіше зрозуміти, ніж розуміння списку, коли ви робите деякі операції з побічними ефектами над першим елементом у списку, який відповідає певній умові, а потім вилучаєте цей елемент зі списку одразу після.


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.> Використовуйте запропоновану вами рамку (де ви заповнюєте код всередині циклу). Я використовую невеликий код, 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


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)

Це має бути значно швидше за все.


З того, що я виміряв, 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

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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.