Issue with appending to an array - python-3.x

I cannot understand why the piece of code described below does what it does
import numpy as np
N = 2
A=[];
B=[];
for i in range(N):
B.append(i)
A.append(B)
The first time the for loop runs (for i =0), A = [[0]]. The second time the loop runs (for i =1), B = [0,1] and so I expect A = [[0],[0,1]], since we are appending B to A. However, when I print A, I get A = [[0,1],[0,1]]. Why do I not get the form I expect?

A and B here are pointing to objects in memory. So, if the object itself is modified in memory, all variables pointing to that object will show the updated value.
Now let's look at the code:
B.append(i) -> list B is appended with value of i.
A.append(B) -> list A is appended with reference of B. But the object itself referenced by B is getting modified in each iteration, and hence the most updated value of B is shown as each element of A at each level of iteration. If you run the loop for more iterations and print A, you'll notice this behavior clearly.
One way of overcoming this issue is appending A with a copy of B.
N = 4
A=[]
B=[]
for i in range(N):
B.append(i)
A.append(B.copy())
print(A)
# Output:
# [[0]]
# [[0], [0, 1]]
# [[0], [0, 1], [0, 1, 2]]
# [[0], [0, 1], [0, 1, 2], [0, 1, 2, 3]]

Related

Difference between including or not including [:]

Both declarations produce the same results but I want to know what the difference is behind the scenes.
a = [1,2,3,4,5]
a[:] = a[0:3]
print(a)
a = [1,2,3,4,5]
a = a[0:3]
print(a)
This can be better explained with another variable.
Let's say that a and b are the same. When we use slice assignment, both a and b get changed because they are the same list. Without assigning it to the slice of a means making a new list that is called a that has no relation to b anymore.
a = b = [0, 1, 2]
a[:] = a[0:2]
# a and b point to the same object and that got changed
print(a) # gives [0, 1]
print(b) # gives [0, 1]
a = b = [0, 1, 2]
a = a[0:2]
# a and b point to two different objects sharing no memory together
print(a) # gives [0, 1]
print(b) # gives [0, 1, 2]

How to get the index of an numpy.ndarray

I would to print the images on index i but I get an error of only integer scalar arrays can be converted to a scalar index. How can I convert I to int for each iteration
I have tried replacing images[i] with images[n] it worked but not the result I want.
c = [1 1 1 0 1 2 2 3 4 1 3]
#c here is list of cluster labels obtained after Affinity propagation
num_clusters = len(set(c))
images = os.listdir(DIR_NAME)
for n in range(num_clusters):
print("\n --- Images from cluster #%d ---" % n)
for i in np.argwhere(c == n):
if i != -1:
print("Image %s" % images[i])
I expect the output to be the name of the image, but I instead get TypeError: only integer scalar arrays can be converted to a scalar index that is because i is of type numpy.ndarray
Look at the doc of np.argwhere, it does not return a list of integers, but a list of lists
x = array([[0, 1, 2], [3, 4, 5]])
np.argwhere(x>1)
>>> array([[0, 2], [1, 0], [1, 1], [1, 2]])
y = np.array([0, 1, 2, 3, 4, 6, 7])
np.argwhere(y>3)
>>> array([[4], [5], [6]])
so without knowing what your c looks like, I assume your i will be of the form np.array([[3]]) instead of an integer, and therefore your code fails. Print i for testing and extract (e.g. i[0][0] and a test that it is non-empty) the desired index first before you do i != -1.
Meta
It is best practise to post a minimal reconstructable example, i.e. other people should be able to copy-paste the code and run it. Moreover, if you post at least a couple lines of the traceback (instead of just the error), we would be able to tell where exactly the error is happening.

Default function values and variables in memory

I came across the following code:
def f(x,l=[]):
for i in range(x):
l.append(i*i)
print(l)
f(2)
f(3,[3,2,1])
f(3)
which returns:
[0, 1]
[3, 2, 1, 0, 1, 4]
[0, 1, 0, 1, 4]
my question is this:
Why, in the third function call f(3), is l not overwritten with l=[] in def (f(x,l=[]) and then return [0,1,4]? What is going on here??
When I do this instead, things work as expected..:
l=[]
[l.append(i*i) for i in range(2)]
print(l)
l=[1, 2, 3]
[l.append(i*i) for i in range(3)]
print(l)
l=[]
[l.append(i*i) for i in range(3)]
print(l)
which prints:
[0, 1]
[1, 2, 3, 0, 1, 4]
[0, 1, 4]
I know the answer has to do with the way python stores data in memory, but I can't wrap my head around this one, even after exploring with id(l) (which prints out the same ID of l for both f(2) and f(3) but a different ID for f(3, [3,2,1]).
Thank you for any insight!
The correct way of doing this is to have a local variable lst inside the function which takes care of the operation you are doing inside the function, rather than operating on a variable you are passing like so.
def f(x,lst=None):
lst = lst or []
for i in range(x):
lst.append(i*i)
print(lst)
f(2)
f(3,[3,2,1])
f(3)
Which gives you output as
[0, 1]
[3, 2, 1, 0, 1, 4]
[0, 1, 4]
The reason the previous approach doesn't work is because when a mutable value as list or dictionary is detected in a default value for an argument, they are evaluated only once at function definition time, which means that modifying the default value of the argument will affect all subsequent calls of the function.
But in my changed function, I am making a new local variable to operate on the list, which is a copy of the argument passed, so we don't get this issue here.

What is the difference between two types of list additions in python?

I'm adding the list with itself in 2 ways. In output the memory location of updated list sometimes matches with parent list and sometimes not.
May I know the explanation of this?
In 1st case I checked with + operator and assigned result to list reference.
But in second case I used += operator.
1st case:
x=[1,2,3]
print(x, id(x))
x+=x
print(x, id(x))
output:
[1, 2, 3] 88777032
[1, 2, 3, 1, 2, 3] 88777032
2nd case:
y=[1,2,3]
print(y, id(y))
y=y+y
print(y, id(y))
output:
[1, 2, 3] 88297352
[1, 2, 3, 1, 2, 3] 88776904
1st case:
x += x just extends existing x by adding x
2nd case:
y = y+y creates a new list by concatenating y two times (y and y) and then assigns the result to newly created object y

Unusual function behavior - returns different results for same variable?

I have a list as part of a game I'm working on. Let's say:
my_list = [1, 2, 3, 4, 5]
I also have a function:
def my_function(my_list):
a = [x for x in my_list if x < 4]
b = [x for x in my_list if x < 4] ## exactly the same as for `a`
print(a)
print(b)
The result of calling my_function(my_list) is:
a prints as it should:
[1, 2, 3]
But b prints an empty list:
[]
Obviously the code above is just an example (it would be highly impractical for me to copy my whole code), but it's pretty much a copy of the issue I'm facing in my code. Does anyone know what could be causing such behavior?

Resources