Python index usage - python

Here is some code:
# Question 9: Deep Reverse
# Define a procedure, deep_reverse, that takes as input a list,
# and returns a new list that is the deep reverse of the input list.
# This means it reverses all the elements in the list, and if any
# of those elements are lists themselves, reverses all the elements
# in the inner list, all the way down.
# Note: The procedure must not change the input list.
# The procedure is_list below is from Homework 6. It returns True if
# p is a list and False if it is not.
def is_list(p):
return isinstance(p, list)
#For example,
def deep_reverse(n):
n.reverse()
for entry in n:
if is_list(entry):
entry.reverse()
deep_reverseA(entry)
return n
def deep_reverseA(n):
for entry in n:
if is_list(entry):
entry.reverse()
deep_reverseA(entry)
return n
p = [1, [2, 3, [4, [5, 6]]]]
print deep_reverse(p)
#>>> [[[[6, 5], 4], 3, 2], 1]
print p
#>>> [1, [2, 3, [4, [5, 6]]]]
q = [1, [2,3], 4, [5,6]]
print deep_reverse(q)
#>>> [ [6,5], 4, [3, 2], 1]
print q
#>>> [1, [2,3], 4, [5,6]]
My problem is that once I run the code the values of p and q change. How can I make them not change. I know that in python indexes are connected so if indexA = indexB and you change indexA then indexB will change. That is the problem I am having with fixing this problem.

I'll just tell you the answer right here, right now, with an explanation.
In python, variables are merely pointers to stored objects. So as you said in your post, if you declare foo = bar then foo not only is equal to bar, but foo is bar. This won't change unless you explicitly say so (for example you set bar = 2). So you need a way to make a copy of the original list.
There's this thing in python called list slicing, and I'm sure you heard of it. Basically you can get a portion of the list from indexA to indexB with my_list[indexA:indexB].
But you can also leave these spaces blank. indexA if not specified defaults to 0, and indexB defaults to -1 (the last element of the list).
So my_list[2:] returns all the elements from my_list[2] to my_list[-1]. Likewise, my_list[:3] returns my_list[0] to my_list[3].
Therefore, calling my_list[:] returns an exact copy of my_list, but not the actual list itself. This is what you need to do.
So applying it to your code:
def deep_reverse(n):
ncopy = n[:] #this is the part you need
#rest of function, replace `n` with `ncopy`
return ncopy
Also, do not apply this to deep_reverseA because in that function you are changing the original list in the copied list. You aren't changing the list you input into deep_reverse. If you did apply this to deep_reverseA, the lists wouldn't actually change (you would be returning a reverse of the copy but not the original)

Related

Interesting results with the '+=' increment operator [duplicate]

This question already has answers here:
Why does += behave unexpectedly on lists?
(9 answers)
Closed last month.
I had learned that n = n + v and n += v are the same. Until this;
def assign_value(n, v):
n += v
print(n)
l1 = [1, 2, 3]
l2 = [4, 5, 6]
assign_value(l1, l2)
print(l1)
The output will be:
[1, 2, 3, 4, 5, 6]
[1, 2, 3, 4, 5, 6]
Now when I use the expanded version:
def assign_value(n, v):
n = n + v
print(n)
l1 = [1, 2, 3]
l2 = [4, 5, 6]
assign_value(l1, l2)
print(l1)
The output will be:
[1, 2, 3, 4, 5, 6]
[1, 2, 3]
Using the += has a different result with the fully expanded operation. What is causing this?
Thats because in the first implementation you are editing the list n itself (and therefore the changes still apply when leaving the function), while on the other implementation you are creating a new temporary list with the same name, so when you leave the function the new list disappears and the variable n is linked to the original list.
the += operator works similarly to x=x+y for immutable objects (since they always create new objects), but for mutable objects such as lists they work differently. x=x+y creats a new object x while x+=y edits the current object.
It may seem counter-intuitive, but they are not always the same. In fact,
a = a + b means a = a.__add__(b), creating a new object
a += b means a = a.__iadd__(b), mutating the object
__iadd__, if absent, defaults to the __add__, but it also can (and it does, in the case of lists) mutate the original object in-place.
This works on how python treats objects and passes variables into functions.
Basically - in first example (with += )
You are passing n and v into function by "pass-by-assignment"
So n gets modified and it will be also modified out of function scope.
In second example - n is reassigned inside of the function to a new list. Which is not seen outside of the function.
In your 1st code. You changes list n itself see the below image..!
In your 2nd code. you just created a temporary list which is cleared when function call ends.. see the below images..!
In the next step when function ends the temporary list clear!!

