Can I not change a list inside a function? - python

# left rotate using slicing
def leftRotate(arr, k, n):
arr = arr[k:] + arr[:k]
print(arr)
arr = [1, 2, 3, 4, 5, 6, 7]
leftRotate(arr, 2, 7)
print(arr)
Result:
[3, 4, 5, 6, 7, 1, 2]
[1, 2, 3, 4, 5, 6, 7]
When I print the array outside the function it is not rotated anymore and remains how it originally was. Can someone help me understand this?

Yes, you can change a list from within a function, but you need to use the correct syntax. As you've seen already, this is not the correct way:
def leftRotate(arr, k, n):
arr = arr[k:] + arr[:k]
I will try to explain why this did not work, and hope to give you a better intuition about what really happens. Inside the scope of the function shown above, there are 3 local variables: arr, k, and n. The right-hand side operations arr[k:] + arr[:k] creates a new list object, without modifying the original list, and this resulting object is bound to the local variable name arr. This does not modify the original object, because such assignment statements in Python are never mutating objects. They will only bind a name in a namespace. Think of it as if you're taking the nametag "arr" off of the old list object, which was passed in as argument, and sticking it on the new list object which was just created. The old list object is not modified by such an operation, only the local namespace is modified - the old list object becomes "anonymous" and is no longer reachable in this scope.
The solution is to use a different kind of assignment statement, a slice assignment, which does mutate:
def leftRotate(arr, k, n):
arr[:] = arr[k:] + arr[:k]
As a final note, there is a list-like data structure in stdlib which provides more efficient rotation operations (at the cost of less-efficient indexing into the middle of the collection). If you're interested in this, read the docs on the collections.deque.

The problem is list slicing is not being applied in place. Effectively a new list is created and assigned to a variable arr scoped to leftRotate, i.e. it can can be accessed within your function only. A method which does work in place will work as expected:
def rev_sort(arr, k, n):
arr.sort(reverse=True)
print(arr)
arr = [1, 2, 3, 4, 5, 6, 7]
rev_sort(arr, 2, 7)
print(arr)
[7, 6, 5, 4, 3, 2, 1]
[7, 6, 5, 4, 3, 2, 1]
In your example, you can have your function return a list and assign it to arr:
def leftRotate(arr, k, n):
arr = arr[k:]+arr[:k]
print(arr)
return arr
arr = [1, 2, 3, 4, 5, 6, 7]
arr = leftRotate(arr, 2, 7)
print(arr)
[3, 4, 5, 6, 7, 1, 2]
[3, 4, 5, 6, 7, 1, 2]

Your problem is because you can't change a variable inside a python function because of the scope. Read this for more info.
But resuming, you need to either return arr and assign it outside. Like this:
#left rotate using slicing
def leftRotate(arr, k, n):
arr=arr[k:]+arr[:k]
return arr
arr = [1, 2, 3, 4, 5, 6, 7]
arr = leftRotate(arr, 2, 7)
print arr
Or if you would like, you could make arr a global. (Check this for more info on that). (Don't recommend this last one, but exists)
arr = [1, 2, 3, 4, 5, 6, 7]
#left rotate using slicing
def leftRotate( k, n):
global arr
arr=arr[k:]+arr[:k]
leftRotate( 2, 7)
print arr
Hope it helped :)

There are a lot of really complicated answers. Here's the "for dummies" version:
You are passing arr into leftRotate()
For nearly all purposes, you can think of this as creating another variable, also called arr which leftRotate() works on. leftRotate()'s arr is not the same as the arr you are passing in to leftRotate(). It is a copy (technically it's not a copy until you assign arr to something else, but close enough for these purposes).
You're not getting your modified arr back out of leftRotate() again.
You can solve this in two ways:
Define arr outside of leftRotate(), and don't pass arr in. I'll call this the "global" approach. Not recommended unless you have a very good reason.
Use return arr after your function completes. Essentially, return 'x' means leftRotate() == 'x'. In 99.99999% of cases, this is what you want.
Therefore, in your example, what you really want is this:
#left rotate using slicing
def leftRotate(arr, k, n):
arr=arr[k:]+arr[:k] # not sure this is right, but once you get the return working, it should be easy to debug
# print arr # changed
return arr
arr = [1, 2, 3, 4, 5, 6, 7]
# leftRotate(arr, 2, 7) # changed
arr = leftRotate(arr, 2, 7)
print arr

