1

Apologies as I'm sure this problem has come up before but none of the examples seem to apply.

I'm trying to create a 2D list (4 by 100) which assigns random values in different ranges in each row.

import random
size_of_meadow = 100
no_of_flowers = 100
no_of_flower_types = 3

flower = [[0] * 5] * no_of_flowers

for row in range(no_of_flowers):
    flower[row][0] = random.randint(0, size_of_meadow - 1)  # x coord
    flower[row][1] = random.randint(0, size_of_meadow - 1)  # y coord
    flower[row][2] = random.randint(1, no_of_flower_types)
if random.randint(0, 100) < 5:
    flower[row][3] = 1
else:
    flower[row][3] = 0

print(flower[0][0])
print(flower[0][1])
print(flower[0][2])
print(flower[0][3])
print(" ")
print(flower[1][0])
print(flower[1][1])
print(flower[1][2])
print(flower[1][3])
# Sorry the above isn't in a for loop

The outputs just show the same set of numbers for each row of the 2D list...

Output:

29
21
2
0

29
21
2
0

I'm new to python and I know I must be missing something fundamental, but after a lot of umming and ahhing I haven't figured it out, any help is appreciated.

Thanks

Yuan JI
  • 2,927
  • 2
  • 20
  • 29
wonderburg
  • 95
  • 1
  • 1
  • 5
  • `print('\n'.join([''.join(['{} '.format(item) for item in row]) for row in flower]))` this might also be helpful – bison Mar 20 '19 at 14:31
  • Possible duplicate of [List of lists changes reflected across sublists unexpectedly](https://stackoverflow.com/questions/240178/list-of-lists-changes-reflected-across-sublists-unexpectedly) – wwii Mar 20 '19 at 15:23

3 Answers3

3

By using [[0] * 5] * no_of_flowers you are creating 100 references to the same list. This means there is ONLY ONE ROW in the memory.

When you modify a row, if affects the row in memory, and since all other "rows" refer to it, they will simply use the list in memory which is up-to-date.

To avoid this scenario, you could use:

flower = [[0 for _ in range(5)] for _ in range(no_of_flowers)]

You could find an example and explanation in wtfpython:

# Let's initialize a row
row = [""]*3 #row i['', '', '']
# Let's make a board
board = [row]*3

Output:

>>> board
[['', '', ''], ['', '', ''], ['', '', '']]
>>> board[0]
['', '', '']
>>> board[0][0]
''
>>> board[0][0] = "X"
>>> board
[['X', '', ''], ['X', '', ''], ['X', '', '']]

Explanation:

When we initialize row variable, this visualization explains what happens in the memory.

enter image description here

And when the board is initialized by multiplying the row, this is what happens inside the memory (each of the elements board[0], board[1] and board[2] is a reference to the same list referred by row)

enter image description here

We can avoid this scenario here by not using row variable to generate board. (Asked in this issue).

>>> board = [['']*3 for _ in range(3)]
>>> board[0][0] = "X"
>>> board
[['X', '', ''], ['', '', ''], ['', '', '']]
Yuan JI
  • 2,927
  • 2
  • 20
  • 29
2

You have one list that contains 100 times the same list

a=[0,0,0]
flower=[a,a,a,a,...]

Better solution will be

import random

size_of_meadow = 100
no_of_flowers = 100
no_of_flower_types = 3

flower = []

for row in range(no_of_flowers):
  flower.append([random.randint(0, size_of_meadow-1),
      random.randint(0, size_of_meadow-1),
      random.randint(1, no_of_flower_types),
      1 if random.randint(0, 100) < 5 else 0])

print(flower[0][0])
print(flower[0][1])
print(flower[0][2])
print(flower[0][3])
print(" ")
print(flower[1][0])
print(flower[1][1])
print(flower[1][2])
print(flower[1][3])
# Sorry the above isn't in a for loop

However flower generator would be even better

Lee
  • 1,427
  • 9
  • 17
  • This is great and produced the results I wanted. I'm still don't entirely understand the mistake I was making but will move forward using this method in future. – wonderburg Mar 20 '19 at 14:36
2

Hey @Lee has it correct but let me elaborate

>>> flower = [[0] * 5] * 100
>>> flower[0]
[0, 0, 0, 0, 0]
>>> flower[1]
[0, 0, 0, 0, 0]
>>> id(flower[0])
4354853640
>>> id(flower[1])
4354853640

Here you can see that each sub list will point to the same location in memory

I would use this method:

flower = [[None for _ in range(5)] for _ in range(no_of_flowers)] 

>>> id(flower[0])
4354853832
>>> id(flower[1])
4354854600
bison
  • 739
  • 5
  • 21
  • 1
    That's what I was thinking, but why are all of them pointing to the same position? I actually expected [[0] * 5] * 100 to run the same way as your code. – epsilonmajorquezero Mar 20 '19 at 14:36
  • This answer produced the output I wanted from just changing the one line of code which is great. – wonderburg Mar 20 '19 at 14:38
  • 1
    @epsilonmajorquezero when you run `[0] * 5` it creates one list `[0, 0, 0, 0, 0]` and then the `* 100` makes a list that has the sub list point to each 100 positions. The list comp actually re-runs in each iteration creating new elements – bison Mar 20 '19 at 14:42
  • 1
    @bison that made the problem really clear, thanks for the explanation – wonderburg Mar 20 '19 at 14:46
  • 1
    Yeah, I see that's what it must be doing according to behaviour but then the behaviour is not really consistent: first [0] * 5 which creates a list of 5 zeroes (which are not the same location in memory) but then this list multiplied again does point to the same position. – epsilonmajorquezero Mar 20 '19 at 14:55