Replace some None in list without affecting other None [duplicate] - python

This question already has answers here:
List of lists changes reflected across sublists unexpectedly
(17 answers)
Closed 4 years ago.
I just met something really strange of Python:
>>> out=[[0]*3]*3
>>> out
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
>>> out[0][1]
0
>>> out[0][1]=9
>>> out
[[0, 9, 0], [0, 9, 0], [0, 9, 0]]
well, obviously, what I want is :
[[0, 9, 0], [0, 0, 0], [0, 0, 0]]
isn't strange? I'm not very familiar with Python, but Python always impresses me with its intuitive behavior. But how it comes up with this?
... and how can I get what I need?
thanks!
Watt

A strange behaviour indeed, but that's only because * operator makes shallow copies, in your case - shallow copies of [0, 0, 0] list. You can use the id() function to make sure that these internal lists are actually the same:
out=[[0]*3]*3
id(out[0])
>>> 140503648365240
id(out[1])
>>> 140503648365240
id(out[2])
>>> 140503648365240
Comprehensions can be used to create different lists as follows:
out = [ [0]*3 for _ in range(3) ]

Using * to duplicate elements in lists is a shallow copy operation, so you will end up with multiple references to the same mutable objects if you use this on a list that contains mutable objects.
Instead, use the following to initialize your nested list:
out = [[0]*3 for _ in range(3)]
You can see that with your method, each entry in out is actually a reference to the same list, which is why you see the behavior that you do:
>>> out = [[0]*3]*3
>>> out[0] is out[1] is out[2]
True

Related

Is it possible to add the output of a generator when making a list in Python?

Is it possible to make a generator's output be added into a list creation, without making a nested list in Python?
I have tried the following code, but it only gives me a generator object, and not the items.
x = 5
expected_list = [3, 0, 0, 0, 0, 0, 3]
list = [3, 0 for i in range(x), 3]
print(list)
I get this error whenever trying to run this code:
list = [3, 0 for i in range(x), 3]
^^^^
SyntaxError: did you forget parentheses around the comprehension target?
If I put parentheses around 0 for i in range(x), I get the list
[3, <generator object <genexpr> at 0x000001A065179A10>, 3]
I would like for the generator object to return 0, 0, 0, 0, 0, without creating a list inside of my list.
Unpack it:
[3, *(0 for i in range(x)), 3]

Weird list append bug in Python? [duplicate]

This question already has answers here:
List of lists changes reflected across sublists unexpectedly
(17 answers)
How to deep copy a list?
(10 answers)
How to get all subsets of a set? (powerset)
(32 answers)
Closed 1 year ago.
I'm trying to write a function in python that returns the powerset of a list inputted to it. In lines 5 and 6, I try to append an element to only the second half of the array but it apparently gets appended to all the elements of the array. Why does this happen and how can I fix it?
Code:
def powerset(array):
ans=[[]]
for elem in array:
ans.extend(ans.copy())
for j in range(int(len(ans)/2), len(ans)):
ans[j].append(elem)
return ans
Example input: [0, 1]
Output returned by above function: [[0, 1, 1], [0, 1, 1], [0, 1, 1], [0, 1, 1]]
Expected Output: [[], [0], [1], [0, 1]]
You're making a shallow copy of ans, means the elements inside the list in ans are not copied, they remain the same.
Use copy.deepcopy().
from copy import deepcopy
def powerset(array):
ans=[[]]
for elem in array:
ans.extend(deepcopy(ans))
for j in range(int(len(ans)/2), len(ans)):
ans[j].append(elem)
return ans
print(powerset([0, 1]))
Output:
[[], [0], [1], [0, 1]]

Python list generation pitfall [duplicate]

