Existe um built-in que remove duplicatas da lista em Python, preservando a ordem? Eu sei que posso usar um conjunto para remover duplicatas, mas isso destrói o pedido original. Eu também sei que posso fazer o meu próprio assim:
def uniq(input):
output = []
for x in input:
if x not in output:
output.append(x)
return output
(Agradecemos o desenrolar desse exemplo de código .)
Mas eu gostaria de me valer de um idioma interno ou mais pitonico, se possível.
Pergunta relacionada: No Python, qual é o algoritmo mais rápido para remover duplicatas de uma lista para que todos os elementos sejam exclusivos e preservem a ordem ?
fonte
seen.add
poderia ter mudado entre as iterações, e o tempo de execução não é inteligente o suficiente para descartar isso. Para jogar pelo seguro, ele deve verificar o objeto toda vez. - Se você olhar para o bytecodedis.dis(f)
, poderá ver que ele é executadoLOAD_ATTR
para oadd
membro em cada iteração. ideone.com/tz1Tllseen_add
é uma melhoria, mas os horários podem ser afetados pelos recursos do sistema no momento. Estaria interessado em ver horários completosseen_add = seen.add
gera apenas um aumento de 1% na velocidade. Isso não é significativo.Edit 2016
Como Raymond apontou , no python 3.5+, onde
OrderedDict
é implementado em C, a abordagem de compreensão da lista será mais lenta do queOrderedDict
(a menos que você realmente precise da lista no final - e mesmo assim, apenas se a entrada for muito curta). Portanto, a melhor solução para 3.5+ éOrderedDict
.Edição importante 2015
Como observa o @abarnert , a
more_itertools
biblioteca (pip install more_itertools
) contém umaunique_everseen
função criada para resolver esse problema sem nenhuma mutação ilegível (not seen.add
) na compreensão da lista. Essa também é a solução mais rápida:Apenas uma importação simples de biblioteca e sem hacks. Isso vem de uma implementação da receita de itertools
unique_everseen
que se parece com:Em Python,
2.7+
oidioma comum aceito(que funciona, mas não é otimizado para velocidade, eu usaria agoraunique_everseen
) para este usocollections.OrderedDict
:Tempo de execução: O (N)
Isso parece muito melhor do que:
e não utiliza o feio hack :
que se baseia no fato de que
set.add
é um método no local que sempre retornaNone
paranot None
avaliaçãoTrue
.Observe, no entanto, que a solução de hack é mais rápida em velocidade bruta, embora tenha a mesma complexidade de tempo de execução O (N).
fonte
[seen.add(x) for x in seq if x not in seen]
, ou, se você não gostar de efeitos colaterais de compreensão, use umfor
loop:for x in seq: seen.add(x) if x not in seen else None
(ainda é uma linha, embora neste caso eu acho que uma linha seja uma propriedade boba que você deve ter em um solução #seen = set(seq)
.No Python 2.7 , a nova maneira de remover duplicatas de um iterável, mantendo-o na ordem original é:
No Python 3.5 , o OrderedDict tem uma implementação em C. Meus horários mostram que agora é a mais rápida e a mais curta das várias abordagens para o Python 3.5.
No Python 3.6 , o ditado regular tornou-se ordenado e compacto. (Esse recurso é válido para CPython e PyPy, mas pode não estar presente em outras implementações). Isso nos fornece uma nova maneira mais rápida de desduplicar, mantendo a ordem:
No Python 3.7 , o dict regular é garantido para ambos ordenados em todas as implementações. Portanto, a solução mais curta e rápida é:
Resposta ao @max: Depois de passar para 3.6 ou 3.7 e usar o ditado regular em vez do OrderedDict , não é possível superar o desempenho de nenhuma outra maneira. O dicionário é denso e rapidamente se converte em uma lista com quase nenhuma sobrecarga. A lista de destino é pré-dimensionada para len (d), que salva todos os redimensionamentos que ocorrem na compreensão de uma lista. Além disso, como a lista de chaves interna é densa, copiar os ponteiros é quase rápido como uma cópia da lista.
fonte
OrderedDict
em uma lista no final. Se eu precisar convertê-lo em uma lista, para pequenas entradas, a abordagem de compreensão da lista ainda é mais rápida em até 1,5 vezes. Dito isto, esta solução é muito mais limpa.set()
ajudaria usuários mais ingênuos a desenvolver códigos reproduzíveis.único →
['1', '2', '3', '6', '4', '5']
fonte
n^2
None
! Referências no processo)for
vez disso, basta usar um loopPara não chutar um cavalo morto (esta pergunta é muito antiga e já tem muitas respostas boas), mas aqui está uma solução usando pandas que é bastante rápida em muitas circunstâncias e é simples de usar.
fonte
A lista nem precisa ser classificada , a condição suficiente é que valores iguais sejam agrupados.
Edit: Presumi que "preservar a ordem" implica que a lista seja realmente ordenada. Se não for esse o caso, a solução do MizardX é a correta.
Edição da comunidade: essa é, no entanto, a maneira mais elegante de "compactar elementos consecutivos duplicados em um único elemento".
fonte
Eu acho que se você quiser manter a ordem,
você pode tentar isso:
OU da mesma forma, você pode fazer isso:
Você também pode fazer isso:
Também pode ser escrito assim:
fonte
No Python 3.7 e superior, é garantido que os dicionários lembrem sua ordem de inserção de chave. A resposta a esta pergunta resume o estado atual das coisas.
A
OrderedDict
solução fica obsoleta e, sem nenhuma declaração de importação, podemos simplesmente emitir:fonte
Para outra resposta muito tardia a outra pergunta muito antiga:
As
itertools
receitas têm uma função que faz isso, usando aseen
técnica de conjunto, mas:key
função padrão .seen.add
vez de procurar N vezes. (f7
também faz isso, mas algumas versões não.)ifilterfalse
, portanto, você só precisa fazer um loop sobre os elementos exclusivos do Python, em vez de todos eles. (Você ainda itera todos eles por dentroifilterfalse
, é claro, mas isso é em C e muito mais rápido.)É realmente mais rápido que
f7
? Depende dos seus dados, então você terá que testá-los e ver. Se você deseja uma lista no final,f7
usa um listcomp e não há como fazer isso aqui. (Você pode diretamente, emappend
vez deyield
ing, ou pode alimentar o gerador nalist
função, mas nenhum deles pode ser tão rápido quanto o LIST_APPEND dentro de um listcomp.) De qualquer forma, geralmente, espremer alguns microssegundos não será tão É importante ter uma função já escrita de fácil compreensão, reutilizável e que não exija DSU quando você desejar decorar.Como em todas as receitas, também está disponível em
more-iterools
.Se você quiser apenas o
key
caso, pode simplificá-lo como:fonte
more-itertools
esta é claramente a melhor resposta. Umafrom more_itertools import unique_everseen
list(unique_everseen(items))
abordagem simples Uma abordagem muito mais rápida que a minha e muito melhor que a resposta aceita, acho que o download da biblioteca vale a pena. Vou comunidade wiki minha resposta e adicione no.Só para acrescentar outra implementação (muito alto desempenho) da funcionalidade de um tal de um módulo externo 1 :
iteration_utilities.unique_everseen
:Horários
Eu fiz alguns horários (Python 3.6) e estes mostram que é mais rápido do que todas as outras alternativas que eu testados, incluindo
OrderedDict.fromkeys
,f7
emore_itertools.unique_everseen
:E apenas para garantir que eu também fiz um teste com mais duplicatas, apenas para verificar se isso faz diferença:
E um contendo apenas um valor:
Em todos esses casos, a
iteration_utilities.unique_everseen
função é a mais rápida (no meu computador).Essa
iteration_utilities.unique_everseen
função também pode manipular valores laváveis na entrada (no entanto, com umO(n*n)
desempenho em vez doO(n)
desempenho quando os valores são laváveis).1 Isenção de responsabilidade: sou o autor desse pacote.
fonte
seen_add = seen.add
- isso é necessário para os benchmarks?dict.fromkeys()
método ao seu gráfico, por favor?ordereddict.fromkeys
?Para nenhum tipo lavável (por exemplo, lista de listas), com base nos MizardX:
fonte
Tomando emprestada a ideia recursiva usada na definição da
nub
função de Haskell para listas, esta seria uma abordagem recursiva:por exemplo:
Eu tentei para aumentar o tamanho dos dados e vi uma complexidade de tempo sub-linear (não definitiva, mas sugere que isso deve ser bom para dados normais).
Também acho interessante que isso possa ser facilmente generalizado para exclusividade por outras operações. Como isso:
Por exemplo, você pode transmitir uma função que usa a noção de arredondamento para o mesmo número inteiro como se fosse "igualdade" para fins de exclusividade, como esta:
então unique (some_list, test_round) forneceria os elementos exclusivos da lista em que exclusividade não significava mais igualdade tradicional (o que é implícito no uso de qualquer tipo de abordagem baseada em conjunto ou em chave de dict para esse problema), mas em vez disso apenas o primeiro elemento que arredonda para K para cada número inteiro possível K que os elementos podem arredondar para, por exemplo:
fonte
filter
mal se beneficiará da chamada anterior. Mas se o número de elementos únicos for pequeno em relação ao tamanho da matriz, isso deve ter um desempenho muito bom.Variante de redução 5 vezes mais rápida, mas mais sofisticada
Explicação:
fonte
Você pode fazer referência a uma compreensão de lista, pois ela está sendo construída pelo símbolo '_ [1]'.
Por exemplo, a função a seguir define uma lista de elementos sem alterar sua ordem, referenciando sua compreensão da lista.
Demo:
Resultado:
fonte
A resposta do MizardX fornece uma boa coleção de múltiplas abordagens.
Isto é o que eu inventei enquanto pensava em voz alta:
fonte
O(n)
operação e você a executa em cada item, a complexidade resultante da sua solução seriaO(n^2)
. Isso é inaceitável para um problema tão trivial.Aqui está uma maneira simples de fazer isso:
que fornece a saída:
fonte
Você poderia fazer um tipo de truque feio de compreensão de lista.
fonte
i,e in enumerate(l)
al[i] for i in range(len(l))
.Abordagem relativamente eficaz com
_sorted_
umnumpy
matrizes:Saídas:
fonte
Uma expressão geradora que usa a consulta O (1) de um conjunto para determinar se deve ou não incluir um elemento na nova lista.
fonte
extend
com uma expressão geradora que depende da coisa que está sendo estendida (portanto +1), masset(n)
é recalculada em cada estágio (que é linear) e isso esbarra na abordagem geral de ser quadrático. De fato, isso é quase certamente pior do que simplesmente usarele in n
. Fazer um conjunto para um único teste de associação não vale a despesa da criação do conjunto. Ainda assim - é uma abordagem interessante.Uma solução recursiva simples:
fonte
Eliminando os valores duplicados em uma sequência, mas preserve a ordem dos itens restantes. Uso da função de gerador de uso geral.
fonte
usuários de pandas devem conferir
pandas.unique
.A função retorna uma matriz NumPy. Se necessário, você pode convertê-lo em uma lista com o
tolist
métodofonte
Se você precisar de um revestimento, talvez isso ajude:
... deve funcionar, mas me corrija se eu estiver errado
fonte
Se você usa rotineiramente
pandas
, e a estética é preferível ao desempenho, considere a função internapandas.Series.drop_duplicates
:Cronometragem:
fonte
isso preservará a ordem e será executado em O (n) tempo. basicamente, a idéia é criar um buraco onde houver uma duplicata encontrada e afundá-la até o fundo. faz uso de um ponteiro de leitura e gravação. sempre que uma duplicata é encontrada, apenas o ponteiro de leitura avança e o ponteiro de gravação permanece na entrada duplicada para substituí-lo.
fonte
Uma solução sem usar módulos ou conjuntos importados:
Dá saída:
fonte
Um método no local
Esse método é quadrático, porque temos uma pesquisa linear na lista para cada elemento da lista (para isso, temos que adicionar o custo de reorganizar a lista por causa dos
del
).Dito isto, é possível operar no local se começarmos do final da lista e prosseguirmos em direção à origem, removendo cada termo presente na sub-lista à sua esquerda
Essa ideia no código é simplesmente
Um teste simples da implementação
fonte
l[:] = <one of the the faster methods>
se quisesse uma operação no local, não?a=[1]; b=a; a[:]=[2]
entãob==[2]
éTrue
e podemos dizer que estamos fazendo isso no local, no entanto, o que você propõe é usar um novo espaço para ter uma nova lista, substituir os dados antigos pelos novos e marcar o dados antigos para a coleta de lixo porque não são mais referenciados por nada, portanto, dizer que está operando no local é um pouco esticar o conceito do que mostrei que é possível ... é ineficiente? sim, mas eu já disse isso com antecedência.A abordagem do zmk usa a compreensão de lista que é muito rápida, mas mantém a ordem naturalmente. Para aplicar a seqüências sensíveis a maiúsculas e minúsculas, pode ser facilmente modificado. Isso também preserva o caso original.
Funções estreitamente associadas são:
fonte
Compreensão de uma lista de liners:
Basta adicionar um condicional para verificar se o valor não está em uma posição anterior
fonte