python - over - Ciclo de una lista de lados alternos
python for loop index (15)
Dos versiones aún no vistas:
b = list(sum(zip(a, a[::-1]), ())[:len(a)])
y
import itertools as it
b = [a[j] for j in it.accumulate(i*(-1)**i for i in range(len(a)))]
Dada una lista
a = [0,1,2,3,4,5,6,7,8,9]
como puedo conseguir
b = [0,9,1,8,2,7,3,6,4,5]
Es decir, ¿produce una nueva lista en la que cada elemento sucesivo se toma alternativamente de los dos lados de la lista original?
El principio básico detrás de su pregunta es el llamado algoritmo de roundrobin. La itertools-documentation-page contiene una posible implementación de la misma:
from itertools import cycle, islice
def roundrobin(*iterables):
"""This function is taken from the python documentation!
roundrobin(''ABC'', ''D'', ''EF'') --> A D E B F C
Recipe credited to George Sakkis"""
pending = len(iterables)
nexts = cycle(iter(it).__next__ for it in iterables) # next instead of __next__ for py2
while pending:
try:
for next in nexts:
yield next()
except StopIteration:
pending -= 1
nexts = cycle(islice(nexts, pending))
así que todo lo que tiene que hacer es dividir su lista en dos listas secundarias, una desde el extremo izquierdo y otra desde el extremo derecho:
import math
mid = math.ceil(len(a)/2) # Just so that the next line doesn''t need to calculate it twice
list(roundrobin(a[:mid], a[:mid-1:-1]))
# Gives you the desired result: [0, 9, 1, 8, 2, 7, 3, 6, 4, 5]
alternativamente, puede crear una lista más larga (que contenga elementos alternos de la secuencia que va de izquierda a derecha y que los elementos de la secuencia completa vayan de derecha a izquierda) y solo tome los elementos relevantes:
list(roundrobin(a, reversed(a)))[:len(a)]
o usándolo como generador explícito con next
:
rr = roundrobin(a, reversed(a))
[next(rr) for _ in range(len(a))]
o la veloz variante sugerida por @Tadhg McDonald-Jensen (¡gracias!):
list(islice(roundrobin(a,reversed(a)),len(a)))
No del todo elegante, pero es un torpe de una sola línea:
a = range(10)
[val for pair in zip(a[:len(a)//2],a[-1:(len(a)//2-1):-1]) for val in pair]
Tenga en cuenta que se supone que está haciendo esto para una lista de longitud uniforme. Si eso se rompe, entonces esto se rompe (cae el término medio). Tenga en cuenta que tengo una idea de here .
No es terriblemente diferente de algunas de las otras respuestas, pero evita una expresión condicional para determinar el signo del índice.
a = range(10)
b = [a[i // (2*(-1)**(i&1))] for i in a]
i & 1
alterna entre 0 y 1. Esto hace que el exponente alterne entre 1 y -1. Esto hace que el divisor de índice se alterne entre 2 y -2, lo que hace que el índice se alterne de un extremo a otro a medida que i
aumenta. La secuencia es a[0]
, a[-1]
, a[1]
, a[-2]
, a[2]
, a[-3]
, etc.
(I iterar i
sobre a
ya que en este caso cada valor de a
es igual a su índice. En general, iterar sobre range(len(a))
.)
No estoy seguro de si esto se puede escribir de forma más compacta, pero es eficiente ya que solo utiliza iteradores / generadores
a = [0,1,2,3,4,5,6,7,8,9]
iter1 = iter(a)
iter2 = reversed(a)
b = [item for n, item in enumerate(
next(iter) for _ in a for iter in (iter1, iter2)
) if n < len(a)]
Por diversión, aquí hay una variante de itertools:
>>> a = [0,1,2,3,4,5,6,7,8,9]
>>> list(chain.from_iterable(izip(islice(a, len(a)//2), reversed(a))))
[0, 9, 1, 8, 2, 7, 3, 6, 4, 5]
Esto funciona donde len(a)
es par. Necesitaría un código especial para una entrada de longitud impar.
¡Disfrutar!
Puede dividir la lista en dos partes alrededor del medio, revertir la segunda mitad y comprimir las dos particiones, así:
a = [0,1,2,3,4,5,6,7,8,9]
mid = len(a)//2
l = []
for x, y in zip(a[:mid], a[:mid-1:-1]):
l.append(x)
l.append(y)
# if the length is odd
if len(a) % 2 == 1:
l.append(a[mid])
print(l)
Salida:
[0, 9, 1, 8, 2, 7, 3, 6, 4, 5]
Una forma de hacer esto para listas de tamaño uniforme (inspiradas en here ):
a = range(10)
b = [val for pair in zip(a[:5], a[5:][::-1]) for val in pair]
Una muy buena de una sola línea en Python 2.7:
results = list(sum(zip(a, reversed(a))[:len(a)/2], ()))
>>>> [0, 9, 1, 8, 2, 7, 3, 6, 4, 5]
Primero, comprima la lista con su inverso, tome la mitad de esa lista, sume las tuplas para formar una tupla y luego conviértala en lista.
En Python 3, zip
devuelve un generador, así que tienes que usar islice
de itertools
:
from itertools import islice
results = list(sum(islice(zip(a, reversed(a)),0,int(len(a)/2)),()))
Edición : parece que esto solo funciona perfectamente para longitudes de listas pares - las longitudes de listas impares omitirán el elemento central :( Una pequeña corrección para int(len(a)/2)
a int(len(a)/2) + 1
le dará un valor medio duplicado, así que tenga cuidado.
Usa las herramientas adecuadas.
from toolz import interleave, take
b = list(take(len(a), interleave((a, reversed(a)))))
Primero, probé algo similar a la solución de Raymond Hettinger con itertools (Python 3).
from itertools import chain, islice
interleaved = chain.from_iterable(zip(a, reversed(a)))
b = list(islice(interleaved, len(a)))
Usted puede simplemente ir y venir:
b = [a.pop(-1 if i%2 else 0) for i in range(len(a))]
Nota: Esto destruye la lista original, a
.
Yo haria algo asi
a = [0,1,2,3,4,5,6,7,8,9]
b = []
i = 0
j = len(a) - 1
mid = (i + j) / 2
while i <= j:
if i == mid and len(a) % 2 == 1:
b.append(a[i])
break
b.extend([a[i], a[j]])
i = i + 1
j = j - 1
print b
cycle
entre obtener elementos desde el iter
hacia adelante y el reversed
. Solo asegúrate de detenerte en len(a)
con islice
.
from itertools import islice, cycle
iters = cycle((iter(a), reversed(a)))
b = [next(it) for it in islice(iters, len(a))]
>>> b
[0, 9, 1, 8, 2, 7, 3, 6, 4, 5]
Esto se puede poner fácilmente en una sola línea, pero luego se vuelve mucho más difícil de leer:
[next(it) for it in islice(cycle((iter(a),reversed(a))),len(a))]
Ponerlo en una línea también le impediría usar la otra mitad de los iteradores si quisiera:
>>> iters = cycle((iter(a), reversed(a)))
>>> [next(it) for it in islice(iters, len(a))]
[0, 9, 1, 8, 2, 7, 3, 6, 4, 5]
>>> [next(it) for it in islice(iters, len(a))]
[5, 4, 6, 3, 7, 2, 8, 1, 9, 0]
>>> [a[-i//2] if i % 2 else a[i//2] for i in range(len(a))]
[0, 9, 1, 8, 2, 7, 3, 6, 4, 5]
Explicación:
Este código recoge números del principio ( a[i//2]
) y del final ( a[-i//2]
) de a
, alternativamente ( if i%2 else
). Se seleccionan un total de números de len(a)
, por lo que esto no produce efectos nocivos, incluso si len(a)
es impar.
[-i//2 for i in range(len(a))]
produce 0, -1, -1, -2, -2, -3, -3, -4, -4, -5
,
[ i//2 for i in range(len(a))]
produce 0, 0, 1, 1, 2, 2, 3, 3, 4, 4
,
y i%2
alterna entre False
y True
,
así que los índices que extraemos de a
son: 0, -1, 1, -2, 2, -3, 3, -4, 4, -5
.
Mi evaluación de pythonicness:
Lo bueno de este one-liner es que es corto y muestra simetría ( +i//2
y -i//2
).
Lo malo, sin embargo, es que esta simetría es engañosa:
Se podría pensar que -i//2
eran lo mismo que i//2
con el signo volteado. Pero en Python, la división de enteros devuelve el piso del resultado en lugar de truncar hacia cero. Entonces -1//2 == -1
.
Además, me parece que acceder a los elementos de la lista por índice es menos pitón que la iteración.
mylist = [0,1,2,3,4,5,6,7,8,9]
result = []
for i in mylist:
result += [i, mylist.pop()]
Nota:
Cuidado: al igual que @Tadhg McDonald-Jensen ha dicho (ver el comentario a continuación) destruirá la mitad del objeto de la lista original.