¿Cómo se eliminan los duplicados de una lista mientras se preserva el orden?


770

¿Hay una función incorporada que elimina los duplicados de la lista en Python, al tiempo que conserva el orden? Sé que puedo usar un conjunto para eliminar duplicados, pero eso destruye el orden original. También sé que puedo rodar el mío así:

def uniq(input):
  output = []
  for x in input:
    if x not in output:
      output.append(x)
  return output

(Gracias por relajarse para ese ejemplo de código ).

Pero me gustaría aprovechar un modismo incorporado o un lenguaje más pitón si es posible.

Pregunta relacionada: en Python, ¿cuál es el algoritmo más rápido para eliminar duplicados de una lista para que todos los elementos sean únicos y se mantenga el orden ?

Respuestas:


763

Aquí tienes algunas alternativas: http://www.peterbe.com/plog/uniqifiers-benchmark

El más 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 qué asignar seen.adda en seen_addlugar de simplemente llamar seen.add? Python es un lenguaje dinámico, y resolver seen.addcada iteración es más costoso que resolver una variable local. seen.addpodría haber cambiado entre iteraciones, y el tiempo de ejecución no es lo suficientemente inteligente como para descartarlo. Para ir a lo seguro, tiene que verificar el objeto cada vez.

Si planea usar esta función mucho en el mismo conjunto de datos, tal vez sería mejor con un conjunto ordenado: http://code.activestate.com/recipes/528878/

O (1) inserción, eliminación y verificación de miembros por operación.

(Pequeña nota adicional: seen.add()siempre regresa None, por lo que lo oranterior solo existe como una forma de intentar una actualización establecida, y no como una parte integral de la prueba lógica).


20
@JesseDhillon seen.addpodría haber cambiado entre iteraciones, y el tiempo de ejecución no es lo suficientemente inteligente como para descartarlo. Para jugar con seguridad, tiene que verificar el objeto cada vez. - Si observa el bytecode con dis.dis(f), puede ver que se ejecuta LOAD_ATTRpara el addmiembro en cada iteración. ideone.com/tz1Tll
Markus Jarderot

55
Cuando intento esto en una lista de listas, obtengo: TypeError: tipo no compartible: 'list'
Jens Timmerman

