Tengo una larga tupla de Python t. Me gustaría obtener los elementos en los índices i1, i2, ..., iN de t de la manera más eficiente posible. Cual es la mejor manera?

Un enfoque es:

(1)    result = [t[j] for j in (i1, i2, ..., iN)]

Pero esto parecería causar N búsquedas separadas en la tupla. ¿Hay una manera mas rápida? Cuando Python hace cortes como este:

(2)    result = t[1:M:3]

Supongo que no realiza búsquedas separadas de M / 3. (¿Tal vez usa una máscara de bits y realiza una operación de copia única?) ¿Hay alguna forma de capitalizar lo que hace Python en (2) para hacer que mi corte de índice arbitrario ocurra en una sola copia?

Gracias.

4
dg99 30 ago. 2011 a las 23:31

5 respuestas

La mejor respuesta

Si está haciendo un montón de búsquedas idénticas, puede valer la pena usar un elemento

from operator import itemgetter
mygetter = itemgetter(i1, i2, ..., iN)
for tup in lots_of_tuples:
    result = mygetter(tup)

Por un lado, la sobrecarga de crear el artículo no vale la pena.

La prueba rápida en iPython muestra:

In [1]: import random

In [2]: from operator import itemgetter

In [3]: t=tuple(range(1000))

In [4]: idxs = tuple(random.randrange(1000) for i in range(20))

In [5]: timeit [t[i] for i in idxs]
100000 loops, best of 3: 2.09 us per loop

In [6]: mygetter = itemgetter(*idxs)

In [7]: timeit mygetter(t)
1000000 loops, best of 3: 596 ns per loop

Obviamente, la diferencia dependerá de la longitud de la tupla, el número de índices, etc.

7
John La Rooy 30 ago. 2011 a las 21:16

Dentro de la comprensión de la lista hay un bucle implícito for, y estoy bastante seguro de que está iterando a través de los valores de tupla con una eficiencia razonable. No creo que pueda mejorar la comprensión de la lista para la eficiencia.

Si solo necesita los valores, puede usar una expresión generadora y evitar crear la lista, para ahorrar un poco de tiempo o memoria.

0
steveha 30 ago. 2011 a las 19:37

El que ha enumerado es la forma más óptima de obtener los elementos de una tupla. Por lo general, no le importa el rendimiento en tales expresiones: es una optimización prematura, e incluso si lo hiciera, tales operaciones ya son demasiado lentas incluso con las optimizaciones, es decir, si optimiza el acceso, el bucle seguirá siendo lento debido a recuento de referencia de las variables temporales y etc.

Si ya tiene un problema de rendimiento o esto ya forma parte del código pesado de la CPU, puede probar varias alternativas:

1) matrices numpy:

>>> arr = np.array(xrange(2000))
>>> mask = np.array([True]*2000)
>>> mask = np.array([False]*2000)
>>> mask[3] = True
>>> mask[300] = True
>>> arr[mask]
array([  3, 300])

2) Puede usar la API de C para copiar los elementos usando PyTuple_GET_ITEM que accede directamente a la matriz interna, pero tenga en cuenta que el uso de la API de C no es trivial y puede introducir muchos errores.

3) Puede usar matrices C con la API C, usando p. la interfaz de búfer de array.array para pegar el acceso a datos a Python.

4) Puede usar Cython con matrices C y un tipo de Cython personalizado para acceder a los datos desde Python.

5) Puedes usar Cython y numpy juntos.

2
Rosh Oxymoron 30 ago. 2011 a las 20:00

Cortar puede ser más eficiente porque tiene más restricciones: el índice debe proceder de manera lineal por una cantidad fija. La comprensión de la lista podría ser completamente aleatoria, por lo que no es posible la optimización.

Aún así, es peligroso hacer suposiciones sobre la eficiencia. Intente cronometrar en ambos sentidos y vea si hay una diferencia significativa.

0
Mark Ransom 30 ago. 2011 a las 19:46

1) ¿Estás segura de que necesitas la operación para ir más rápido?

2) Otra opción es operator.itemgetter: Devuelve una tupla elegida por sus índices:

>>> t = tuple(string.ascii_uppercase)
>>> operator.itemgetter(13,19,4,21,1)(t)
('N', 'T', 'E', 'V', 'B')

El módulo operator se implementa en C, por lo que probablemente superará a un bucle de Python.

0
Ned Batchelder 30 ago. 2011 a las 21:13