".append" and "+" semantic difference inside of functions - python

I'm having some trouble to understand what is the difference between these 3 examples.
#example1
list1 = [1,2,3,4]
list1= list1+[6]
list1.append(1000)
print("Example 1: ", list1)
# example 2
def f(j):
j= j + [6]
j.append(1000)
list2 = [1,2,3,4]
f(list2)
print("Example 2: ", list2)
# example 3
def f(j):
j.append(1000)
j= j +[6]
list3 = [1,2,3,4]
f(list3)
print("Example 2: ", list3)
Output:
The first one I did some simple addition using (+) and (.append), it worked fine.
The second one I created a function. I guess I understood the results. In my opinion, it remained the same because the changes that I've done in the original list were only made locally, so, after the function had finished, the original list have remained the same. Am I right?
The third one I can't understand. Because it is exactly like the second one, I've just changed the order of the elements, however the output is completely different.

Example 2 creates a new list with j = j + [6], and the only reference to the that list is the local variable j, so the change is not visible after f returns.
Example 3 appends the value to the original list referenced by j, which list3 still refers to.

Related

Why does Python list comprehension seem to behave differently than list "multiplication"? [duplicate]

This question already has answers here:
List of lists changes reflected across sublists unexpectedly
(17 answers)
Closed 8 months ago.
Asking out of curiosity. For the sake of making a point I was trying to make a function that returns an "identity matrix" of n dimensions and then printing it in the most concise way.
First I came up with this:
def identity(n):
zeros = [[0 for j in range(n)] for i in range(n)]
for i in range(n):
zeros[i][i] = 1
return zeros
for i in range(5):
print(identity(5)[i])
This works as intended, however then I tried making the syntax shorter by doing this:
def identity(n):
zeros = [[0]*n]*n
for i in range(n):
zeros[i][i] = 1
return zeros
for i in range(5):
print(identity(5)[i])
And this for some reason changes every single element to a one, but I can't seem to figure out why?. This isn't an important question but help is much appreciated!
lists are kept by reference in python.
This means that if you have:
list_a = [1,2,3,4]
list_b = list_a
list_a and list_b are actually pointing to the same object in memory. so if you change an element of list_a:
list_a[2] = 9
then the same element for list_b will change because they are pointing to the same object. i.e. list_a literally equals list_b in every way.
That's what's happening in your code as well.
When you loop through and assign each value then it's as if you were explicitly creating a new list and assigning it to each element of your outter list:
l = []
l.append([1,2,3,4])
l.append([1,2,3,4])
...
but in the second piece of code, it is as if you are repeatedly appending the same value to the list:
l = []
la = [1,2,3,4]
l.append(la)
l.append(la)
It's because list comprehension performs shallow copy on the element.
All elements in zeros are refering to the same list.
Try these and see the results:
n = 4
zeros = [[0]*n]*n
zeros[0] = 1
print(zeros)
n = 4
lst = [0]*n
zeros = [lst]*n
print(zeros)
lst[0] = 1
print(zeros)
You can learn more about difference between shallow and deep copy here.
This is because when you multiply a list with a number, it duplicates it, but it does so by copying the reference of each element.
If you do id(zeros[0][0]) and id(zeros[0][1]) for the second case, you will see that they are the same, i.e. they refer to the same element and modifying one modifies them all.
This is not true for the first case where every 0 is instantiated seperately and has its own id.
Edit:
def zeros(n):
return [[0]*n]*n
x = zeros(5)
for i in range(5):
for j in range(5):
assert id(x[0][0]) == id(x[i][j])

Updating the iterator in for loop - Python

I stumbled upon this conundrum and would like to know how Python is working that causes these different outputs from the 2 sets of code. Here are the 2 for loop codes:
First case
l = [1,2,3]
for i in l:
print(i)
l = 0
Second case
l = [1,2,3]
for i in l:
print(i)
l.append(3)
For the first scenario, the output is 1, 2, 3, even though I was expecting that it should return an error since l would be updated to 0. So I tried to see what the output might be if I were to append an item to l, and it does work as I expected, which is that l does get updated, and prints out 1,2,3,3,3,3,3,3,.... infinitely.
Could someone explain why just reassigning l to something doesn't get reflected in the for loop but appending an item to l does get reflected in the for loop?
Thank you!
This is the difference between re-binding a variable name and mutating the underlying object.
Case 1: You rebind the name l to a new value, but that doesn't change the list object, which is currently iterated through.
Case 2: You actually mutate the same list object.
When you are assigning the value l=0 inside the loop, you are actually creating a new variable l that is local inside the loop, you can verify this by printing the id of the variable inside the loop which represents the memory address for the variable reference:
l = [1, 2, 3]
for i in l:
print(id(l))
print(i)
l = 0
print(id(l))
1927830512328 ->id before assigning l=0
1
140728450314480 ->id after assigning l=0
140728450314480
2
140728450314480
140728450314480
3
140728450314480
It is recommended that you print the address value. You can check the address values from l to id(l).
In the first case, if you run l = 0 in the for statement and output id(l), a value that differs from the initial address value is displayed. This indicates that the existing l and l in the for statement are different variables.
In the second case, the address value does not change. This is because the original address was accessed through a function called append and added a value.

Same print statement provides different output for sorting algorithm (Insertion Sort)

