Como você remove duplicatas de uma lista enquanto preserva a ordem?


770

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 ?

Respostas:


763

Aqui você tem algumas alternativas: http://www.peterbe.com/plog/uniqifiers-benchmark

O mais rápido:

def f7(seq):
    seen = set()
    seen_add = seen.add
    return [x for x in seq if not (x in seen or seen_add(x))]

Por que atribuir seen.addao seen_addinvés de apenas ligar seen.add? Python é uma linguagem dinâmica, e resolver seen.addcada iteração é mais caro do que resolver uma variável local.seen.addpoderia 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ê planeja usar muito essa função no mesmo conjunto de dados, talvez seja melhor usar um conjunto ordenado: http://code.activestate.com/recipes/528878/

O (1) inserção, exclusão e verificação de membros por operação.

(Nota adicional pequena: seen.add()sempre retorna None, portanto, o ordescrito acima existe apenas como uma maneira de tentar uma atualização definida, e não como parte integrante do teste lógico.)


20
O @JesseDhillon seen.addpoderia 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 bytecode dis.dis(f), poderá ver que ele é executado LOAD_ATTRpara o addmembro em cada iteração. ideone.com/tz1Tll
Markus Jarderot

5
Quando eu tento isso em uma lista de listas eu recebo: TypeError: Tipo unhashable: 'list'
Jens Timmerman

7
Sua solução não é a mais rápida. No Python 3 (não testou 2), isso é mais rápido (lista de 300k entradas - 0,045s (sua) vs 0,035s (esta)): seen = set (); return [x for x em linhas se x não for visto e não seen.add (x)]. Não consegui encontrar nenhum efeito de velocidade da linha seen_add que você fez. #
user136036

3
@ user136036 Por favor, faça um link para seus testes. Quantas vezes você os executou? seen_addé uma melhoria, mas os horários podem ser afetados pelos recursos do sistema no momento. Estaria interessado em ver horários completos
jamylak

2
Para quem está escrevendo código Python, você realmente deve pensar duas vezes antes de sacrificar a legibilidade e as convenções de Python comumente aceitas apenas para espremer mais alguns nanossegundos por loop. Testar com e sem seen_add = seen.addgera apenas um aumento de 1% na velocidade. Isso não é significativo.
Sleblanc 14/05/19

