Как удалять элементы из списка при итерации?
Описание проблемы
Я перебираю список кортежей в Python и пытаюсь удалить их, если они соответствуют определённым критериям. У меня есть следующий код:
for tup in somelist:
if determine(tup):
code_to_remove_tup
Вместо code_to_remove_tup
я не знаю, что использовать для удаления элемента. Не могу понять, как удалить элемент в таком виде.
Можете подсказать, как правильно удалить элементы из списка при итерации?
5 ответ(ов)
Вам необходимо создать копию списка и сначала итерироваться по ней, иначе итерация завершится с возможными непредсказуемыми результатами.
Например (в зависимости от типа списка):
for tup in somelist[:]:
# ваш код...
Пример:
>>> 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
[]
В первом случае, когда вы итерируетесь по оригинальному списку somelist
, вы изменяете его во время итерации, что приводит к тому, что некоторые элементы пропускаются, и результат окажется неожиданным. Во втором случае вы создаете копию списка с помощью somelist[:]
, и изменения не влияют на итерируемый объект, что приводит к ожидаемому результату — список становится пустым.
Чтобы удалить элементы из списка в Python, лучше проходить по нему в обратном порядке. Это позволяет избежать проблем, связанных с тем, что индексы перемещаются, когда элементы удаляются. Вот пример:
for i in range(len(somelist) - 1, -1, -1):
if some_condition(somelist, i):
del somelist[i]
Таким образом, если вы удаляете элемент, то последующие индексы не изменятся для уже пройденных элементов, что позволяет избежать ошибок.
Пользователям Python 2: замените range
на xrange
, чтобы не создавать большой список в памяти. Это поможет сэкономить ресурсы, особенно если somelist
большой.
Для тех, кто предпочитает функциональное программирование, можно использовать следующий подход:
somelist[:] = filter(lambda tup: not determine(tup), somelist)
или, если вы хотите использовать функции из модуля itertools
, можно воспользоваться следующим кодом:
from itertools import ifilterfalse
somelist[:] = list(ifilterfalse(determine, somelist))
Однако стоит отметить, что ifilterfalse
доступен только в Python 2. В Python 3 вместо него используется filter
, который возвращает итератор. Если вы используете Python 3, вам нужно просто вернуться к первому решению, однако может потребоваться преобразование в список, если вы хотите сохранить изменяемость somelist
.
Ваша задача заключается в удалении элементов из большого списка, и вы правильно заметили, что дублирование списка может быть дорогостоящим процессом, особенно если количество удалений невелико по сравнению с количеством оставшихся элементов. Ваш подход с низким уровнем вроде бы имеет смысл.
Код, который вы привели, обрабатывает список, удаляя элементы на месте. Это позволяет избежать создания новой копии списка, что действительно может быть более эффективным при небольшом количестве удалений.
Однако стоит обратить внимание на то, что удаление элементов из списка в Python не всегда является оптимальным с точки зрения производительности. Когда вы используете del
, все элементы после удаляемого смещаются на одну позицию влево, что может привести к временным затратам O(n) в худшем случае для каждого удаления.
Если количество удалений довольно небольшое, ваш метод вполне приемлем, но если вы планируете много удалений, то есть альтернативы, такие как создание нового списка, в который вы добавляете только те элементы, которые не подлежат удалению. Это позволит избежать постоянного сдвига элементов:
filtered_array = [item for item in array if not someTest(item)]
Такой подход создаст новый список, что может потребовать дополнительной памяти, но при большом количестве удалений он будет гораздо более эффективным с точки зрения производительности.
В итоге, выбирайте метод в зависимости от вашей конкретной ситуации - если удалений немного, ваш подход хорош. Если же предсказывается большое количество удалений, рассматривайте необходимость в создании нового списка как более эффективный вариант.
Большинство ответов здесь предлагают создать копию списка. Однако у меня был случай, когда список был достаточно длинным (110K элементов), и было разумнее продолжать уменьшать список, а не создавать его копию.
Во-первых, вам нужно заменить цикл foreach
на цикл while
:
i = 0
while i < len(somelist):
if determine(somelist[i]):
del somelist[i]
else:
i += 1
Значение i
не изменяется в блоке if
, потому что вам нужно будет получить значение нового элемента ИЗ ТОГО ЖЕ ИНДЕКСА, как только старый элемент будет удалён.
Почему используется string.join(list), а не list.join(string)?
Создание словаря с помощью генератора словарей
Как получить полный путь к директории текущего файла?
UnicodeDecodeError: Кодек 'charmap' не может декодировать байт X в позиции Y: символ отображается как <неопределённый>
Найти все файлы с расширением .txt в директории на Python