Como remover itens de uma lista durante a iteração?

934

Estou percorrendo uma lista de tuplas no Python e estou tentando removê-las se elas atenderem a certos critérios.

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

O que devo usar no lugar de code_to_remove_tup? Não consigo descobrir como remover o item dessa maneira.

lfaraone
fonte
A maioria das respostas nesta página não explica realmente por que a remoção de elementos durante a iteração em uma lista produz resultados estranhos, mas a resposta aceita nesta pergunta gera e provavelmente é uma fraude melhor para iniciantes que encontram esse problema pela primeira vez.
ggorlen

Respostas:

827

Você pode usar uma compreensão de lista para criar uma nova lista contendo apenas os elementos que não deseja remover:

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

Ou, atribuindo à fatia somelist[:], você pode alterar a lista existente para conter apenas os itens que deseja:

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

Essa abordagem pode ser útil se houver outras referências a somelistessa necessidade para refletir as alterações.

Em vez de uma compreensão, você também pode usar itertools. No Python 2:

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

Ou no Python 3:

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

Por uma questão de clareza e para aqueles que consideram o uso da [:]notação hackish ou fuzzy, aqui está uma alternativa mais explícita. Teoricamente, ele deve executar o mesmo em relação ao espaço e ao tempo do que os versículos acima.

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

Também funciona em outros idiomas que podem não ter a capacidade de substituir itens das listas Python, com modificações mínimas. Por exemplo, nem todos os idiomas convertem listas vazias para o mesmo Falseque o Python. Você pode substituir while somelist:por algo mais explícito como while len(somelist) > 0:.

David Raznick
fonte
4
Você pode torná-lo mais rápido se souber que apenas alguns serão excluídos, ou seja, somente exclua-os e deixe os outros no lugar em vez de reescrevê-los?
highBandWidth
20
E se minha lista for grande e não puder fazer uma cópia?
jpcgt
15
@jpcgt Você deve usar somelist[:] = (x for x in somelist if determine(x))isso para criar um gerador que pode não criar cópias desnecessárias.
Rostislav Kondratenko
8
@RostislavKondratenko: list_ass_slice()função que implementa somelist[:]=chamadas PySequence_Fast()internamente. Esta função sempre retorna uma lista ou seja, a solução da @Alex Martelli que já utiliza uma lista em vez de um gerador é provavelmente mais eficiente
JFS
6
Você gostaria de explicar quais são as diferenças entre atribuir a compreensão da lista à lista e clonar a lista, por favor? A lista original não somelistseria alterada nos dois métodos?
Bowen Liu
589

As respostas que sugerem a compreensão da lista ESTÃO QUASE corretas - exceto que elas criam uma lista completamente nova e depois atribuem o mesmo nome à lista antiga, pois NÃO modificam a lista antiga no local. É diferente do que você faria com a remoção seletiva, como na sugestão de @ Lennart - é mais rápido, mas se sua lista for acessada por várias referências, o fato de você estar apenas substituindo uma das referências e NÃO alterando o objeto da lista por si só pode levar a erros sutis e desastrosos.

Felizmente, é extremamente fácil obter a velocidade da compreensão da lista E a semântica necessária da alteração no local - apenas código:

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

Observe a diferença sutil com outras respostas: esta NÃO está atribuindo a um nome de barra - está atribuindo a uma fatia da lista que por acaso é a lista inteira, substituindo assim o conteúdo da lista no mesmo objeto de lista Python , em vez de apenas recolocar uma referência (do objeto de lista anterior para o novo objeto de lista) como as outras respostas.

Alex Martelli
fonte
1
Como faço a mesma tarefa fatiada com um dict? No Python 2.6?
21311 PaulMcG
11
@Paul: Como os ditados não são ordenados, as fatias não fazem sentido para os ditados. Se você deseja substituir o conteúdo do dict apelo conteúdo do dict b, use a.clear(); a.update(b).
Sven Marnach
1
Por que 'reposicionar' uma das referências substituindo o que a variável se refere a causar bugs? Parece que isso seria apenas um problema em potencial em aplicativos multithread, e não single thread.
Derek Dahmer
59
@Derek x = ['foo','bar','baz']; y = x; x = [item for item in x if determine(item)];Isso reatribui xao resultado da compreensão da lista, mas yainda se refere à lista original['foo','bar','baz'] . Se você esperava xe yse referiu à mesma lista, pode ter introduzido bugs. Você evitar isso através da atribuição de uma fatia de toda a lista, como Alex shows, e eu mostro aqui: x = ["foo","bar","baz"]; y = x; x[:] = [item for item in x if determine(item)];. A lista é modificada no local. garantindo que todas as referências à lista ( aqui xe yaqui) se refiram à nova lista.
Steven T. Snyder
De fato, usando filtera função também cria uma nova lista, não modifica elementos no lugar ... únicaolist[:] = [i for i in olist if not dislike(i)]
John Strood
303

