2

I'm basically asking the exact same question as was asked here, but for Python 3.4.0.

In 3.4.0, this code:

a = ["Spears", "Adele", "NDubz", "Nicole", "Cristina"]
b = [1, 2, 3, 4, 5]

combined = zip(a, b)
random.shuffle(combined)

a[:], b[:] = zip(*combined)

does not work. What is the correct way to do this in 3.4.0?

TuringTux
  • 559
  • 1
  • 12
  • 26
david.keck
  • 23
  • 4

4 Answers4

5

In python 3, zip returns a zip object (i.e. it's itertools.izip from python 2).

You need to force it to materialize the list:

combined = list(zip(a, b))
roippi
  • 25,533
  • 4
  • 48
  • 73
  • Thanks so much. That worked great. Could you tell me why casting to a list later in the code didn't work well? i.e. combined = zip(a,b) random.shuffle(list(combined)) print(list(combined)) Returns an empty list – david.keck Apr 30 '14 at 03:50
  • `random.shuffle` shuffles *in-place*. Doing `list(combined)` returns a new list (and consumes `combined`), so doing `random.shuffle` on that shuffles it, then it is immediately thrown away. Doing `list(combined)` again gives you an empty list because you consumed the `zip` object in the previous step. – roippi Apr 30 '14 at 04:10
  • Wow. That was a great answer. Thank you so much. :) – david.keck Apr 30 '14 at 04:41
2

If memory was tight, you can write your own shuffle function to avoid the need to create the zipped list. The one from Python is not very complicated

def shuffle(self, x, random=None, int=int):
    """x, random=random.random -> shuffle list x in place; return None.

    Optional arg random is a 0-argument function returning a random
    float in [0.0, 1.0); by default, the standard random.random.

    Do not supply the 'int' argument.
    """

    randbelow = self._randbelow
    for i in reversed(range(1, len(args[0]))):
        # pick an element in x[:i+1] with which to exchange x[i]
        j = randbelow(i+1) if random is None else int(random() * (i+1))
        x[i], x[j] = x[j], x[i]

Your function could be this:

def shuffle2(a, b):
    for i in reversed(range(1, len(a))):
        j = int(random.random() * (i+1))
        a[i], a[j] = a[j], a[i]
        b[i], b[j] = b[j], b[i]

To shuffle an arbitrary number of lists in unison

def shuffle_many(*args):
    for i in reversed(range(1, len(args[0]))):
        j = int(random.random() * (i+1))
        for x in args: 
            x[i], x[j] = x[j], x[i]

eg

>>> import random
>>> def shuffle_many(*args):
...     for i in reversed(range(1, len(args[0]))):
...         j = int(random.random() * (i+1))
...         for x in args: 
...             x[i], x[j] = x[j], x[i]
... 
>>> a = ["Spears", "Adele", "NDubz", "Nicole", "Cristina"]
>>> b = [1, 2, 3, 4, 5]
>>> shuffle_many(a, b)
>>> a
['Adele', 'Spears', 'Nicole', 'NDubz', 'Cristina']
>>> b
[2, 1, 4, 3, 5]
John La Rooy
  • 295,403
  • 53
  • 369
  • 502
0

Change combined = zip(a,b) to combined = list(zip(a,b)). You need a list, not an iterator, in order to shuffle in place.

happydave
  • 7,127
  • 1
  • 26
  • 25
0

In Python 3, zip returns an iterator rather than a list, so cast it to a list before shuffling it:

combined = list(zip(a, b))
Peter DeGlopper
  • 36,326
  • 7
  • 90
  • 83