Mysterious duplicate arrays in Python

I am trying to understand why the first for loop does not return what the second for loop is returning. Can anyone explain this clearly to me? My searching did not unearth any results since I don't know what this problem is called.
arr = [1,2,3]
tempArr = []
res = []
for num in range(0,3):
tempArr.append(arr[num])
res.append(tempArr)
print(tempArr, res)
print()
tempArr = []
res = []
for num in range(0,3):
tempArr.append(arr[num])
res.append(list(tempArr))
print(tempArr, res)
Returns:
[1] [[1]]
[1, 2] [[1, 2], [1, 2]]
[1, 2, 3] [[1, 2, 3], [1, 2, 3], [1, 2, 3]]
[1] [[1]]
[1, 2] [[1], [1, 2]]
[1, 2, 3] [[1], [1, 2], [1, 2, 3]]
Lists in Python are mutable sequences. This means that you can modify the elements of a list once it is created.
You are creating a list and assigning it to the variable 'tempArr'. Then in the first loop you are appending 'tempArr' to the 'res' list.
Note that because lists are mutable, you are never changing what 'tempArr' is, you only modify its content, so you are adding three times the same list to 'res'. When you modify the 'tempArr' list, you modify all the lists inside 'res' (again, because they are the same list)
An explicit example you can play with is the following:
# Create a new list
tempArr = []
# Create a list containing the initial one (which is empty)
res = [tempArr]
# Now, lets modify the 'tempArr'
tempArr.append("This wasn't here before")
# Make your prediction on what will happen now:
print(res)
Another thing we can do is play with the 'is' comparison, in your code:
arr = [1,2,3]
tempArr = []
res = []
for num in range(0,3):
tempArr.append(arr[num])
res.append(tempArr)
# Check if the element we have appended into 'res' is the 'tempArr' list. The result is always True.
print(tempArr is res[-1])
print(tempArr, res)
# Let's compare the three elements of the 'res' list and check if they are the same list, we see that they are.
print(res[0] is res[1] is res[2])
On the other hand, in the second loop, you are not appending the 'tempArr' list into 'res', you are first creating a new list by calling "list(tempArr)", and then appending this new list into 'res'. Now we can play a little bit with this:
tempArr = []
res = []
for num in range(0,3):
tempArr.append(arr[num])
res.append(list(tempArr))
# Check if the element we have appended into 'res' is the 'tempArr' list.
print(tempArr is res[-1])
# This time the result is False. So we are not appending 'tempArr', but a new list with the same elements.
print(tempArr, res)
# Let's compare again the three elements of the 'res' list and see if they are the same elements:
print(res[0] is res[1] is res[2])
As a consequence, because in the second loop we are creating new lists, when you modify the initial one, this doesn't affect the elements inside the 'res' list.
list(...) makes a copy. Without making a copy, the same reference is appended, thus subsequent changes show up later in earlier elements as well.
To see the references, do a print(list(map(id, res))). If the printed number is the same, then it's the same list object.
Because we use list() to make shallow copy
This means list(tempArr) will now be a new and independent object with the same contents as tempArr

Can someone explain this expression: a[len(a):] = [x] equivalent to list.append(x)