Você precisa tirar uma cópia da lista e iterá-la primeiro, ou a iteração falhará com o que pode ser resultados inesperados.

Por exemplo (depende de que tipo de lista):

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

Um exemplo:

>>> 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
[]
Lennart Regebro
fonte
13
@ Zen Porque o segundo repete sobre uma cópia da lista. Portanto, quando você modifica a lista original, não modifica a cópia em que itera.
Lennart Regebro
3
O que há de melhor em fazer somelist [:] em comparação com list (somelist)?
Mariusz Jamro
3
list(somelist)irá converter um iterável em uma lista. somelist[:]faz uma cópia de um objeto que suporta fatiamento. Então eles não fazem necessariamente a mesma coisa. Neste caso, eu quero fazer uma cópia do somelistobjeto, então eu uso[:]
Lennart Regebro 5/02
33
Nota para quem lê isso, isso é MUITO lento para as listas. remove()precisa passar por cima da lista INTEIRA para cada iteração, de modo que levará uma eternidade.
Vitiral
7
O tempo grande não importa quando se lida com listas de apenas uma dúzia de itens. Geralmente, claro e simples para os futuros programadores entenderem, é muito mais valioso que o desempenho.
Steve Steve
127
for i in range(len(somelist) - 1, -1, -1):
    if some_condition(somelist, i):
        del somelist[i]

Você precisa ir para trás, caso contrário, é como serrar o galho de árvore em que você está sentado :-)

Usuários do Python 2: substitua rangepor xrangepara evitar a criação de uma lista codificada

John Machin
fonte
13
Em versões recentes do Python, você pode fazer isso ainda mais limpa, usando o reversed()builtin
ncoghlan
16
reversed () não cria uma nova lista, ele cria um iterador reverso sobre a sequência fornecida. Como enumerate (), você precisa colocá-lo em list () para obter uma lista dele. Você pode estar pensando de ordenados (), que faz criar uma nova lista de cada vez (ele tem que, por isso pode classificá-lo).
Ncoghlan
1
@Mauris porque enumerateretorna um iterador e reversedespera uma sequência. Eu acho que você poderia fazer reversed(list(enumerate(somelist)))se não se importar em criar uma lista extra na memória.
drevicko
2
Isso é O (N * M) para matrizes; é muito lento se você remover muitos itens de uma lista grande. Então não é recomendado.
Sam Watkins
2
@ SamWatkins Sim, essa resposta é para quando você remove alguns elementos de uma matriz muito grande. Menos uso de memória, mas pode ser mmais lento.
Navin
52

Tutorial oficial do Python 2 4.2. "para declarações"

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

Esta parte dos documentos deixa claro que:

  • você precisa fazer uma cópia da lista iterada para modificá-la
  • Uma maneira de fazer isso é com a notação de fatia [:]

Se você precisar modificar a sequência em que estiver repetindo enquanto estiver dentro do loop (por exemplo, para duplicar itens selecionados), é recomendável primeiro fazer uma cópia. A iteração sobre uma sequência não faz uma cópia implicitamente. A notação de fatia torna isso especialmente conveniente:

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

Documentação do Python 2 7.3. "A declaração for"

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

Esta parte dos documentos diz mais uma vez que você precisa fazer uma cópia e fornece um exemplo real de remoção:

Nota: Existe uma sutileza quando a sequência está sendo modificada pelo loop (isso só pode ocorrer para sequências mutáveis, ou seja, listas). Um contador interno é usado para rastrear qual item é usado a seguir, e isso é incrementado em cada iteração. Quando este contador atinge o comprimento da sequência, o loop termina. Isso significa que, se o conjunto excluir o item atual (ou anterior) da sequência, o próximo item será ignorado (uma vez que obtém o índice do item atual que já foi tratado). Da mesma forma, se o conjunto inserir um item na sequência antes do item atual, o item atual será tratado novamente na próxima vez no loop. Isso pode levar a erros desagradáveis ​​que podem ser evitados fazendo uma cópia temporária usando uma fatia de toda a sequência, por exemplo,

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

No entanto, eu discordo dessa implementação, pois ele .remove()precisa repetir a lista inteira para encontrar o valor.

Melhores soluções alternativas

Ou:

Geralmente você só quer ir mais rápido .append() opção por padrão, a menos que a memória seja uma grande preocupação.

Python poderia fazer isso melhor?

Parece que essa API específica do Python poderia ser melhorada. Compare, por exemplo, com:

  • Java ListIterator :: remove quais documentos "Esta chamada só pode ser feita uma vez por chamada para a próxima ou a anterior"
  • C ++ std::vector::eraseque retorna um interator válido para o elemento após o removido

ambos deixam claro que você não pode modificar uma lista que está sendo iterada, exceto com o próprio iterador, e oferece maneiras eficientes de fazer isso sem copiar a lista.

Talvez a lógica subjacente é que as listas Python são assumidos como matriz dinâmica apoiada, e, por conseguinte, qualquer tipo de remoção será tempo de qualquer maneira ineficiente, enquanto Java tem uma hierarquia de interface mais agradável com ambos ArrayListe LinkedListimplementaçõesListIterator .

Parece não haver um tipo explícito de lista vinculada no stdlib do Python: Lista vinculada do Python

Ciro Santilli adicionou uma nova foto
fonte
48

Sua melhor abordagem para esse exemplo seria uma compreensão de lista

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

Nos casos em que você está fazendo algo mais complexo do que chamar uma determinefunção, prefiro construir uma nova lista e simplesmente anexá-la à medida que for avançando. Por exemplo

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

Copiar a lista usando removepode fazer com que seu código pareça um pouco mais limpo, conforme descrito em uma das respostas abaixo. Definitivamente, você não deve fazer isso para listas extremamente grandes, pois isso envolve primeiro copiar a lista inteira e também executar uma O(n) removeoperação para cada elemento que está sendo removido, tornando este um O(n^2)algoritmo.

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

Para quem gosta de programação funcional:

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

ou

from itertools import ifilterfalse
somelist[:] = list(ifilterfalse(determine, somelist))
Cide
fonte
1. A compreensão da lista e as expressões geradoras são emprestadas da Haskell, uma linguagem funcional pura; eles são exatamente tão funcionais quanto filter, e mais pitônicos. 2. Se você precisar lambdausarmap ou filter, a lista comp ou genexpr é sempre a melhor opção; mape filterpode ser um pouco mais rápido quando a função de transformação / predicado é um Python embutido implementado em C e o iterável não é trivialmente pequeno, mas eles são sempre mais lentos quando você precisa de um lambdaque o listcomp / genexpr possa evitar.
ShadowRanger #
13

Eu precisava fazer isso com uma lista enorme e duplicar a lista parecia caro, especialmente porque, no meu caso, o número de exclusões seria pequeno se comparado aos itens que permanecem. Adotei essa abordagem de baixo nível.

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

O que não sei é a eficiência de algumas exclusões em comparação com a cópia de uma lista grande. Por favor, comente se você tiver alguma idéia.

Michael
fonte
No meu caso, preciso mover esses elementos "indesejados" para outra lista. Você tem algum novo comentário sobre esta solução? Eu também acho que é melhor usar algumas exclusões em vez de duplicar a lista.
Gustavovelascoh
Esta é a resposta certa se o desempenho for um problema (embora seja o mesmo que @Alexey). Dito isto, a escolha de listuma estrutura de dados em primeiro lugar deve ser cuidadosamente considerada, pois a remoção do meio de uma lista leva um tempo linear no comprimento da lista. Se você realmente não precisa de acesso aleatório ao k-ésimo item seqüencial, talvez considere OrderedDict?
max
@GVelascoh, por que não criar newlist = [], e newlist.append(array[i])logo antes del array[i]?
max
2
Observe que isso provavelmente é ineficiente no tempo: se list()for uma lista vinculada, o acesso aleatório é caro, se list()for uma matriz, as exclusões são caras, porque precisam mover todos os seguintes elementos adiante. Um iterador decente pode melhorar as coisas para a implementação da lista vinculada. No entanto, isso pode ser eficiente em termos de espaço.
Ciro Santilli escreveu
10

