2

Consider random.shuffle on a list-of-objects (i.e. not primitive types like int). I'm talking about any general object; the numbers in the following are only an example.

Since random.shuffle acts in-place and returns None, that makes it slightly clunky to shuffle a slice or subset of that list. For a slice (or subset), is there a better way than taking the slice (or a deepcopy), shuffling it, then overwriting the original list slice with the result? i.e. as follows:

import copy
import random

class SomeNumber(object): # just a stupid class to illustrate the point
    def __init__(self, n):
        self.n = n
    def __repr__(self):
        return str(self.n)

ll = [ SomeNumber(i) for i in range(6) ]
# [0, 1, 2, 3, 4, 5]

# Say we want to shuffle slice 2:end
#ld = copy.deepcopy(ll[2:])
ld = ll[2:]
# [2, 3, 4, 5]
random.shuffle(ld)
# [3, 2, 4, 5]

# ll didn't get shuffled though, so must overwrite the original slice...
ll[2:] = ld
# [0, 1, 5, 3, 4, 2]

del ld

Consider also the more general case where I might want to pass a boolean vector of which elements are to be included in the shuffle. Then extract that subset, shuffle it and reinsert it. Can you find a good idiom?

PS @ReutSharabani suggests something like:

def partial_shuffle(lst, imin, imax):
    lst[imin:imax] = sorted(lst[imin:imax], key=lambda x: random.random())
    return lst
smci
  • 32,567
  • 20
  • 113
  • 146
  • if you want to shuffle a slice of the original list, why are you using a deep copy? can't you just slice -> shuffle -> compose back? – Reut Sharabani Dec 07 '14 at 15:03
  • @ReutSharabani: True. I updated the question. But is there a better idiom? Consider the more general case where we pass a boolean vector of what to shuffle/keep, mentioned at bottom. What is the best idiom? – smci Dec 07 '14 at 15:08
  • what about this: http://stackoverflow.com/questions/17649875/why-does-random-shuffle-returns-none (see second answer!) – Reut Sharabani Dec 07 '14 at 15:10
  • 1
    If you meant alecxe's answer mentioning the optional 2nd arg `random.shuffle(x[, random])`, where *random is a 0-argument function returning a random float in [0.0, 1.0); (by default, this is the function random().* Code using that will surely be even more clunky. – smci Dec 07 '14 at 15:11
  • 1
    My bad, I meant the answer... I thought it wasn't the selected answer for some reason. See my posted answer. – Reut Sharabani Dec 07 '14 at 15:16
  • @ReutSharabani ok I think you meant [Martijn Pieter's answer (the accepted one)](https://stackoverflow.com/a/17649901/202229). Always best to give the actual link. – smci Apr 16 '20 at 08:29

1 Answers1

1

as seen on: Why does random.shuffle return None?

#!/usr/bin/env python

import copy
import  random

class SomeNumber(object): # just a stupid class to illustrate the point
    def __init__(self, n):
        self.n = n
    def __repr__(self):
        return str(self.n)

ll = [ SomeNumber(i) for i in range(6) ]
print ll

ll[2:] = sorted(ll[2:], key=lambda x: random.random())

print ll

It uses sorted to act as shuffle (sort according to a random key), but I'm not sure if it's really random.

You could create a shuffle function to return a shuffled copy of a list:

#!/usr/bin/env python
import random
# the function
def shuffle(ll, include_list=None):
    if not include_list:
        include_list = [True] * len(ll)
    # tag the list (default: all, for use of slices) and shuffle
    tagged_list = [x for x in zip(include_list, ll)]
    shuffle_list = [x for x in tagged_list if x[0]]
    random.shuffle(shuffle_list)

    # re-insert to tagged list by false tags, enumerate indices changed
    false_indices = sum(map(lambda x: [x[0]] if x[1][0] else [], enumerate(tagged_list)), [])
    for shuffle_index, tagged_index in enumerate(false_indices):
        tagged_list[tagged_index] = shuffle_list[shuffle_index]
    # return only the values
    return [x[1] for x in tagged_list]

# now use it whenever you want to shuffle something

l = [1, 2, 3, 4, 5]
print "shuffeled: %s" % shuffle(l)
# original list stays the same (not in-place)
print "original: %s" % l
# now to shuffle a part of a list you can:

print "shuffeled (2+): %s" % shuffle(l[2:])

# with a boolean filter list:
print "shuffle indices 0, 2, 4: %s " % shuffle(l, [True, False, True, False, True])
Community
  • 1
  • 1
Reut Sharabani
  • 30,449
  • 6
  • 70
  • 88
  • 1
    So: `def partial_shuffle(lst, imin, imax): lst[imin:imax] = sorted(lst[imin:imax], key=lambda x: random.random()) return lst` – smci Dec 07 '14 at 15:22
  • You could write your shuffle function that does return the shuffled list using `random.shuffle`, this is probably what I'd do... Just be sure to copy the list (you can use `l[:]`) before shuffling. – Reut Sharabani Dec 07 '14 at 15:29
  • That's exactly that this question is asking you to do ;-) So where's the code? – smci Dec 07 '14 at 15:37
  • I was staying loyal to your code. If you're using it once, maybe a function is an overkill... I'll add it anyway... – Reut Sharabani Dec 07 '14 at 15:40
  • See bottom of my answer, I'm also asking for a unified implementation with ***Consider also the more general case where I might want to pass a boolean vector of which elements are to be included in the shuffle.*** – smci Dec 07 '14 at 15:49
  • @smci hope that's what you had in mind... A little voodoo but not to horrible :) – Reut Sharabani Dec 07 '14 at 16:45