I have coded up the algorithm for insertion sort and have ensured that it works; however, depending on how I print the sorted list, the output is different. For example,
x = some_unsorted_list
x = insertionSort(x)
print(x)
will print the sorted list just fine. On the other hand,
x = some_unsorted_list
insertionSort(x)
print(x)
will print some partially sorted-unsorted list. Here is my code for selection sort:
def insertionSort(L):
endList = len(L) - 1
for i in range(1,endList+1,1):
insertVal = L[i]
j = i - 1
while j >= 0 and L[j] > insertVal:
L[j+1] = L[j]
j -= 1
L[j+1] = insertVal
#print(L)
return L
Note that the print statement commented out always provides the sorted list, which is causing my confusion.
Edit (Important information I left out): Here's how I'm calling the insertion sort. Let v1, v2, and v3 be unsorted lists.
for i in [v1,v2,v3]:
x = list(i)
insertionSort(x)
print(x)
And recall, this is where errors arise. However the following code does not produce errors:
print(insertionSort(v1))
print(insertionSort(v2))
print(insertionSort(v3))
When you pass a variable to a function, a new copy is made. So, when you are passing x to your function, a new variable L will be made which will have the same value as x.
Now, we have 2 different variables named x and L.
In your function, you are making changes to L and outside the function, you are printing x. Therefore, you are getting wrong results. Because L was sorted NOT x.
The difference among codes
In your first code snippet x = insertionSort(x), overrides the value of x with the value returned by function (which will be a sorted list always). So, it prints the right result.
In your second code snippet, you are NOT overriding the value of x. Therefore, you are getting wrong results.

List not returning the proper values as an output

I am trying to write a small function (as an exercise). This function takes a list of values and returns the values that are odd numbers. I have gotten the function to give me the right answer with the print() function, but I am not able to do the same with a return statement.
def odd_nr(list1):
i = 0
for list1[i] in list1:
if list1[i] % 2 != 0:
print(list1[i])
i += 1
return list1
odd_nr([1,2,3,4,5,6])
The output is:
1
3
5
[1, 3, 5, 6, 5, 6]
I am not able to figure out why the return statement gives this output. I tried different indentations, I tried different variants of the return statement, but I just can't seem to get it to work.
What am I doing wrong?
try:
def odd_nr(list1):
results = []
for number in list1:
if number % 2 != 0:
print(number)
results.append(number)
return results
odd_nr([1,2,3,4,5,6])
Further Explanation:
Any function can take something and return something. This something can also be nothing, None.
Your function takes a list, and returns a list, but it is returning the same list that it is taking in.
The print statement is not a return value. Which means a print is not something that the function returns, it is a side-effect, a side door, for us humans to see, mostly.
To return a list of only odd numbers, you need to collect them in another list as you iterate through your original input list.
Then once you are done, return the list that has collected all the odd numbers. Hope this helps.
I also updated your code a bit, for list1[i] in list1, even though it works, it is hard to understand and it does so for the wrong reasons, see below. You can simply do for number in list1 and also not worry about incrementing any counters(i in your case).
Explanation on why for list1[i] in list works:
This is interesting. By choosing list1[i] as the current iteratee, we will be mutating our list while iterating; it is lucky that each iteratee is equal, in value to the list element it is mutating. Here is an example to illustrate what is happening. It is easy to see when we do not update i:
list1= [1,2,3,4]
i=0
for list1[i] in list1:
print(list1[i])
print(list1)
Output:
1
2
3
4
[4, 2, 3, 4]
You just need to return list1[:i] instead of returning the whole list1. Demo with that:
>>> odd_nr([1,2,3,4,5,6])
1
3
5
[1, 3, 5]
With your unusual but correct for list1[i] in list1 and the according update of i, you're moving all the odd numbers to the front of the list and counting them with i. All that's left to do is to only return that front portion.
Alternatively, delete the unwanted back portion with del list1[i:] before you do return list1.

Logical Bug in the Code Related to Slicing in Python

Consider the following piece of code that generates all subsets of size k of an array [1,2,3,...,n]:
def combinations(n, k):
result = []
directed_combinations(n, k, 1, [], result)
return result
def directed_combinations(n, k, offset, partial_combination, result):
if len(partial_combination) == k:
new_partial = [x for x in partial_combination]
result.append(new_partial)
return
num_remaining = k - len(partial_combination)
i = offset
# kind of checks if expected num remaining is no greater than actual num remaining
while i <= n and num_remaining <= n - i + 1:
partial_combination.append(i)
directed_combinations(n, k, i + 1, partial_combination, result)
del partial_combination[-1]
# partial_combination = partial_combination[:-1] <-- same funcationality as line above, but produces weird bug.
i += 1
print(combinations(n=4,k=2))
For example, combinations(n=4,k=2) will generate all subsets of length 2 of [1,2,3,4].
There are two lines in the code that produce a list with the last element removed. I tried accomplishing it with del and creating a brand new list by slicing off the last element (i.e. [-1]). The version with del produces the correct result. But, version with [-1] doesn't. There is no runtime error; just a logical bug (i.e. incorrect result).
I suspect this has something to do with creating a new list when doing slicing vs. keeping the same list with del. I can't seem to understand why this is an issue.
I didn't notice at first that your function is recursive (should've read your tags better).
You're right, functionally the two are almost the same. Here is the exact same thing:
# del partial_combination[-1] # working (mutate)
# partial_combination = partial_combination[:-1] # different (rebind)
partial_combination[:] = partial_combination[:-1] # same (mutate)
The result of each of the above will be that you end up with a list containing the same elements. But while del and partial_combination[:] mutate your original list, the middle one rebinds the name to a new list with the same elements. When you pass on this new list to the next recursive step, it will operate on its own copy rather than on the single list the previous recursive levels are working on.
To prove this, you can call print(id(partial_combination)) after each of the above options, and see that the id changes in the rebinding case, while it stays the same throughout the mutating ones.

Categories

Resources