1

can anyone explain what's happening behind this? I tried to search for similar behavior but most of the questions on iterating nested lists

r1=[0 for i in range(3)]
result=[r1 for j in range(3)]
result[0][0]=4 # it will edit all the 0th elements of the sublists
print(result)

r1=[0 for i in range(3)]
result=[r1 for j in range(3)]
result[1][0]=4 # it will edit all the 0th elements of the sublists
print(result)

r1=[0 for i in range(3)]
result=[r1 for j in range(3)]
result[0][1]=4 # it will edit all the 1st elements of the sublists
print(result)

result = [[0 for x in range(3)] for y in range(3)]
result[0][0]=4
print(result) # it will edit only the first element of first sublist

the output is

[[4, 0, 0], [4, 0, 0], [4, 0, 0]]
[[4, 0, 0], [4, 0, 0], [4, 0, 0]]
[[0, 4, 0], [0, 4, 0], [0, 4, 0]]
[[4, 0, 0], [0, 0, 0], [0, 0, 0]]
krishna
  • 177
  • 19
  • 30
  • 60
  • Do `print([id(x) for x in result])` and all should become clear... – alani Sep 29 '20 at 07:41
  • Try also `result = [r1.copy() for j in range(3)]` - note the `copy` here, and also look at the IDs as shown above... – alani Sep 29 '20 at 07:43
  • As your list is made of ints, you can also just do `result = [r1[:] for j in range(3)]`. Or, just get rid of `r1` completely: `result = [[0 for i in range(3)] for j in range(3)]` – Tomerikoo Sep 29 '20 at 07:54

1 Answers1

2

The problem comes from how you construct your nested list. The following:

r1=[0 for i in range(3)]
result=[r1 for j in range(3)]

Creates a list of 3 r1. And not a list of 3 different lists which is why, when you modify any of result[j] it modifies r1 and therefore all others result[j]. As pointed out by others, that's the exact same problem as described here, with a different expression: result = [r1] * 3

On the other hand, on your last test you create what you expect, a list of 3 distinct lists:

result = [[0 for x in range(3)] for y in range(3)]

You can do the same using r1:

result = [[x for x in r1] for y in range(3)]

Here the inner "loop" simply copy the elements of r1 into a new (distinct) list:

r1 = [0 for i in range(3)]
result = [[x for x in r1] for y in range(3)]
result[0][0] = 4
print(result)

Prints:

[[4, 0, 0], [0, 0, 0], [0, 0, 0]]

You can observe the original problem in this simpler example where modifications on r1 are also impacting r2:

r1 = [0 for i in range(3)]
r2 = r1 
r1[0] = 4
print(r2)

The reason behind this behaviour is simply that r1 and r2 are two names for the same object. When we write r1[0] = 4 we don't change the value of r1 we change the value of the list r1 refers to (or points to* if you prefer). This is not specific to python and any data structure that can be modified "in-place" will suffer this "problem" (I suggested reading about mutable vs. immutable data structure to better understand what's going on here).

cglacet
  • 8,873
  • 4
  • 45
  • 60
  • I suggest that `r1.copy()` is a clearer way to write your inner list comprehension, but agreed. *[edit to comment: I see that this requires python 3 though]* – alani Sep 29 '20 at 07:46
  • I was simply suggesting using the same form, but to be honest I think I would use the list comprehension in practice too. Not sure what the majority of people would do, or what the standard is. I would read both without problem tho. – cglacet Sep 29 '20 at 07:47
  • I agree with the list-comp, I would just do `result = [[0 for _ in range(3)] for _ in range(3)]` and get rid of `r1` altogether... – Tomerikoo Sep 29 '20 at 07:52
  • I prefer to leave the OP code as it is and only modify what is required to be modified. But I agree that this is quite standard to name unused variables `_`. And I most often use that form too in my code. Maybe `r1` is not something we have control over, that's why I prefer not too guess and leave it like it is. – cglacet Sep 29 '20 at 08:04