Forma elegante para remover itens da sequência em Python?[duplicado]
-
09-06-2019 - |
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?
Solução
Duas maneiras fáceis de realizar a filtragem são:
Usando
filter
:names = filter(lambda name: name[-5:] != "Smith", names)
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.