77
Tu solución no es la más rápida. En Python 3 (no probó 2) esto es más rápido (lista de entradas de 300k - 0.045s (la suya) vs 0.035s (esta): visto = conjunto (); devuelve [x para x en líneas si x no está en visto y no seen.add (x)]. No pude encontrar ningún efecto de velocidad de la línea seen_add que hiciste.
user136036

3
@ user136036 Enlace a sus pruebas. ¿Cuántas veces los corriste? seen_addes una mejora, pero los tiempos pueden verse afectados por los recursos del sistema en ese momento.
Estaría

2
Para cualquiera que esté escribiendo código Python, realmente debería pensarlo dos veces antes de sacrificar la legibilidad y las convenciones de Python comúnmente acordadas solo para exprimir unos pocos nanosegundos más por ciclo. Las pruebas con y sin seen_add = seen.addrendimientos solo aumentan un 1% la velocidad. Apenas es significativo.
sleblanc

343

Editar 2016

Como señaló Raymond , en Python 3.5+ donde OrderedDictse implementa en C, el enfoque de comprensión de la lista será más lento que OrderedDict(a menos que realmente necesite la lista al final, e incluso entonces, solo si la entrada es muy corta). Entonces, la mejor solución para 3.5+ es OrderedDict.

Edición importante 2015

Como señala @abarnert , la more_itertoolsbiblioteca ( pip install more_itertools) contiene una unique_everseenfunción que está diseñada para resolver este problema sin mutaciones ilegibles ( not seen.add) en las comprensiones de listas. Esta también es la solución más rápida:

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

Solo una importación de biblioteca simple y sin hacks. Esto proviene de una implementación de la receta de itertools unique_everseenque se ve así:

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

En Python, 2.7+el idioma común aceptado (que funciona pero no está optimizado para la velocidad, ahora usaríaunique_everseen ) para estos usos collections.OrderedDict:

Tiempo de ejecución: O (N)

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

Esto se ve mucho mejor que:

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

y no utiliza el truco feo :

not seen.add(x)

que se basa en el hecho de que set.addes un método in situ que siempre devuelveNone por lo que se not Noneevalúa True.

Sin embargo, tenga en cuenta que la solución de pirateo es más rápida en velocidad bruta, aunque tiene la misma complejidad de tiempo de ejecución O (N).


55
¿Convertir a algún tipo de dict personalizado solo para tomar las llaves? Solo otra muleta.
Nakilon

3
@Nakilon Realmente no veo cómo es una muleta. No expone ningún estado mutable, por lo que es muy limpio en ese sentido. Internamente, los conjuntos de Python se implementan con dict () ( stackoverflow.com/questions/3949310/… ), por lo que básicamente estás haciendo lo que el intérprete hubiera hecho de todos modos.
Imran

Simplemente use los efectos secundarios y hágalo [seen.add(x) for x in seq if x not in seen], o si no le gustan los efectos secundarios de la comprensión, simplemente use un forbucle: for x in seq: seen.add(x) if x not in seen else None(sigue siendo una línea, aunque en este caso creo que una línea es una propiedad tonta para tratar de tener en un solución.
ely

@EMS Eso no preserva el orden. Podrías hacer lo mismo seen = set(seq).
flornquake

1
@CommuSoft Estoy de acuerdo, aunque prácticamente casi siempre es O (n) debido al peor de los casos muy improbable
jamylak

110

En Python 2.7 , la nueva forma de eliminar duplicados de un iterable mientras se mantiene en el orden original es:

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

En Python 3.5 , el OrderedDict tiene una implementación en C. Mis tiempos muestran que este es ahora el más rápido y el más corto de los diversos enfoques para Python 3.5.

En Python 3.6 , el dict regular se hizo ordenado y compacto. (Esta característica es válida para CPython y PyPy pero puede no estar presente en otras implementaciones). Eso nos da una nueva forma más rápida de deducir mientras se conserva el orden:

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

En Python 3.7 , el dict regular está garantizado para ambos ordenados en todas las implementaciones. Entonces, la solución más corta y rápida es:

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

Respuesta a @max: una vez que te mueves a 3.6 o 3.7 y usas el dict regular en lugar de OrderedDict , no puedes superar el rendimiento de ninguna otra manera. El diccionario es denso y se convierte fácilmente en una lista casi sin sobrecarga. La lista de destino está dimensionada previamente para len (d), lo que guarda todos los cambios de tamaño que se producen en una comprensión de la lista. Además, dado que la lista de claves internas es densa, copiar los punteros es casi tan rápido como una copia de la lista.


Es más rápido que cualquier otro enfoque en mi máquina (python 3.5) siempre que no me convierta OrderedDicten una lista al final. Si necesito convertirlo en una lista, para entradas pequeñas, el enfoque de comprensión de la lista es aún más rápido hasta 1,5 veces. Dicho esto, esta solución es mucho más limpia.
max

77
El único inconveniente es que los "elementos" iterables deben ser hashaable - sería bueno tener el equivalente para iterables con elementos arbitrarios (como una lista de listas)
Mr_and_Mrs_D

La iteración del orden de inserción sobre un dict proporciona una funcionalidad que atiende más casos de uso que la eliminación de duplicados. Por ejemplo, los análisis científicos se basan en cálculos reproducibles que la iteración de dictado no determinista no admite. La reproducibilidad es un objetivo actual importante en el modelado científico computacional, por lo que agradecemos esta nueva característica. Aunque sé que es trivial construir con un dict determinista, un determinista de alto rendimiento set()ayudaría a los usuarios más ingenuos a desarrollar códigos reproducibles.
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
Vale la pena señalar que esto se ejecuta enn^2
goncalopp

25
Ick 2 advertencias: usar una lista para las pruebas de membresía (lento, O (N)) y usar una lista de comprensión de los efectos secundarios (¡construir otra lista de Nonereferencias en el proceso!)
Martijn Pieters

1
Estoy de acuerdo con @MartijnPieters, no hay absolutamente ninguna razón para la comprensión de la lista con efectos secundarios. Solo use un forbucle en su lugar
jamylak

31

No patear a un caballo muerto (esta pregunta es muy antigua y ya tiene muchas buenas respuestas), pero aquí hay una solución con pandas que es bastante rápida en muchas circunstancias y es muy fácil 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)]