Pode ser inteligente também criar apenas uma nova lista se o item atual da lista atender aos critérios desejados.

tão:

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

e para evitar ter que codificar novamente o projeto inteiro com o novo nome da lista:

originalList[:] = newList

observe, da documentação do Python:

copy.copy (x) Retorna uma cópia superficial de x.

copy.deepcopy (x) Retorna uma cópia profunda de x.

ntk4
fonte
3
Isso não adiciona novas informações que não estavam na resposta aceita anos antes.
Mark Amery
2
É simples e é apenas outra maneira de analisar um problema @MarkAmery. É menos condensado para as pessoas que não gostam de sintaxe de codificação compactada.
Ntk4 23/06
9

Esta resposta foi originalmente escrita em resposta a uma pergunta que foi marcada como duplicada: Removendo coordenadas da lista em python

Existem dois problemas no seu código:

1) Ao usar remove (), você tenta remover números inteiros, ao passo que precisa remover uma tupla.

2) O loop for ignorará os itens da sua lista.

Vamos analisar o que acontece quando executamos seu código:

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

O primeiro problema é que você está passando 'a' e 'b' para remove (), mas remove () aceita apenas um único argumento. Então, como podemos fazer com que remove () funcione corretamente com sua lista? Precisamos descobrir o que é cada elemento da sua lista. Nesse caso, cada um é uma tupla. Para ver isso, vamos acessar um elemento da lista (a indexação começa em 0):

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

Aha! Cada elemento de L1 é na verdade uma tupla. Então é isso que precisamos passar para remover (). Tuplas em python são muito fáceis, elas são simplesmente feitas colocando valores entre parênteses. "a, b" não é uma tupla, mas "(a, b)" é uma tupla. Então, modificamos seu código e o executamos novamente:

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

Esse código é executado sem nenhum erro, mas vamos olhar para a lista que ele gera:

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

Por que (1, -2) ainda está na sua lista? Acontece que modificar a lista enquanto usa um loop para iterar sobre ela é uma péssima idéia sem cuidados especiais. O motivo pelo qual (1, -2) permanece na lista é que os locais de cada item na lista foram alterados entre as iterações do loop for. Vamos ver o que acontece se alimentarmos o código acima com uma lista mais longa:

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

Como você pode deduzir desse resultado, sempre que a instrução condicional for avaliada como verdadeira e um item da lista for removido, a próxima iteração do loop ignorará a avaliação do próximo item da lista porque seus valores agora estão localizados em índices diferentes.

A solução mais intuitiva é copiar a lista, iterar sobre a lista original e modificar apenas a cópia. Você pode tentar fazer o seguinte:

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)

No entanto, a saída será idêntica à anterior:

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

Isso ocorre porque, quando criamos o L2, o python não criou um novo objeto. Em vez disso, apenas referenciou L2 ao mesmo objeto que L1. Podemos verificar isso com 'is', que é diferente de meramente "igual" (==).

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

Podemos fazer uma cópia verdadeira usando copy.copy (). Então tudo funciona como esperado:

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

Finalmente, há uma solução mais limpa do que ter que fazer uma cópia totalmente nova de L1. A função reversed ():

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

Infelizmente, não posso descrever adequadamente como reversed () funciona. Retorna um objeto 'listreverseiterator' quando uma lista é passada para ele. Para fins práticos, você pode pensar nisso como criar uma cópia invertida de seu argumento. Esta é a solução que eu recomendo.

Cinghiale
fonte
4

Se você quiser fazer mais alguma coisa durante a iteração, pode ser bom obter o índice (que garante que você possa fazer referência a ele, por exemplo, se você tiver uma lista de dictos) e o conteúdo real do item da lista.

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]

enumeratedá acesso ao item e ao índice de uma só vez. reversedé para que os índices que você deseja excluir posteriormente não sejam alterados.