Related

Recursion with list but I got some [..]

I tried to create a problem like [0,1,2,[0,1,2,[0,1,2,[0,1,2] ] ]... by using recursion it but it got some bugs that I don't know where it came from.
It creates an output like this:
[1, 2, 3, 4, 5, [...]]
So let's make an example x = [1,2,3,4,5] and len_ = len(x) for the recursion. The code will be like this:
list_ = [1,2,3,4,5]
n = len(list_)
dull = []
def BlastedField3(n):
global list_
list_ += [list_]
if n == 1:
return list_
if n > 1:
return list_
return BlastedFild(n-1)
print(BlastedField3(n))
So I was imagining that list_ += [list_] was something like per each recursion of the function the list_ += [list_] working too.But! it works but the bug [...] is coming up next to it.
Is there a way to solve this without this bug [...] or just try a loop?
The problem of creating a self-containing list can be solved by not using references but shallow copies and by not reusing global variables (whenever you feel the need for those, look at your design long and hard).
base = [1,2,3,4,5]
def blasted(n):
if n <= 1:
return base[:] # shallow copy to never accidentally expose your base
return base + [blasted(n-1)]
>>> blasted(1)
[1, 2, 3, 4, 5]
>>> blasted(2)
[1, 2, 3, 4, 5, [1, 2, 3, 4, 5]]
>>> blasted(3)
[1, 2, 3, 4, 5, [1, 2, 3, 4, 5, [1, 2, 3, 4, 5]]]
Actually the use of shallow copies is not strictly necessary as the code never mutates the base list. But I would still recommend it because if someone were to mutate any result of the blast, base might be affected.

List pass by reference confusion in Python

I have this snippet of code that just sorts a list of numbers that are guaranteed to be between 0 and R-1 (inclusive). The following code does the sort correctly but I don't understand why the input passed in remains unmodified.
def bucket(arr, R):
assert type(arr) is list
for i in arr:
assert i >=0 and i < R
b = [0] * R
for i in arr:
b[i]+=1
arr = []
for ind, v in enumerate(b):
arr = arr + [ind] * v
print(arr)
Why is inp in this example unchanged after the function has been called:
>>> inp
[3, 1, 4, 5, 4, 5, 5, 5, 1, 5]
>>> bucket(inp, 8)
[1, 1, 3, 4, 4, 5, 5, 5, 5, 5]
>>> inp # unchanged, why?
[3, 1, 4, 5, 4, 5, 5, 5, 1, 5]
Because you create a new variable called arr in the line arr = [] and from this point on you operate on a new list. Similarly you always create new lists inside the following for-loop with the arr = arr + [ind] * v operations.
You could simply change it to:
def bucket(arr, R):
assert type(arr) is list
for i in arr:
assert i >= 0 and i < R
b = [0] * R
for i in arr:
b[i] += 1
arr[:] = [] # remove all items from the list (in-place)
for ind, v in enumerate(b):
arr.extend([ind] * v) # extend the list in-place, you could also use "arr += [ind] * v"
print(arr)
Example:
>>> inp = [3, 1, 4, 5, 4, 5, 5, 5, 1, 5]
>>> bucket(inp, 8)
[1, 1, 3, 4, 4, 5, 5, 5, 5, 5]
>>> inp
[1, 1, 3, 4, 4, 5, 5, 5, 5, 5]
By assigning [] to arr you are losing the reference to the existing array, and creating a new one.
To change it, you could use
inp.sort()
More info on sort vs sorted
Python passes by assignment, and the semantics are extremely similar to Java's pass by value.
The confusion arises because you are passing the pointer by value. This means you cannot modify the pointer inside the function, but no one can stop you from modifying what the pointer is pointing to (i.e. the data) So, for example:
x = 3
def change_x(x):
x = 5
return x
change_x(x)
print x
# Outputs 3
The value of x outside the function (in this case, 3) was copied before x entered the function, so the assignment does nothing. This is typically what we expect when we hear pass by value.
x = [1,2,3]
print(id(x))
# Outputs some "address"
def change_x(x):
x.clear()
x.extend([4,5,6])
print(id(x))
# Outputs the SAME address as above
return x
change_x(x)
print(x)
# Outputs [4,5,6]
What the heck. I thought we just said we couldn't change x. The catch is we didn't change x! We just changed the data x is pointing to (there is a subtle, but important difference here). x is still the same pointer. Note that the addresses output by id are the same.
x = [1,2,3]
print(id(x))
# Outputs some "address"
def change_x(x):
x = [4,5,6]
print(id(x))
# Outputs some DIFFERENT "address". The pointer to x was changed (inside this function).
return x
change_x(x)
print(x)
# Outputs [1,2,3]
It copied the value of x before it was passed in (the pointer value, not the data, was copied). So, again, reassignment does nothing. Note that the id function outputs different values this time. So after the function returns, we get the original value of x (the original value of the pointer, that is)

Pythonic way to append output of function to several lists

I have a question that I haven't quite found a good solution to. I'm looking for a better way to append function output to two or more lists, without using temp variables. Example below:
def f():
return 5,6
a,b = [], []
for i in range(10):
tmp_a, tmp_b = f()
a.append(tmp_a)
b.append(temp_b)
I've tried playing around with something like zip(*f()), but haven't quite found a solution that way.
Any way to remove those temp vars would be super helpful though, thanks!
Edit for additional info:
In this situation, the number of outputs from the function will always equal the number of lists that are being appended to. The main reason I'm looking to get rid of temps is for the case where there are maybe 8-10 function outputs, and having that many temp variables would get messy (though I don't really even like having two).
def f():
return 5,6
a,b = zip(*[f() for i in range(10)])
# this will create two tuples of elements 5 and 6 you can change
# them to list by type casting it like list(a), list(b)
First solution: we make a list of all results, then transpose it
def f(i):
return i, 2*i
# First make a list of all your results
l = [f(i) for i in range(5)]
# [(0, 0), (1, 2), (2, 4), (3, 6), (4, 8)]
# then transpose it using zip
a, b = zip(*l)
print(list(a))
print(list(b))
# [0, 1, 2, 3, 4]
# [0, 2, 4, 6, 8]
Or, all in one line:
a, b = zip(*[f(i) for i in range(5)])
A different solution, building the lists at each iteration, so that you can use them while they're being built:
def f(i):
return 2*i, i**2, i**3
doubles = []
squares = []
cubes = []
results = [doubles, squares, cubes]
for i in range(1, 4):
list(map(lambda res, val: res.append(val), results, f(i)))
print(results)
# [[2], [1], [1]]
# [[2, 4], [1, 4], [1, 8]]
# [[2, 4, 6], [1, 4, 9], [1, 8, 27]]
print(cubes)
# [1, 8, 27]
Note about list(map(...)): in Python3, map returns a generator, so we must use it if we want the lambda to be executed.list does it.
For your specific case, the zip answers are great.
Using itertools.cycle and itertools.chain is a different approach from the existing answers that might come in handy if you have a lot of pre-existing lists that you want to append to in a round-robin fashion. It also works when your function returns more values than you have lists.
>>> from itertools import cycle, chain
>>> a, b = [], [] # new, empty lists only for demo purposes
>>> for l, v in zip(cycle([a, b]), (chain(*(f() for i in range(10))))):
... l.append(v)
...
>>> a
[5, 5, 5, 5, 5, 5, 5, 5, 5, 5]
>>> b
[6, 6, 6, 6, 6, 6, 6, 6, 6, 6]
I'd do
tmp = f()
a.append(tmp[0])
b.append(tmp[1])
Not sure how pythonic it is for you though.