La lista ni siquiera tiene que ser ordenada , la condición suficiente es que los valores iguales se agrupen.

Editar: Supuse que "preservar el orden" implica que la lista está realmente ordenada. Si este no es el caso, entonces la solución de MizardX es la correcta.

Edición comunitaria: sin embargo, esta es la forma más elegante de "comprimir elementos consecutivos duplicados en un solo elemento".


1
¡Pero esto no preserva el orden!

1
Hrm, esto es problemático, ya que no puedo garantizar que los valores iguales se agrupen sin recorrer una vez la lista, momento en el cual podría haber eliminado los duplicados.
Josh Glover el

Supuse que "mantener el orden" implicaba que la lista estaba realmente ordenada.
Rafał Dowgird

1
Quizás la especificación de la lista de entrada es un poco confusa. Los valores ni siquiera necesitan agruparse: [2, 1, 3, 1]. Entonces, ¿qué valores mantener y cuáles eliminar?

1
@igorkf Ignorando el segundo elemento de los pares.
Rafał Dowgird el

24

Creo que si quieres mantener el orden,

puedes probar esto:

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

O de manera similar, puedes hacer esto:

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

También puedes hacer esto:

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

También se puede escribir así:

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

3
Sus dos primeras respuestas suponen que el orden de la lista se puede reconstruir utilizando una función de clasificación, pero esto puede no ser así.
Richard

55
La mayoría de las respuestas se centran en el rendimiento. Para las listas que no son lo suficientemente grandes como para preocuparse por el rendimiento, lo ordenado (set (list1), key = list1.index) es lo mejor que he visto. Sin importación adicional, sin función adicional, sin variable adicional, y es bastante simple y legible.
Derek Veit

23

En Python 3.7 y superior, se garantiza que los diccionarios recordarán su orden de inserción de claves. La respuesta a esto pregunta resume el estado actual de las cosas.

La OrderedDictsolución se vuelve obsoleta y sin ninguna declaración de importación simplemente podemos emitir:

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

12

Para otra respuesta muy tardía a otra pregunta muy antigua:

Las itertoolsrecetas tienen una función que hace esto, usando la seentécnica de configuración, pero:

  • Maneja una keyfunción estándar .
  • No utiliza hacks indecorosos.
  • Optimiza el bucle preencuadernando en seen.addlugar de buscarlo N veces. ( f7también hace esto, pero algunas versiones no lo hacen)
  • Optimiza el bucle mediante el uso ifilterfalse, por lo que solo tiene que recorrer los elementos únicos en Python, en lugar de todos ellos. (Todavía iteras sobre todos dentro ifilterfalse, por supuesto, pero eso está en C, y mucho más rápido).

¿Es realmente más rápido que f7? Depende de sus datos, por lo que tendrá que probarlos y verlos. Si desea una lista al final, f7use una lista de compilación, y no hay forma de hacerlo aquí. (Puede directamente en appendlugar de yielding, o puede alimentar el generador en la listfunción, pero ninguno puede ser tan rápido como el LIST_APPEND dentro de un listcomp.) En cualquier caso, por lo general, exprimir algunos microsegundos no será tan rápido importante como tener una función fácil de entender, reutilizable y ya escrita que no requiere DSU cuando se desea decorar.

Como con todas las recetas, también está disponible en more-iterools .

Si solo quiere el no- keycase, puede simplificarlo como:

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

Pasé por alto por completo que more-itertoolsesta es claramente la mejor respuesta. Un simple from more_itertools import unique_everseen list(unique_everseen(items))Un enfoque mucho más rápido que el mío y mucho mejor que la respuesta aceptada, creo que la descarga de la biblioteca vale la pena. Voy a la comunidad wiki mi respuesta y
agregaré

12

Sólo para añadir otra aplicación (de buen calidad) de funcionalidad, una de un 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]

Tiempos

