reverse method mutating input - python

For an assignment we were asked to create a function that would reverse all the elements in an arbitrarily nested list. So inputs to the function should return something like this:
>>> seq = [1,[2,[3]]]
>>> print arb_reverse(seq)
[[[3],2],1]
>>> seq = [9,[[],[0,1,[[],[2,[[],3]]]],[],[[[4],5]]]]
>>> print arb_reverse(seq)
[[[[5,[4]]],[],[[[[3,[]],2],[]],1,0],[]],9]
I came up with a recursive solution which works well:
def arb_reverse(seq):
result = []
for element in reversed(seq):
if not is_list(element):
result.append(element)
else:
result.append(arb_reverse(element))
return result
But for a bit of a personal challenge I wanted to create a solution without the use of recursion. One version of this attempt resulted in some curious behavior which I am not understanding. For clarification, I was NOT expecting this version to work properly but the resulting input mutation does not make sense. Here is the iterative version in question:
def arb_reverse(seq):
elements = list(seq) #so input is not mutated, also tried seq[:] just to be thorough
result = []
while elements:
item = elements.pop()
if isinstance(item, list):
item.reverse() #this operation seems to be the culprit
elements += item
else:
result.append(item)
return result
This returns a flattened semi-reversed list (somewhat expected), but the interesting part is what it does to the input (not expected)...
>>> a = [1, [2, [3]]]
>>> arb_reverse(a)
[2, 3, 1]
>>> a
[1, [[3], 2]]
>>> p = [1, [2, 3, [4, [5, 6]]]]
>>> print arb_reverse(p)
[2, 3, 4, 5, 6, 1]
>>> print p
[1, [[[6, 5], 4], 3, 2]]
I was under the impression that by passing the values contained in the input to a variable using list() or input[:] as i did with elements, that I would avoid mutating the input. However, a few print statements later revealed that the reverse method had a hand in mutating the original list. Why is that?

The list() call is making a new list with shallow-copied lists from the original.
Try this (stolen from here):
from copy import deepcopy
listB = deepcopy(listA)

Try running the following code through this tool http://people.csail.mit.edu/pgbovine/python/tutor.html
o1 = [1, 2, 3]
o2 = [4, 5, 6]
l1 = [o1, o2]
l2 = list(l1)
l2[0].reverse()
print l2
print l1
Specifically look at what happens when l2[0].reverse() is called.
You'll see that when you call list() to create a copy of the list, the lists still reference the same objects.

Related

I want to know list parmeters in python function like a[:]

pset = []
def powe(a):
powehelp(a,0)
def powehelp(a, ind):
if len(a) == ind:
pset.append(a)
return
powehelp(a[:], ind+1)
a.pop(ind)
powehelp(a[:],ind)
powe([1,2,3])
print(pset)
This code creates its subset, and in this code I want to know why I can't use powehelp(a, ind+1) instead of powehelp(a[:], ind+1)?
I know that a[:] means get all the values ​​of list a.
When using a, the result is [[], [], [], []].
Your powehelp function uses pop, which means it alters the list it is given.
If you pass a into it, it is a that gets altered, and evidently ends up empty.
a[:] creates a copy of a. If you pass a[:] into powehelp, your original list a is unaffected.
The given a is a list in itself, and so appending a list to a list would make a nested list.
a = [1, 2, 3]
b = []
b.append(a)
b
[[1, 2, 3]]
When you pset.append(a) you insert the list a into the endtail of the list pset so you result is a nested list. If I understand your requirement correctly, you're looking to concatenate your lists, as in:
c = [4, 5, 6]
a + c
[1, 2, 3, 4, 5, 6]

local and global variable usage in Python

I am testing a reverse function. My code is as follow:
def deep_reverse(L):
new_list = []
for element in L:
new_list += [element[::-1]]
L = new_list[::-1]
L = [[1, 2], [3, 4], [5, 6, 7]]
deep_reverse(L)
print(L)
I expect my output L can be like [[7, 6, 5], [4, 3], [2, 1]], but it only works inside my function.
I wasn't able to change it in the global variable. How can I make it happen? Thank you
L is just a local variable, and assigning a new object to it won't change the original list; all it does is point the name L in the function to another object. See Ned Batchelder's excellent explanation about Python names.
You could replace all elements in the list L references by assigning to the [:] identity slice instead:
L[:] = new_list[::-1]
However, mutable sequences like lists have a list.reverse() method, so you can just reverse the list in-place with that:
L.reverse()
Do the same for each sublist:
def deep_reverse(L):
""" assumes L is a list of lists whose elements are ints
Mutates L such that it reverses its elements and also
reverses the order of the int elements in every element of L.
It does not return anything.
"""
for element in L:
element.reverse()
L.reverse()

