29

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?

davidism
  • 121,510
  • 29
  • 395
  • 339
Pythonic
  • 2,091
  • 3
  • 21
  • 34
  • 1
    why not deque?! `l1=list(range(10)); d1=deque(l1); [d1.pop() if i%2 else d1.popleft() for i,_ in enumerate(l1) if d1]` – denfromufa Apr 17 '16 at 05:57

15 Answers15

52
>>> [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.

Norman
  • 1,975
  • 1
  • 20
  • 25
  • You currently hold the record for only one liner that doesn't need a scroll bar and works with odd length lists, since the OP has [stated they like one liners](http://stackoverflow.com/questions/36533553/how-to-elegantly-cycle-a-list-from-alternating-sides/36533868?noredirect=1#comment60671692_36533951) I'd be inclined to say this is the best answer (+1 btw) – Tadhg McDonald-Jensen Apr 10 '16 at 18:54
  • 2
    This is also one of the few answers that doesn't run into issues with odd-sized lists. Well done! – Akshat Mahajan Apr 10 '16 at 18:55
  • It fails on the "elegantly" part though, as no one looking at that code can immediately understand what it does. – Matsemann Apr 14 '16 at 10:26
  • @Matsemann at the time of posting this was the cleanest way to do it, Noman [has acknowledged](http://stackoverflow.com/questions/36533553/how-to-elegantly-cycle-a-list-from-alternating-sides/36533624#comment60674430_36533624) that this answer is not immediately obvious and that he prefers my answer for that reason. – Tadhg McDonald-Jensen Apr 14 '16 at 15:08
  • 1
    @Matsemann Nah, I wouldn't say no one ;-) But I appreciate what you mean, and I agree. Tadhg's answer is now by far the most pythonic here, in my mind. For the record, I wouldn't mind in the least if OP unaccepted my answer to promote the spirit of true readability. – Norman Apr 14 '16 at 17:28
29

cycle between getting items from the forward iter and the reversed one. Just make sure you stop at len(a) with 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]

This can easily be put into a single line but then it becomes much more difficult to read:

[next(it) for it in islice(cycle((iter(a),reversed(a))),len(a))]

Putting it in one line would also prevent you from using the other half of the iterators if you wanted to:

>>> 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]
Tadhg McDonald-Jensen
  • 20,699
  • 5
  • 35
  • 59
  • 1
    Actually, I prefer the two-liner you made from @canaaerus' idea over the accepted answer. Seeing `i//2` I still have to think about why the index needs to be halved, but with `[next(iters[n%2]) for n in a]` I understand *immediately*. IMHO, that two-liner is the truest rendition of "Take numbers alternatingly from front and end of list." I feel it deserves a more prominent position than at the end of your answer. – Norman Apr 10 '16 at 20:51
  • @norman I have since edited my post to use `itertools.cycle` which I am even more fond of then my previous version. – Tadhg McDonald-Jensen Apr 13 '16 at 04:19
  • Whoa nice :-) That *is* even better. Are there any limits to the readability of Python code!? – Norman Apr 13 '16 at 08:42
8

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.

Akshat Mahajan
  • 9,543
  • 4
  • 35
  • 44
6

If you don’t mind sacrificing the source list, a, you can just pop back and forth:

b = [a.pop(-1 if i % 2 else 0) for i in range(len(a))]

Edit:

b = [a.pop(-bool(i % 2)) for i in range(len(a))]
Zach Gates
  • 4,045
  • 1
  • 27
  • 51
6

Use the right toolz.

from toolz import interleave, take

b = list(take(len(a), interleave((a, reversed(a)))))

First, I tried something similar to Raymond Hettinger's solution with itertools (Python 3).

from itertools import chain, islice

interleaved = chain.from_iterable(zip(a, reversed(a)))
b = list(islice(interleaved, len(a)))
skrx
  • 19,980
  • 5
  • 34
  • 48
5

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

chepner
  • 497,756
  • 71
  • 530
  • 681
  • Damn it, I was just fiddling around with power of -1 myself :-D – Norman Apr 10 '16 at 19:02
  • Heh. Feel free to add this to your answer, and I'll delete; it might be slightly more efficient than a branch, but it is decidedly less readable :) As such, it's barely worth an extra answer. – chepner Apr 10 '16 at 19:04
  • 1
    you might want to use `for i in range(len(a))` for any list other then the direct result of the `range` function. – Tadhg McDonald-Jensen Apr 10 '16 at 19:08
5

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)))
MSeifert
  • 145,886
  • 38
  • 333
  • 352
  • I tend to like this answer, although probably the most efficient use is `b = list(islice(roundrobin(a,reversed(a)),len(a)))` since `reversed` doesn't need to create a whole new list like `[::-1]` does and you might as well use `islice` to cut off the end since again, less list creation overhaul. – Tadhg McDonald-Jensen Apr 11 '16 at 14:09
  • @TadhgMcDonald-Jensen I've included the `reversed` in the examples and included your suggestion (I hope properly attributed). Thank you for the comment. – MSeifert Apr 11 '16 at 14:39
4

Not sure, whether this can be written more compactly, but it is efficient as it only uses iterators / generators

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)]
bodo
  • 1,005
  • 15
  • 31
  • 3
    `iters = iter(a), reversed(a) ; b = [next(iters[n%2]) for n in range(len(a))]` originally I posted that in my answer but have since figured out a version I like even more. – Tadhg McDonald-Jensen Apr 13 '16 at 04:17
4

Not at all elegant, but it is a clumsy one-liner:

a = range(10)
[val for pair in zip(a[:len(a)//2],a[-1:(len(a)//2-1):-1]) for val in pair]

Note that it assumes you are doing this for a list of even length. If that breaks, then this breaks (it drops the middle term). Note that I got some of the idea from here.

Community
  • 1
  • 1
Doctorambient
  • 356
  • 3
  • 5
4

For fun, here is an itertools variant:

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

This works where len(a) is even. It would need a special code for odd-lengthened input.

Enjoy!

Raymond Hettinger
  • 216,523
  • 63
  • 388
  • 485
4

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)))]
RootTwo
  • 4,288
  • 1
  • 11
  • 15
  • Using `accumulate` to get a sequence of indices `[0, -1, 1, -2, 2, -3, 3, -4, 4, -5]` is a great idea. – pylang Jun 03 '17 at 06:19
3
mylist = [0,1,2,3,4,5,6,7,8,9]
result = []

for i in mylist:
    result += [i, mylist.pop()]

Note:

Beware: Just like @Tadhg McDonald-Jensen has said (see the comment below) it'll destroy half of original list object.

Sнаđошƒаӽ
  • 16,753
  • 12
  • 73
  • 90
felipsmartins
  • 13,269
  • 4
  • 48
  • 56
3

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
supamaze
  • 8,169
  • 2
  • 11
  • 11
3

One way to do this for even-sized lists (inspired by this post):

a = range(10)

b = [val for pair in zip(a[:5], a[5:][::-1]) for val in pair]
Community
  • 1
  • 1
Pythonic
  • 2,091
  • 3
  • 21
  • 34
2

You can partition the list into two parts about the middle, reverse the second half and zip the two partitions, like so:

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)

Output:

[0, 9, 1, 8, 2, 7, 3, 6, 4, 5]
Sнаđошƒаӽ
  • 16,753
  • 12
  • 73
  • 90