python np array change element value in for loop - python

I have a question about the element value change in np 2D-array. Here I have a example:
a=np.arange(10).reshape(2,5)
for i in a: # go through the rows of array
i=np.array([0,0,0,0,0])
print a
The return value is
array([[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9]])
which means there is no change to the original array. Does it mean the i in the for loop is a copy of each row of array a? If i is the copy, then it makes sense because the change of a copy will not affect the original value. But I try the following code:
a=np.arange(10).reshape(2,5)
for i in a: # go through the rows of array
i[:]=np.array([0,0,0,0,0])
print a
The return result is
array([[0, 0, 0, 0, 0],
0, 0, 0, 0, 0]])
so I don'y understand why i[:] can work here if the i is the copy. If this question is duplicated, could you please provide the link?
Thanks.

i is not a copy. Keep in mind that for loops make repetitive assignments from the items in the iterable to the target list (on the left side of in). So at each iteration, i holds a reference to each subarray in a.
Here:
for i in a:
i = np.array([0,0,0,0,0])
You assign each subarray in a to the name i and immediately afterwards assign the name to an entirely unrelated object. Try i = 'unrelated' in that loop, and notice that has no effect (I mean on a) other than assign i to a new string object.
The second case assigns the subarrays in a to i in succession (like the first case) but afterwards performs an in place modification via the reference i.
More clearly, the first iteration of the second case is same as:
i = a[0]
i[:] = np.array([0,0,0,0,0])
And the second iteration:
i = a[1]
i[:] = np.array([0,0,0,0,0])
Notice also how in the transition from the first to second iteration i = a[1] does not modify the previous reference a[0] but instead reassigns the name i to a new object a[1].

Related

Using For Loops in Recursive Function to get every possible Combination

