Cycle a list from alternating sides

2020-02-08 05:56发布

Given a list

a = [0,1,2,3,4,5,6,7,8,9]

how can I get

b = [0,9,1,8,2,7,3,6,4,5]

That is, produce a new list in which each successive element is alternately taken from the two sides of the original list?

15条回答
Summer. ? 凉城
2楼-- · 2020-02-08 06:20
>>> [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]

Explanation:
This code picks numbers from the beginning (a[i//2]) and from the end (a[-i//2]) of a, alternatingly (if i%2 else). A total of len(a) numbers are picked, so this produces no ill effects even if len(a) is odd.
[-i//2 for i in range(len(a))] yields 0, -1, -1, -2, -2, -3, -3, -4, -4, -5,
[ i//2 for i in range(len(a))] yields 0, 0, 1, 1, 2, 2, 3, 3, 4, 4,
and i%2 alternates between False and True,
so the indices we extract from a are: 0, -1, 1, -2, 2, -3, 3, -4, 4, -5.

My assessment of pythonicness:
The nice thing about this one-liner is that it's short and shows symmetry (+i//2 and -i//2).
The bad thing, though, is that this symmetry is deceptive:
One might think that -i//2 were the same as i//2 with the sign flipped. But in Python, integer division returns the floor of the result instead of truncating towards zero. So -1//2 == -1.
Also, I find accessing list elements by index less pythonic than iteration.

查看更多
祖国的老花朵
3楼-- · 2020-02-08 06:22

The basic principle behind your question is a so-called roundrobin algorithm. The itertools-documentation-page contains a possible implementation of it:

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

so all you have to do is split your list into two sublists one starting from the left end and one from the right end:

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]

alternatively you could create a longer list (containing alternating items from sequence going from left to right and the items of the complete sequence going right to left) and only take the relevant elements:

list(roundrobin(a, reversed(a)))[:len(a)]

or using it as explicit generator with next:

rr = roundrobin(a, reversed(a))
[next(rr) for _ in range(len(a))]

or the speedy variant suggested by @Tadhg McDonald-Jensen (thank you!):

list(islice(roundrobin(a,reversed(a)),len(a)))
查看更多
冷血范
4楼-- · 2020-02-08 06:23

Not terribly different from some of the other answers, but it avoids a conditional expression for determining the sign of the index.

a = range(10)
b = [a[i // (2*(-1)**(i&1))] for i in a]

i & 1 alternates between 0 and 1. This causes the exponent to alternate between 1 and -1. This causes the index divisor to alternate between 2 and -2, which causes the index to alternate from end to end as i increases. The sequence is a[0], a[-1], a[1], a[-2], a[2], a[-3], etc.

(I iterate i over a since in this case each value of a is equal to its index. In general, iterate over range(len(a)).)

查看更多
▲ chillily
5楼-- · 2020-02-08 06:23

Two versions not seen yet:

b = list(sum(zip(a, a[::-1]), ())[:len(a)])

and

import itertools as it

b = [a[j] for j in it.accumulate(i*(-1)**i for i in range(len(a)))]
查看更多
仙女界的扛把子
6楼-- · 2020-02-08 06:24

I would do something like this

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
查看更多
放我归山
7楼-- · 2020-02-08 06:27

A very nice one-liner in Python 2.7:

results = list(sum(zip(a, reversed(a))[:len(a)/2], ()))
>>>> [0, 9, 1, 8, 2, 7, 3, 6, 4, 5]

First you zip the list with its reverse, take half that list, sum the tuples to form one tuple, and then convert to list.

In Python 3, zip returns a generator, so you have have to use islice from itertools:

from itertools import islice
results = list(sum(islice(zip(a, reversed(a)),0,int(len(a)/2)),()))

Edit: It appears this only works perfectly for even-list lengths - odd-list lengths will omit the middle element :( A small correction for int(len(a)/2) to int(len(a)/2) + 1 will give you a duplicate middle value, so be warned.

查看更多
登录 后发表回答