Tenho uma lista de listas em Python:
k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]]
E eu quero remover elementos duplicados dele. Era se fosse uma lista normal não de listas que eu poderia usar set
. Mas, infelizmente, essa lista não é hashable e não pode fazer um conjunto de listas. Apenas de tuplas. Assim, posso transformar todas as listas em tuplas e usar definir e voltar para listas. Mas isso não é rápido.
Como isso pode ser feito da maneira mais eficiente?
O resultado da lista acima deve ser:
k = [[5, 6, 2], [1, 2], [3], [4]]
Eu não me importo em preservar a ordem.
Nota: esta questão é semelhante, mas não exatamente o que eu preciso. Procurado SO, mas não encontrei duplicata exata.
Avaliação comparativa:
import itertools, time
class Timer(object):
def __init__(self, name=None):
self.name = name
def __enter__(self):
self.tstart = time.time()
def __exit__(self, type, value, traceback):
if self.name:
print '[%s]' % self.name,
print 'Elapsed: %s' % (time.time() - self.tstart)
k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [5, 2], [6], [8], [9]] * 5
N = 100000
print len(k)
with Timer('set'):
for i in xrange(N):
kt = [tuple(i) for i in k]
skt = set(kt)
kk = [list(i) for i in skt]
with Timer('sort'):
for i in xrange(N):
ks = sorted(k)
dedup = [ks[i] for i in xrange(len(ks)) if i == 0 or ks[i] != ks[i-1]]
with Timer('groupby'):
for i in xrange(N):
k = sorted(k)
dedup = list(k for k, _ in itertools.groupby(k))
with Timer('loop in'):
for i in xrange(N):
new_k = []
for elem in k:
if elem not in new_k:
new_k.append(elem)
"loop in" (método quadrático) o mais rápido de todos para listas curtas. Para listas longas, é mais rápido que todos, exceto o método groupby. Isso faz sentido?
Para uma lista curta (a do código), 100.000 iterações:
[set] Elapsed: 1.3900001049
[sort] Elapsed: 0.891000032425
[groupby] Elapsed: 0.780999898911
[loop in] Elapsed: 0.578000068665
Para uma lista mais longa (aquela no código duplicada 5 vezes):
[set] Elapsed: 3.68700003624
[sort] Elapsed: 3.43799996376
[groupby] Elapsed: 1.03099989891
[loop in] Elapsed: 1.85900020599
Respostas:
itertools
muitas vezes oferece as soluções mais rápidas e poderosas para este tipo de problemas, e é bem vale a pena ficar intimamente familiarizado com -!)Edit : como mencionei em um comentário, os esforços de otimização normais são focados em grandes entradas (a abordagem big-O) porque é muito mais fácil que oferece bons retornos sobre os esforços. Mas às vezes (essencialmente para "gargalos tragicamente cruciais" em profundos loops internos de código que estão ultrapassando os limites dos limites de desempenho), pode ser necessário entrar em muito mais detalhes, fornecendo distribuições de probabilidade, decidindo quais medidas de desempenho otimizar (talvez o limite superior ou o percentil 90 é mais importante do que uma média ou mediana, dependendo dos aplicativos), realizando verificações possivelmente heurísticas no início para escolher algoritmos diferentes dependendo das características dos dados de entrada e assim por diante.
Medidas cuidadosas de desempenho de "ponto" (código A versus código B para uma entrada específica) fazem parte desse processo extremamente caro e o módulo de biblioteca padrão
timeit
ajuda aqui. No entanto, é mais fácil usá-lo em um prompt de shell. Por exemplo, aqui está um pequeno módulo para mostrar a abordagem geral para este problema, salve-o comonodup.py
:Observe a verificação de sanidade (realizada quando você acabou de fazer
python nodup.py
) e a técnica de içamento básica (faça nomes globais constantes locais para cada função para velocidade) para colocar as coisas em pé de igualdade.Agora podemos executar verificações na pequena lista de exemplos:
confirmando que a abordagem quadrática tem constantes pequenas o suficiente para torná-la atraente para listas minúsculas com poucos valores duplicados. Com uma pequena lista sem duplicatas:
a abordagem quadrática não é ruim, mas as classificadas e agrupadas são melhores. Etc etc.
Se (como a obsessão com o desempenho sugere) esta operação está em um loop interno central de seu aplicativo extraindo limites, vale a pena tentar o mesmo conjunto de testes em outras amostras de entrada representativas, possivelmente detectando alguma medida simples que poderia permitir heuristicamente escolha uma ou outra abordagem (mas a medida deve ser rápida, é claro).
Também vale a pena considerar manter uma representação diferente para
k
- por que tem que ser uma lista de listas em vez de um conjunto de tuplas em primeiro lugar? Se a tarefa de remoção de duplicatas for frequente e a criação de perfil mostrar que ela é o gargalo de desempenho do programa, manter um conjunto de tuplas o tempo todo e obter uma lista de listas apenas se e onde necessário, pode ser mais rápido no geral, por exemplo.fonte
Fazendo manualmente, criando uma nova
k
lista e adicionando entradas não encontradas até agora:Simples de compreender, e você preserva a ordem da primeira ocorrência de cada elemento, se isso for útil, mas acho que é quadrática em complexidade, pois você está procurando
new_k
cada elemento por completo .fonte
k = ([[1, 2], [4], [5, 6, 2], [1, 2], [3], [5, 2], [6], [8], [9]] +[[x] for x in range(1000)]) *5
mostrará o comportamento quadrático muito bemNão sei se é necessariamente mais rápido, mas você não precisa usar para tuplas e conjuntos.
fonte
random
e cronometrartime
.Todas as
set
soluções relacionadas a esse problema até agora requerem a criação de um todoset
antes da iteração.É possível tornar isso preguiçoso e, ao mesmo tempo, preservar a ordem, iterando a lista de listas e adicionando a um "visto"
set
. Então, só produzirá uma lista se ela não for encontrada neste rastreadorset
.Esta
unique_everseen
receita está disponível naitertools
documentação . Também está disponível natoolz
biblioteca de terceiros :Observe que a
tuple
conversão é necessária porque as listas não são hashable.fonte
Mesmo a sua lista "longa" é muito curta. Além disso, você os escolheu para corresponder aos dados reais? O desempenho varia de acordo com a aparência real desses dados. Por exemplo, você tem uma lista curta repetida indefinidamente para fazer uma lista mais longa. Isso significa que a solução quadrática é linear em seus benchmarks, mas não na realidade.
Para listas realmente grandes, o código definido é sua melhor aposta - é linear (embora precise de muito espaço). Os métodos sort e groupby são O (n log n) e o método loop in é obviamente quadrático, então você sabe como eles escalarão conforme n se tornar realmente grande. Se esse for o tamanho real dos dados que você está analisando, quem se importa? É minúsculo.
Aliás, estou vendo uma notável aceleração se não formar uma lista intermediária para fazer o conjunto, ou seja, se eu substituir
com
A solução real pode depender de mais informações: Tem certeza de que uma lista de listas é realmente a representação de que você precisa?
fonte
Lista de tupla e {} pode ser usada para remover duplicatas
fonte
Crie um dicionário com tupla como chave e imprima as chaves.
fonte
Isso deve funcionar.
fonte
Estranhamente, as respostas acima remove as 'duplicatas', mas e se eu quiser remover também o valor duplicado ?? O seguinte deve ser útil e não cria um novo objeto na memória!
e o / p é:
fonte
Outra solução provavelmente mais genérica e simples é criar um dicionário codificado pela versão da string dos objetos e obter os valores () no final:
O problema é que isso só funciona para objetos cuja representação de string é uma chave única boa o suficiente (o que é verdadeiro para a maioria dos objetos nativos).
fonte