I want to create a funciton getCombinations that given a list of positive integers and a maximum amount, appends all the possible combinations of the given integers that sum to the given amount to a list outside of the function.
For example:
combinations=[]
getCombinations([5,2,1], 10)
print(combinations)
should return:
[[5, 5], [5, 2, 2, 1], [5, 2, 1, 1, 1], [5, 1, 1, 1, 1, 1], [2, 2, 2, 2, 2], ... , [1, 1, 1, 1, 1, 1, 1, 1, 1, 1,]
I tried to use a recursive function that loops over the given integers, and appends them to the list current_combinations.
If the sum of that list equals the given amount, it should append the current_combination.
If the sum is smaller it goes one level deeper and appends new numbers from the list.
combinations = []
def getCombinations(num_types, max_amount, current_combi=None):
if current_combi is None:
current_combi = []
for num in num_types:
current_combi.append(num)
if sum(current_combi) == max_amount:
combinations.append(current_combi)
elif sum(current_combi) < max_amount:
getCombinations(num_types, max_amount, current_combi)
current_combi = current_combi[:-1]
getCombinations([5, 2, 1], 10)
print(combinations)
But this only outputs a fraction of the anticipated result:
[[5, 5], [5, 2, 2, 1], [5, 2, 2, 1], [5, 2, 1, 2], [5, 2, 1, 1, 1], [5, 2, 2, 1], [5, 2, 2, 1], [5, 2, 1, 2], [5, 2, 1, 1, 1]]
Help would be much appreciated, thank you.
There are two main problems here.
First, there is the algorithmic problem, which is that in every recursive call, you start at the beginning of the list of possible values. That will inevitably lead to duplicated lists. You want the produced lists to be sorted in descending order (or, at least, in the same order as the original list), and you need to maintain that order by not recursing over values you have already finished with.
The second problem is subtler; it has to do with the way you handle the current_combi arrays, with emphasis on the plural. You shouldn't use multiple arrays; when you do, it's very easy to get confused. What you should do is use exactly one array, and only make a copy when you need to add it to the result.
You might need to pull out a pad of paper and a pencil and play computer to see what's going on, but I'll try to describe it. The key is that:
current_combi.append(num) modifies the contents of current_combi;
passing current_combi as a function argument does not create a new list;
current_combi = current_combi[:-1] does create a new list, and does not modify the old one.
So, you enter getCombinations and create a current_combi list. Then you push the first value onto the new list (so it's now [5] and recursively call getCombinations, passing the same list. Inside the recursive call, the first value is appended again onto the list (which is still the same list); that list is now [5, 5]. That's a valid result, so you add it to the accumulated results, and then you create a new current_combi. At this point, the original current_combi is still [5, 5] and the new one is [5]. Then the for loop continues (in the recursive call), but the rest of that loop no longer has access to the original current_combi. So we can fast forward to the end of the for loop, ignoring the recursive subcalls, and return to the top level.
When we return to the top level, current_combi is the list which was originally created, and that list had 5 appended to it twice, once in the top-level for loop and again when the first recursive call started. So it's still [5. 5], which is unexpected. A fundamental property of recursive backtracking is that the problem state variable be the same before and after each recursive call. But that property has been violated. So now at the end of the top-level for loop, an attempt is made to remove the 5 added at the beginning. But since that list is now [5, 5], removing the last element produces [5] instead of []. As a result, lists starting with 2 are never produced, and lists starting 5, 2 are produced twice.
OK, let's fix that. Instead of making copies of the list at uncontrolled points in the execution, we'll just use one list consistently, and make a copy when we add it to the accumulated results:
# This one still doesn't work. See below.
def getCombinations(num_types, max_amount, current_combi=None):
if current_combi is None:
current_combi = []
for num in num_types:
current_combi.append(num)
if sum(current_combi) == max_amount:
combinations.append(current_combi[:]) # Add a copy to results
elif sum(current_combi) < max_amount:
getCombinations(num_types, max_amount, current_combi)
current_combi.pop() # Restore current_combi
But that doesn't actually fix the first problem noted above: that the recursion should not reuse values which have already been used. Instead of looping over the values in num_types, we need to loop over its suffixes:
def getCombinations(num_types, max_amount, current_combi=None):
if current_combi is None:
current_combi = []
values = num_types[:] # Avoid modifying the caller's list
while values:
# values.pop(0) removes the first element in values and returns it
current_combi.append(values.pop(0))
if sum(current_combi) == max_amount:
combinations.append(current_combi[:]) # Add a copy to results
elif sum(current_combi) < max_amount:
getCombinations(values, max_amount, current_combi)
# Restore current_combi
current_combi.pop()
In the above, I was trying to roughly follow the logic of your original. However, the handling of values could be made more efficient by passing the starting index in the list instead of modifying the list. Also, there is no need to rescan the candidate combination in order to add up its value, since we can just add the value we just added (or, as in the following code, subtract it from the target). Finally, since a lot of the arguments to the recursive call are always the same, they can be replaced by a closure:
def getCombinations(num_types, max_amount):
results = []
candidate = []
def helper(first_index, amount_left):
for index in range(first_index, len(num_types)):
value = num_types[index]
if amount_left == value:
results.append(candidate + [value])
elif amount_left > value:
candidate.append(value)
helper(index, amount_left - value)
candidate.pop()
helper(0, max_amount)
return results
That's still not the optimal implementation, but I hope it shows how to evolve this implementation.

Does Python automatically update variables whose value is another object? [duplicate]

This question already has answers here:
How do I clone a list so that it doesn't change unexpectedly after assignment?
(24 answers)
Closed 3 years ago.
Before asking, I read the accepted answer to the question "How do I pass a variable by reference?" and documentation linked in that same answer: "How do I write a function with output parameters (call by reference)?"
I have a related question: Does Python automatically synchronize a variable whose value is a reference to another object? In other words, if I assign an object as the value of a variable, is the variable updated whenever the object is modified?
I have a specific problem where it appears that Python updates the value of a variable with an object as its value without any code explicitly triggering an update. I created a function that was supposed to be part of a solution to the ROT13 (rotate right 13 times) problem: shift an array to the right 13 times. Here is the function's code:
array = [0, 1, 2, 3, 4, 5]
print(array)
backup = array
#backup = [n for n in array]
for i in range( 1, (len(backup)) ):
array[i] = backup[i - 1]
array[0] = backup[-1]
backup = array
print(array)
The output of that code is wrong: [0, 0, 0, 0, 0, 0] .
However, when I replace line 3 (backup = array) with backup = [n for n in array], the answer is correct: [5, 0, 1, 2, 3, 4]
I inferred that whenever the for-loop executed, the value of backup was updated because its value is inherently a reference to the object array. It appears to me that when array[1] was assigned the value zero, backup[1] was also assigned zero instead of holding the value 1. Because of that, the for-loop simply assigned the value zero to every other variable in backup thru array.
If I instead assigned backup to a list object distinct from array using backup = [n for n in array], modifying array would not modify backup.
What is the actual cause of this behavior?
In your example backup and array are both references to the same object. That is clear with this code example:
>>> array=[1,2,3,4]
>>> backup=array
>>> id(array)
4492535840
>>> id(backup)
4492535840
So your code is equivalent to this:
array = [0, 1, 2, 3, 4, 5]
print(array)
for i in range( 1, (len(array)) ):
array[i] = array[i - 1]
array[0] = array[-1]
print(array)
Does that help?
There’s no synchronization going on. Instead, there’s only one list. Both variables reference that same list – you can think of the variables as pointing to it or as tagging it, if that helps. You perform operations on values and not on variables, so given that there’s only one list, all of the operations change it and read changes back from the same one.

Rearrange list in-place by modifying the original list, put even-index values at front

I am relatively new to python and I am still trying to learn the basics of the language. I stumbled upon a question which asks you to rearrange the list by modifying the original. What you are supposed to do is move all the even index values to the front (in reverse order) followed by the odd index values.
Example:
l = [0, 1, 2, 3, 4, 5, 6]
l = [6, 4, 2, 0, 1, 3, 5]
My initial approach was to just use the following:
l = l[::-2] + l[1::2]
However, apparently this is considered 'creating a new list' rather than looping through the original list to modify it.
As such, I was hoping to get some ideas or hints as to how I should approach this particular question. I know that I can use a for loop or a while loop to cycle through the elements / index, but I don't know how to do a swap or anything else for that matter.
You can do it by assigning to a list slice instead of a variable:
l[:] = l[::2][::-1] + l[1::2]
Your expression for the reversed even elements was also wrong. Use l[::2] to get all the even numbers, then reverse that with [::-1].
This is effectively equivalent to:
templ = l[::2][::-1] + l[1::2]
for i in range(len(l)):
l[i] = templ[i]
The for loop modifies the original list in place.

What is the meaning of arr[:] in assignment in numpy?

I occasionally use numpy, and I'm trying to become smarter about how I vectorize operations. I'm reading some code and trying to understand the semantics of the following:
arr_1[:] = arr_2
In this case,
I understand that in arr[:, 0], we're selecting the first column of the array, but I'm confused about what the difference is between arr_1[:] = arr_2 and arr_1 = arr_2
Your question involves a mix of basic Python syntax, and numpy specific details. In many ways it is the same for lists, but not exactly.
arr[:, 0] returns the 1st column of arr (a view), arr[:,0]=10 sets the values of that column to 10.
arr[:] returns arr (alist[:] returns a copy of a list). arr[:]=arr2 performs an inplace replacement; changing the values of arr to the values of arr2. The values of arr2 will be broadcasted and copied as needed.
arr=arr2 sets the object that the arr variable is pointing to. Now arr and arr2 point to the same thing (whether array, list or anything else).
arr[...]=arr2 also works when copying all the data
Play about with these actions in an interactive session. Try variations in the shape of arr2 to see how values get broadcasted. Also check id(arr) to see the object that the variable points to. And arr.__array_interface__ to see the data buffer of the array. That helps you distinguish views from copies.
arr_1[:] = ... changes the elements of the existing list object that arr_1 refers to.
arr_1 = ... makes the name arr_1 refer to a different list object.
The main difference is what happens if some other name also referred to the original list object. If that's the case, then the former updates the thing that both names refer to; while the latter changes what one name refers to while leaving the other referring to the original thing.
>>> a = [0]
>>> b = a
>>> a[:] = [1]
>>> print(b)
[1] <--- note, change reflected by a and b
>>> a = [2]
>>> print(b)
[1] <--- but now a points at something else, so no change to b
Perhaps it is best to understand by using id to examine the memory location of each variable.
import numpy as np
arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])
>>> id(arr1)
4595568512
>>> id(arr2)
4595566192
# Slice assignment
arr1[:] = arr2
>>> arr1
array([4, 5, 6])
>>> id(arr1) # The object still points to the same memory location of `arr1`.
4595568512
# Reassignment.
arr1 = arr2
>>> id(arr1) # The object is now pointing to the object located to where `arr2` points.
4595566192
Using arr_1[:] = arr_2 is a shortcut for arr_1.__setitem__(slice(None, None), arr_2). The reason that is used instead of arr_1 = arr_2 is when you use __setitem__, you are modifying arr_1, whereas when you say arr_1 = arr_2, you are redefining arr_1. Using __setitem__, therefore, will modify other references to the arr_1 object rather than just redefining arr_1.

