be-ns
be-ns

Reputation: 75

More 'Pythonic' Way to alternate list shuffle

Trying to 'shuffle' a list with even number of items. Splitting the list, L in half and alternating taking an element from each.

I've tried pop, but that approach was unable to get me to a one-liner. (while loop) and I know there is likely some more succinct way to move through it.

The shuffle from random isn't exactly what I need, either – because that randomizes the entire order instead of alternating between the split list.

If a one-liner isn't possible, is that because it's more readable in a while loop?

def shuffle(L):
    '''
    I apologize in advance for how wet the following code is... 
    Example:
    >>> shuffle([1, 2, 3, 4, 5, 6])
    [1, 4, 2, 5, 3, 6]
    '''
    return [L[:(len(L)//2)][0], L[(len(L)//2):][0], L[:(len(L)//2)][1], L[(len(L)//2):][1], L[:(len(L)//2)][2], L[(len(L)//2):][2]]

other attempt:

def shuffle(L):
    x, L_first, L_next, L = len(L), L[:(len(L)//2)], L[(len(L)//2):], []
    while len(L) != x:
        L.extend([L_first.pop(0), L_next.pop(0)])
    return L

Upvotes: 2

Views: 410

Answers (5)

KingMak
KingMak

Reputation: 1478

Found two solutions. First one is very unpythonic (using python 2.7)

a = [1, 2, 3, 4, 5, 6] # intial array

Method One (using string magic):

[int(p) for p in ' '.join([str(x) + ' ' + str(y) for x, y in zip(a[:len(a) / 2], a[len(a) / 2:])]).split(' ')]

Method Two:

[i for Tuple in zip(a[:len(a) / 2], a[len(a) / 2:]) for i in Tuple]

Upvotes: 0

f5r5e5d
f5r5e5d

Reputation: 3706

list comprehension with index calculation using modulo and floor division

[ L[(i + (i % 2)*len(L))//2] for i in range(len(L)) ] # for case of even len(L)

still one line for the general case

[ L[i//2 + (i % 2)*len(L)//2] for i in range(2*(len(L)//2)) ] + [L[-1]]*(len(L) % 2)

the index calc (i + (i % 2)*len(L))//2

can be parsed as adding

i//2 which gives 0, 0, 1, 1, 2, 2 ...

and

(i % 2)*len(L)//2 where (i % 2) alternates 0, 1 for even/odd i

0, len(L)//2, 0, len(L)//2, 0, len(L)//2 ...

sum:

0, len(L)//2, 1, 1 + len(L)//2, 2, 2 + len(L)//2 ...

Upvotes: 1

MSeifert
MSeifert

Reputation: 152647

One possibility (requires an external library but the recipe can also be found in the itertools-recipes section) is:

from iteration_utilities import roundrobin

def shuffle(L):
    return list(roundrobin(L[:len(L)//2], L[len(L)//2:]))

This is probably slower than list assignment but it also works for arbitary amounts of iterables without problems and it doesn't require odd-sized-input handling:

>>> shuffle([1, 2, 3, 4, 5, 6, 7])
[1, 4, 2, 5, 3, 6, 7]
>>> shuffle([1, 2, 3, 4, 5, 6])
[1, 4, 2, 5, 3, 6]

I did some timings and @user2357112 definetly has the fastest solution but my solution is at least on the second place (note that this graph is in log-log, that means the difference in absolute terms may seem smaller than it really is!):

enter image description here


Disclaimer: I'm the author of that iteration_utilities library.

Upvotes: 2

miradulo
miradulo

Reputation: 29690

If I understand correctly, you could also opt for itertools.chain.from_iterable after zipping to get the alternating effect.

from itertools import chain

def shuff(l):
    return list(chain.from_iterable(zip(l[:len(l)//2], l[len(l)//2:])))

Demo

>>> shuff(list(range(1, 7))
[1, 4, 2, 5, 3, 6]

Upvotes: 3

user2357112
user2357112

Reputation: 280564

Use slice assignment with a step:

def shuffle(l):
    result = [None] * len(l)

    # Put the first half of l in the even indices of result
    result[::2] = l[:len(l)//2]

    # Put the second half of l in the odd indices of result
    result[1::2] = l[len(l)//2:]

    return result

Upvotes: 4

Related Questions