maneira elegante de remover itens da seqüência em Python?

votos
51

Quando estou escrevendo código em Python, muitas vezes eu preciso remover itens de uma lista ou outro tipo de sequência com base em alguns critérios. Eu não encontrei uma solução que é elegante e eficiente, como a remoção de itens de uma lista que você está iteração é ruim. Por exemplo, você não pode fazer isso:

for name in names:
    if name[-5:] == 'Smith':
        names.remove(name)

Eu geralmente acabam fazendo algo parecido com isto:

toremove = []
for name in names:
    if name[-5:] == 'Smith':
        toremove.append(name)
for name in toremove:
    names.remove(name)
del toremove

Esta é innefficient, bastante feio e possivelmente de buggy (como é que lidar com várias entradas de 'John Smith'?). Alguém tem uma solução mais elegante, ou pelo menos um mais eficiente?

Como aproximadamente um que trabalha com dicionários?

Publicado 20/08/2008 em 18:41
fonte usuário
Em outras línguas...                            


14 respostas

votos
53

Duas maneiras fáceis de realizar apenas a filtragem são:

  1. usando filter:

    names = filter(lambda name: name[-5:] != "Smith", names)

  2. Usando compreensões lista:

    names = [name for name in names if name[-5:] != "Smith"]

Note-se que ambos os casos, manter os valores para os quais a função predicado avaliar para True, então você tem de inverter a lógica (ou seja, você diz "manter as pessoas que não têm o sobrenome Smith" em vez de "remover as pessoas que têm o sobrenome Smith ").

Editar engraçado ... postou duas pessoas individualmente ambas as respostas que sugeri como eu estava postando meu.

Respondeu 20/08/2008 em 18:50
fonte usuário

votos
37

Você também pode iterar para trás sobre a lista:

for name in reversed(names):
    if name[-5:] == 'Smith':
        names.remove(name)

Isto tem a vantagem de que ele não cria uma nova lista (como filterou uma lista de compreensão) e usa um iterador em vez de uma cópia da lista (como [:]).

Note que, embora a remoção de elementos durante a iteração para trás é seguro, inseri-los é um pouco mais complicado.

Respondeu 08/10/2008 em 02:24
fonte usuário

votos
28

A resposta óbvia é o que John e um par de outras pessoas deram, a saber:

>>> names = [name for name in names if name[-5:] != "Smith"]       # <-- slower

Mas que tem a desvantagem de que ele cria um novo objeto de lista, em vez de reutilizar o objeto original. Eu fiz algumas profiling e experimentação, e o método mais eficiente que eu vim com é:

>>> names[:] = (name for name in names if name[-5:] != "Smith")    # <-- faster

Atribuindo a "nomes [:]" basicamente significa "substituir o conteúdo da lista de nomes com o seguinte valor". É diferente de apenas atribuir aos nomes, na medida em que não cria um novo objeto lista. O lado direito da atribuição é uma expressão gerador (note o uso de parênteses em vez de colchetes). Isso fará com que Python para fazer uma iteração através da lista.

Alguns perfis rápida sugere que este é cerca de 30% mais rápido do que a abordagem de compreensão lista, e cerca de 40% mais rápido do que a abordagem filtro.

Ressalva : embora esta solução é mais rápido que a solução óbvia, é mais obscura, e conta com mais técnicas Python avançados. Se você usá-lo, eu recomendo acompanhando-o com um comentário. É, provavelmente, só vale a pena usar em casos onde você realmente se preocupam com o desempenho desta operação em particular (que é bastante rápido, não importa o quê). (No caso em que eu usei isso, eu estava fazendo uma pesquisa * feixe, e usou isso para remover pontos de busca a partir do feixe de pesquisa.)

Respondeu 09/01/2011 em 15:41
fonte usuário

votos
10

Usando uma compreensão da lista

list = [x for x in list if x[-5:] != "smith"]
Respondeu 20/08/2008 em 18:49
fonte usuário

votos
4

Há momentos em que a filtragem (usando filtro ou uma compreensão da lista) não funciona. Isso acontece quando algum outro objeto está segurando uma referência para a lista que você está modificando e você precisa modificar a lista no lugar.

for name in names[:]:
    if name[-5:] == 'Smith':
        names.remove(name)

A única diferença em relação ao código original é o uso de names[:], em vez de namesno loop for. Dessa forma, as iterações de código mais de uma cópia (rasa) da lista e as remoções trabalho como esperado. Desde a cópia lista é superficial, é bastante rápido.

Respondeu 05/10/2008 em 12:48
fonte usuário

votos
3

filtro seria fantástico para isso. Um exemplo simples:

names = ['mike', 'dave', 'jim']
filter(lambda x: x != 'mike', names)
['dave', 'jim']

Edit: compreensão da lista de Corey é incrível também.

Respondeu 20/08/2008 em 18:49
fonte usuário

votos
2

Se a lista deve ser filtrada no local e o tamanho da lista é bastante grande, os de um algoritmo mencionado nas respostas anteriores, que são baseados em list.remove (), pode ser inadequado, porque a sua complexidade computacional é O (n ^ 2) . Neste caso, você pode usar a seguinte função não-assim pythônico:

