Ciclo de una lista de lados alternos

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?

 >>> [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 python que por iteración.

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] 

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 las longitudes de lista par – las longitudes de lista impar 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.

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 .

Utilice la herramienta correcta.

 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))) 

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)) .)

El principio básico detrás de su pregunta es el llamado algoritmo de roundrobin. La itertools documentación de itertools 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') --> ADEBFC 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 contiene elementos alternos de la secuencia que va de izquierda a derecha y los elementos de la secuencia completa que van de derecha a izquierda) y solo tomar 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))) 
 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.

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!

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 obtener 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 aquí .

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)))] 

Una forma de hacer esto para listas de tamaño uniforme (inspiradas en esta publicación ):

 a = range(10) b = [val for pair in zip(a[:5], a[5:][::-1]) for val in pair] 

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 

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]