fantabolous
fonte
Por que a obtenção do índice é mais relevante no caso de você ter uma lista de ditados do que em qualquer outro tipo de lista? Isso não faz sentido, tanto quanto eu posso dizer.
Mark Amery
4

Você pode querer usar filter() disponíveis como internos.

Para mais detalhes confira aqui

Xolve
fonte
4

A maioria das respostas aqui deseja que você crie uma cópia da lista. Eu tinha um caso de uso em que a lista era bastante longa (itens de 110 mil) e era mais inteligente continuar reduzindo a lista.

Primeiro, você precisará substituir o loop foreach pelo while ,

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

O valor de inão é alterado no bloco if porque você deseja obter o valor do novo item DO MESMO ÍNDICE, depois que o item antigo for excluído.

Mujeeb
fonte
3

Você pode tentar fazer o loop inverso, para que some_list faça algo como:

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)

Dessa forma, o índice está alinhado e não sofre as atualizações da lista (independentemente de você exibir o elemento atual ou não).

Queequeg
fonte
Fazer um loop reversed(list(enumerate(some_list)))seria mais simples do que calcular os índices por conta própria.
Mark Amery
@ MarkAmery não acha que pode alterar a lista dessa maneira.
precisa
3

Uma solução possível, útil se você deseja não apenas remover algumas coisas, mas também fazer algo com todos os elementos em um único loop:

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
Alexey
fonte
Você realmente deve apenas usar compreensões. Eles são muito mais fáceis de entender.
Beefster
E se eu quiser remover badcoisas, fazer algo com isso e também fazer algo com goodcoisas em um loop?
Alexey
1
Na verdade, percebi que há alguma esperteza aqui em que você faz uma cópia da lista com uma fatia aberta ( alist[:]). E como você pode estar fazendo algo sofisticado, ele realmente tem um caso de uso. Boa revisão é boa. Pegue o meu voto.
Beefster 29/03
2

Eu precisava fazer algo semelhante e, no meu caso, o problema era a memória - eu precisava mesclar vários objetos de conjunto de dados em uma lista, depois de fazer algumas coisas com eles, como um novo objeto, e precisava me livrar de cada entrada na qual eu estava mesclando evite duplicar todos eles e aumentar a memória. No meu caso, ter os objetos em um dicionário em vez de uma lista funcionou bem:

`` ``

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

`` ``

rafa
fonte
2

TLDR:

Eu escrevi uma biblioteca que permite que você faça isso:

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'

É melhor usar outro método, se possível, que não exija a modificação do iterável durante a iteração, mas, para alguns algoritmos, pode não ser tão simples assim. E, portanto, se você tiver certeza de que deseja realmente o padrão de código descrito na pergunta original, é possível.

Deve funcionar em todas as seqüências mutáveis, não apenas nas listas.


Resposta completa:

Editar: o último exemplo de código nesta resposta fornece um caso de uso do motivo pelo qual às vezes você pode modificar uma lista no lugar em vez de usar uma compreensão da lista. A primeira parte das respostas serve como tutorial de como uma matriz pode ser modificada no local.

A solução segue a partir deste resposta (para uma pergunta relacionada) do remetente. O que explica como o índice da matriz é atualizado durante a iteração em uma lista que foi modificada. A solução abaixo foi projetada para rastrear corretamente o índice da matriz, mesmo que a lista seja modificada.

Baixar fluidIter.pya partir daqui https://github.com/alanbacon/FluidIterator , é apenas um único arquivo assim não há necessidade de instalar git. Como não há instalador, você precisará garantir que o arquivo esteja no caminho python. O código foi escrito para o python 3 e não foi testado no 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))

Isso produzirá a seguinte saída:

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]

Acima, usamos o popmétodo no objeto da lista de fluidos. Outros métodos iteráveis comuns também são implementadas, como del fluidL[i], .remove, .insert, .append, .extend. A lista também pode ser modificada usando fatias ( sortereverse métodos não são implementados).

A única condição é que você deve modificar a lista apenas no local, se em algum momento fluidLou tiver lsido reatribuído para um objeto de lista diferente, o código não funcionaria. O fluidLobjeto original ainda seria usado pelo loop for, mas ficaria fora do escopo para modificação.

ie

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

Se quisermos acessar o valor atual do índice da lista, não podemos usar enumerar, pois isso conta apenas quantas vezes o loop for foi executado. Em vez disso, usaremos o objeto iterador diretamente.

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

