>>> k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]]
>>> import itertools
>>> k.sort()
>>> list(k for k,_ in itertools.groupby(k))
[[1, 2], [3], [4], [5, 6, 2]]
itertoolsa 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 timeitayuda 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 como nodup.py:
import itertools
k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]]
def doset(k, map=map, list=list, set=set, tuple=tuple):
return map(list, set(map(tuple, k)))
def dosort(k, sorted=sorted, xrange=xrange, len=len):
ks = sorted(k)
return [ks[i] for i in xrange(len(ks)) if i == 0 or ks[i] != ks[i-1]]
def dogroupby(k, sorted=sorted, groupby=itertools.groupby, list=list):
ks = sorted(k)
return [i for i, _ in itertools.groupby(ks)]
def donewk(k):
newk = []
for i in k:
if i not in newk:
newk.append(i)
return newk
# sanity check that all functions compute the same result and don't alter k
if __name__ == '__main__':
savek = list(k)
for f in doset, dosort, dogroupby, donewk:
resk = f(k)
assert k == savek
print '%10s %s' % (f.__name__, sorted(resk))
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:
$ python -mtimeit -s'import nodup' 'nodup.doset(nodup.k)'
100000 loops, best of 3: 11.7 usec per loop
$ python -mtimeit -s'import nodup' 'nodup.dosort(nodup.k)'
100000 loops, best of 3: 9.68 usec per loop
$ python -mtimeit -s'import nodup' 'nodup.dogroupby(nodup.k)'
100000 loops, best of 3: 8.74 usec per loop
$ python -mtimeit -s'import nodup' 'nodup.donewk(nodup.k)'
100000 loops, best of 3: 4.44 usec per loop
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:
$ python -mtimeit -s'import nodup' 'nodup.donewk([[i] for i in range(12)])'
10000 loops, best of 3: 25.4 usec per loop
$ python -mtimeit -s'import nodup' 'nodup.dogroupby([[i] for i in range(12)])'
10000 loops, best of 3: 23.7 usec per loop
$ python -mtimeit -s'import nodup' 'nodup.doset([[i] for i in range(12)])'
10000 loops, best of 3: 31.3 usec per loop
$ python -mtimeit -s'import nodup' 'nodup.dosort([[i] for i in range(12)])'
10000 loops, best of 3: 25 usec per loop
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.