This question already has answers here:
List of lists changes reflected across sublists unexpectedly
(17 answers)
Closed 1 year ago.
Attempt 1
>>> width = 3
>>> height = 2
>>> zeros = [[0]*width]*height
>>> print(zeros)
[[0, 0, 0], [0, 0, 0]]
>>> zeros[1][2] = "foo"
>>> print(zeros)
[[0, 0, 'foo'], [0, 0, 'foo']]
Expected result was [[0, 0, 0], [0, 0, 'foo']]. It seems that the * operator populated each row with references to the same object, so modifying zeros[1] also modified zeros[0].
Attempt 2
So now we create the initial list object using list comprehension instead:
>>> zeros = [[0 for i in range(width)] for j in range(height)]
>>> print(zeros)
[[0, 0, 0], [0, 0, 0]]
>>> zeros[1][2] = "foo"
>>> print(zeros)
[[0, 0, 0], [0, 0, 'foo']]
Great, this is the expected result.
The question is...
If the * operator populates each row with references to the same object, then why doesn't every element within each row refer to the same object?
That is, with a (partial) understanding of why Attempt 1 didn't produce the expected output, I would expect that if ANY element of zeros is modified, ALL elements would be changed, that is:
>>> zeros = [[0]*width]*height
>>> print(zeros)
[[0, 0, 0], [0, 0, 0]]
>>> zeros[1][2] = "foo"
>>> print(zeros)
is expected to return:
>>> print(zeros)
[['foo', 'foo', 'foo'], ['foo', 'foo', 'foo']]
but instead returns:
>>> print(zeros)
[[0, 0, 'foo'], [0, 0, 'foo']]
So why does the second * operator populate each row with references to the same object, but each of the elements WITHIN any given row are unique objects?
Answer
List of lists changes reflected across sublists unexpectedly
tl;dr:
Integers are immutable, so [0]*3 creates copies the elements of [0], not references. However [0,0,0] is an object, so [0,0,0]*2 creates a new reference to the original object.
The fundamental disconnect here is that you believe:
zeros[1][2] = "foo"
Is *mutating the element in the rows. It isn't. It is mutating the list at zeros[1], which happens to be the same list at zeros[0].
You can't mutate int objects. Although, they are, in fact, the same int objects:
>>> zeros = [[0]*2]*2
>>> zeros
[[0, 0], [0, 0]]
>>> [[id(x) for x in zs] for zs in zeros]
[[4413403792, 4413403792], [4413403792, 4413403792]]
So, using int objects might actually obscure this, since you might have heard that small integers are cached in CPython. They are always the same object. But consider a user-defined class (which is mutable):
>>> from dataclasses import dataclass
>>> #dataclass
... class Foo:
... foo: int
...
>>> fs = [[Foo(0)]*2]*3
>>> fs
[[Foo(foo=0), Foo(foo=0)], [Foo(foo=0), Foo(foo=0)], [Foo(foo=0), Foo(foo=0)]]
>>> fs[0][0].foo = 8
>>> fs
[[Foo(foo=8), Foo(foo=8)], [Foo(foo=8), Foo(foo=8)], [Foo(foo=8), Foo(foo=8)]]
So note, with the zeros example, there is no method we can call:
zeros[0][0].some_method()
Such that the int object at zeros[0][0] would be mutated, as would happen in the equivalent case of fs[0][0].foo = 8, because int objects don't expose mutator methods. That is what immutable means by definition.

Initializing a two-dimensional array in Python? [duplicate]

This question already has answers here:
List of lists changes reflected across sublists unexpectedly
(17 answers)
Closed 6 years ago.
I wonder What is the difference between this two different method of 2D matrix initialization in python.
m =[]
K = []
for x in range(0,W+1):
m.append(0)
for x in range(0,n+1):
K.append(m)
and
l = []
for j in range(0,n+1):
l.append([])
for i in range(0,W+1):
l[j].append(0))
when I tried to print l and K the both gave the same answer but when I tried to implement it in code the output of program changed.
the earlier one(K) gave an incorrect answer but later one(l) when implemented in the program gave a correct answer.
In the first one, you are storing the same reference to list m in k. Change in m will be reflected in all the reference.
Where as in second, you are creating new lists in l. Hence, change in one is independent of another.
Alternatively, simpler way to initialize list is as:
Holding same reference to list (similar to your first approach) is as:
>>> row, columns = 2, 3
>>> my_list = [[0]*columns]*row
>>> my_list
[[0, 0, 0], [0, 0, 0]]
>>> my_list[0][1] = 1 # changing value
>>> my_list
[[0, 1, 0], [0, 1, 0]]
# ^ ^ Reflected in all
Holding different reference to the list (similar to your second approach) is as:
>>> my_list = [[0]*columns for i in range(row)]
>>> my_list
[[0, 0, 0], [0, 0, 0]]
>>> my_list[0][1] = 1 # changing value
>>> my_list
[[0, 1, 0], [0, 0, 0]]
# ^ only one value is changed

difference between list and array(np)? [duplicate]

This question already has answers here:
List of lists changes reflected across sublists unexpectedly
(17 answers)
Closed 8 years ago.
I was trying add ones to some specific position in a 100*100 zero matrix. My code was something this:
adjacency=[[0]*100]*100
while ...
x=...
y=...
adjacency[x][y]=adjacency[x][y]+1
But it adds to other positions as well. In face, adjacency[0]=adjacency[1]=..=adjacency[99], while there's no way that the calculation of x, y in while-loop will produce this kind of result.
So, I changed to array. The only thing I changed was:
adjacency=np.zeros((100,100))
And the result is right this time. Adjacency[i] no longer equals to each other.
Does anyone know why? (Using Python2.7.)
The * operator is not always a good thing for creating lists, specially multi-dimensional lists. This operator just creates copies of the elements in the list to populate the larger list. This works fine for primitive types, but not for other objects.
For example, when you write [0] * 100, it creates a list of 100 elements and assigns 0 to each item. Now you have the reference of a 100-element list. So when you write [[0] * 100] * 100], you get a list of 100 references of the list [0] * 100. So when you change a value, it affects all the 'rows', because all the rows are referencing the same list.
Here's a smaller example:
>>> a = [0] * 3
>>> print a
[0, 0, 0]
>>> b = [a] * 2
>>> print b
[[0, 0, 0], [0, 0, 0]]
>>> a[0] = 1
>>> print a
[1, 0, 0]
>>> print b
[[1, 0, 0], [1, 0, 0]]
>>> b[0][1] = 2
>>> print b
[[1, 2, 0], [1, 2, 0]]
The first row list is copied every time to the 99 other positions. You need to create a new list for each row, something like this:
adjacency=[[0] * 100 for y in range(100)]

Categories

Resources