def filter_inplace(func, original_list):
  """ Filters the original_list in-place.

  Removes elements from the original_list for which func() returns False.

  Algrithm's computational complexity is O(N), where N is the size
  of the original_list.
  """

  # Compact the list in-place.
  new_list_size = 0
  for item in original_list:
    if func(item):
      original_list[new_list_size] = item
      new_list_size += 1

  # Remove trailing items from the list.
  tail_size = len(original_list) - new_list_size
  while tail_size:
    original_list.pop()
    tail_size -= 1


a = [1, 2, 3, 4, 5, 6, 7]

# Remove even numbers from a in-place.
filter_inplace(lambda x: x & 1, a)

# Prints [1, 3, 5, 7]
print a

Edit: Na verdade, a solução a https://stackoverflow.com/a/4639748/274937 é superior a solução meu. É mais Python e funciona mais rápido. Então, aqui está uma implementação nova filter_inplace ():

def filter_inplace(func, original_list):
  """ Filters the original_list inplace.

  Removes elements from the original_list for which function returns False.

  Algrithm's computational complexity is O(N), where N is the size
  of the original_list.
  """
  original_list[:] = [item for item in original_list if func(item)]
Respondeu 02/04/2012 em 17:20
fonte usuário

votos
2

Para responder à sua pergunta sobre como trabalhar com dicionários, você deve observar que Python 3.0 incluirá compreensões de dicionários :

>>> {i : chr(65+i) for i in range(4)}

Nesse meio tempo, você pode fazer uma compreensão quasi-dict desta forma:

>>> dict([(i, chr(65+i)) for i in range(4)])

Ou como uma resposta mais direta:

dict([(key, name) for key, name in some_dictionary.iteritems if name[-5:] != 'Smith'])
Respondeu 07/10/2008 em 15:33
fonte usuário

votos
2

Ambas as soluções, filtro e compreensão requer a construção de uma nova lista. Eu não sei o suficiente dos internos do Python para ter certeza, mas eu acho que uma abordagem mais tradicional (mas menos elegante) poderia ser mais eficiente:

names = ['Jones', 'Vai', 'Smith', 'Perez']

item = 0
while item <> len(names):
    name = names [item]
    if name=='Smith':
        names.remove(name)
    else:
        item += 1

print names

De qualquer forma, para listas curtas, eu ficar com uma das duas soluções propostas anteriormente.

Respondeu 20/08/2008 em 19:20
fonte usuário

votos
2
names = filter(lambda x: x[-5:] != "Smith", names);
Respondeu 20/08/2008 em 18:48
fonte usuário

votos
1

Aqui está a minha filter_inplaceaplicação que pode ser usado para filtrar os itens de uma lista no local, eu vim com isso em minha própria forma independente antes de encontrar nesta página. É o mesmo algoritmo que o PabloG publicado, só fez mais genérico para que você possa usá-lo para filtrar listas no lugar, ele também é capaz de remover a partir da lista com base na comparisonFuncse inverteu está definido True; uma espécie de filtro de revertida se você quiser.

def filter_inplace(conditionFunc, list, reversed=False):
    index = 0
    while index < len(list):
        item = list[index]

        shouldRemove = not conditionFunc(item)
        if reversed: shouldRemove = not shouldRemove

        if shouldRemove:
            list.remove(item)
        else:
            index += 1
Respondeu 15/03/2013 em 15:12
fonte usuário

votos
1

No caso de um conjunto.

toRemove = set([])  
for item in mySet:  
    if item is unwelcome:  
        toRemove.add(item)  
mySets = mySet - toRemove 
Respondeu 07/12/2009 em 05:01
fonte usuário

votos
1

O filtro e compreensões lista são ok para o seu exemplo, mas eles têm um par de problemas:

  • Eles fazem uma cópia de sua lista e retornar o novo, e que será ineficiente quando a lista original é muito grande
  • Eles podem ser muito complicado quando os critérios para escolher itens (no seu caso, se o nome [-5:] == 'Smith') é mais complicado, ou tem várias condições.

Sua solução original é realmente mais eficiente para muito grandes listas, mesmo se podemos concordar que é mais feio. Mas se você se preocupa que você pode ter vários 'John Smith', ele pode ser corrigido, excluindo base na posição e não em valor:

names = ['Jones', 'Vai', 'Smith', 'Perez', 'Smith']

toremove = []
for pos, name in enumerate(names):
    if name[-5:] == 'Smith':
        toremove.append(pos)
for pos in sorted(toremove, reverse=True):
    del(names[pos])

print names

Não podemos escolher uma solução sem considerar o tamanho da lista, mas para grandes listas Eu preferiria a sua solução de 2-pass em vez do filtro ou listas compreensões

Respondeu 02/10/2008 em 19:44
fonte usuário

votos
-2

Bem, este é claramente um problema com a estrutura de dados que você está usando. Use um hashtable por exemplo. Algumas implementações suportam várias entradas por chave, para que se possa quer estourar o mais novo elemento off, ou remover todos eles.

Mas esta é, eo que você vai encontrar a solução é, elegância através de uma estrutura de dados diferente, não algoritmo. Talvez você possa fazer melhor se ele está classificado, ou algo assim, mas iteração em uma lista é o seu único método aqui.

edit: um se dá conta de que ele pediu 'eficiência' ... todos estes métodos sugeridos apenas iterar sobre a lista, que é o mesmo que o que ele sugeriu.

Respondeu 20/08/2008 em 18:46
fonte usuário

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more