Using list comprehensions to make a funcion more pythonic

I'm doing some Google Python Class exercises and I'm trying to find a pythonic solution to the following problem.
D. Given a list of numbers, return a list where all adjacent ==
elements have been reduced to a single element, so [1, 2, 2, 3]
returns [1, 2, 3]. You may create a new list or modify the passed in
list.
My try, which is working perfectly is the following:
def remove_adjacent(nums):
result = []
for num in nums:
if len(result) == 0 or num != result[-1]:
result.append(num)
return result
For example, with remove_adjacent([2, 2, 3, 3, 3]) the output is [2, 3]. Everything's ok.
I'm trying to use list comprehensions in order to archieve this in a more pythonic way, so my try is the following:
def remove_adjacent(nums):
result = []
result = [num for num in nums if (len(result)==0 or num!=result[-1])]
return result
This, with the same input [2, 2, 3, 3, 3], the output is [2, 2, 3, 3, 3] (the same). Meeeh! Wrong.
What I'm doing wrong with the list comprehensions? Am I trying to do something which is impossible to do with list comprehensions? I know it's a bit weird to initialize the list (result = []), so maybe it's not posible to do it using list comprehensions in this case.
Am I trying to do something which is impossible to do with list comprehensions?
Yep. A list comprehension can't refer to itself by name, because the variable doesn't get bound at all until the comprehension is completely done evaluating. That's why you get a NameError if you don't have result = [] in your second code block.
If it's not cheating to use standard modules, consider using groupby to group together similar values in your list:
>>> import itertools
>>> seq = [1, 2, 2, 3]
>>> [k for k,v in itertools.groupby(seq)]
[1, 2, 3]
>>> seq = [2,2,3,3,3]
>>> [k for k,v in itertools.groupby(seq)]
[2, 3]
For the sake of learning, I'd suggest using core reduce function:
def remove_adjacent(lst):
return reduce(lambda x, y: x+[y] if not x or x[-1] != y else x, lst, [])

Python: Why still list elements are not disappeared after using procedure?

