¿Cuál es la forma más eficiente de rotar una lista en Python? En este momento tengo algo como esto:
>>> def rotate(l, n):
... return l[n:] + l[:n]
...
>>> l = [1,2,3,4]
>>> rotate(l,1)
[2, 3, 4, 1]
>>> rotate(l,2)
[3, 4, 1, 2]
>>> rotate(l,0)
[1, 2, 3, 4]
>>> rotate(l,-1)
[4, 1, 2, 3]
¿Hay una mejor manera?
rotate
es la palabra correcta, noshift
.Respuestas:
A
collections.deque
está optimizado para tirar y empujar en ambos extremos. Incluso tienen unrotate()
método dedicado .fuente
collections.deque rotate()
es más rápido que cortar según wiki.python.org/moin/TimeComplexitydeque.rotate
requiere una conversión de tipo a undeque
objeto, que es más lento quel.append(l.pop(0))
. Entonces, si tiene un objeto deque para comenzar, asegúrese de que sea el más rápido. De lo contrario, usel.append(l.pop(0))
.deque.rotate
es O (k) pero la conversión de tipo de la lista a deque es O (n) . Entonces, si comienza con una lista, usar deque.rotate es O (n) + O (k) = O (n).l.append(l.pop(0))
por otro lado es O (1).¿Qué hay de solo usar
pop(0)
?fuente
l.append(l.pop(0)
. Que si no me equivoco es O (1).Numpy puede hacer esto usando el
roll
comando:fuente
Depende de lo que quieras que suceda cuando hagas esto:
Es posible que desee cambiar su:
a:
fuente
La forma más simple en que puedo pensar:
fuente
collections.deque
es más rápido pero para la mayoría de los casos comunes de longitud de lista en una sola iteración, o cualquier caso de iteraciones múltiples,a.append(a.pop(0))
será más rápido que la conversión de tipo a dequeSi solo desea iterar sobre estos conjuntos de elementos en lugar de construir una estructura de datos separada, considere usar iteradores para construir una expresión generadora:
fuente
Esto también depende de si desea cambiar la lista en su lugar (mutarla) o si desea que la función devuelva una nueva lista. Porque, según mis pruebas, algo como esto es al menos veinte veces más rápido que su implementación que agrega dos listas:
De hecho, incluso agregar un
l = l[:]
encabezado para operar en una copia de la lista aprobada sigue siendo el doble de rápido.Diversas implementaciones con algo de tiempo en http://gist.github.com/288272
fuente
l[:n] = []
ir pordel l[:n]
. Solo una alternativa.del
sigue siendo una declaración en Py3. Sin embargox.__delitem__(y) <==> del x[y]
, por lo que, si lo prefiere, utilizando métodos,l.__delitem__(slice(n))
también es equivalente y funciona tanto en 2 y 3.Solo algunas notas sobre el tiempo:
Si está comenzando con una lista,
l.append(l.pop(0))
es el método más rápido que puede usar. Esto se puede mostrar solo con la complejidad del tiempo:Entonces, si está comenzando con
deque
objetos, puede hacerlodeque.rotate()
a costa de O (k). Pero, si el punto de partida es una lista, la complejidad temporal del usodeque.rotate()
es O (n).l.append(l.pop(0)
es más rápido en O (1).Solo por el bien de la ilustración, aquí hay algunos tiempos de muestra en iteraciones de 1M:
Métodos que requieren conversión de tipo:
deque.rotate
con objeto de deque: 0.12380790710449219 segundos (el más rápido)deque.rotate
con conversión de tipo: 6.853878974914551 segundosnp.roll
con nparray: 6.0491721630096436 segundosnp.roll
con conversión de tipo: 27.558452129364014 segundosEnumere los métodos mencionados aquí:
l.append(l.pop(0))
: 0.32483696937561035 segundos (el más rápido)shiftInPlace
": 4.819645881652832 segundosEl código de tiempo utilizado se encuentra a continuación.
colecciones.deque
Mostrando que la creación de deques de listas es O (n):
Si necesita crear objetos deque:
1 millón de iteraciones @ 6.853878974914551 segundos
Si ya tiene objetos deque:
1M iteraciones @ 0.12380790710449219 segundos
np.roll
Si necesita crear nparrays
1 millón de iteraciones @ 27.558452129364014 segundos
Si ya tiene nparrays:
1 millón de iteraciones a 6.0491721630096436 segundos
"Cambio en el lugar"
No requiere conversión de tipo
1 millón de iteraciones a 4.819645881652832 segundos
l.append (l.pop (0))
No requiere conversión de tipo
1M iteraciones @ 0.32483696937561035
fuente
l = [random.random() for i in range(100000)]
l.append(l.pop(0))
es el mejor rendimiento para cambiar listas cortas (alrededor de 7 elementos) por uno?l.append(l.pop(0))
como respuesta: esta pregunta se cierra como un duplicado. ¿Quizás votarás por reabrirlo?También me interesé en esto y comparé algunas de las soluciones sugeridas con perfplot (un pequeño proyecto mío).
Resulta que
es, con mucho, el método más rápido para pequeños turnos
n
.Para más grande
n
,no está mal
Esencialmente, perfplot realiza el cambio para aumentar matrices grandes y mide el tiempo. Aquí están los resultados:
shift = 1
:shift = 100
:Código para reproducir la trama:
fuente
l.append(l.pop(0))
cuanto a una respuesta: esta pregunta se cierra como un duplicado. ¿Quizás votarás por reabrirlo?Posiblemente un ringbuffer sea más adecuado. No es una lista, aunque es probable que pueda comportarse lo suficiente como una lista para sus propósitos.
El problema es que la eficiencia de un cambio en una lista es O (n), que se vuelve significativa para listas lo suficientemente grandes.
Cambiar en un ringbuffer es simplemente actualizar la ubicación del cabezal, que es O (1)
fuente
Para una implementación inmutable, puede usar algo como esto:
fuente
Si su objetivo es la eficiencia (¿ciclos? ¿Memoria?), Es mejor que mire el módulo de matriz: http://docs.python.org/library/array.html
Las matrices no tienen la sobrecarga de las listas.
Sin embargo, en lo que respecta a las listas puras, lo que tiene es tan bueno como puede esperar hacer.
fuente
Creo que estás buscando esto:
fuente
Otra alternativa:
fuente
Tomo este modelo de costos como referencia:
http://scripts.mit.edu/~6.006/fall07/wiki/index.php?title=Python_Cost_Model
Su método de segmentar la lista y concatenar dos sublistas son operaciones de tiempo lineal. Sugeriría usar pop, que es una operación de tiempo constante, por ejemplo:
fuente
collections.dequeue
pop y appendleft, que son O (1) ops. En mi primera respuesta anterior, insertar es O (n).collections.deque
No sé si esto es 'eficiente', pero también funciona:
EDITAR: Hola de nuevo, ¡acabo de encontrar un gran problema con esta solución! Considere el siguiente código:
El método shift_classlist () ejecuta el mismo código que mi solución x.insert (0, x.pop ()), otherlist es una lista independiente de la clase. Después de pasar el contenido de otherlist a la lista MyClass.classlist, llamar a shift_classlist () también cambia la lista otherlist:
CONSOLA DE SALIDA:
Yo uso Python 2.7. No sé si eso es un error, pero creo que es más probable que haya entendido mal algo aquí.
¿Alguien de ustedes sabe por qué sucede esto?
fuente
x.classlist = otherlist
hace que se hagax.classlist
referencia a la misma listaotherlist
y luego, cuando la llamasx.shift_classlist()
, muta la lista y porque ambos nombres se refieren al mismo objeto de lista. Ambos nombres parecen cambiar porque son solo alias para el mismo objeto. Use en sux.classlist = otherlist[:]
lugar para asignar una copia de la lista.El siguiente método es O (n) en su lugar con memoria auxiliar constante:
Tenga en cuenta que en python, este enfoque es terriblemente ineficiente en comparación con otros, ya que no puede aprovechar las implementaciones nativas de ninguna de las piezas.
fuente
Tengo algo similar Por ejemplo, para cambiar por dos ...
fuente
Creo que tienes la forma más eficiente
fuente
¿Cuál es el caso de uso? A menudo, en realidad no necesitamos una matriz totalmente desplazada, solo necesitamos acceder a un puñado de elementos en la matriz desplazada.
Obtener segmentos de Python es tiempo de ejecución O (k) donde k es el segmento, por lo que una rotación dividida es tiempo de ejecución N. El comando de rotación de eliminación también es O (k). ¿Podemos hacerlo mejor?
Considere una matriz que es extremadamente grande (digamos, tan grande que sería computacionalmente lenta dividirla). Una solución alternativa sería dejar solo la matriz original y simplemente calcular el índice del elemento que habría existido en nuestro índice deseado después de un cambio de algún tipo.
Acceder a un elemento desplazado se convierte así en O (1).
fuente
La siguiente función copia la lista enviada a un templista, de modo que la función emergente no afecta la lista original:
Pruebas:
Salida:
fuente
Jon Bentley en Programming Pearls (Columna 2) describe un algoritmo elegante y eficiente para rotar un
n
vector de elementosx
dejado por lasi
posiciones:Esto se puede traducir a Python de la siguiente manera:
Manifestación:
fuente
Para una lista
X = ['a', 'b', 'c', 'd', 'e', 'f']
y un valor de desplazamiento deseadoshift
menor que la longitud de la lista , podemos definir la función de lalist_shift()
siguiente maneraEjemplos,
list_shift(X,1)
devuelve['b', 'c', 'd', 'e', 'f', 'a']
list_shift(X,3)
devuelve['d', 'e', 'f', 'a', 'b', 'c']
fuente
list_shift
en su respuesta es idéntica a la funciónshift
en la pregunta original, por lo que esta no es una respuesta a la pregunta real: "¿Hay una mejor manera?"Por ejemplo, dado
la función debería volver
[9, 7, 6, 3, 8]
. Se hicieron tres rotaciones:Para otro ejemplo, dado
la función debería volver
[0, 0, 0]
Dado
la función debería volver
[1, 2, 3, 4]
fuente
Estaba buscando una solución para este problema. Esto resuelve el propósito en O (k).
fuente
para una funcionalidad similar a shift en otros idiomas:
fuente
L.pop(0)