Pergunta

Esta pergunta já tem uma resposta aqui:

Quando estou a escrever código em Python, muitas vezes precisa remover itens de uma lista ou de outro tipo de sequência com base em alguns critérios.Ainda não encontrei uma solução que é elegante e eficiente, como a remoção de itens de uma lista que você está atualmente 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

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

Como sobre aquele que trabalha com dicionários?

Foi útil?

Solução

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

  1. Usando filter:

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

  2. Usando a lista de compreensões:

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

Note que em ambos os casos de manter os valores de para os quais a função de predicado avalia a True, então você tem que inverter a lógica (por exemplo,você diz "manter as pessoas que não tem o sobrenome Smith" em vez de "remover as pessoas que tem o sobrenome Smith").

Editar Engraçado...duas pessoas individualmente postado ambas as respostas que eu sugeri como eu estava postando o meu.

Outras dicas

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

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

Este tem a vantagem de que ele não cria uma nova lista (como filter ou uma lista de compreensão) e utiliza uma iteração em vez de uma lista de cópia (como [:]).

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

A resposta mais óbvia é a que John e um par de outras pessoas deu, 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 alguns perfis e experimentação, e o método mais eficiente eu cheguei é:

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

Atribuir aos "nomes[:]" significa, basicamente, "substituir o conteúdo da lista de nomes com o seguinte valor".É diferente de apenas atribuir nomes, que ele não cria um novo objeto da lista.Do lado direito da atribuição que é um gerador de expressão (observe o uso dos parênteses, ao invés de colchetes).Isso fará com que o Python para iterar através da lista.

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

Ressalva:embora esta solução é mais rápida do que a solução óbvia, é mais obscura, e depende de mais avançado Python técnicas.Se você usá-lo, eu recomendo que o acompanha com um comentário.É, provavelmente, só vale a pena usar em casos onde você realmente se preocupam com o desempenho desta operação específica (o que é muito rápido, não importa o que).(No caso eu usei esse, eu estava fazendo Um* feixe de pesquisa, e usou isso para remover pontos de pesquisa de de pesquisa de de feixe.)

Usando uma lista de compreensão

list = [x for x in list if x[-5:] != "smith"]

Há vezes quando a filtragem (usando filtro ou de uma lista de compreensão) não funciona.Isto acontece quando algum outro objeto está segurando uma referência para a lista que você está modificando e você precisar modificar a lista em seu lugar.

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

A única diferença de que o código original está o uso de names[:] em vez de names no loop.De que forma o código itera através de uma (rasa) cópia da lista e remoções de trabalho conforme o esperado.Desde que a lista de copiar é raso, é bastante rápida.

filtro seria maravilhoso para isso.Exemplo simples:

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

Editar: Corey compreensão lista é incrível.

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

Ambas as soluções, filtro e compreensão requer a criação de uma nova lista.Eu não sei o suficiente do Python internos para ter certeza, mas eu acho que que um mais tradicional (mas menos elegante) abordagem 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 maneira, para listas curtas, gosto de ficar com qualquer uma das duas soluções propostas anteriormente.

Para responder a sua pergunta sobre como trabalhar com dicionários, deve-se observar que o Python 3.0 incluirá dict compreensões:

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

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

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

Ou como uma resposta direta:

dict([(key, name) for key, name in some_dictionary.iteritems if name[-5:] != 'Smith'])

Se a lista deve ser filtrada no local e o tamanho da lista é bastante grande, então os algoritmos mencionados nas respostas anteriores, que são baseados na lista.remove(), pode ser inadequado, devido a sua complexidade computacional é O(n^2).Neste caso, você pode usar as seguintes não-tão pythonic função:

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

Editar:Na verdade, a solução em https://stackoverflow.com/a/4639748/274937 é superior ao de minas solução.É mais pythonic funciona mais rápido.Então, aqui está um novo filter_inplace() execução:

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

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

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

Sua solução original, na verdade, é mais eficiente para a grande lista, mesmo se podemos concordar que é mais feio.Mas se você se preocupar que você pode ter vários 'John Smith', ele pode ser corrigido por exclusão com 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 de eu prefiro o 2-passe a solução em vez de o filtro ou listas de compreensões

No caso de um conjunto.

toRemove = set([])  
for item in mySet:  
    if item is unwelcome:  
        toRemove.add(item)  
mySets = mySet - toRemove 

Aqui é o meu filter_inplace de implementação que podem ser utilizados para filtrar os itens de uma lista no local, surgiu-me esta no meu próprio, de forma independente antes de encontrar esta página.É o mesmo algoritmo que o que PabloG postado, apenas fez mais genérico, de modo que você pode usá-lo para filtrar a lista no local, ele também é capaz de remover da lista com base no comparisonFunc se é revertida conjunto True;uma espécie de revertidas filtro, se você vai.

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

Bem, este é claramente um problema com a estrutura de dados que você está usando.Use um hashtable, por exemplo.Algumas implementações suportam múltiplas entradas por chave, portanto, pode-pop o mais novo elemento de fora, ou de remover todos eles.

Mas este é, e o 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 é ordenada, ou algo assim, mas iteração em uma lista, é o único método aqui.

editar: não percebem que ele pediu para 'eficiência'...todos estes métodos sugeridos basta iterar sobre a lista, que é o mesmo como o que ele sugeriu.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top