python - indexes - Indexar una lista con un índice único
remove index mongodb (6)
Tengo una lista que dice l = [10,10,20,15,10,20]
. Quiero asignarle a cada valor único un cierto "índice" para obtener [1,1,2,3,1,2]
.
Este es mi código:
a = list(set(l))
res = [a.index(x) for x in l]
Que resulta ser muy lento.
l
tiene elementos 1M y 100K elementos únicos. También probé el mapa con lambda y clasificación, lo que no ayudó. ¿Cuál es la forma ideal de hacer esto?
Bueno, supongo que depende de si quieres que devuelva los índices en ese orden específico o no. Si quieres que el ejemplo regrese:
[1,1,2,3,1,2]
entonces puedes mirar las otras respuestas enviadas. Sin embargo, si solo te importa obtener un índice único para cada número único, entonces tengo una solución rápida para ti
import numpy as np
l = [10,10,20,15,10,20]
a = np.array(l)
x,y = np.unique(a,return_inverse = True)
y para este ejemplo la salida de y es:
y = [0,0,2,1,0,2]
Probé esto para 1,000,000 entradas y se hizo esencialmente de manera inmediata.
La lentitud de su código surge porque a.index(x)
realiza una búsqueda lineal y realiza esa búsqueda lineal para cada uno de los elementos en l
. Entonces, para cada uno de los elementos de 1M, realiza (hasta) comparaciones de 100K.
La forma más rápida de transformar un valor en otro es buscarlo en un mapa. Tendrá que crear el mapa y completar la relación entre los valores originales y los valores que desee. Luego recupere el valor del mapa cuando encuentre otro del mismo valor en su lista.
Aquí hay un ejemplo que hace una sola pasada a través de l
. Puede haber margen para una mayor optimización para eliminar la necesidad de reasignar repetidamente res
cuando se agrega a ella.
res = []
conversion = {}
i = 0
for x in l:
if x not in conversion:
value = conversion[x] = i
i += 1
else:
value = conversion[x]
res.append(value)
Para completitud, también puede hacerlo con entusiasmo:
from itertools import count wordid = dict(zip(set(list_), count(1)))
Utiliza un conjunto para obtener las palabras únicas en
list_
, empareja cada una de esas palabras únicas con el siguiente valor decount()
(que cuenta hacia arriba) y construye un diccionario a partir de los resultados.
Respuesta original , escrita por nneonneo.
Puede usar collections.OrderedDict()
para conservar los elementos únicos en orden y, pasar por encima de la enumeración de estos elementos únicos ordenados para obtener un dict de elementos y esos índices (según su orden) y luego pasar este diccionario con la lista principal de operator.itemgetter()
para obtener el índice correspondiente para cada elemento:
>>> from collections import OrderedDict
>>> from operator import itemgetter
>>> itemgetter(*lst)({j:i for i,j in enumerate(OrderedDict.fromkeys(lst),1)})
(1, 1, 2, 3, 1, 2)
Puedes hacerlo en O(N)
vez usando un valor defaultdict
y una lista de comprensión:
>>> from itertools import count
>>> from collections import defaultdict
>>> lst = [10, 10, 20, 15, 10, 20]
>>> d = defaultdict(count(1).next)
>>> [d[k] for k in lst]
[1, 1, 2, 3, 1, 2]
En Python 3, use __next__
lugar de next
.
Si te preguntas cómo funciona?
El default_factory
(es decir, count(1).next
en este caso) pasado a defaultdict
se defaultdict
solo cuando Python encuentra una clave faltante, por lo que para 10 el valor va a ser 1, luego para los próximos 10 ya no es una clave que falta por lo tanto, se usa el 1 previamente calculado, ahora el 20 es nuevamente una clave faltante y Python llamará a la default_factory
para obtener su valor y así sucesivamente.
d
al final se verá así:
>>> d
defaultdict(<method-wrapper ''next'' of itertools.count object at 0x1057c83b0>,
{10: 1, 20: 2, 15: 3})
Su solución es lenta porque su complejidad es O(nm)
siendo m
el número de elementos únicos en l
: a.index()
es O(m)
y lo llama para cada elemento en l
.
Para hacerlo O(n)
, deshacerse del index()
y almacenar índices en un diccionario:
>>> idx, indexes = 1, {}
>>> for x in l:
... if x not in indexes:
... indexes[x] = idx
... idx += 1
...
>>> [indexes[x] for x in l]
[1, 1, 2, 3, 1, 2]
Si l
contiene solo enteros en un rango conocido, también puede almacenar índices en una lista en lugar de un diccionario para búsquedas más rápidas.