This question already has answers here:
List of lists changes reflected across sublists unexpectedly
(17 answers)
Closed 9 years ago.
I have experienced some problem by using a nested list in Python in the code shown bleow.
Basically, I have a 2D list contains all 0 values, I want to update the list value in a loop.
However, Python does not produce the result I want. Is there something that I misunderstand about range() and Python list indices?
some_list = 4 * [(4 * [0])]
for i in range(3):
for j in range(3):
some_list[i+1][j+1] = 1
for i in range(4):
print(some_list[i])
The results I expected are:
[0, 0, 0, 0]
[0, 1, 1, 1]
[0, 1, 1, 1]
[0, 1, 1, 1]
But the actual results from Python are:
[0, 1, 1, 1]
[0, 1, 1, 1]
[0, 1, 1, 1]
[0, 1, 1, 1]
What's going on here?
The problem is caused by the fact that python chooses to pass lists around by reference.
Normally variables are passed "by value", so they operate independently:
>>> a = 1
>>> b = a
>>> a = 2
>>> print b
1
But since lists might get pretty large, rather than shifting the whole list around memory, Python chooses to just use a reference ('pointer' in C terms). If you assign one to another variable, you assign just the reference to it. This means that you can have two variables pointing to the same list in memory:
>>> a = [1]
>>> b = a
>>> a[0] = 2
>>> print b
[2]
So, in your first line of code you have 4 * [0]. Now [0] is a pointer to the value 0 in memory, and when you multiply it, you get four pointers to the same place in memory. BUT when you change one of the values then Python knows that the pointer needs to change to point to the new value:
>>> a = 4 * [0]
>>> a
[0, 0, 0, 0]
>>> [id(v) for v in a]
[33302480, 33302480, 33302480, 33302480]
>>> a[0] = 1
>>> a
[1, 0, 0, 0]
The problem comes when you multiply this list - you get four copies of the list pointer. Now when you change one of the values in one list, all four change together:
>>> a[0][0] = 1
>>> a
[[1, 0, 0, 0], [1, 0, 0, 0], [1, 0, 0, 0], [1, 0, 0, 0]]
The solution is to avoid the second multiplication. A loop does the job:
>>> some_list = [(4 * [0]) for _ in range(4)]
Actually all the objects in your list are same, so changing one changes others too:
In [151]: some_list = 4 * [(4 * [0])]
In [152]: [id(x) for x in some_list]
Out[152]: [148641452, 148641452, 148641452, 148641452]
In [160]: some_list[0][1]=5 #you think you changed the list at index 0 here
In [161]: some_list
Out[161]: [[0, 5, 0, 0], [0, 5, 0, 0], [0, 5, 0, 0], [0, 5, 0, 0]] #but all lists are changed
Create your list this way:
In [156]: some_list=[[0]*4 for _ in range(4)]
In [157]: some_list
Out[157]: [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
In [158]: [id(x) for x in some_list]
Out[158]: [148255436, 148695180, 148258380, 148255852]
In [163]: some_list[0][1]=5
In [164]: some_list
Out[164]: [[0, 5, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]] #works fine in this case
Related
This question already has answers here:
List of lists changes reflected across sublists unexpectedly
(17 answers)
Closed 1 year ago.
I have a nested list containing nothing but 0's, defined by a size of 5 by 5, and I want to be able to set specific values int he nested list to 1 using direct indexing. This is the solution I currently have:
Grid = [[0] * 5] * 5
ObX = 2
print(ObX)
ObY = 3
print(ObY)
Grid[ObY][ObX] = 1
print(Grid)
Expected output:
2
3
[[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 1, 0, 0], [0, 0, 0, 0, 0]]
Actual Output:
2
3
[[0, 0, 1, 0, 0], [0, 0, 1, 0, 0], [0, 0, 1, 0, 0], [0, 0, 1, 0, 0], [0, 0, 1, 0, 0]]
As far as I can tell, it's ignoring the "ObY" index and just deciding that it wants to replace every value in each list at index "ObX". What am I doing wrong?
The list of lists you created contains the same list 5 times, changing it once changes all of its copies
In [1]: Grid = [[0] * 5] * 5
In [2]: [id(x) for x in Grid]
Out[2]:
[140392398718848,
140392398718848,
140392398718848,
140392398718848,
140392398718848]
Use this to create 5 different lists
In [5]: Grid =[[0]*5 for _ in range(5)]
In [6]: [id(x) for x in Grid]
Out[6]:
[140392397938112,
140392396267776,
140392397478656,
140392398276224,
140392398427200]
I want to change all items in array A (in axis=1) into 0, according to the following criteria (toy code):
import numpy as np
A = np.array([[1,3], [2,5], [6,2]] )
B = np.array([[1,1,0,0,0],[1,0,0,2,0],[0,0,2,2,2],[0,0,0,2,0],[6,6,0,0,0]])
for i in A:
if i[1]<=2:
B[B==i[0]]=0
# result
>>> B
array([[1, 1, 0, 0, 0],
[1, 0, 0, 2, 0],
[0, 0, 2, 2, 2],
[0, 0, 0, 2, 0],
[0, 0, 0, 0, 0]])
But, in numpy way, that is NO 'for' loops :) Thanks!
You can use a conditional list comprehension to create a list of the first value in a tuple pair where the second value is less than or equal to two (in the example for A, it is the last item which gives a value of 6).
Then use slicing with np.isin to find the elements in B what are contained within the values from the previous condition, and then set those values to zero.
target_val = 2
B[np.isin(B, [a[0] for a in A if a[1] <= target_val])] = 0
>>> B
array([[1, 1, 0, 0, 0],
[1, 0, 0, 2, 0],
[0, 0, 2, 2, 2],
[0, 0, 0, 2, 0],
[0, 0, 0, 0, 0]])
Alternatively, you could also use np.where instead of slicing.
np.where(np.isin(B, [a[0] for a in A if a[1] <= target_val]), 0, B)
In one line: B[np.isin(B, A[A[:, 1] <= 2][:, 0])] = 0
Explanation:
c = A[:, 1] <= 2 # broadcast the original `if i[1]<=2:` check along axis=1
# i.e., mask A according to where the second values of the pairs are <= 2
d = c[:, 0] # index with the mask, and select the old `i[0]` values, here just `6`
e = np.isin(B, d) # mask B according to where the values are in the above
B[e] = 0 # and zero out those positions, i.e. where the old B value is 6
This question already has answers here:
List of lists changes reflected across sublists unexpectedly
(17 answers)
Closed 5 years ago.
I hope to set up a matrix/list through for loop but I observed different results between 1D list and 2D list of the same method. I know this is a stupid way to create a list through for loop but could anyone give me a hint what caused the difference please?
m = [[0] * 4] * 4
print m
m[0] = range(4)
for i in range(4):
m[i][0] = i
print m
And the result is:
[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
[[0, 1, 2, 3], [3, 0, 0, 0], [3, 0, 0, 0], [3, 0, 0, 0]]
I was expecting to be [[0, 1, 2, 3], [1, 0, 0, 0], [2, 0, 0, 0], [3, 0, 0, 0]].
However it worked find in 1D list as below
tmp = [0,0,0,0]
for i in range(4):
tmp[i] = i
print tmp
It printed [0,1,2,3] as I expected.
Change the first line to get:
#m = [[0] * 4] * 4
m = [([0] * 4) for _ in range(4)]
print m
m[0] = range(4)
for i in range(4):
m[i][0] = i
print m
What happened before was that you were creating four copies of the same list. Each copy looks to the same memory spaces. Python does this to conserve memory. So each time you write to one of the lists, you are in fact writing to all of them, and because the last value you write is 3, that's the value you get in all of the lists.
When you are doing m = [[0] * 4] * 4, you are creating a 2D matrix of all zeros. In other words, a list of four [0, 0, 0, 0] lists. Now, with m[0] = range(4), you are setting the first inner list to be [0,1,2,3]. Now when you do,
for i in range(4):
m[i][0] = i
you are expecting for each inner lists first element to be changed. This is one of python's quicks creep in. Object reference. When you initially create the 2D list, you are essentially referencing one list from each of the four indices. Then you replace m[0] to a separate list here: m[0] = range(4). However, all there indices refer to the same list object [0, 0, 0, 0]. And when the loop eventually finishes, value of i is 3 and that is assigned to the first element of the list. And since in index 1, 2 and 3 of m was referencing that list, all three show up as same - [3, 0, 0, 0]. Hope this helps.
Supposed I created the 2D array of 3x4 with zeros initialization.
Then, I want to add 100 into arr[0][1] and I expect that 100 is added Only into arr[0][1] cell. Refer to Case 1 and Case 2:
From Case 1:
what I saw is that 100 is added into every row with column 1 of arr. It is not what i expected.
From Case 2
The result is what i have expected. Can anyone help me explain?
Case 1:
>>> arr = [[0]*4]*3
>>> arr
[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
>>> len(arr)
3
>>> len(arr[0])
4
>>> type(arr)
<class 'list'>
>>> arr[0] [1] = 100
>>> arr
[[0, 100, 0, 0], [0, 100, 0, 0], [0, 100, 0, 0]]
Case 2:
>>> arr = [[0]*4 for _ in range(3)]
>>> arr
[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
>>> type(arr)
<class 'list'>
>>> arr[0] [1] = 100
>>> arr
[[0, 100, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
[0]*4 creates a list of references to the same object 0. Since integers are immutable, that's OK; changing one element replaces it with a new object. However, [[0]*4]*3] creates a list with 3 references to the same, mutable list object that contains 4 references to 0. Changing an element of one list, as you've seen, shows up as a change in all 3 references.
In the list comprehension, a new list [0]*4 is created for each value of _, rather than simply storing a new reference to the same list.
This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Python list problem
I try to initialise a matrix in python.
First I did this:
>>> M=[[0]*4]*4
But here is my probleme, every line is changing when I change the first one:
>>> M
[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
>>> M[1][1]=1
>>> M
[[0, 1, 0, 0], [0, 1, 0, 0], [0, 1, 0, 0], [0, 1, 0, 0]]
So I did it this way:
>>> M= [ [ 0 for i in range(4) ] for j in range(4) ]
And ut works fine:
>>> M
[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
>>> M[1][1]=1
>>> M
[[0, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
My question is:
What these two expressions really mean ? ans why the first one is behaving this way ?
Thanks in advance for you help.
When you multiply those lists, Python is copying them by reference rather than creating entirely new objects.
A simple example might help, showing what happens with copy by reference:
>>> pie = ['apple', 'cherry', 'pecan']
>>> pie_copy = pie
>>> pie_copy[0] = 'banana'
>>> pie
['banana', 'cherry', 'pecan']
>>> pie is pie_copy
True
>>> new_pie = ['banana', 'cherry', 'pecan']
>>> pie is new_pie
False
In the same way that pie_copy and pie point to the same list, when building lists by multiplying, all the copies point to the same list.
In your second snippet using range() and list comprehensions, you aren't taking a single list and copying it several times; each iteration in the comprehension is creating a new list, so you don't suffer from the same copy by reference problem.
Because here M=[[0]*4]*4
You create a links on objects.
It's the similar as
>>> a = [0, 0, 0]
>>> b = [a,a,a]
>>> b
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
>>> a[1] = 1
>>> b
[[0, 1, 0], [0, 1, 0], [0, 1, 0]]
>>>
UPD links I meant references, sorry if little confusing
Say a is some python object. Then [a] * 4 is equivalent to [a, a, a, a]. What this means depends on whether a is mutable or not. Numbers, strings and tuples are not, so if a is one of these kinds of objects (0 in your example), then you get 4 independently changeable copies. Lists, dictionaries and sets are mutable, and in this case you just get 4 references to the same object, in your case the list [0] * 4. Exploiting this knowledge, you'll see that you can do this:
M = [[0] * 4 for i in range(4)]
and get what you want.