Hice algunos tiempos (Python 3.6) y estos muestran que es más rápido que todas las otras alternativas que probé, incluyendo OrderedDict.fromkeys, f7y 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()

ingrese la descripción de la imagen aquí

Y solo para asegurarme de que también hice una prueba con más duplicados solo para verificar si hay alguna diferencia:

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

ingrese la descripción de la imagen aquí

Y uno que contiene solo un 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()

ingrese la descripción de la imagen aquí

En todos estos casos, la iteration_utilities.unique_everseenfunción es la más rápida (en mi computadora).


Esta iteration_utilities.unique_everseenfunción también puede manejar valores no compartibles en la entrada (sin embargo, con un O(n*n)rendimiento en lugar del O(n)rendimiento cuando los valores son hashables).

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

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

1 Descargo de responsabilidad: soy el autor de ese paquete.


No entiendo la necesidad de esta línea: seen_add = seen.add- ¿Es esto necesario para los puntos de referencia?
Alex

@Alex Este es el enfoque dado en esta respuesta . Tendría más sentido preguntarlo allí. Acabo de utilizar el enfoque de esa respuesta para comparar los tiempos.
MSeifert

¿puede agregar el dict.fromkeys()método a su gráfico por favor?
Boris

No estoy realmente seguro si tengo lo mismo para hacer los tiempos pronto. ¿Crees que es mucho más rápido que el ordereddict.fromkeys?
MSeifert

"Esta función iteration_utilities.unique_everseen también puede manejar valores no compartibles en la entrada" - sí, esto es realmente importante. Si tiene una lista de dictos de dictos de dictos, etc., esta es la única forma de hacer el trabajo, incluso a pequeña escala.
Roko Mijic

6

Para tipos no hashaable (por ejemplo, lista de listas), basado en 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 prestada la idea recursiva utilizada para definir la nubfunción de Haskell para listas, este sería un enfoque recursivo:

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

p.ej:

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

Lo probé para aumentar el tamaño de los datos y vi una complejidad de tiempo sub-lineal (no definitiva, pero sugiere que esto debería estar bien para los datos normales).

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

También creo que es interesante que otras operaciones puedan generalizar fácilmente a la unicidad. Me gusta esto:

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 ejemplo, podría pasar una función que usa la noción de redondeo al mismo número entero como si fuera "igualdad" para propósitos de unicidad, como este:

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

entonces unique (some_list, test_round) proporcionaría los elementos únicos de la lista donde la unicidad ya no significaba igualdad tradicional (lo que está implícito en el uso de cualquier tipo de enfoque basado en conjuntos o en dict-key para este problema) sino que en su lugar tenía la intención de tomar solo el primer elemento que se redondea a K para cada posible entero K al que los elementos podrían redondear, por ejemplo:

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
Tenga en cuenta que el rendimiento se verá afectado cuando la cantidad de elementos únicos sea muy grande en relación con la cantidad total de elementos, ya que el uso de cada llamada recursiva sucesiva filterapenas se beneficiará de la llamada anterior. Pero si el número de elementos únicos es pequeño en relación con el tamaño de la matriz, esto debería funcionar bastante bien.
ely

3

Variante de reducción 5 veces más rápida pero más 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]

Explicación:

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

Puede hacer referencia a una comprensión de la lista, ya que está siendo construida por el símbolo '_ [1]'.
Por ejemplo, la siguiente función unifica una lista de elementos sin cambiar su orden haciendo referencia a su comprensión de la lista.

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

Manifestación:

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

Salida:

[1, 2, 3, 4, 5]

2
También tenga en cuenta que lo convertiría en una operación O (n ^ 2), donde crear un conjunto / dict (que tiene un tiempo de búsqueda constante) y agregar solo elementos nunca vistos anteriormente será lineal.
ely

Esto es Python 2.6, solo yo creo. Y sí, es O (N ^ 2)
jamylak

2

La respuesta de MizardX ofrece una buena colección de múltiples enfoques.

Esto es lo que se me ocurrió mientras pensaba en voz alta:

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

Su solución es buena, pero toma la última apariencia de cada elemento. Para tomar la primera aparición, use: [x para i, x en enumerate (mylist) si x no está en mylist [: i]]
Rivka