Shifting the elements of an array in python

I am trying to shift the elements of an array cyclically so all elements are replaced with the previous element, and the last rotates to the first poaition, like so: shift(1, [5, 6, 7])=>[7, 5, 6].
The following code only returns [7,5]. Could someone please tell me what is causing this to happen? I went through the code step by step and simply could not find a solution. I also tried 3 different interpreters.
def shift(key, array):
counter = range(len(array)-1)
new = counter
for i in counter:
new[i] = array[i-key]
return new
print shift(1, [5, 6, 7])
range(5) returns [0, 1, 2, 3, 4]. It excludes 5.
Just remove the -1 from range(len(array)-1) and it should work.
You could also use list slicing:
def shift(key, array):
return array[-key:] + array[:-key]
Here is the python way:
def shift(key, array):
return array[-key:]+array[:-key]
You need to remove the -1 from your range:
counter = range(len(array))
If you want a faster method though,
You could instead try using a deque?
from collections import deque
def shift(key, array):
a = deque(array) # turn list into deque
a.rotate(key) # rotate deque by key
return list(a) # turn deque back into a list
print (shift(1, [5, 6, 7]))
The answers are good, but it doesn't work if the key is greater than the length of the array. If you think the key will be larger than the array length, use the following:
def shift(key, array):
return array[key % len(array):] + array[:key % len(array)]
A positive key will shift left and a negative key will shift right.
The numpy package contains the roll function to perform exactly this task:
import numpy as np
b=[5,6,7]
c=np.roll(b,1).tolist()
>>> c
[7, 5, 6]
A function using this and returning a list is:
def shift(array,key):
return np.roll(array,key).tolist()
#!/usr/bin/env python
def ashift(key,array):
newqueue = array[-key:]
newqueue.extend( array[:-key] )
return newqueue
print ashift( 1, [5,6,7] )
print ashift( 2, [5,6,7] )
Results in:
$ ./shift
[7, 5, 6]
[6, 7, 5]
The only potential penalty is if the array is sufficiently large, you may encounter memory issues, as this operation is doing a copy. Using a "key" with an absolute value greater than the length of the array will result in wrapping and results may not be as expected, but will not error out.
Good old fashioned POP & APPEND
arr = [5, 6, 7]
for _ in range(0, 2):
shift = arr.pop(0)
arr.append(shift)
print(arr)
=>[7, 5, 6]
You can use numpy roll
>>> x = np.arange(10)
>>> np.roll(x, 2)
array([8, 9, 0, 1, 2, 3, 4, 5, 6, 7])
>>> np.roll(x, -2)
array([2, 3, 4, 5, 6, 7, 8, 9, 0, 1])

