I want to re-assign each item in a list in Python.
In [20]: l = [1,2,3,4,5]
In [21]: for i in l:
....: i = i + 1
....:
....:
But the list didn't change at all.
In [22]: l
Out[22]: [1, 2, 3, 4, 5]
I want to know why this happened. Could any body explain the list iterating in detail? Thanks.
You can't do it like that, you are merely changing the value binded to the name i. On each iteration of the for loop, i is binded to a value in the list. It is not a pointer in the sense that by changing the value of i you are changing a value in the list. Instead, as I said before, it is simply a name and you are just changing the value that name refers to. In this case, i = i + 1, binds i to the value i + 1. So you aren't actually affecting the list itself, to do that you have to set it by index.
>>> L = [1,2,3,4,5]
>>> for i in range(len(L)):
L[i] = L[i] + 1
>>> L
[2, 3, 4, 5, 6]
Some pythonistas may prefer to iterate like this:
for i, n in enumerate(L): # where i is the index, n is each number
L[i] = n + 1
However you can easily achieve the same result with a list comprehension:
>>> L = [1,2,3,4,5]
>>> L = [n + 1 for n in L]
>>> L
[2, 3, 4, 5, 6]
For more info: http://www.effbot.org/zone/python-objects.htm
This is because of how Python handles variables and the values they reference.
You should modify the list element itself:
for i in xrange(len(l)):
l[i] += 1
>>> a = [1, 2, 3, 4, 5]
>>> a = [i + 1 for i in a]
>>> a
[2, 3, 4, 5, 6]
Initially i is a pointer to the item inside the list, but when you reassign it, it will point to the new number, that is why the list will not be changed.
For a list of mutable objects it would work:
class Number(object):
def __init__(self,n):
self.n=n
def increment(self):
self.n+=1
def __repr__(self):
return 'Number(%d)' % self.n
a = [Number(i) for i in xrange(5)]
print a
for i in a:
i.increment()
print a
But int are not mutable, when you do an operation on them you get a new int object, and that is why it doesn't work in your case.
Related
I was going through the topic about list in Learning Python 5E book.
I notice that if we do concatenation on list, it creates new object. Extend method do not create new object i.e. In place change.
What actually happens in case of Concatenation?
For example
l = [1,2,3,4]
m = l
l = l + [5,6]
print l,m
#output
([1,2,3,4,5,6], [1,2,3,4])
And if I use Augmented assignment as follows,
l = [1,2,3,4]
m = l
l += [5,6]
print l,m
#output
([1,2,3,4,5,6], [1,2,3,4,5,6])
What is happening in background in case of both operations?
There are two methods being used there: __add__ and __iadd__. l + [5, 6] is a shortcut for l.__add__([5, 6]). l's __add__ method returns the result of addition to something else. Therefore, l = l + [5, 6] is reassigning l to the addition of l and [5, 6]. It doesn't affect m because you aren't changing the object, you are redefining the name. l += [5, 6] is a shortcut for l.__iadd__([5, 6]). In this case, __iadd__ changes the list. Since m refers to the same object, m is also affected.
Edit: If __iadd__ is not implemented, for example with immutable types like tuple and str, then Python uses __add__ instead. For example, x += y would be converted to x = x + y. Also, in x + y if x does not implement __add__, then y.__radd__(x), if available, will be used instead. Therefore x += y could actually be x = y.__radd__(x) in the background.
You can understand this better by inspecting the objects referenced in memory. Lets use id for the inspection
In the first case:
>>> l = [1,2,3,4]
>>> id(l)
4497052232
>>> m = l
>>> id(m)
4497052232
>>> l = l + [5,6]
>>> id(l)
4497052448
>>> print l, id(l), m, id(m)
[1, 2, 3, 4, 5, 6] 4497052448 [1, 2, 3, 4] 4497052232
>>>
Notice that l = l + [5,6] creates a new object in memory, and l references that.
In the second case:
>>> l = [1,2,3,4]
>>> id(l)
4497052520
>>> m = l
>>> id(m)
4497052520
>>> l += [5,6]
>>> id(l)
4497052520
>>> print l, id(l), m, id(m)
[1, 2, 3, 4, 5, 6] 4497052520 [1, 2, 3, 4, 5, 6] 4497052520
>>>
l += [5,6] references to the same object in memory. Hence the result.
So, basically += is the equivalent of inplace add.. More on this can be read here
So more or less i am trying to figure this out:
a = [1,2]
b = [3,4,5]
c = [6,7,8,9]
z = [a,b,c]
def raiseInt():
x = 0
for int in z:
a[x] + 1
b[x] + 1
c[x] + 1
I'm trying to increase every int in every list by 1
I will try to make my code self-explanatory. Note the name of each object
a = [1,2]
b = [3,4,5]
c = [6,7,8,9]
z = [a,b,c]
def raiseInt(list_to_increase_by_1):
#x = 0 we may actually do not need to assign this variable.
for sublist in list_to_increase_by_1:
for index_of_element, element in enumerate(sublist):
sublist[index_of_element] += 1
return list_to_increase_by_1
print raiseInt(z)
best practice related details
As you can see from Moinuddin Quadri's comments, I do not use the variable (iteratively assigned) element. In this case, the convention is about naming it _, so as to explicit that tha variable is actually not used.Which thus turns the code into
def raiseInt(list_to_increase_by_1):
for sublist in list_to_increase_by_1:
for index_of_element, _ in enumerate(sublist):
sublist[index_of_element] += 1
return list_to_increase_by_1
more best practice related details
As can be read from Paul Ronney's comment
[strictly doing as above] is contrary to python best practice, that any in place modifying function returns None. It should do one or the other and if the choice is to return it should return a copy and leave the original intact
Thus, folowing this comment implies to define raiseInt as an in place modifying function, as follows
def raiseInt(list_to_increase_by_1):
for sublist in list_to_increase_by_1:
for index_of_element, _ in enumerate(sublist):
sublist[index_of_element] += 1
I.e. with the function returning None
A very simple way to do this
a = [1,2]
b = [3,4,5]
c = [6,7,8,9]
z = [a,b,c]
def raiseInt():
for L in z:
for i in range(len(L)):
L[i] +=1
print(z)
raiseInt()
print(z)
output
[[1, 2], [3, 4, 5], [6, 7, 8, 9]]
[[2, 3], [4, 5, 6], [7, 8, 9, 10]]
Iterate over the lists in z and then iterate for a number of times equal to the lists length and increment each element.
You could certainly improve your function by passing the lists to be operated on in as a parameter. This would make your function more generally useful, as it is, it can only operate on that one list called z.
def raiseInt():
for v in z:
for i in range(len(v)):
v[i] += 1;
As an exercise, I am writing an implementation of selection sort (as well as insertion sort and quick sort); for me, the best way to gain a deeper understanding of a function is implementing it myself. I have the following code:
def selectionSort(L):
S = []
i = 0
a = len(L)
while i <= a:
S.append(min(L)) #Find min(L) and add it to set S
L.remove(min(L)) #Remove that same element min(L) from L
i += 1
return S #Return the sorted list
L = [int(x) for x in input('Input a list to be sorted: ').split()]
print(selectionSort(L))
The idea here is to have the user input a list of integers to be sorted and run the selectionSort() function on the list. For the life of me, I cannot figure out why min(L) is throwing the error min() arg is an empty sequence. I have written other code which takes a list as input in the same manner and it works fine.
You are looping once too many:
i = 0
a = len(L)
while i <= a:
#
i += 1
Here i goes from 0 through to len(a) inclusive, so you loop len(a) + 1 times. But there are only len(a) items in the list.
Your options are to pick one of the following
start i at 1, not at 0
stop at i < a (dropping the =) to not include len(a)
Use a for i in range(len(a)) loop; this produces values from 0 through to len(a) - 1.
Simply test if L is empty with
while L:
and remove a and i from your code altogether.
The latter option leads to less code and is clearer:
def selectionSort(L):
S = []
while L:
pick = min(L)
S.append(pick)
L.remove(pick)
return S #Return the sorted list
Note that I store the result of min(L) first to avoid scanning the list twice.
Your issue should already be fixed by #AChampion's comment, I just want to add some clarifications about what you're trying to do. The following code should work for you, but it still has some other issues:
def selectionSort(L):
S = []
i = 0
a = len(L)
while i < a: # Or even better: for i in range(len(L)) and without i += 1
S.append(min(L)) #Find min(L) and add it to set S
L.remove(min(L)) #Remove that same element min(L) from L
i += 1
return S
But, look at the following outputs:
>>> my_list = [3, 5, 1, 9, 2, 7, 4, 7, 4]
>>> selectionSort(my_list)
[1, 2, 3, 4, 4, 5, 7, 7, 9]
>>>
>>> my_list
[]
You see, the input my_list is empty now, because it is altered inside your function. To fix this issue, you can do: (because you may still need to use your original list)
def my_sort(my_list):
temp = my_list[:]
sorted_list = []
for i in range(len(temp)):
sorted_list.append(min(temp))
temp.remove(min(temp))
return sorted_list
Now, inside our function, we use a copy of our original list temp = my_list[:], this is equivalent to temp = copy.copy(my_list):
>>> import copy
>>>
>>> my_list = [3, 5, 1, 9, 2, 7, 4, 7, 4]
>>> id(my_list)
36716416
>>> l1 = my_list
>>> id(l1)
36716416 # In this case the id of l1 is the same as my_list's id!
>>> l2 = my_list[:]
>>> id(l2)
36738832 # This is NOT the same as my_list's id
>>> l3 = copy.copy(my_list)
>>> id(l3)
36766424 # This is NOT the same as my_list's id
Output of the new function:
>>> my_list = [3, 5, 1, 9, 2, 7, 4, 7, 4]
>>> my_sort(my_list)
[1, 2, 3, 4, 4, 5, 7, 7, 9]
>>>
>>> my_list
[3, 5, 1, 9, 2, 7, 4, 7, 4] # Our original list is still alive!
You may need:
As per your goal (implementing your own function for learning purpose), you may also need to use your own min function instead of the built-in one, the following is an example that you can try:
def my_min(my_list):
min = my_list[0]
for i in range(1, len(my_list)):
if min > my_list[i]:
min = my_list[i]
return min
Output:
>>> l1 = [3, 7, 2, 5]
>>> my_min(l1)
2
Input list: [1, 2, 3, 4, 5]
Output: [5, 4, 3, 2, 1]
I know how to do it with for loop, but my assignment is to do it with while loop; which I have no idea to do. Here is the code I have so far:
def while_version(items):
a = 0
b = len(items)
r_list = []
while (a!=b):
items[a:a] = r_list[(-a)-1]
a+=1
return items
I would say to make the while loop act like a for loop.
firstList = [1,2,3]
secondList=[]
counter = len(firstList)-1
while counter >= 0:
secondList.append(firstList[counter])
counter -= 1
The simplest way would be:
def while_version(items):
new_list = []
while items: # i.e. until it's an empty list
new_list.append(items.pop(-1))
return new_list
This will reverse the list:
>>> l1 = [1, 2, 3]
>>> l2 = while_version(l)
>>> l2
[3, 2, 1]
Note, however, that it also empties the original list:
>>> l1
[]
To avoid this, call e.g. l2 = while_version(l1[:]).
The trivial answer
Given
a = [1, 2, 3, 4, 5]
then
a[::-1]
returns
[5, 4, 3, 2, 1]
In your code:
You use r_list[(-a)+1], buy you have never assigned r_list any value (just "r_list = []")
I think your are confusing "items" with "r_list". So I think you want to return "r_list" instead of "items" (the input parameter)
The assignment should be "r_list[a] = items[-a-1]", but that doesn't work. You should use "r_list.append(items[-a-1])"
The return should be "return r_list"
"while (a!=b)" should be "while (a < b)" for readeability
Hope this helps
I'm supposed to create a function, which input is a list and two numbers, the function reverses the sublist which its place is indicated by the two numbers.
for example this is what it's supposed to do:
>>> lst = [1, 2, 3, 4, 5]
>>> reverse_sublist (lst,0,4)
>>> lst [4, 3, 2, 1, 5]
I created a function and it works, but I'm not sure is it's in place.
This is my code:
def reverse_sublist(lst,start,end):
sublist=lst[start:end]
sublist.reverse()
lst[start:end]=sublist
print(lst)
def reverse_sublist(lst,start,end):
lst[start:end] = lst[start:end][::-1]
return lst
Partial reverse with no temporary list (replace range with xrange if you use Python 2):
def partial_reverse(list_, from_, to):
for i in range(0, int((to - from_)/2)):
(list_[from_+i], list_[to-i]) = (list_[to-i], list_[from_+i])
list_ = [1, 2, 3, 4, 5, 6, 7, 8]
partial_reverse(list_, 3, 7)
print(list_)
Easiest way to reverse a list in a partial or complete manner.
listVar = ['a','b','c','d']
def listReverse(list,start,end):
while(start<end):
temp = list[start]
list[start] = list[end] #Swaping
list[end]=temp
start+=1
end-=1
print(list)
listReverse(listVar,1,3)
Output : - ['a', 'd', 'c', 'b']
Not sure if you have a similar problem as mine, but i needed to reverse a list in place.
The only piece I was missing was [:]
exStr = "String"
def change(var):
var[:] = var[::-1] # This line here
print(exStr) #"String"
change(exStr)
print(exStr) #"gnirtS"
... I'm not sure is it's in place.
...
lst[start:end]=sublist
Yes, it's in place. lst is never rebound, only its object mutated.
Just use a slice:
>>> lst = [1, 2, 3, 4, 5]
>>> lst[0:len(lst[3::-1])]=lst[3::-1]
>>> lst
[4, 3, 2, 1, 5]
Or, perhaps easier to understand:
>>> lst = [1, 2, 3, 4, 5]
>>> sl=lst[3::-1]
>>> lst[0:len(sl)]=sl
>>> lst
[4, 3, 2, 1, 5]
lst[::-1] is the idiomatic way to reverse a list in Python, The following show how and that it was in-place:
>>> lst = [1, 2, 3, 4, 5]
>>> id(lst)
12229328
>>> lst[:] = lst[::-1]
>>> lst
[5, 4, 3, 2, 1]
>>> id(lst)
12229328
Try some crazy slicing, see Explain Python's slice notation and http://docs.python.org/2.3/whatsnew/section-slices.html
x = [1,2,3,4,5,6,7,8]
def sublist_reverse(start_rev, end_rev, lst):
return lst[:end_rev-1:start_rev-1]+lst[:[end_rev]
print sublist_reverse(0,4,x)
[out]:
[8, 7, 6, 5, 4, 3, 2, 1]
I have two ways for in-place reversal, the simple way is to loop through the list half-way, swapping the elements with the respective mirror-elements. By mirror-element I mean (first, last), (2nd, 2nd-last), (3rd, 3rd-last), etc.
def reverse_list(A):
for i in range(len(A) // 2): # half-way
A[i], A[len(A) - i - 1] = A[len(A) - i - 1], A[i] #swap
return A
The other way is similar to the above but using recursion as opposed to a "loop":
def reverse_list(A):
def rev(A, start, stop):
A[start], A[stop] = A[stop], A[start] # swap
if stop - start > 1: # until halfway
rev(A, start + 1, stop - 1)
return A
return rev(A, 0, len(A) - 1)
I've conducted a tiny experiment and it seems that any assignment to list slice causes memory allocation:
import resource
resource.setrlimit(resource.RLIMIT_AS, (64 * 1024, 64 * 1024))
try:
# Python 2
zrange = xrange
arr_size = 3 * 1024
except NameError:
# Python 3
zrange = range
arr_size = 4 * 1024
arr = list(zrange(arr_size))
# We could allocate additional 100 integers, so there should be enough free memory
# to allocate a couple of variables for indexes in the statement below
# additional_memory = list(zrange(100))
# MemoryError is raised here
arr[:] = zrange(arr_size)
So you have to use for loop to reverse a sublist in place.
PS: If you want to repeat this test, you should ensure that setrlimit RLIMIT_AS works fine on your platform. Also arr_size may vary for different python implementations.
Two methods in-place and constant memory:
def reverse_swap(arr, start=None, end=None):
"""
Swap two edge pointers until meeting in the center.
"""
if start is None:
start = 0
if end is None:
end = len(arr)
i = start
j = end - 1
while i < j:
arr[i], arr[j] = arr[j], arr[i]
i += 1
j -= 1
def reverse_slice(arr, start=None, end=None):
"""
Use python slice assignment but use a generator on the right-hand-side
instead of slice notation to prevent allocating another list.
"""
if start is None:
start = 0
if end is None:
end = len(arr)
arr[start:end] = (arr[i] for i in range(end - 1, start - 1, -1))
The simplest way is probably to use slice assignment and reversed():
lst[start:end] = reversed(lst[start:end])
You could also slice and reverse at the same time, however this generally requires either using negative indexes or specially handling the case when start = 0, i.e.:
lst[start:end] = lst[end-len(lst)-1:start-len(lst)-1:-1]
or
lst[start:end] = lst[end-1::-1] if start == 0 else lst[end-1:start-1:-1]
OTOH, using slicing alone is faster than the reversed() solution.
Much cleaner way to do this
a = [1,2,3,4,5,6,7,8,9]
i = 2
j = 7
while (i<j):
a[i],a[j]=a[j],a[i]
j -= 1
i += 1
print(a)
lst = [1,2,3,4,5,6,7,8]
Suppose you have to reverse 2nd position to 4th position in place.
lst[2:5] = lst[2:5][::-1]
Output:
[1,2,5,4,3,6,7,8]