343

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 que OrderedDict(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_itertoolsbiblioteca ( pip install more_itertools) contém uma unique_everseenfunçã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:

>>> from  more_itertools import unique_everseen
>>> items = [1, 2, 0, 1, 3, 2]
>>> list(unique_everseen(items))
[1, 2, 0, 3]

Apenas uma importação simples de biblioteca e sem hacks. Isso vem de uma implementação da receita de itertools unique_everseenque se parece com:

def unique_everseen(iterable, key=None):
    "List unique elements, preserving order. Remember all elements ever seen."
    # unique_everseen('AAAABBBCCDAABBB') --> A B C D
    # unique_everseen('ABBCcAD', str.lower) --> A B C D
    seen = set()
    seen_add = seen.add
    if key is None:
        for element in filterfalse(seen.__contains__, iterable):
            seen_add(element)
            yield element
    else:
        for element in iterable:
            k = key(element)
            if k not in seen:
                seen_add(k)
                yield element

Em Python, 2.7+o idioma comum aceito (que funciona, mas não é otimizado para velocidade, eu usaria agora unique_everseen) para este uso collections.OrderedDict:

Tempo de execução: O (N)

>>> from collections import OrderedDict
>>> items = [1, 2, 0, 1, 3, 2]
>>> list(OrderedDict.fromkeys(items))
[1, 2, 0, 3]

Isso parece muito melhor do que:

seen = set()
[x for x in seq if x not in seen and not seen.add(x)]

e não utiliza o feio hack :

not seen.add(x)

que se baseia no fato de que set.addé um método no local que sempre retorna Nonepara not Noneavaliação True.

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


5
Convertendo para algum tipo personalizado de ditado apenas para pegar as chaves? Apenas outra muleta.
Nakilon

3
@ Nakilon Eu realmente não vejo como é uma muleta. Ele não expõe nenhum estado mutável, portanto é muito limpo nesse sentido. Internamente, os conjuntos Python são implementados com dict () ( stackoverflow.com/questions/3949310/… ), então basicamente você está apenas fazendo o que o intérprete faria de qualquer maneira.
Imran

Basta usar efeitos colaterais e fazer [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 um forloop: 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 #
ely

@EMS Isso não preserva a ordem. Você poderia muito bem fazer seen = set(seq).
flornquake

1
@CommuSoft Concordo, embora praticamente é quase sempre O (n), devido ao pior caso de super altamente improvável
jamylak

110

No Python 2.7 , a nova maneira de remover duplicatas de um iterável, mantendo-o na ordem original é:

>>> from collections import OrderedDict
>>> list(OrderedDict.fromkeys('abracadabra'))
['a', 'b', 'r', 'c', 'd']

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:

>>> list(dict.fromkeys('abracadabra'))
['a', 'b', 'r', 'c', 'd']

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 é:

>>> list(dict.fromkeys('abracadabra'))
['a', 'b', 'r', 'c', 'd']

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.


É mais rápido do que qualquer outra abordagem na minha máquina (python 3.5) , desde que não seja convertida OrderedDictem 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.
máximo

7
A única pegadinha é que os "elementos" iteráveis deve ser Hashable - seria bom ter o equivalente para iterables com elementos arbitrários (como uma lista de listas)
Mr_and_Mrs_D

A iteração da ordem de inserção sobre um ditado fornece funcionalidade que atende a mais casos de uso do que a remoção de duplicatas. Por exemplo, as análises científicas se baseiam em cálculos reproduzíveis que a iteração não determinística de ditado não suporta. A reprodutibilidade é um dos principais objetivos atuais da modelagem científica computacional, por isso agradecemos esse novo recurso. Embora eu saiba que é trivial criar com um ditado determinístico, um determinístico de alto desempenho set()ajudaria usuários mais ingênuos a desenvolver códigos reproduzíveis.
Arthur

41
sequence = ['1', '2', '3', '3', '6', '4', '5', '6']
unique = []
[unique.append(item) for item in sequence if item not in unique]

único → ['1', '2', '3', '6', '4', '5']


28
É importante notar que este é executado emn^2
goncalopp

25
Ick. 2 greves: (a construção de uma outra lista de Usando uma lista para testes de associação (lento, O (N)) e usando uma compreensão lista para os efeitos colaterais None! Referências no processo)
Martijn Pieters

1
Concordo com @MartijnPieters, não há absolutamente nenhuma razão para a compreensão da lista com efeitos colaterais. Em forvez disso, basta usar um loop
jamylak 17/02/19

31

Para 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.

import pandas as pd

my_list = [0, 1, 2, 3, 4, 1, 2, 3, 5]

>>> pd.Series(my_list).drop_duplicates().tolist()
# Output:
# [0, 1, 2, 3, 4, 5]

27
from itertools import groupby
[ key for key,_ in groupby(sortedList)]

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".


1
Mas isso não preserva a ordem!

1
Isso é problemático, pois não posso garantir que valores iguais sejam agrupados sem repetir a lista uma vez, quando poderia ter podado as duplicatas.
21139 Josh Glover

Eu assumi que "preservar a ordem" implicava que a lista fosse realmente ordenada.
Rafał Dowgird

1
Talvez a especificação da lista de entrada seja um pouco obscura. Os valores nem precisam ser agrupados: [2, 1, 3, 1]. Então, quais valores manter e quais excluir?

1
@igorkf Ignorando o segundo elemento do (s) par (es).
Rafał Dowgird 13/11/19

24

Eu acho que se você quiser manter a ordem,

você pode tentar isso:

list1 = ['b','c','d','b','c','a','a']    
list2 = list(set(list1))    
list2.sort(key=list1.index)    
print list2

OU da mesma forma, você pode fazer isso:

list1 = ['b','c','d','b','c','a','a']  
list2 = sorted(set(list1),key=list1.index)  
print list2 

Você também pode fazer isso:

list1 = ['b','c','d','b','c','a','a']    
list2 = []    
for i in list1:    
    if not i in list2:  
        list2.append(i)`    
print list2

Também pode ser escrito assim:

list1 = ['b','c','d','b','c','a','a']    
list2 = []    
[list2.append(i) for i in list1 if not i in list2]    
print list2 

3
Suas duas primeiras respostas assumem que a ordem da lista pode ser reconstruída usando uma função de classificação, mas pode não ser assim.
Richard

5
A maioria das respostas está focada no desempenho. Para listas que não são grandes o suficiente para se preocupar com o desempenho, a ordenada (conjunto (lista1), chave = lista1.index) é a melhor coisa que eu já vi. Nenhuma importação extra, nenhuma função extra, nenhuma variável extra e é bastante simples e legível.
Derek Veit

23

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 OrderedDictsolução fica obsoleta e, sem nenhuma declaração de importação, podemos simplesmente emitir:

>>> lst = [1, 2, 1, 3, 3, 2, 4]
>>> list(dict.fromkeys(lst))
[1, 2, 3, 4]

12

Para outra resposta muito tardia a outra pergunta muito antiga:

As itertoolsreceitas têm uma função que faz isso, usando a seentécnica de conjunto, mas:

  • Lida com uma keyfunção padrão .
  • Não usa hacks indecorosos.
  • Otimiza o loop pré-vinculando em seen.addvez de procurar N vezes. ( f7também faz isso, mas algumas versões não.)
  • Otimiza o loop usando 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 dentro ifilterfalse, é 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, f7usa um listcomp e não há como fazer isso aqui. (Você pode diretamente, em appendvez de yielding, ou pode alimentar o gerador na listfunçã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 keycaso, pode simplificá-lo como:

def unique(iterable):
    seen = set()
    seen_add = seen.add
    for element in itertools.ifilterfalse(seen.__contains__, iterable):
        seen_add(element)
        yield element

Eu esqueci completamente que more-itertoolsesta é claramente a melhor resposta. Uma from 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.
jamylak

12

Só para acrescentar outra implementação (muito alto desempenho) da funcionalidade de um tal de um módulo externo 1 : iteration_utilities.unique_everseen:

>>> from iteration_utilities import unique_everseen
>>> lst = [1,1,1,2,3,2,2,2,1,3,4]

>>> list(unique_everseen(lst))
[1, 2, 3, 4]

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, f7e more_itertools.unique_everseen:

%matplotlib notebook

from iteration_utilities import unique_everseen
from collections import OrderedDict
from more_itertools import unique_everseen as mi_unique_everseen

def f7(seq):
    seen = set()
    seen_add = seen.add
    return [x for x in seq if not (x in seen or seen_add(x))]

def iteration_utilities_unique_everseen(seq):
    return list(unique_everseen(seq))

def more_itertools_unique_everseen(seq):
    return list(mi_unique_everseen(seq))

def odict(seq):
    return list(OrderedDict.fromkeys(seq))

from simple_benchmark import benchmark

b = benchmark([f7, iteration_utilities_unique_everseen, more_itertools_unique_everseen, odict],
              {2**i: list(range(2**i)) for i in range(1, 20)},
              'list size (no duplicates)')
b.plot()

insira a descrição da imagem aqui

E apenas para garantir que eu também fiz um teste com mais duplicatas, apenas para verificar se isso faz diferença:

import random

b = benchmark([f7, iteration_utilities_unique_everseen, more_itertools_unique_everseen, odict],
              {2**i: [random.randint(0, 2**(i-1)) for _ in range(2**i)] for i in range(1, 20)},
              'list size (lots of duplicates)')
b.plot()

insira a descrição da imagem aqui

E um contendo apenas um valor:

b = benchmark([f7, iteration_utilities_unique_everseen, more_itertools_unique_everseen, odict],
              {2**i: [1]*(2**i) for i in range(1, 20)},
              'list size (only duplicates)')
b.plot()

insira a descrição da imagem aqui

Em todos esses casos, a iteration_utilities.unique_everseenfunção é a mais rápida (no meu computador).


Essa iteration_utilities.unique_everseenfunção também pode manipular valores laváveis ​​na entrada (no entanto, com um O(n*n)desempenho em vez do O(n)desempenho quando os valores são laváveis).

>>> lst = [{1}, {1}, {2}, {1}, {3}]

>>> list(unique_everseen(lst))
[{1}, {2}, {3}]

1 Isenção de responsabilidade: sou o autor desse pacote.


Não compreendo a necessidade desta linha: seen_add = seen.add- isso é necessário para os benchmarks?
Alex #

@ Alex Esta é a abordagem dada nesta resposta . Faria mais sentido perguntar lá. Eu apenas usei a abordagem dessa resposta para comparar os tempos.
MSeifert 6/07/19

você pode adicionar o dict.fromkeys()método ao seu gráfico, por favor?
Boris

Não tenho muita certeza se tenho o mesmo para fazer os horários em breve. Você acha que é muito mais rápido que o ordereddict.fromkeys?
MSeifert

"Esta função iteration_utilities.unique_everseen também pode manipular valores laváveis ​​na entrada" - sim, isso é realmente importante. Se você tem uma lista de ditados de ditados, etc, esta é a única maneira de fazer o trabalho, mesmo em pequena escala.
Roko Mijic

6

Para nenhum tipo lavável (por exemplo, lista de listas), com base nos MizardX:

def f7_noHash(seq)
    seen = set()
    return [ x for x in seq if str( x ) not in seen and not seen.add( str( x ) )]

3

Tomando emprestada a ideia recursiva usada na definição da nubfunção de Haskell para listas, esta seria uma abordagem recursiva:

def unique(lst):
    return [] if lst==[] else [lst[0]] + unique(filter(lambda x: x!= lst[0], lst[1:]))

por exemplo:

In [118]: unique([1,5,1,1,4,3,4])
Out[118]: [1, 5, 4, 3]

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

In [122]: %timeit unique(np.random.randint(5, size=(1)))
10000 loops, best of 3: 25.3 us per loop

In [123]: %timeit unique(np.random.randint(5, size=(10)))
10000 loops, best of 3: 42.9 us per loop

In [124]: %timeit unique(np.random.randint(5, size=(100)))
10000 loops, best of 3: 132 us per loop

In [125]: %timeit unique(np.random.randint(5, size=(1000)))
1000 loops, best of 3: 1.05 ms per loop

In [126]: %timeit unique(np.random.randint(5, size=(10000)))
100 loops, best of 3: 11 ms per loop

Também acho interessante que isso possa ser facilmente generalizado para exclusividade por outras operações. Como isso:

import operator
def unique(lst, cmp_op=operator.ne):
    return [] if lst==[] else [lst[0]] + unique(filter(lambda x: cmp_op(x, lst[0]), lst[1:]), cmp_op)

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:

def test_round(x,y):
    return round(x) != round(y)

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:

In [6]: unique([1.2, 5, 1.9, 1.1, 4.2, 3, 4.8], test_round)
Out[6]: [1.2, 5, 1.9, 4.2, 3]

1
Observe que o desempenho ficará ruim quando o número de elementos exclusivos for muito grande em relação ao número total de elementos, pois o uso de cada chamada recursiva sucessiva filtermal 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.
Ely

3

Variante de redução 5 vezes mais rápida, mas mais sofisticada

>>> l = [5, 6, 6, 1, 1, 2, 2, 3, 4]
>>> reduce(lambda r, v: v in r[1] and r or (r[0].append(v) or r[1].add(v)) or r, l, ([], set()))[0]
[5, 6, 1, 2, 3, 4]

Explicação:

default = (list(), set())
# use list to keep order
# use set to make lookup faster

def reducer(result, item):
    if item not in result[1]:
        result[0].append(item)
        result[1].add(item)
    return result

>>> reduce(reducer, l, default)[0]
[5, 6, 1, 2, 3, 4]

3

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.

def unique(my_list): 
    return [x for x in my_list if x not in locals()['_[1]']]

Demo:

l1 = [1, 2, 3, 4, 1, 2, 3, 4, 5]
l2 = [x for x in l1 if x not in locals()['_[1]']]
print l2

Resultado:

[1, 2, 3, 4, 5]

2
Observe também que a tornaria uma operação O (n ^ 2), onde a criação de um set / dict (que tem tempo de pesquisa constante) e a adição de apenas elementos não vistos anteriormente serão lineares.
ely

Este é o Python 2.6, só eu acredito. E sim, é O (n ^ 2)
jamylak

2

A resposta do MizardX fornece uma boa coleção de múltiplas abordagens.

Isto é o que eu inventei enquanto pensava em voz alta:

mylist = [x for i,x in enumerate(mylist) if x not in mylist[i+1:]]

Sua solução é boa, mas é a última aparência de cada elemento. Para levar a primeira utilização aparência: [x para i, x em enumerar (mylist) se x não em mylist [: i]]
Rivka

7
Como a pesquisa em uma lista é uma O(n)operação e você a executa em cada item, a complexidade resultante da sua solução seria O(n^2). Isso é inaceitável para um problema tão trivial.
Nikita Volkov

2

Aqui está uma maneira simples de fazer isso:

list1 = ["hello", " ", "w", "o", "r", "l", "d"]
sorted(set(list1 ), key=lambda x:list1.index(x))

que fornece a saída:

["hello", " ", "w", "o", "r", "l", "d"]

1

Você poderia fazer um tipo de truque feio de compreensão de lista.

[l[i] for i in range(len(l)) if l.index(l[i]) == i]

Prefere i,e in enumerate(l)a l[i] for i in range(len(l)).
Evpok

1

Abordagem relativamente eficaz com _sorted_um numpymatrizes:

b = np.array([1,3,3, 8, 12, 12,12])    
numpy.hstack([b[0], [x[0] for x in zip(b[1:], b[:-1]) if x[0]!=x[1]]])

Saídas:

array([ 1,  3,  8, 12])

1
l = [1,2,2,3,3,...]
n = []
n.extend(ele for ele in l if ele not in set(n))

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.


1
Uso inteligente de extendcom uma expressão geradora que depende da coisa que está sendo estendida (portanto +1), mas set(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 usar ele 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.
John Coleman # 1

1

Uma solução recursiva simples:

def uniquefy_list(a):
    return uniquefy_list(a[1:]) if a[0] in a[1:] else [a[0]]+uniquefy_list(a[1:]) if len(a)>1 else [a[0]]

1

Eliminando os valores duplicados em uma sequência, mas preserve a ordem dos itens restantes. Uso da função de gerador de uso geral.

# for hashable sequence
def remove_duplicates(items):
    seen = set()
    for item in items:
        if item not in seen:
            yield item
            seen.add(item)

a = [1, 5, 2, 1, 9, 1, 5, 10]
list(remove_duplicates(a))
# [1, 5, 2, 9, 10]



# for unhashable sequence
def remove_duplicates(items, key=None):
    seen = set()
    for item in items:
        val = item if key is None else key(item)
        if val not in seen:
            yield item
            seen.add(val)

a = [ {'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 1, 'y': 2}, {'x': 2, 'y': 4}]
list(remove_duplicates(a, key=lambda d: (d['x'],d['y'])))
# [{'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 2, 'y': 4}]

1

usuários de pandas devem conferir pandas.unique.

>>> import pandas as pd
>>> lst = [1, 2, 1, 3, 3, 2, 4]
>>> pd.unique(lst)
array([1, 2, 3, 4])

A função retorna uma matriz NumPy. Se necessário, você pode convertê-lo em uma lista com o tolistmétodo


1
Agradável. Eu nunca imaginaria usar pandas para isso, mas funciona
seralouk

0

Se você precisar de um revestimento, talvez isso ajude:

reduce(lambda x, y: x + y if y[0] not in x else x, map(lambda x: [x],lst))

... deve funcionar, mas me corrija se eu estiver errado


é uma expressão condicional por isso é bom
code22

0

Se você usa rotineiramente pandas, e a estética é preferível ao desempenho, considere a função interna pandas.Series.drop_duplicates:

    import pandas as pd
    import numpy as np

    uniquifier = lambda alist: pd.Series(alist).drop_duplicates().tolist()

    # from the chosen answer 
    def f7(seq):
        seen = set()
        seen_add = seen.add
        return [ x for x in seq if not (x in seen or seen_add(x))]

    alist = np.random.randint(low=0, high=1000, size=10000).tolist()

    print uniquifier(alist) == f7(alist)  # True

Cronometragem:

    In [104]: %timeit f7(alist)
    1000 loops, best of 3: 1.3 ms per loop
    In [110]: %timeit uniquifier(alist)
    100 loops, best of 3: 4.39 ms per loop

0

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.

def deduplicate(l):
    count = {}
    (read,write) = (0,0)
    while read < len(l):
        if l[read] in count:
            read += 1
            continue
        count[l[read]] = True
        l[write] = l[read]
        read += 1
        write += 1
    return l[0:write]

0

Uma solução sem usar módulos ou conjuntos importados:

text = "ask not what your country can do for you ask what you can do for your country"
sentence = text.split(" ")
noduplicates = [(sentence[i]) for i in range (0,len(sentence)) if sentence[i] not in sentence[:i]]
print(noduplicates)

Dá saída:

['ask', 'not', 'what', 'your', 'country', 'can', 'do', 'for', 'you']

isso é complexidade O (N ** 2) + fatias de lista a cada vez.
Jean-François Fabre

0

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

for i in range(len(l)-1,0,-1): 
    if l[i] in l[:i]: del l[i] 

Um teste simples da implementação

In [91]: from random import randint, seed                                                                                            
In [92]: seed('20080808') ; l = [randint(1,6) for _ in range(12)] # Beijing Olympics                                                                 
In [93]: for i in range(len(l)-1,0,-1): 
    ...:     print(l) 
    ...:     print(i, l[i], l[:i], end='') 
    ...:     if l[i] in l[:i]: 
    ...:          print( ': remove', l[i]) 
    ...:          del l[i] 
    ...:     else: 
    ...:          print() 
    ...: print(l)
[6, 5, 1, 4, 6, 1, 6, 2, 2, 4, 5, 2]
11 2 [6, 5, 1, 4, 6, 1, 6, 2, 2, 4, 5]: remove 2
[6, 5, 1, 4, 6, 1, 6, 2, 2, 4, 5]
10 5 [6, 5, 1, 4, 6, 1, 6, 2, 2, 4]: remove 5
[6, 5, 1, 4, 6, 1, 6, 2, 2, 4]
9 4 [6, 5, 1, 4, 6, 1, 6, 2, 2]: remove 4
[6, 5, 1, 4, 6, 1, 6, 2, 2]
8 2 [6, 5, 1, 4, 6, 1, 6, 2]: remove 2
[6, 5, 1, 4, 6, 1, 6, 2]
7 2 [6, 5, 1, 4, 6, 1, 6]
[6, 5, 1, 4, 6, 1, 6, 2]
6 6 [6, 5, 1, 4, 6, 1]: remove 6
[6, 5, 1, 4, 6, 1, 2]
5 1 [6, 5, 1, 4, 6]: remove 1
[6, 5, 1, 4, 6, 2]
4 6 [6, 5, 1, 4]: remove 6
[6, 5, 1, 4, 2]
3 4 [6, 5, 1]
[6, 5, 1, 4, 2]
2 1 [6, 5]
[6, 5, 1, 4, 2]
1 5 [6]
[6, 5, 1, 4, 2]

In [94]:                                                                                                                             

Antes de postar, procurei no corpo das respostas por 'lugar' sem sucesso. Se outras pessoas resolverem o problema de maneira semelhante, me avise e removerei minha resposta o mais rápido possível.
Gboffi #

Você poderia usar l[:] = <one of the the faster methods>se quisesse uma operação no local, não?
timgeb

@timgeb Sim e não ... Quando eu valorizo, a=[1]; b=a; a[:]=[2]então b==[2]é Truee 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.
gboffi

0

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.

def DelDupes(aseq) :
    seen = set()
    return [x for x in aseq if (x.lower() not in seen) and (not seen.add(x.lower()))]

Funções estreitamente associadas são:

def HasDupes(aseq) :
    s = set()
    return any(((x.lower() in s) or s.add(x.lower())) for x in aseq)

def GetDupes(aseq) :
    s = set()
    return set(x for x in aseq if ((x.lower() in s) or s.add(x.lower())))

0

Compreensão de uma lista de liners:

values_non_duplicated = [value for index, value in enumerate(values) if value not in values[ : index]]

Basta adicionar um condicional para verificar se o valor não está em uma posição anterior

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.