Grasping the basics of in-place operations - python

So...
a = [2,3,4,5]
for x in a:
x += 1
a = [2,3,4,5]
Nada.
but if I ...
a[2] += 1
a = [2,3,5,5]
Clearly my mind fails to comprehend the basics. print(x) returns only the integer within the cell so it should simply add the one automatically for each list cell. What's the solution and what am I not grasping?

In this case you are defining a new variable x, that references each element of a in turn. You cannot modify the int that x refers to, because ints are immutable in Python. When you use the += operator, a new int is created and x refers to this new int, rather than the one in a. If you created a class that wrapped up an int, then you could use your loop as-is because instances of this class would be mutable. (This isn't necessary as Python provides better ways of doing what you want to do)
for x in a:
x += 1
What you want to do is generate a new list based on a, and possibly store it back to a.
a = [x + 1 for x in a]

To understand what's happening here, consider these two pieces of code. First:
for i in range(len(a)):
x = a[i]
x += 1
Second:
for x in a:
x += 1
These two for loops do exactly the same thing to x. You can see from the first that changing the value of x doesn't change a at all; the same holds in the second.
As others have noted, a list comprehension is a good way to create a new list with new values:
new_a = [x + 1 for x in a]
If you don't want to create a new list, you can use the following patterns to alter the original list:
for i in range(len(a)): # this gets the basic idea across
a[i] += 1
for i, _ in enumerate(a): # this one uses enumerate() instead of range()
a[i] += 1
for i, x in enumerate(a): # this one is nice for more complex operations
a[i] = x + 1

If you want to +1 on elements of a list of ints:
In [775]: a = [2,3,4,5]
In [776]: b=[i+1 for i in a]
...: print b
[3, 4, 5, 6]
Why for x in a: x += 1 fails ?
Because x is an immutable object that couldn't be modified in-place. If x is a mutable object, += might work:
In [785]: for x in a:
...: x+=[1,2,3] #here x==[] and "+=" does the same thing as list.extend
In [786]: a
Out[786]: [[1, 2, 3], [1, 2, 3]]

When you say
for x in a:
x += 1
Python simply binds the name x with the items from a on each iteration. So, in the first iteration x will be referring to the item which is in the 0th index of a. But when you say
x += 1
it is equivalent to
x = x + 1
So, you are adding 1 to the value of x and making x refer to the newly created number (result of x + 1). That is why the change is not visible in the actual list.
To fix this, you can add 1 to each and every element like this
for idx in range(len(a)):
a[idx] += 1
Now the same thing happens but we are replacing the old element at index i with the new element.
Output
[3, 4, 5, 6]
Note: But we have to prefer the list comprehension way whenever possible, since it leave the original list altered but constructs a new list based on the old list. So, the same thing can be done like this
a = [item + 1 for item in a]
# [3, 4, 5, 6]
The major difference is that, earlier we were making changes to the same list now we have created a new list and make a refer to the newly created list.

In your for loop, you declare a new variable x,
for x in a
It's this variable you next adds one to
x += 1
And then you do nothing with x.
You should save the xsomewhere if you want to use it later on :)

The variable x inside the for loop is a copy of each cell in the a list. If you modify x you will not affect a.
A more "correct" way to increment each element of a list by one is using a list comprehension:
a = [elem + 1 for elem in a]
You could also use the map function:
a = map(lambda x: x + 1, a)

when you put a[2], you are reffering to the third variable in the array 'a'
because the first element which in your case is 2 is stored at a[0] similarly, 3 at a[1] ,4 at a[2] and 5 at a[3]

Related

Python: Get item(s) at index list