Sorting Function. Explanantion

def my_sort(array):
length_of_array = range(1, len(array))
for i in length_of_array:
value = array[i]
last_value = array[i-1]
if value<last_value:
array[i]=last_value
array[i-1]=value
my_sort(array)
return array
I know what the function does in general. Its a sorting alogarithm.... But i dont know how what each individual part/section does.
Well, I have to say that the best way to understand this is to experiment with it, learn what it is using, and, basically, learn Python. :)
However, I'll go through the lines one-by-one to help:
Define a function named my_sort that accepts one argument named array. The rest of the lines are contained in this function.
Create a range of numbers using range that spans from 1 inclusive to the length of array non-inclusive. Then, assign this range to the variable length_of_array.
Start a for-loop that iterates through the range defined in the preceding line. Furthermore, assign each number returned to the variable i. This for-loop encloses lines 4 through 9.
Create a variable value that is equal to the item returned by indexing array at position i.
Create a variable last_value that is equal to the item returned by indexing array at position i-1.
Test if value is less than last_value. If so, run lines 7 through 9.
Make the i index of array equal last_value.
Make the i-1 index of array equal value.
Rerun my_sort recursively, passing in the argument array.
Return array for this iteration of the recursive function.
When array is finally sorted, the recursion will end and you will be left with array all nice and sorted.
I hope this shed some light on the subject!
I'll see what I can do for you. The code, for reference:
def my_sort(array):
length_of_array = range(1, len(array))
for i in length_of_array:
value = array[i]
last_value = array[i-1]
if value<last_value:
array[i]=last_value
array[i-1]=value
my_sort(array)
return array
def my_sort(array):
A function that takes an array as an argument.
length_of_array = range(1, len(array))
We set the variable length_of_array to a range of numbers that we can iterate over, based on the number of items in array. I assume you know what range does, but if you don't, in short you can iterate over it in the same way you'd iterate over a list. (You could also use xrange() here.)
for i in length_of_array:
value = array[i]
last_value = array[-1]
What we're doing is using the range to indirectly traverse the array because there's the same total of items in each. If we look closely, though, value uses the i as its index, which starts off at 1, so value is actually array[1], and last_value is array[1-1] or array[0].
if value<last_value:
array[i]=last_value
array[i-1]=value
So now we're comparing the values. Let's say we passed in [3, 1, 3, 2, 6, 4]. We're at the first iteration of the loop, so we're essentially saying, if array[1], which is 1, is less than array[0], which is 3, swap them. Of course 1 is less than 3, so swap them we do. But since the code can only compare each item to the previous item, there's no guarantee that array will be properly sorted from lowest to highest. Each iteration could unswap a properly swapped item if the item following it is larger (e.g. [2,5,6,4] will remain the same on the first two iterations -- they will be skipped over by the if test -- but when it hits the third, 6 will swap with 4, which is still wrong). In fact, if we were to finish this out without the call to my_sort(array) directly below it, our original array would evaluate to [1, 3, 2, 3, 4, 6]. Not quite right.
my_sort(array)
So we call my_sort() recursively. What we're basically saying is, if on the first iteration something is wrong, correct it, then pass the new array back to my_sort(). This sounds weird at first, but it works. If the if test was never satisfied at all, that would mean each item in our original list was smaller than the next, which is another way (the computer's way, really) of saying it was sorted in ascending order to begin with. That's the key. So if any list item is smaller than the preceding item, we jerk it one index left. But we don't really know if that's correct -- maybe it needs to go further still. So we have to go back to the beginning and (i.e., call my_sort() again on our newly-minted list), and recheck to see if we should pull it left again. If we can't, the if test fails (each item is smaller than the next) until it hits the next error. On each iteration, this teases the same smaller number leftward by one index until it's in its correct position. This sounds more confusing than it is, so let's just look at the output for each iteration:
[3, 1, 3, 2, 6, 4]
[1, 3, 3, 2, 6, 4]
[1, 3, 2, 3, 6, 4]
[1, 2, 3, 3, 6, 4]
[1, 2, 3, 3, 4, 6]
Are you seeing what's going on? How about if we only look at what's changing on each iteration:
[3, 1, ... # Wrong; swap. Further work ceases; recur (return to beginning with a fresh call to my_sort()).
[1, 3, 3, 2, ... # Wrong; swap. Further work ceases; recur
[1, 3, 2, ... # Wrong; swap. Further work ceases; recur
[1, 2, 3, 3, 6, 4 # Wrong; swap. Further work ceases; recur
[1, 2, 3, 3, 4, 6] # All numbers all smaller than following number; correct.
This allows the function to call itself as many times as it needs to pull a number from the back to the front. Again, each time it's called, it focuses on the first wrong instance, pulling it one left until it puts it in its proper position. Hope that helps! Let me know if you're still having trouble.

Categories

Resources