77
Dado que buscar en una lista es una O(n)operación y la realiza en cada elemento, la complejidad resultante de su solución sería O(n^2). Esto es simplemente inaceptable para un problema tan trivial.
Nikita Volkov

2

Aquí hay una manera simple de hacerlo:

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

eso da la salida:

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

1

Podrías hacer una especie de truco de comprensión de listas feo.

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

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

1

Enfoque relativamente eficaz con _sorted_unas numpymatrices:

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

Salidas:

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

Una expresión generadora que usa la búsqueda O (1) de un conjunto para determinar si se incluye o no un elemento en la nueva lista.


1
Uso inteligente de extendcon una expresión generadora que depende de la cosa que se está extendiendo (por lo tanto, +1), pero set(n)se recalcula en cada etapa (que es lineal) y esto hace que el enfoque general sea cuadrático. De hecho, esto es casi peor que simplemente usarlo ele in n. Hacer un set para una prueba de membresía no vale la pena el gasto de la creación del set Aún así, es un enfoque interesante.
John Coleman

1

Una solución recursiva simple:

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

Elimina los valores duplicados en una secuencia, pero conserva el orden de los elementos restantes. Uso de la función de generador de propósito general.

# 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

Los usuarios de pandas deben consultar pandas.unique.

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

La función devuelve una matriz NumPy. Si es necesario, puede convertirlo en una lista con el tolistmétodo


1
Buena esa. Nunca me imagino usando pandas para eso, pero funciona
seralouk

0

Si necesita un revestimiento, entonces esto podría ayudar:

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

... debería funcionar pero corrígeme si me equivoco


se trata de una expresión condicional así que es bueno
code22

0

Si usa habitualmente pandas, y se prefiere la estética sobre el rendimiento, considere la función incorporada 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

Sincronización:

    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

esto preservará el orden y se ejecutará en O (n) tiempo. Básicamente, la idea es crear un agujero donde se encuentre un duplicado y hundirlo hasta el fondo. hace uso de un puntero de lectura y escritura. cada vez que se encuentra un duplicado, solo el puntero de lectura avanza y el puntero de escritura permanece en la entrada duplicada para sobrescribirlo.

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

Una solución sin usar módulos o 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)

Da salida:

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

esto es O (N ** 2) complejidad + corte de lista cada vez.
Jean-François Fabre

0

Un método en el lugar

Este método es cuadrático, porque tenemos una búsqueda lineal en la lista para cada elemento de la lista (a eso tenemos que agregar el costo de reorganizar la lista debido a la dels).

Dicho esto, es posible operar en el lugar si comenzamos desde el final de la lista y procedemos hacia el origen eliminando cada término que está presente en la sublista a su izquierda

Esta idea en código es simplemente

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

Una prueba simple de la implementación

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 publicar, he buscado en el cuerpo de las respuestas 'lugar' en vano. Si otros han resuelto el problema de manera similar, avíseme y eliminaré mi respuesta lo antes posible.
gboffi

Podrías usarlo l[:] = <one of the the faster methods>si quisieras una operación in situ, ¿no?
timgeb

@timgeb Sí y no ... Cuando lo hago a=[1]; b=a; a[:]=[2], el b==[2]valor es Truey podemos decir que lo estamos haciendo en el lugar, sin embargo, lo que propone es utilizar un nuevo espacio para tener una nueva lista, reemplazar los datos antiguos con los nuevos y marcar el datos antiguos para la recolección de basura porque ya no se hace referencia a nada, por lo que decir que está funcionando en el lugar es un poco estirar un poco el concepto de lo que he demostrado que es posible ... ¿es ineficiente? Sí, pero ya lo dije de antemano.
gboffi

0

El enfoque de zmk utiliza la comprensión de listas que es muy rápida, pero mantiene el orden de forma natural. Para aplicar a cadenas sensibles a mayúsculas y minúsculas, se puede modificar fácilmente. Esto también conserva el 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()))]

Las funciones estrechamente asociadas son:

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

Una lista de comprensión de la lista:

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

Simplemente agregue un condicional para verificar que el valor no esté en una posición anterior

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.