I am trying to write something that takes a list, and gets the item(s) at an index, either one or multiple.
The example below which I found in another post here works great when I have more than one index. This example doesnt work if b = a single index.
a = [-2,1,5,3,8,5,6]
b = [1,2,5]
c = [ a[i] for i in b]
How do I get this to work with both 1 and multiple index?
Example:
a = [-2,1,5,3,8,5,6]
b = 2
c = [ a[i] for i in b] doesnt work in this case
You can actually check if the type your trying to use for fetching the indices is a list (or a tuple, etc.). Here it is, wrapped into a function:
def find_values(in_list, ind):
# ind is a list of numbers
if isinstance(ind, list):
return [in_list[i] for i in ind]
else:
# ind is a single numer
return [in_list[ind]]
in_list = [-2,1,5,3,8,5,6]
list_of_indices = [1,2,5]
one_index = 3
print(find_values(in_list, list_of_indices))
print(find_values(in_list, one_index))
The function takes the input list and the indices (renamed for clarity - it's best to avoid single letter names). The indices can either be a list or a single number. If isinstance determines your input is a list, it proceeds with a list comprehension. If it's a number - it just treats it as an index. If it is anything else, the program crashes.
This post gives you more details on isinstance and recognizing other iterables, like tuples, or lists and tuples together.
a = [-2, 1, 5, 3, 8, 5, 6]
a2 = [-2]
b = [1, 2, 5]
b2 = [1]
c = [a[i] for i in b]
c2 = [a2[i-1] for i in b2]
The first item of the list is 0, the list with one item is perfectly valid.
Instead of creating a list that manually validates the value of list b in the list a, you could create a separate 3 line code to print out the overlapping intersection of list a and b by this:
a = [-2,1,5,3,8,5,6]
b = [3,4,6]
for i in range(0,len(b)):
if b[i] in a:
print(b[i])
By doing so, you would be able to print out the overlapping intersection even if there were 1 or even no value stored in list b.

delete element by deleting it from list

Let's say I have:
a = 1
b = 2
C = 'r'
my_list = [a,b,c]
Now let's say that a, b and c are unknown and I don't know their names.
If I do:
for x in my_list: del x
it doesn't work. a, b, c have not been deleted.
Can someone explain me why?
As #Coldspeed mentions in his comment, the variable x which you delete is not the same as the element in the list object.
Similar behaviour will be seen if you try to assign to x:
for x in my_list: x='bla' #does not modify anything in my_list
However, as the items are references to the same memory block, the comparison x is my_list[0] will equate to True in the first loop iteration.
As such, it is possible to perform operations on the list through usage of the shared reference, for example:
for x in my_list[:]: my_list.remove(x) #results in an empty list
Care has to be taken to first create a copy of the list and iterate over these items though, as was done in the previous lines. If you are hasty and loop over the items of a dynamically changing list, you will run into some more python magic.
for x in my_list: my_list.remove(x) #the first element gets deleted, then the second element in the list, which now has length 2, is deleted.
#Final result is the list [2] remaining
you have multiple issues here:
1. variable in list
a = 1
b = 2
my_list = [a,b]
assigns the values 1 and 2 to the list, not the vars. You can use mutable objects to get you desire: Immutable vs Mutable types
2. deleting a copy from a listvalue
for x in my_list:
del x
like in 1. x is just the value from the list (e.g. 1, 2, 'c'), but even worse, its a additional reference count to the memory.
Deleting it results in decreasing the counter, not deleting the value from memory, since at least one more counter is given by the original list (and in your case the vars (a,b,c) from the beginning).
More Info: Arguments are passed by assignment
3. deleting while iterating
for x in my_list:
del x
contains an other problem. If you would change the code to mylist.remove(x), to at least remove the entrie from the list, you would also skip every second member of the list. Quick Example:
li = [1,2,3]
for x in li:
li.remove(x)
first iteration would be x = 1. Deleting 1 from li results in li = [2,3]. Then the loop continous with the second position in the list: x=3 and deleting it. 2 was skipped.
This can be avoided by using a copy of the list using the [:] operator:
for x in li[:]:
li.remove(x)
This finaly results in an empty list

Python: Changing Elements in List

I have been observing this strange behaviour for a while. Now I would like to know the reason.
See the example below.
Can someone explain why - and whether there are other options more similar to the first version that do what the second does.
>>> a
>>> [1, 0, 1, 1]
>>> for el in a:
el = 1
>>> a
>>> [1, 0, 1, 1]
>>> for i in range(len(a)):
a[i] = 1
>>> a
>>> [1, 1, 1, 1]
Your first snippet:
for el in a:
Gets only the values of the items in a, they're not references to the items in the list. So when you try to reassign it, you only change the value of el, not the item in your list.
While this:
a[i]
Retrieves the items of a themselves, not just the values.
To change all the values of a, you can create a new copy and reassign it back to a:
a = [1 for _ in a]
This is the most effective way. If you want to have both the value, and the index to reassign it, use enumerate:
for index, el in enumerate(a):
print el #print the current value
a[index] = 1 #change it,
print el #and print the new one!
Hope this helps!
I generally end up using something like this:
a = [1 for el in a]
List comprehension is my preferred way of updating items in a list avoiding indices.
Pythons variables are names for objects, not names for positions in memory. Hence
el = 1
Does not change the object el is pointing to to be 1. Instead it make el now point to the object 1. It does not modify anything in the list at all. For that you have to modify the list directly:
a[2] = 1
fast enumeration gives a immutable copy of the element thus the speed advantage over the index iteration method.
for el in a:# el is immutable
where as
for i in range(len(a)):
a[i] = 1
modifies the value at its memory location as listname[<index>] is actually baseaddress+index*offset. one of the reasons why index start with zero

Python: Using del in for loops

I was iterating through a list with a for loop, when I realized del seemed to not work. I assume this is because i is representing an object of the for loop and the del is simply deleting that object and not the reference.
And yet, I was sure I had done something like this before and it worked.
alist = [6,8,3,4,5]
for i in alist:
if i == 8:
del i
In my code its actually a list of strings, but the result is the same: even though the if conditional is satisfied, deleting i has no effect.
Is there a way I can delete a number or string in this way? Am I doing something wrong?
Thanks.
Your idea as to why you are seeing that behavior is correct. Hence, I won't go over that.
To do what you want, use a list comprehension to filter the list:
>>> alist = [6,8,3,4,5]
>>> [x for x in alist if x != 8]
[6, 3, 4, 5]
>>> alist = [6,8,8,3,4,5]
>>> [x for x in alist if x != 8]
[6, 3, 4, 5]
>>>
This approach is also a lot more efficient than a for-loop.
The for loop assigns a new value to i at each run.
So, essentially, your for loop above does
i = 6
i = 8
del i
i = 3
i = 4
i = 5
which has no effect.
del does not delete an object. It simply decrements the reference count of the object referenced by its argument. In your code
alist = [6,8,3,4,5]
for i in alist:
if i == 8:
del i
you have 6 objects: 5 separate integers, and a list of 5 references (one per integer). The for loop works by executing its body once per element in alist, with i holding a reference to a different element in alist in each iteration. Whichever object is referenced by i has a reference count of at least 2: the reference held by alist and i itself. When you call del i, you are simply decrementing its reference count by making i point to nothing.
While the following techinically works, by deleting all (known) references to the object, it has its own problems (involving modifying a list you are currently iterating over) and should not be used.
>>> alist=[6,8,3,4,5]
>>> for i, a in enumerate(alist):
... if a == 8:
... del a # delete the loop index reference
... del alist[i] # delete the reference held by the list
>>> alist
[6,3,4,5]
Instead, simply use a list comprehension to build a new list to replace the old one
alist = [ x for x in alist if x != 8 ]
If you really want to use del you need to use it on the list:
del alist[i]
(note that in this case i is an index, not the value you want to remove)
But really here you should probably just create another list using list comprehension:
[x for x in alist if x != 8]

python list comprehension double for

vec = [[1,2,3], [4,5,6], [7,8,9]]
print [num for elem in vec for num in elem] <----- this
>>> [1, 2, 3, 4, 5, 6, 7, 8, 9]
This is tricking me out.
I understand elem is the lists inside of the list from for elem in vic
I don't quite understand the usage of num and for num in elem in the beginning and the end.
How does python interpret this?
What's the order it looks at?
Lets break it down.
A simple list-comprehension:
[x for x in collection]
This is easy to understand if we break it into parts: [A for B in C]
A is the item that will be in the resulting list
B is each item in the collection C
C is the collection itself.
In this way, one could write:
[x.lower() for x in words]
In order to convert all words in a list to lowercase.
It is when we complicate this with another list like so:
[x for y in collection for x in y] # [A for B in C for D in E]
Here, something special happens. We want our final list to include A items, and A items are found inside B items, so we have to tell the list-comprehension that.
A is the item that will be in the resulting list
B is each item in the collection C
C is the collection itself
D is each item in the collection E (in this case, also A)
E is another collection (in this case, B)
This logic is similar to the normal for loop:
for y in collection: # for B in C:
for x in y: # for D in E: (in this case: for A in B)
# receive x # # receive A
To expand on this, and give a great example + explanation, imagine that there is a train.
The train engine (the front) is always going to be there (the result of the list-comprehension)
Then, there are any number of train cars, each train car is in the form: for x in y
A list comprehension could look like this:
[z for b in a for c in b for d in c ... for z in y]
Which would be like having this regular for-loop:
for b in a:
for c in b:
for d in c:
...
for z in y:
# have z
In other words, instead of going down a line and indenting, in a list-comprehension you just add the next loop on to the end.
To go back to the train analogy:
Engine - Car - Car - Car ... Tail
What is the tail? The tail is a special thing in list-comprehensions. You don't need one, but if you have a tail, the tail is a condition, look at this example:
[line for line in file if not line.startswith('#')]
This would give you every line in a file as long as the line didn't start with a hashtag (#), others are just skipped.
The trick to using the "tail" of the train is that it is checked for True/False at the same time as you have your final 'Engine' or 'result' from all the loops, the above example in a regular for-loop would look like this:
for line in file:
if not line.startswith('#'):
# have line
please note: Though in my analogy of a train there is only a 'tail' at the end of the train, the condition or 'tail' can be after every 'car' or loop...
for example:
>>> z = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
>>> [x for y in z if sum(y)>10 for x in y if x < 10]
[5, 6, 7, 8, 9]
In regular for-loop:
>>> for y in z:
if sum(y)>10:
for x in y:
if x < 10:
print x
5
6
7
8
9
From the list comprehension documentation:
When a list comprehension is supplied, it consists of a single expression followed by at least one for clause and zero or more for or if clauses. In this case, the elements of the new list are those that would be produced by considering each of the for or if clauses a block, nesting from left to right, and evaluating the expression to produce a list element each time the innermost block is reached.
In other words, pretend that the for loops are nested. Reading from left to right your list comprehension can be nested as:
for elem in vec:
for num in elem:
num # the *single expression* from the spec
where the list comprehension will use that last, innermost block as the values of the resulting list.
Your code equals:
temp = []
for elem in vec:
for num in elem:
temp.append(num)
You can look at list comprehension just as sequential statements. This applies for any levels of for and if statements.
For example, consider double for loop with their own ifs:
vec = [[1,2,3], [4,5,6], [7,8,9]]
result = [i for e in vec if len(e)==3 for i in e if i%2==0]
Here the list comprehension is same as:
result = []
for e in vec:
if len(e)==3:
for i in e:
if i%2==0:
result.append(i)
As you can see list comprehension is simply for and if without indentations but in same sequence.

Categories