Tengo una lista de listas en Python:
k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]]
Y quiero eliminar elementos duplicados de él. Si fuera una lista normal, no de listas que pudiera usar set
. Pero, lamentablemente, esa lista no se puede usar con hash y no se pueden crear conjuntos de listas. Solo de tuplas. Entonces puedo convertir todas las listas en tuplas y luego usar set y volver a listas. Pero esto no es rápido.
¿Cómo se puede hacer esto de la manera más eficiente?
El resultado de la lista anterior debería ser:
k = [[5, 6, 2], [1, 2], [3], [4]]
No me importa mantener el orden.
Nota: esta pregunta es similar pero no es exactamente lo que necesito. Busqué SO pero no encontró un duplicado exacto.
Benchmarking:
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)
"bucle" (método cuadrático) más rápido de todos para listas cortas. Para listas largas es más rápido que todos, excepto el método groupby. ¿Esto tiene sentido?
Para una lista corta (la que está en el código), 100000 iteraciones:
[set] Elapsed: 1.3900001049
[sort] Elapsed: 0.891000032425
[groupby] Elapsed: 0.780999898911
[loop in] Elapsed: 0.578000068665
Para una lista más larga (el que está en el código duplicado 5 veces):
[set] Elapsed: 3.68700003624
[sort] Elapsed: 3.43799996376
[groupby] Elapsed: 1.03099989891
[loop in] Elapsed: 1.85900020599
Respuestas:
itertools
a menudo ofrece las soluciones más rápidas y poderosas para este tipo de problemas, y está bien vale la pena conseguir íntimamente familiarizados con -!)Editar : como mencioné en un comentario, los esfuerzos de optimización normales se centran en grandes entradas (el enfoque de Big-O) porque es mucho más fácil que ofrece buenos retornos de los esfuerzos. Pero a veces (esencialmente para "cuellos de botella trágicamente cruciales" en bucles internos profundos de código que superan los límites de los límites de rendimiento) es posible que sea necesario entrar en muchos más detalles, proporcionar distribuciones de probabilidad, decidir qué medidas de rendimiento optimizar (tal vez el límite superior o el percentil 90 es más importante que el promedio o la mediana, dependiendo de las aplicaciones), realizando comprobaciones posiblemente heurísticas al principio para elegir diferentes algoritmos según las características de los datos de entrada, y así sucesivamente.
Las medidas cuidadosas del rendimiento del "punto" (código A frente al código B para una entrada específica) son parte de este proceso extremadamente costoso, y el módulo de biblioteca estándar
timeit
ayuda aquí. Sin embargo, es más fácil usarlo en un indicador de shell. Por ejemplo, aquí hay un módulo corto para mostrar el enfoque general para este problema, guárdelo comonodup.py
:Tenga en cuenta la verificación de cordura (realizada cuando acaba de hacerlo
python nodup.py
) y la técnica de elevación básica (haga que los nombres globales constantes sean locales para cada función para la velocidad) para poner las cosas en pie de igualdad.Ahora podemos ejecutar comprobaciones en la pequeña lista de ejemplos:
confirmando que el enfoque cuadrático tiene constantes lo suficientemente pequeñas como para hacerlo atractivo para listas pequeñas con pocos valores duplicados. Con una lista corta sin duplicados:
el enfoque cuadrático no es malo, pero el tipo y el grupo por grupo son mejores. Etcétera etcétera.
Si (como sugiere la obsesión con el rendimiento) esta operación se encuentra en un bucle interno central de su aplicación de superación de los límites, vale la pena probar el mismo conjunto de pruebas en otras muestras de entrada representativas, posiblemente detectando alguna medida simple que podría permitirle heurísticamente elija uno u otro enfoque (pero la medida debe ser rápida, por supuesto).
También vale la pena considerar mantener una representación diferente para
k
: ¿ por qué tiene que ser una lista de listas en lugar de un conjunto de tuplas en primer lugar? Si la tarea de eliminación de duplicados es frecuente y la creación de perfiles muestra que es el cuello de botella del rendimiento del programa, mantener un conjunto de tuplas todo el tiempo y obtener una lista de listas de él solo si es necesario y donde sea necesario, podría ser más rápido en general, por ejemplo.fuente
Haciéndolo manualmente, creando una nueva
k
lista y agregando entradas no encontradas hasta ahora:Simple de comprender, y conserva el orden de la primera aparición de cada elemento en caso de que sea útil, pero supongo que es cuadrático en complejidad ya que está buscando la totalidad de
new_k
cada elemento.fuente
k = ([[1, 2], [4], [5, 6, 2], [1, 2], [3], [5, 2], [6], [8], [9]] +[[x] for x in range(1000)]) *5
mostrará muy bien el comportamiento cuadráticoNo sé si es necesariamente más rápido, pero no tienes que usar tuplas y conjuntos.
fuente
random
y cronometretime
.Todas las
set
soluciones relacionadas con este problema hasta ahora requieren la creación de un enteroset
antes de la iteración.Es posible hacer esto lento, y al mismo tiempo preservar el orden, iterando la lista de listas y agregando a un "visto"
set
. Luego, solo genere una lista si no se encuentra en este rastreadorset
.Esta
unique_everseen
receta está disponible en lositertools
documentos . También está disponible en latoolz
biblioteca de terceros :Tenga en cuenta que la
tuple
conversión es necesaria porque las listas no son hash.fuente
Incluso su lista "larga" es bastante corta. Además, ¿los eligió para que coincidieran con los datos reales? El rendimiento variará según el aspecto real de estos datos. Por ejemplo, tiene una lista corta repetida una y otra vez para hacer una lista más larga. Esto significa que la solución cuadrática es lineal en sus puntos de referencia, pero no en la realidad.
Para listas realmente grandes, el código de conjunto es su mejor opción: es lineal (aunque requiere mucho espacio). Los métodos de ordenar y agrupar son O (n log n) y el método de bucle es obviamente cuadrático, por lo que sabe cómo escalarán a medida que n se vuelva realmente grande. Si este es el tamaño real de los datos que está analizando, ¿a quién le importa? Es diminuto.
Por cierto, veo una aceleración notable si no formo una lista intermedia para hacer el conjunto, es decir, si reemplazo
con
La solución real puede depender de más información: ¿Está seguro de que una lista de listas es realmente la representación que necesita?
fuente
La lista de tuplas y {} se pueden usar para eliminar duplicados.
fuente
Cree un diccionario con tupla como clave e imprima las claves.
fuente
Esto debería funcionar.
fuente
Curiosamente, las respuestas anteriores eliminan los 'duplicados', pero ¿qué pasa si también quiero eliminar el valor duplicado? ¡Lo siguiente debería ser útil y no crea un nuevo objeto en la memoria!
y el o / p es:
fuente
Otra solución probablemente más genérica y simple es crear un diccionario codificado por la versión de cadena de los objetos y obtener los valores () al final:
El problema es que esto solo funciona para objetos cuya representación de cadena es una clave única suficientemente buena (lo cual es cierto para la mayoría de los objetos nativos).
fuente