Paul Buga Paul Buga - 5 months ago 6
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.

Answer

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" courses will be the same, as you've already noticed. Each update seems to add a pair of identical Tables 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())
    print(len(myPeople))  # My addition.
    # 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 Tables 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])]]
Comments