Paul Buga - 1 year ago 55
Python Question

# random.shuffle is acting strange in list of lists made with list multiplication

My

`createIndividual`
function is trying to take a list of lists of lists called
`courses`
, and then add members of a global list variable (
`PEOPLE`
) to it randomly. The code is:

``````def createIndividual(courses):
# Courses is equal to an individual, but
# without people
individual = courses.copy()
for course in individual:
myPeople = PEOPLE.copy()
random.shuffle(myPeople)
for table in course:
while len(table) < table.maximum:
table.append(myPeople.pop())
return individual
``````

I ran this function a bunch of times, trying to get lots of copies with different results. However, each time it created the exact same results for the
`course`
variable within each individual.

The results differed between runs of the program, but were the same within a single run.

An example value of the
`courses`
argument would be:

``````[[[], [], []],
[[], [], []]]
``````

The innermost "lists" are actually
`Table()`
classes, which have a
`maximum`
value defined, but in all other ways act like lists. The
`maximum`
attribute of each
`Table`
is 3.

The list of
`PEOPLE`
would be:

``````[1, 2, 3, 4, 5, 6, 7, 8, 9]
``````

The first
`individual`
created was:

``````[[[4, 3, 8], [5, 9, 2], [1, 6, 7]],
[[4, 3, 8], [5, 9, 2], [1, 6, 7]]]
``````

The second
`individual`
created was:

``````[[[9, 8, 3], [7, 2, 1], [6, 5, 4]],
[[9, 8, 3], [7, 2, 1], [6, 5, 4]]]
``````

These continue with apparent randomness, but each
`course`
in the
`individual`
is always identical to the other
`course`
s within the same
`individual`
, regardless of how I change the parameters.

This question really needs more detail to answer it with confidence, but since there's a common error that could produce this weird result (and since I wasn't able to reproduce it any other way)...

I think the problem is not in your `createIndividual` function, but in the data structure you're feeding it. Here's a bit of my `main` function that produced exactly the random output you expected:

``````from pprint import pprint
# pprint is essential for pretty-printing deeply nested data.

class Table(object):
...
# Guesswork on my part, plus a custom __str__ and __repr__.

def main():
# This creates a list of two lists-of-three-Tables.
distinct_courses = [[Table() for __ in range(3)] for __ in range(2)]

filled_courses = createIndividual(distinct_courses)
pprint(filled_courses)
``````

Output:

``````[[Table([1, 2, 3]), Table([5, 8, 6]), Table([7, 4, 9])],
[Table([7, 5, 3]), Table([2, 6, 8]), Table([9, 1, 4])]]
``````

To reproduce your problem, I had to create `courses` using the list-multiplication syntax, which doesn't do what most beginners (and some tutorials) think it does:

## 4.6.1. Common Sequence Operations

[table omitted]

Notes:

1. [...] Note that items in the sequence s are not copied; they are referenced multiple times. This often haunts new Python programmers; [...]

Further explanation is available in the FAQ entry How do I create a multidimensional list?.

I'm guessing somewhere in your code, you did something like this:

``````def bogus_main():
# This creates a single lists-of-three-Tables...
course = [Table() for __ in range(3)]
# ...then creates a list of two references to the _same_ list.
aliased_courses = [course] * 2

filled_courses = createIndividual(aliased_courses)
pprint(filled_courses)
``````

Output using the aliased lists:

``````[[Table([7, 9, 2]), Table([8, 6, 1]), Table([5, 3, 4])],
[Table([7, 9, 2]), Table([8, 6, 1]), Table([5, 3, 4])]]
``````

Since both `courses[0]` and `courses[1]` point to the same list, two weird things happen. First, the contents of the "two" `course`s will be the same, as you've already noticed. Each update seems to add a pair of identical `Table`s to two different lists, although it's really just adding one `Table` to one list... and then printing that list twice... You can see this in action by adding an extra `pprint` to `createIndividual`:

``````[[Table([]), Table([]), Table([])],
[Table([]), Table([]), Table([])]]

[[Table([7, 9, 2]), Table([]), Table([])],
[Table([7, 9, 2]), Table([]), Table([])]]

[[Table([7, 9, 2]), Table([8, 6, 1]), Table([])],
[Table([7, 9, 2]), Table([8, 6, 1]), Table([])]]

[[Table([7, 9, 2]), Table([8, 6, 1]), Table([5, 3, 4])],
[Table([7, 9, 2]), Table([8, 6, 1]), Table([5, 3, 4])]]

[[Table([7, 9, 2]), Table([8, 6, 1]), Table([5, 3, 4])],
[Table([7, 9, 2]), Table([8, 6, 1]), Table([5, 3, 4])]]

[[Table([7, 9, 2]), Table([8, 6, 1]), Table([5, 3, 4])],
[Table([7, 9, 2]), Table([8, 6, 1]), Table([5, 3, 4])]]

[[Table([7, 9, 2]), Table([8, 6, 1]), Table([5, 3, 4])],
[Table([7, 9, 2]), Table([8, 6, 1]), Table([5, 3, 4])]]
``````

Second, notice how the last three "updates" don't actually change anything? Something should have failed when you tried to add 18 values to only 9 slots. Unfortunately, the `maximum` field that protects you from over-filling a `Table` also saves you from the error that could have tipped you off earlier. From `createIndividual`:

``````for table in course:
while len(table) < table.maximum:
# Once the "first" aliased course list is full, this will
# never pop another person, because there's no place to
# store them.
table.append(myPeople.pop())
# Prints 6, 3, and 0 during the first `course`, then prints
# 9, 9, and 9... myPeople never changes the second time through.
``````

After the "first" (really, the only) three `Table`s are filled, they're all at `maximum` length, so neither `individual` or `myPeople` will change again.

Contrast the list-comprehension version in my `main`, which creates six different `Table` objects, like you'd expect:

``````[[Table([]), Table([]), Table([])],
[Table([]), Table([]), Table([])]]

[[Table([1, 2, 3]), Table([]), Table([])],
[Table([]), Table([]), Table([])]]

[[Table([1, 2, 3]), Table([5, 8, 6]), Table([])],
[Table([]), Table([]), Table([])]]

[[Table([1, 2, 3]), Table([5, 8, 6]), Table([7, 4, 9])],
[Table([]), Table([]), Table([])]]

[[Table([1, 2, 3]), Table([5, 8, 6]), Table([7, 4, 9])],
[Table([7, 5, 3]), Table([]), Table([])]]

[[Table([1, 2, 3]), Table([5, 8, 6]), Table([7, 4, 9])],
[Table([7, 5, 3]), Table([2, 6, 8]), Table([])]]

[[Table([1, 2, 3]), Table([5, 8, 6]), Table([7, 4, 9])],
[Table([7, 5, 3]), Table([2, 6, 8]), Table([9, 1, 4])]]
``````
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download