I define this function to do: [1,2,3] --> [2,3,1]
def shift_to_left(p):
p.append(p[0])
return p[1:]
When I check like this, results are ok:
p1 = [1,2,3]
print p1
p1 = shift_to_left(p1)
print p1
The result:
[1, 2, 3]
[2, 3, 1]
However, when I introduce another list and concatenate as I go the result is different:
ss = []
p1 = [1,2,3]
ss.append(p1)
p1 = shift_to_left(p1)
ss.append(p1)
print ss
The result
[[1, 2, 3, 1], [2, 3, 1]]
But I want:
[1,2,3]
[2,3,1]
why is it happening?
Thanks very much,
In Python, most arguments are taken by reference.
Your function, shift_to_left, actually mutates its argument (through the use of append), but then returns a slice (which is a shallow copy of the list).
When you replace your original variable with the output of shift_to_left, this behaviour is hidden:
In [1]: def shift_to_left(p):
...: p.append(p[0])
...: return p[1:]
...:
In [2]: xs = [1, 2, 3]
In [3]: xs = shift_to_left(xs)
In [4]: xs
Out[4]: [2, 3, 1]
But if we instead assign the result into a new variable, we can see that the original list has indeed been changed:
In [5]: ys = shift_to_left(xs)
In [6]: ys
Out[6]: [3, 1, 2]
In [7]: xs
Out[7]: [2, 3, 1, 2]
Our result, ys, is the slice of xs from the second element onwards. That's what you expected.
But xs itself has also been changed by the call to append: it's now one element longer than before.
This is what you're experiencing in your second example.
If you do not want this behaviour, one way of avoiding it is by passing a copy of your list to shift_to_left:
In [8]: zs = shift_to_left(ys[:])
In [9]: zs
Out[9]: [1, 2, 3]
In [10]: ys
Out[10]: [3, 1, 2]
Here, you can see that the original list ys has not been modified, as shift_to_left was given a copy of it, not the object itself. (This is still passing by reference, of course; it's just not a reference to ys).
Alternatively, and probably more reasonably, you could change shift_to_left itself, so that it does not modify its arguments:
def shift_to_left(xs):
return xs[1:] + xs[0] # build a new list in the return statement
The big problem with both these approaches is that they create lots of copies of lists, which can be incredibly slow (and use a lot of memory) when the lists are large.
Of course, as #Marcin points out, if this is more than an academic exercise, you should probably use one of the built-in data structures such as deque.
If you want to shift/rotate elements in a list, I think better would be to use a deque, rather than reinvent the wheel. For example:
from collections import deque
d = deque([1,2,3])
d.rotate(-1)
print(d)
#[2, 3, 1]
If you run your code here, you can notice that ss remains pointing to the original (mutated in your shift function because of p.append(p[0])) copy of p1, where as p1 points to a knew list all together when it gets reassigned, resulting in the behavior. (Step 10 out of 11)
(p becomes mutated, and ss[0] = p)
(p1 gets assigned to a new list altogether, which is latter appended to ss)
why is it happening?
return p[1:] is "non-destructive": it creates a new list. However, p.append(p[0]) is "destructive": it changes p itself.
First you append p1 to ss. This makes [[1, 2, 3]], where [1, 2, 3] is p1.
Then you do your shift_to_left, which changes p1 to [1, 2, 3, 1] and returns [2, 3, 1]. Because p1 is contained in ss, your ss becomes [[1, 2, 3, 1]], and then you append the new p1 to form [[1, 2, 3, 1], [2, 3, 1]].
A better implementation would be purely non-destructive:
def shift_to_left(p):
return p[1:] + [p[0]]
Try this:
p1 = [1,2,3]
p1 = shift_to_left(p1)
ss = []
ss.extend(p1)
print ss
That prints [2, 3, 1]. Use extend() instead because append() will create an array in an array. Also you had an extra call to ss.append(p1).

Are there efficiency differences in extend vs. adding vs. appending in Python?

I refer to list operations:
L = myList + otherList
L = myList.append([5])
L = myList.extend(otherList)
I am curious if there are efficiency differences among these operations.
These are totally different operations.
They have different purposes, so efficiency wouldn't matter. append is used to append a single value to a list, extend is used for multiple values, and the addition is for when you don't want to modify the original list, but to have another list with the extra values added on.
>>> lst = [1, 2, 3]
>>> lst2 = [5, 6]
>>> lst.append(4) # appending
>>> lst
[1, 2, 3, 4]
>>> lst.extend(lst2) # extending
>>> lst
[1, 2, 3, 4, 5, 6]
>>> lst + lst2 # addition
[1, 2, 3, 4, 5, 6, 5, 6]
Also note that list.append and list.extend operate in-place, so assigning the result to a variable will make that variable hold the value None.
Your example here is sort of misleading in the case of append.
>>> l1 = [1,2,3,4]
>>> l1.append([5])
>>> l1
[1, 2, 3, 4, [5]]
Append takes a single item and appends it to the end of the existing list. By passing in an iterable to append, you're adding another list (in this case) within a list.
extend takes an iterable and essentially calls append for each item in the iterable`, adding the items onto the end of the existing list.
The mylist + otherlist is the only interesting case here, as the result of using the + operator creates a new list, using more memory.
Timing them answers your question about efficiency in regards of speed:
import timeit
def first():
mylist + otherlist
def second():
mylist.append(otherlist)
def third():
mylist.extend(otherlist)
for test in (first, second, third):
mylist = [1, 2, 3, 4]
otherlist = [5]
print "%s: %f" % (test, timeit.timeit(test, number=1000000))
On my machine the result was:
<function first at 0x10ff3ba28>: 0.320835
<function second at 0x10ff3baa0>: 0.275077
<function third at 0x10ff3bb18>: 0.284508
Showing that the first example was clearly slowest.

Categories

Resources