I'm at the very beginning of learning Python 3. Getting to know the language basics. There is a method to the list data type:
list.append(x)
and in the tutorial it is said to be equivalent to this expression:
a[len(a):] = [x]
Can someone please explain this expression? I can't grasp the len(a): part. It's a slice right? From the last item to the last? Can't make sense of it.
I'm aware this is very newbie, sorry. I'm determined to learn Python for Blender scripting and the Game Engine, and want to understand well all the constructs.
Think back to how slices work: a[beginning:end].
If you do not supply one of them, then you get all the list from beginning or all the way to end.
What that means is if I ask for a[2:], I will get the list from the index 2 all the way to the end of the list and len(a) is an index right after the last element of the array... so a[len(a):] is basically an empty array positioned right after the last element of the array.
Say you have a = [0,1,2], and you do a[3:] = [3,4,5], what you're telling Python is that right after [0,1,2 and right before ], there should be 3,4,5.
Thus a will become [0,1,2,3,4,5] and after that step a[3:] will indeed be equal to [3,4,5] just as you declared.
Edit: as chepner commented, any index greater than or equal to len(a) will work just as well. For instance, a = [0,1,2] and a[42:] = [3,4,5] will also result in a becoming [0,1,2,3,4,5].
One could generally state that l[len(l):] = [1] is similar to append, and that is what is stated in the docs, but, that is a special case that holds true only when the right hand side has a single element.
In the more general case it is safer to state that it is equivalent to extend for the following reasons:
Append takes an object and appends that to the end; with slice assignment you extend a list with the given iterable on the right hand side:
l[len(l):] = [1, 2, 3]
is equivalent to:
l.extend([1, 2, 3])
The same argument to append would cause [1, 2, 3] to be appended as an object at the end of l. In this scenario len(l) is simply used in order for the extending of the list to be performed at the end of l.
Some examples to illustrate their difference:
l = [1, 2]
l[len(l):] = [1, 2] # l becomes [1, 2, 1, 2]
l.extend([1, 2]) # l becomes [1, 2, 1, 2, 1, 2]
l.append([1, 2]) # l becomes [1, 2, 1, 2, 1, 2, [1, 2]]
As you note, l.append(<iterable>) doesn't actually append each value in the iterable, it appends the iterable itself.

function that given a list returns a list of list decreasing