What value do I use in a slicing range to include the last value in a numpy array?

Imagine some numpy array, e.g. x = np.linspace(1,10).
x[i:j] gives me a view into x for the range [i,j).
I love that I can also do x[i:-k] which excludes the last k elements.
However, in order to include the last element I need to do x[i:].
My question is this: How do I combine these two notations if I for instance need to loop over k.
Say that I want to do this:
l = list()
for k in [5,4,3,2,1]:
l.append(x[:-k])
l.append(x[:])
What annoys me is that last line. In this simple example of course it doesn't do much of a difference, but sometimes this becomes much more annoying. What I miss is something more DRY-like.
The following snippet course does NOT yield the desired result, but represents the style of code I seek:
l = list()
for k in [5,4,3,2,1,0]:
l.append(x[:-k])
It's a bit of a pain, but since -0 is the same as 0, there is no easy solution.
One way to do it would be:
l = list()
for k in [5,4,3,2,1,0]:
l.append(x[:-k or None])
This is because when k is 0, -k or None is None, and x[:None] will do what you want. For other values of k, -k or None will be -k.
I am not sure if I like it myself though.
You can't, because -0 doesn't slice that way in python (it becomes 0)
You could just do the old school:
l = list()
for k in [5,4,3,2,1,0]:
l.append(x[:len(x)-k])
The value None, in a slice, is the same as putting nothing there. In other words, x[:None] is the same as x[:]. So:
l = list()
for k in [-5,-4,-3,-2,-1,None]:
l.append(x[:k])
However… this code is a lot easier to write as a list comprehension:
l = [x[:k] for k in (-5,-4,-3,-2,-1,None)]
Or… you might want to look at whatever it is you're trying to do and see if there's a higher-level abstraction that makes sense, or maybe just another way to organize things that's more readable (even if it's a bit more verbose). For example, depending on what x actually represents, this might be more understandable (or it might be less, of course):
l = []
for k in range(6):
l.insert(0, x)
x = x[:-1]
Perhaps this, then:
l = list()
for k in [5,4,3,2,1,None]:
l.append(x[:-k if k else None])
If x is simply range(10), the above code will produce:
[[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4, 5],
[0, 1, 2, 3, 4, 5, 6],
[0, 1, 2, 3, 4, 5, 6, 7],
[0, 1, 2, 3, 4, 5, 6, 7, 8],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]]

Categories