Isso produzirá o seguinte:

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]

A FluidIterableclasse apenas fornece um wrapper para o objeto de lista original. O objeto original pode ser acessado como uma propriedade do objeto fluido da seguinte forma:

originalList = fluidArr.fixedIterable

Mais exemplos / testes podem ser encontrados na if __name__ is "__main__":seção na parte inferior de fluidIter.py. Vale a pena analisar porque explicam o que acontece em várias situações. Como: Substituir grandes seções da lista usando uma fatia. Ou usando (e modificando) o mesmo iterável em loops aninhados.

Como afirmei no início: esta é uma solução complicada que prejudicará a legibilidade do seu código e dificultará a depuração. Portanto, outras soluções, como as compreensões de lista mencionadas na resposta de David Raznick, devem ser consideradas primeiro. Dito isto, encontrei momentos em que essa classe me foi útil e mais fácil de usar do que acompanhar os índices de elementos que precisam ser excluídos.


Edit: Como mencionado nos comentários, esta resposta realmente não apresenta um problema para o qual essa abordagem fornece uma solução. Vou tentar abordar isso aqui:

A compreensão da lista fornece uma maneira de gerar uma nova lista, mas essas abordagens tendem a olhar para cada elemento isoladamente, em vez do estado atual da lista como um todo.

ie

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

Mas e se o resultado do testFuncdepende dos elementos que já foram adicionados newList? Ou os elementos ainda emoldList podem ser adicionados a seguir? Ainda pode haver uma maneira de usar uma compreensão de lista, mas ela começará a perder sua elegância e, para mim, é mais fácil modificar uma lista no local.

O código abaixo é um exemplo de algoritmo que sofre do problema acima. O algoritmo reduzirá uma lista para que nenhum elemento seja múltiplo de qualquer outro elemento.

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)

A saída e a lista reduzida final são mostradas abaixo

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]
Ressonância
fonte
É difícil dizer se isso foi superdimensionado porque não está claro qual problema está tentando resolver; o que a remoção de elementos usando essa abordagem alcança que some_list[:] = [x for x in some_list if not some_condition(x)]não alcança? Sem uma resposta para isso, por que alguém deveria acreditar que baixar e usar sua biblioteca de 600 linhas completa com erros de digitação e código comentado é uma solução melhor para o problema deles do que o one-liner? -1.
Mark Amery
@MarkAmery. O principal caso de uso para quando isso ocorre ao tentar determinar se um item deve ser removido (ou adicionado ou movido) com base não apenas no item em si, mas no estado de outro item na lista ou no estado da lista como um todo. Por exemplo, nas compreensões de lista não é possível escrever algo como some_list[:] = [x for x in some_list if not some_condition(y)]onde yé um elemento de lista diferente x. Nem seria possível escrever some_list[:] = [x for x in some_list if not some_condition(intermediateStateOf_some_list)].
Ressonância
2

O método mais eficaz é compreensão da lista, muitas pessoas mostram seu caso, é claro, também é uma boa maneira de obter um iteratormeio filter.

Filterrecebe uma função e uma sequência. Filteraplica a função passada a cada elemento por sua vez e decide se deve reter ou descartar o elemento, dependendo se o valor de retorno da função é Trueou False.

Há um exemplo (obtenha as probabilidades na tupla):

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

Cuidado: Você também não pode manipular iteradores. Às vezes, os iteradores são melhores que as seqüências.

chseng
fonte
2

O loop for será repetido pelo índice ..

considere que você tem uma lista,

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

você está usando a variável de lista chamada lis. e você está usando o mesmo para remover ..

sua variável

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

durante a 5ª iteração,

seu número 35 não era primo, então você o removeu de uma lista.

lis.remove(y)

e o próximo valor (65) passa para o índice anterior.

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

então a quarta iteração executada apontou para a quinta.

é por isso que seu loop não cobre 65, pois foi movido para o índice anterior.

portanto, você não deve referenciar a lista em outra variável que ainda faça referência ao original em vez de copiar.

ite = lis #dont do it will reference instead copy

o mesmo acontece com a cópia da lista usando list[::]

agora você vai dar,

[5, 7, 13, 29]

O problema é que você removeu um valor de uma lista durante a iteração e o índice da lista será recolhido.

