Cycle a list from alternating sides

后端 未结 15 2107
温柔的废话
温柔的废话 2020-12-14 01:03

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 lis

相关标签:
15条回答
  • 2020-12-14 01:49

    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.

    0 讨论(0)
  • 2020-12-14 01:49

    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)))
    
    0 讨论(0)
  • 2020-12-14 01:50

    You can just pop back and forth:

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

    Note: This destroys the original list, a.

    0 讨论(0)
  • 2020-12-14 01:51

    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)))
    
    0 讨论(0)
  • 2020-12-14 01:52

    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
    
    0 讨论(0)
  • 2020-12-14 01:57

    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)]
    
    0 讨论(0)
提交回复
热议问题