skyork
skyork

Reputation: 7381

Fix first element, shuffle the rest of a list/array

Is it possible to shuffle only a (continuous) part of a given list (or array in numpy)?

If this is not generally possible, how about the special case where the first element is fixed while the rest of the list/array need to be shuffled? For example, I have a list/array:

to_be_shuffled = [None, 'a', 'b', 'c', 'd', ...]

where the first element should always stay, while the rest are going to be shuffled repeatedly.

One possible way is to shuffle the whole list first, and then check the first element, if it is not the special fixed element (e.g. None), then swap its position with that of the special element (which would then require a lookup).

Is there any better way for doing this?

Upvotes: 6

Views: 2500

Answers (4)

kojiro
kojiro

Reputation: 77079

I thought it would be interesting and educational to try to implement a slightly more general approach than what you're asking for. Here I shuffle the indices to the original list (rather than the list itself), excluding the locked indices, and use that index-list to cherry pick elements from the original list. This is not an in-place solution, but implemented as a generator so you can lazily pick elements.

Feel free to edit if you can improve it.

import random

def partial_shuf(input_list, fixed_indices):
    """Given an input_list, yield elements from that list in random order
    except where elements indices are in fixed_indices."""
    fixed_indices = sorted(set(i for i in fixed_indices if i < len(input_list)))
    i = 0
    for fixed in fixed_indices:
        aslice = range(i, fixed)
        i = 1 + fixed
        random.shuffle(aslice)
        for j in aslice:
            yield input_list[j]
        yield input_list[fixed]
    aslice = range(i, len(input_list))
    random.shuffle(aslice)
    for j in aslice:
        yield input_list[j]

print '\n'.join(' '.join((str(i), str(n))) for i, n in enumerate(partial_shuf(range(4, 36), [0, 4, 9, 17, 25, 40])))

assert sorted(partial_shuf(range(4, 36), [0, 4, 9, 17, 25, 40])) == range(4, 36)

Upvotes: 4

Joel Cornett
Joel Cornett

Reputation: 24788

I took the shuffle function from the standard library random module (found in Lib\random.py) and modified it slightly so that it shuffles only a portion of the list specified by start and stop. It does this in place. Enjoy!

from random import randint

def shuffle(x, start=0, stop=None):
    if stop is None:
        stop = len(x)

    for i in reversed(range(start + 1, stop)):
        # pick an element in x[start: i+1] with which to exchange x[i]
        j = randint(start, i)
        x[i], x[j] = x[j], x[i]

For your purposes, calling this function with 1 as the start parameter should do the trick.

Upvotes: 3

jfs
jfs

Reputation: 414139

numpy arrays don't copy data on slicing:

numpy.random.shuffle(a[1:])

Upvotes: 5

David Robinson
David Robinson

Reputation: 78590

Why not just

import random
rest = to_be_shuffled[1:]
random.shuffle(rest)
shuffled_lst = [to_be_shuffled[0]] + rest

Upvotes: 10

Related Questions