então você pode tentar entender.

que suporta todos os iteráveis ​​como, lista, tupla, ditado, string etc.

Mohideen bin Mohammed
fonte
Isso me ajudou a entender por que meu código estava falhando.
Wahid Sadik 13/04
2

Se você deseja excluir elementos de uma lista durante a iteração, use um loop while para alterar o índice atual e o índice final após cada exclusão.

Exemplo:

i = 0
length = len(list1)

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

    i += 1
NoName
fonte
1

As outras respostas estão corretas: geralmente é uma má idéia excluir de uma lista que você está iterando. A iteração reversa evita as armadilhas, mas é muito mais difícil seguir o código que faz isso; portanto, geralmente é melhor usar uma compreensão de lista ou filter.

No entanto, há um caso em que é seguro remover elementos de uma sequência que você está iterando: se você estiver removendo apenas um item enquanto estiver iterando. Isso pode ser garantido usando a returnou a break. Por exemplo:

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

Isso geralmente é mais fácil de entender do que a compreensão de uma lista quando você está realizando algumas operações com efeitos colaterais no primeiro item de uma lista que atenda a alguma condição e removendo esse item da lista imediatamente depois.

Beefster
fonte
1

Eu posso pensar em três abordagens para resolver seu problema. Como exemplo, vou criar uma lista aleatória de tuplas somelist = [(1,2,3), (4,5,6), (3,6,6), (7,8,9), (15,0,0), (10,11,12)]. A condição que eu escolho é sum of elements of a tuple = 15. Na lista final, teremos apenas as tuplas cuja soma não é igual a 15.

O que eu escolhi é um exemplo escolhido aleatoriamente. Sinta-se livre para alterar a lista de tuplas e a condição que eu escolhi.

Método 1.> Use a estrutura que você sugeriu (onde uma preenche um código dentro de um loop for). Eu uso um código pequeno com delpara excluir uma tupla que atenda à referida condição. No entanto, este método perderá uma tupla (que satisfaça a referida condição) se duas tuplas colocadas consecutivamente atenderem à condição especificada.

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

Método 2.> Construa uma nova lista que contenha elementos (tuplas) onde a condição especificada não é atendida (isso é o mesmo que remover elementos da lista em que a condição especificada é atendida). A seguir está o código para isso:

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

Método 3.> Encontre índices onde a condição especificada é atendida e use os elementos remover (tuplas) correspondentes a esses índices. A seguir está o código para isso.

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

O método 1 e o método 2 são mais rápidos que o método 3 . O método2 e o método3 são mais eficientes que o método1. Eu prefiro o method2 . Para o exemplo mencionado acima,time(method1) : time(method2) : time(method3) = 1 : 1 : 1.7

Siddharth Satpathy
fonte
0

Para qualquer coisa que possa ser realmente grande, eu uso o seguinte.

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)

Isso deve ser significativamente mais rápido do que qualquer outra coisa.

CENTURION
fonte
Pelo que medi, o NumPy começa a ser mais rápido para listas de mais de 20 elementos e alcança filtragem> 12x mais rápida para grandes listas de 1000 elementos e mais.
Georgy
0

Em algumas situações, em que você está fazendo mais do que simplesmente filtrar um item de uma lista por vez, deseja que sua iteração seja alterada durante a iteração.

Aqui está um exemplo em que copiar a lista antecipadamente está incorreto, a iteração reversa é impossível e a compreensão da lista também não é uma opção.

""" 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
fonte
0

Se você usar a nova lista posteriormente, poderá simplesmente definir o elem como Nenhum e depois julgá-lo no ciclo posterior, como este

for i in li:
    i = None

for elem in li:
    if elem is None:
        continue

Dessa forma, você não precisa copiar a lista e é mais fácil de entender.

john zhang
fonte
-1

coloque uma lista de números e você deseja remover todos os números que são divisíveis por 3,

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

usando list comprehension, isso criará uma nova lista e criará um novo espaço de memória

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

usando a lambda filterfunção, isso criará uma nova lista resultante e consumirá espaço de memória

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

sem consumir espaço de memória para nova lista e modificar lista existente

for index, value in enumerate(list_number):
    if list_number[index]%3==0:
        list_number.remove(value)
sahasrara62
fonte