Python in place object update in a for loop - python

In Python >= 3.5:
x = np.zeros((2,3))
for x_e in x:
x_e += 123
This operation returns a 2x3 matrix of all 123's. Whereas the following returns all zeros:
x = np.zeros((2,3))
for x_e in x:
x_e = 123
This is a little off putting to me since x_e is an element from x and it doesn't totally feel like x is being updated.
Okay, I think this is a thing, and it is called 'in place' update? (similar to an in place algorithm?)
But, the jarring thing is this does not work with lists:
x = [0,0,0]
for x_e in x:
x_e += 123
This returns the list
[0, 0, 0]
I would appreciate if someone can enlighten me on what precisely is happening here.

In the first snippet you perform an in-place addition on a ndarray object. Since each x_e is an ndarray the inplace operation succeeds and alters the elements.
In the second snippet you're just reassinging to the loop-variable. Nothing is performed on the elements of x.
In the third snippet you don't have a multidimentional list so each x_e is actually an int. Using += on an int doesn't change in-place, it returns a new integer object (which you don't store).
The following might be more related to the first:
x = [[0, 0, 0] for _ in range(3)]
for x_e in x:
x_e += [123]
here each element of x is a list object [0, 0, 0] on which you add, in-place, the element 123. After executing, x will be:
[[0, 0, 0, 123], [0, 0, 0, 123], [0, 0, 0, 123]]

Imagine you have that:
>>> x = np.array(range(5))
>>> x
array([0, 1, 2, 3, 4])
So now:
>>> x+123
array([123, 124, 125, 126, 127])
As you can see the '+' action is mapped to the array. So when you do
create an array full of 0's and add 123 to the sub lists the array is consisted of is logical to have the above result.

Related

How to add a 1 into a list based on the values of another list, which give the position of where 1 should go

I have two lists:
a = [1,4,5]
x = [0,0,0,0,0,0,0,0,0,0]
I am stuck on making it so that it could look like this:
x= [0,1,0,0,1,1,0,0,0,0,0]
So that the number 1 is added to list x based on the value of list a, where the list values in a determine the position of where 1 should go. I am not sure if that makes much sense but it would help a lot.
You can use a for loop to iterate through list a and set the indexes of list x of its values to 1. So for example:
a = [1, 4, 5]
x = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
for index in a: # Iterating through the list a
x[index] = 1 # Setting the values to 1
print(x)
Alternative way,
using numpy fast and efficient
np_x = np.array(x)
np_x[a]=1

how do I change assign values to array I pass to a function?

I have a 5x2 dimensional array which I want to assign specific values to, but I'm having trouble doing so even looking at multiple explanations of Python passing by value rather than reference. The relevant code is as follows:
def do_something(x, y):
x = 0
y = 0
return_value = -1 #error may occur in this code
#some complex operation is performed that manipulates x and y
return_value = 0
return return_value
def main():
result = [[0]*5]*2
for i in range(5):
do_something(result[i][0], result[i][1])
I clearly can't say result = do_something(...) because I still want to know what do_something returned to see if the complex operation is done right. Append doesn't exactly solve my problem, because x and y are manipulated inside the code. Result[i].append(...) would work if I knew what I wanted to append. But even when the complex operation goes right, the array doesn't change.
(I have tried passing result, then r.append(...) but that doesn't work either) Any help?
There is no problem in do_something function.
At main function,
result = [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]
So, please change as below code.
do_something(result[0][i], result[0][i])
Python does pass parameters by reference. However, there are two points to consider:
First, when passing immutable types like integers, floats, strings etc. they will not be effected by the code running in the function.
x = 1.0
def int_do_somthing(x):
x += 1
int_do_somthing(x)
print(x) # 1.0
Second, when you reassign the value of a variable to a new variable within the function scope, the original one is not effected.
l = [1,1,1]
def do_nothing(l):
l = l + [2] # defines a new l that only exist in the function scope
do_nothing(l)
print(l) # [1,1,1]
def do_somthing(l):
l += [2] # modify existing l
do_somthing(l)
print(l) # [1,1,1,2]
So in your case, pass array slices, not individual values to the function, and dont reassign the entire slice but rather edit the values they contain (access with [] operator).
result[i][0] and result[i][1] are integer type values. Since integer is primitive type, changing their values inside the function won't affect the actual values outside the list. You should pass in the list instead since it is not a primitive type and thus any changes you made inside the function will also affect the list outside the function.
Also, you probably don't want to use result = [[0]*5]*2 to initialize your array. One reason is that it will initialize a 2x5 array instead of 5x2. The other more important reason is that it will only duplicate the reference to the list [0, 0, 0, 0, 0] twice. Therefore, if you change only 1 value inside a row, the other row will also have that value. For example:
result = [[0] * 5] * 2
print(result) # This will print out [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]
result[0][1] = 1
print(result) # This will print out [[0, 1, 0, 0, 0], [0, 1, 0, 0, 0]]
The alternative way you can do is to use result = [[0 for x in range(5)] for y in range(2)] which will give array with desired values but they are not duplicated reference like the above.
result = [[0 for x in range(5)] for y in range(2)]
print(result) # This will print out [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]
result[0][1] = 1
print(result) # This will print out [[0, 1, 0, 0, 0], [0, 0, 0, 0, 0]]
I've modified your code according to my suggestion and it seems to work well:
def do_something(arr):
# Assign value below
arr[0] = 1
arr[1] = 2
def main():
result = [[0 for x in range(2)] for y in range(5)]
for i in range(5):
do_something(result[i])
print result
main()

What range function does to a Python list?

I am not able to figure out what is happening here. Appending reference to range function is kind of creating a recursive list at index 3.
>>> x = range(3)
[0, 1, 2]
>>> x.append(x)
[0, 1, 2, [...]]
>>> x[3][3][3][3][0] = 5
[5, 1, 2, [...]]
Whereas, when I try this:
>>> x = range(3)
[0, 1, 2]
>>> x.append(range(3))
[0, 1, 2, [0, 1, 2]]
I can easily deduce the reason for the second case but not able to understand what appending reference to range function is doing to the list appended.
In python2, ranges are lists.
lists, and most of the things in python are objects with identities.
li = [0,1]
li[1] = li # [0, [...]]
# ^----v
id(li) # 2146307756
id(li[1]) # 2146307756
Since you're putting the list inside itself, you're creating a recursive data structure.
First this is strange, and you probably should not use this in practice. The issue is not specific to range function and has to do with references. When you call x.append(x), you essentially say that x[-1] is x. So when you modify x[0], you also modify x[-1][0], x[-1][-1][0] etc.
To see that this is not range specific, you could use copy.copy:
from copy import copy
x = range(1)
x.append(x) # here x[1] is reference to x itself (same object)
print(x[0], x[1][0], x[1][1][0])
x[0] = 1
print(x[0], x[1][0], x[1][1][0]) # all values change
#
x = range(1)
x.append(copy(x)) # x[1] is a copy of x at previous state (new object)
print(x[0], x[1][0]) # cannot call x[1][1][0] -> x[1][1] is an int
x[0] = 1
print(x[0], x[1][0]) # only the first value changes
Output:
(0, 0, 0)
(1, 1, 1)
(0, 0)
(1, 0)

'int' object does not support item assignment

This is my code,and it appears a problem like the topic. i am a primary learner and i don't know where is the problem. could u help me fix the code and tell me which part of knowledge i need to review. thanks in advance.
x = int(raw_input("enter the value of x:"))
y = int(raw_input("enter the value of y:"))
i = 0
j = 0
array=[x*y]
for i in range(x):
array.append([0 for j in range(y)])
for i in range(x-1):
for j in range(y-1):
array[i][j]=i * j
print array
The problem is that you are creating a list of mixed types, integers and lists, and then trying to access the integer value as if it was a list.
Let's use a simple example:
x = 2
y = 3
i = 0
j = 0
array = [x*y]
Now, let's look at what array currently contains:
array
>> 6
Now we run your first for loop:
for i in range(x):
array.append([0 for j in range(y)])
And let's check the new value of array:
array
>> [6, [0, 0, 0], [0, 0, 0]]
So now we see that the first element of array is an integer. The remaining elements are all lists with three elements.
The error occurs in the first pass through your nested for loops. In the first pass, i and j are both zero.
array[0][0] = 0*0
>> TypeError: 'int' object does not support item assignment
Since array[0] is an integer, you can't use the second [0]. There is nothing there to get. So, like Ashalynd said, the array = x*y seems to be the problem.
Depending on what you really want to do, there could be many solutions. Let's assume you want the first element of your list to be a list of length y, with each value equal to x*y. Then replace array = [x*y] with:
array = [x*y for i in range(y)]
Try inspect your array.
array[2*3] will result in [6].
array.append([0 for j in range(3)]) will then result in [6,[0,0,0]], your first element is not an array.
There is in indent error with your for loops.
I think what you are trying to do is this:
array2=[]
for i in range(2):
array2.append([0 for j in range(3)])
Your syntax is so wrong. First of all if you store variables i=0, j=0 so why you use them in for loop? It doesn't make any sense.
Second, just debug it, put print (array) after the list.
if x=5 and y=5 ;
[25, [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0,
0], [0, 0, 0, 0, 0]]
This is your array, and this line;
array[i][j]=i * j
Throwing error? Why? Because;
j=0
i=0
array[i]=25
array[i][j] = 2
So that's why you got error.Integers, which is 2 here, doesn't support item assignment. Try to fix your syntax.

Replacing selected elements in a list in Python

I have a list: mylist = [0, 0, 0, 0, 0]
I only want to replace selected elements, say the first, second, and fourth by a common number, A = 100.
One way to do this:
mylist[:2] = [A]*2
mylist[3] = A
mylist
[100, 100, 0, 100, 0]
I am looking for a one-liner, or an easier method to do this. A more general and flexible answer is preferable.
Especially since you're replacing a sizable chunk of the list, I'd do this immutably:
mylist = [100 if i in (0, 1, 3) else e for i, e in enumerate(mylist)]
It's intentional in Python that making a new list is a one-liner, while mutating a list requires an explicit loop. Usually, if you don't know which one you want, you want the new list. (In some cases it's slower or more complicated, or you've got some other code that has a reference to the same list and needs to see it mutated, or whatever, which is why that's "usually" rather than "always".)
If you want to do this more than once, I'd wrap it up in a function, as Volatility suggests:
def elements_replaced(lst, new_element, indices):
return [new_element if i in indices else e for i, e in enumerate(lst)]
I personally would probably make it a generator so it yields an iteration instead of returning a list, even if I'm never going to need that, just because I'm stupid that way. But if you actually do need it:
myiter = (100 if i in (0, 1, 3) else e for i, e in enumerate(mylist))
Or:
def elements_replaced(lst, new_element, indices):
for i, e in enumerate(lst):
if i in indices:
yield new_element
else:
yield e
def replace_element(lst, new_element, indices):
for i in indices:
lst[i] = new_element
return lst
It's definitely a more general solution, not a one-liner though. For example, in your case, you would call:
mylist = replace_element(mylist, 100, [0, 1, 3])
Numpy supports this if you're not opposed to using an np.ndarray:
>>> a = np.zeros(5)
>>> a[[0,1,3]] = 100
>>> a
array([ 100., 100., 0., 100., 0.])
Is this what you're looking for? Make a list of the indexes you want to change, and then loop through that list to change the values.
els_to_replace = [0, 1, 3]
mylist = [0, 0, 0, 0, 0]
for index in els_to_replace:
mylist[index] = 100
mylist
Out[9]: [100, 100, 0, 100, 0]
I like a list comprehension:
[100 if index in [1, 4] else 0 for index, x in enumerate(mylist) ]
Not a huge fan of this one, but you could try this (although I think all of the above are much more concise and easy to read):
In [22]: from operator import setitem
In [23]: mylist = [0, 0, 0, 0, 0]
In [24]: indeces_to_replace = [0, 1, 3]
In [25]: _ = map(lambda x: setitem(mylist, x, 100), indeces_to_replace)
In [26]: mylist
Out[26]: [100, 100, 0, 100, 0]
Aside from the questionable readability and need for an import, #abarnert pointed out a few additional issues, namely that map still creates an unnecessary list (which is discarded with the _ but created nonetheless) and that it won't work in Python 3 because map returns an iterator in Python 3.x. You can use the six module to simulate the behavior of map in Python 3.x from Python 2.x, and in combination with collections.deque (again as suggested by #abarnert), you can achieve the same output without creating the additional list in memory because a deque that can contain a maximum of 0 items will discard everything it receives from the map iterator (note that with six, map is simulated by using itertools.imap).
Again, there is absolutely no need to ever use this - every solution above/below is better :)
In [1]: from collections import deque
In [2]: from six.moves import map
In [3]: from operator import setitem
In [4]: mylist = [0, 0, 0, 0, 0]
In [5]: indeces_to_replace = [0, 1, 3]
In [6]: deque(map(lambda x: setitem(mylist, x, 100), indeces_to_replace), maxlen=0)
Out[6]: deque([], maxlen=0)
In [7]: mylist
Out[7]: [100, 100, 0, 100, 0]

Categories