I'd make a function in python, that given a list returns a list of list, in which every element is the list given decreased by one.
Input: list_decreaser([0,3,4,5,6,7,8)
Output: [[0,3,4,5,6,7],[0,3,4,5,6],[0,3,4,5],[0,3,4],[0,3],[0]]
My attempt:
def list_decreaser(list):
listresult = []
for x in range(len(list)-1):
list.remove(list[x])
listresult.append(list)
return listresult
The code appends the same list multiple times. It should append copy of the list.
And use del list[..] instead of list.remove(list[..]) to delete an item at specific index.
def list_decreaser(xs):
listresult = []
for i in range(len(xs)-1, 0, -1): # <--- interate backward
del xs[i]
listresult.append(xs[:]) # <----
return listresult
print(list_decreaser([0,3,4,5,6,7,8]))
Or using list comprehension:
>>> xs = [0,3,4,5,6,7,8]
>>> [xs[:i] for i in range(len(xs)-1, 0, -1)]
[[0, 3, 4, 5, 6, 7], [0, 3, 4, 5, 6], [0, 3, 4, 5], [0, 3, 4], [0, 3], [0]]
BTW, don't use list as a variable name. It shadows builtin list function.
The problem is that you're appending the same list over and over again. You keep mutating the list in-place, but you're never creating a new list. So you end up with a list of N references to the same empty list.
This is the same problem discussed in two FAQ questions. I think How do I create a multidimensional list explains it best.
Anyway, what you need to do is append a new list each time through the loop. There are two ways to do that.
First, you can append a copy of the current list, instead of the list itself:
def list_decreaser(list):
listresult = []
for x in range(len(list)-1):
list.remove(list[x])
listresult.append(list[:]) # this is the only change
return listresult
This solves your problem, but it leaves a few new problems:
First, list.remove(list[x]) is a very bad idea. If you give it, say, [0, 1, 2, 0], what happens when you try to remove that second 0? You're calling list.remove(0), and there's no way the list can know you wanted the second 0 rather than the first! The right thing to do is call del list[x] or list.pop(x).
But once you fix that, you're removing the elements from the wrong side. x is 0, then 1, then 2, and so on. You remove element 0, then element 1 (which is the original element 2), then element 2 (which is the original element 4), and eventually get an IndexError. Even if you fixed the "skipping an index" issue (which is also explained in the FAQ somewhere), you'd still be removing the first elements rather than the last ones. You can fix that by turning the range around. However, there's an even easier way: Just remove the last element each time, instead of trying to figure out which x is the right thing, which you can do by specifying -1, or just calling pop with no argument. And then you can use a much simpler loop, too:
def list_decreaser(list):
listresult = []
while list:
list.pop()
listresult.append(list[:])
return listresult
Of course this appends the last, empty list, which you apparently didn't want. You can fix that by doing while len(list) >= 1, or putting an if list: listresult.append(list[:]), or in various other ways.
Alternatively, you can make new truncated lists instead of truncating and copying the same list over and over:
def list_decreaser(list):
listresult = []
while len(list):
list = list[:-1]
listresult.append(list)
return listresult
Note that in this second version, rather than changing the value stored in list, we're creating a new list and storing that new list in list.
use this
def list_decreaser(list1):
listresult = []
for i in list1:
list1 = list[:-1]
listresult.append(list1)
return listresult

Modifying a list iterator in Python not allowed?

Simple example:
myList = [1, 2, 3, 4, 5]
for obj in myList:
obj += 1
print myList
prints
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
while:
myList = [1, 2, 3, 4, 5]
for index in range(0,len(myList)):
myList[index] += 1
print myList
prints
[1, 2, 3, 4, 5]
[2, 3, 4, 5, 6]
Conclusion:
Lists can be modified in place using global list access Lists can
List items can NOT be modified in place using the iterator object
All example code I can find uses the global list accessors to modify the list inplace.
Is it so evil to modify a list iterator?
The reason obj += 1 does not do what you expect is that this statement does not modify obj in-place. Instead, it computes the new value, and rebinds the variable obj to point to the new value. This means that the contents of the list remain unchanged.
In general it is possible to modify the list while iterating over it using for obj in myList. For example:
myList = [[1], [2], [3], [4], [5]]
for obj in myList:
obj[0] += 1
print(myList)
This prints out:
[[2], [3], [4], [5], [6]]
The difference between this and your first example is that here, the list contains mutable objects, and the code modifies those objects in-place.
Note that one could also write the loop using a list comprehension:
myList = [val+1 for val in myList]
I think you've misunderstood what an "iterator object" is. A for loop is not an iterator object. For all intents and purposes, a for loop like this:
myList = [0, 1, 2, 3, 4]
for x in myList:
print x
does this (but more efficiently and less verbosely):
i = 0
while i < len(myList)
x = myList[i]
print x
i += 1
So you see, any changes made to x are lost as soon as the next loop starts, because the value of x is overwritten by the value of the next item in the list.
As others have observed, it is possible to alter the value of a list while iterating over it. (But don't change its length! That's where you get into trouble.) One elegant way to do so is as follows:
for i, x in enumerate(myList):
myList[i] = some_func(x)
Update: It's also important to understand that no copying goes on in a for loop. In the above example, i and x -- like all variables in Python -- are more like pointers in C/C++. As the for loop progresses, obj points at myList[0], myList[1], etc, in turn. And like a C/C++ pointer, the properties of the object pointed to are not changed when the pointer is changed. But also like a C pointer, you can directly modify the thing pointed at, because it's not a copy. In C, this is done by dereferencing the pointer; in Python, this is done by using a mutable object. That's why NPE's answer works. If i and x were even shallow copies, it wouldn't be possible to do what he does.
The reason you can't directly change ints the way you can change lists (as in NPE's answer), is that ints aren't mutable. Once a 5 object is created, nothing can change its value. That's why passing around a pointer to 5 is safe in Python -- no side-effects can occur, because the thing pointed to is immutable.
in for obj in myList:, in every iteration, obj is a (shallow) copy of the element in myList. So the change on the obj does nothing to myList's elements.
It's different with the Perl for my $obj (#myList) {}
You are confused. Consider your first snippet:
myList = [1, 2, 3, 4, 5]
for obj in myList:
obj += 1
print a
obj is not some kind of magical pointer into the list. It is a variable which holds a reference to an object which happens to also be in myList. obj += 1 has the effect of increasing the value stored in obj. Your code then does nothing with that value.
To be clear: There are no copies in this code example. obj is a variable, which holds an object in the list. That is all.
In the first example the integer is copied into obj which is increased by 1.
The list is not changed.
If you would use a class instance and perform operations on it, it would be changed.
Modification in list is allowed. Your code examples arbove are pretty garbled...
myList = [1, 2, 3, 4, 5]
for index in range(0,len(myList)):
myList[index] += 1
print myList
This works.

Categories