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)
Related
please guys, this function is meant to swap a group of items in a list, like the first k items with the last k items, but I dont know how to get the function to work
def swap(L, k):
L[len(L)-k:], L[:k] = L[:k], L[len(L)-k:]
yet when I run this with doctest, it fails
swap([1, 2, 3, 4, 5, 6],2)
Fix the indexes, return a list from the method, and print the resulting list:
def swap(L, k):
return L[(-k):] + L[k:(-k)] + L[:k]
lst = swap([1, 2, 3, 4, 5, 6], 2)
print(lst)
# [5, 6, 3, 4, 1, 2]
Note that the original list is not modified - what is returned is a different list with elements swapped.
The function works perfectly, but you have no other references to the list you're passing in, so it gets discarded. Simply save the list to a name before passing it in.
By the way, the len(L) is unnecessary.
def swap(L, k):
"""
>>> lst = [1, 2, 3, 4, 5, 6]
>>> swap(lst, 2)
>>> lst
[5, 6, 3, 4, 1, 2]
"""
L[-k:], L[:k] = L[:k], L[-k:]
def swap(L, k):
J = L[:k]
L[:k] = L[-2:]
L[-2:] = J
P = [1,2,3,4,5,6]
swap(P,2)
# P ==[5, 6, 3, 4, 1, 2]
# 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
I would like to append to a new list all elements of an existing list of lists after a specific point
m = [[1,2,3],[4,5,10],[6,2,1]]
specific point = m[0][2]
newlist = [3,4,5,10,6,2,1]
You can directly slice off the remainder of the first target list and then add on all subsequent elements, eg:
m = [[1,2,3],[4,5,10],[6,2,1]]
y, x = 0, 2
new_list = m[y][x:] + [v for el in m[y+1:] for v in el]
# [3, 4, 5, 10, 6, 2, 1]
Here's a couple of functional approaches for efficiently iterating over your data.
If sublists are evenly sized, and you know the index from where to begin extracting elements, use chain + islice:
from itertools import chain, islice
n = 3 # Sublist size.
i,j = 0,2
newlist = list(islice(chain.from_iterable(m), i*n + j, None))
If you don't know the size of your sublists in advance, you can use next to discard the first portion of your data.
V = chain.from_iterable(m)
next(v for v in V if v == m[i][j])
newlist = list(V)
newlist.insert(m[i][j], 0)
This assumes there is no identical value earlier in the sequence.
You can put a conditional in your iteration and only add based on that condition. Once you hit that specific index, make your condition true. Something like this:
m = [[1,2,3],[4,5,10],[6,2,1]]
specific_point = (0,2)
newlist = [3,4,5,10,6,2,1]
output = []
for i in range(len(m)):
for j in range(len(m[i])):
if (i,j) < specific_point:
continue
output.append(m[i][j])
output:
[3, 4, 5, 10, 6, 2, 1]
why not flatten the initial list and go from there
flat_list = [item for sublist in m for item in sublist]
would return [1,2,3,4,5,10,6,2,1] so now you're really on flat_list[2:]
Most of the answers only work for this specific shape of nested list, but it's also possible to create a solution that works with any shape of nested list.
def flatten_from(sequence, path=[]):
start = path.pop(0) if path else 0
for item in sequence[start:]:
if isinstance(item, (list, tuple)):
yield from flatten_from(item, path)
else:
yield item
With the example from the question
>>> list(flatten_from([[1, 2, 3], [4, 5, 10], [6, 2, 1]], [0, 2]))
[3, 4, 5, 10, 6, 2, 1]
It also works with any shape and level of nesting of the input data
m = [[1], [[2], [3, 4, 5, 6, 7]], 8, [9, [10, 11]]]
flatten_from(m, [])) # 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
flatten_from(m, [2]) # 8, 9, 10, 11
flatten_from(m, [1, 1, 3]) # 6, 7, 8, 9, 10, 11
This is a bit of a bastard algorithm, though. On one hand, it uses nice functional programming concepts: recursion and yield.
On the other hand it relies on the side effect of mutating the path argument with list.pop, so it's not a pure function.
Below solution will work for your case where your array is restricted to list of list and the size of 'sublist' is consistent throughout i.e "3" in your case
m = [[1,2,3],[4,5,10],[6,2,1]] #input 2D array
a, b = 0, 2 #user input --> specific point a and b
flat_list_m = [item for firstlist in m for item in firstlist] #flat the 2D list
print (flat_list_m[len(m[0])*a+b:]) #print from specific position a and b, considering your sublist length is consistent throughout.
I hope this helps! :)
My problem is that I have a list, for example
l =[1, 2, 3, 4, 5, 15]
and I would like to divide it in two lists, list1 that would have a single element of the actual list which should be the sum of all other numbers in the list, and list2 containing rest. So the output for this would be ([1, 2, 3, 4, 5], [15]) if its possible if not, return False.
This is one way, though not necessarily optimal. It uses the, in my opinion underused, for...else... construct.
I've also reversed the range iterator. This is more efficient in the case you provided.
l = [1, 2, 3, 4, 5, 15]
def splitter(l):
for i in reversed(range(len(l))):
if sum(l[:i]) == sum(l[i:]):
return [l[:i], l[i:]]
else:
return False
splitter(l) # [[1, 2, 3, 4, 5], [15]]
Should it be possible for the positions of the values to change in the list? If not you can try an iteration such as:
l = [1, 2, 3, 4, 5, 15]
dividable = "False"
x = 0
while dividable == "False":
l1 = l[0:x]
l2 = l[x:len(l)]
if sum(l1) == sum(l2):
dividable = "True"
elif x == len(l):
#not possible
break
else:
x += 1
This answer should help in all cases.
No imports required and no sorting required for the data.
def split_list(l):
dividable=False
index=0
for i in range(len(l)):
if l[i]==sum(l)-l[i]:
dividable=True
index=i
break
if dividable:
l1=l[index]
l.remove(l[index])
return (l1,l)
else:
return False
Might not be the optimised way, but a better and clear way to understand for beginners.
split_list([1,2,3,4,5,15])
[15],[1,2,3,4,5]
Hope this helps. Thanks
what about this?
l =[1, 2, 3, 4, 5, 15]
l=sorted(l)
track=[]
for i in l:
track.append(i)
if sum(track) in l and len(track)==len(l[1:]):
print(track,[sum(track)])
output:
[1, 2, 3, 4, 5], [15]
You need to do a couple of steps:
1) Sort the list from small to large. (Into a new list if you don't want to alter the original)
2) Sum every other element of the list and see if it's equal.
3) If false return false
4) if true:
Store the last (biggest) value in a variable and delete this from the duplicate of the original list.
Make a second list with only that last value in it.
Create another new list and add the altered duplicate list and the list made of the biggest element.
Return the last created list.
Then you're done
Brute force:
import itertools
x = [1, 2, 3, 4, 5, 15]
for size in range(1,len(x)):
for sublist in itertools.combinations(x, size):
comp = x[:]
for n in sublist:
comp.remove(n)
if sum(comp) == sum(sublist):
print(comp, sublist)
[1, 2, 3, 4, 5] (15,)
[15] (1, 2, 3, 4, 5)
This approach can handle duplicated numbers.
Using numpy:
def split(l):
c = np.cumsum(l)
idx = np.flatnonzero(np.equal(l, c[-1] / 2.0))
return (l[:idx[0]], l[idx[0]:]) if idx.size > 0 else False
Alternatively, if using Python > 3.2:
import itertools
def split(l):
c = list(itertools.accumulate(l))
h = c[-1] / 2.0
if h in c:
i = l.index(h)
return l[:i], l[i:]
return False
Finally, if you want to use "pure" Python (no imports):
def split(l):
c = [sum(l[:k]) for k in range(1, len(l) + 1)]
h = c[-1] / 2.0
if h in c:
i = l.index(h)
return l[:i], l[i:]
return False
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