Find minimum and maximum with recursion efficiently - python

Is there a way to to recursively find both minimum and maximum in a list efficiently? I wrote this with python, but it's hugely badly efficient, as is call the function with the same list both for max and both for min each time.
def f(l):
if len(l)==1 : return [l[0],l[0]]
return [max(l[0],f(l[1:])[0]),min(l[0],f(l[1:])[1])]
l=[1,3,9,-3,-30,10,100]
print(f(l))
output: [100, -30]
--
Have you any idea on how to improve it? Is it possible to do it even without passing any other variable to the function?

In Python, a recursive implementation will in any case be much slower than iterative one because of:
call overhead
object creation, incl. partial list construction
not using some of Python's efficient constructs like for .. in loop
You cannot eliminate the former if you're specifically required to do a recursive algorithm, but you can cut on object construction. The list construction is especially taxing since all the elements are copied each time.
instead of constructing a new list each iteration, pass the same list and the current index in it
and in your function, you are constructing a new list not once but twice!
You're also making two recursive calls each iteration. Each of them will also make two calls etc, resulting in a total number of calls a whopping 1+2+4+...+2**(N-1) = 2**N-1! To add insult to injury, the two calls are completely redundant since they both produce the same result.
since the current list element is used multiple times, a few microsecods can also be cut off by caching it in a variable instead of retrieving each time.
def rminmax(l,i=0,cmin=float('inf'),cmax=float('-inf')):
e=l[i]
if e<cmin: cmin=e
if e>cmax: cmax=e
if i==len(l)-1:
return (cmin,cmax)
return rminmax(l,i+1,cmin,cmax)
Also note that due to CPython's stack size limit, you won't be able to process lists longer than a number slightly lower than sys.getrecursionlimit() (slightly lower because the interactive loop machinery also takes up some call stack frames). This limitation may not apply in other Python implementations.
Here's some performance comparison on my machine on sample data:
In [18]: l=[random.randint(0,900) for _ in range(900)]
In [29]: timeit rminmax(l)
1000 loops, best of 3: 395 µs per loop
# for comparison:
In [21]: timeit f(l) #your function
# I couldn't wait for completion; definitely >20min for 3 runs
In [23]: timeit f(l) #sjf's function
100 loops, best of 3: 2.59 ms per loop

I am not sure why you want to use recursion to find the min and max as you can simply pass a list to min and max.
def f(l):
return min(l), max(l)
If you are trying to do this as an exercise in recursion, I don't see a way to solve it without passing the min and max down the recursive call.
def f(l, min_=None, max_=None):
if not l:
return min_,max_
min_ = l[0] if min_ is None else min(l[0], min_)
max_ = l[0] if max_ is None else max(l[0], max_)
return f(l[1:], min_, max_)

There is a way to do this (And recursion in python really is very slow; see the other answers if you want a robust implementation). Think about your recursive formulation from left to right: at each level of recursion, take the min/max of the current item in the list and the result returned from the next level of recursion.
Then (for python>= 2.5, we can use the ternary operator):
def find_min(ls, idx):
return ls[idx] if idx == len(ls) - 1 else min(ls[idx], find_min(ls, idx+1))
find_max is analogous; you can just replace min with max.
If you want a simpler definition, you can wrap a function that only accepts ls around find_min/find_max and make that function call find_min(ls, 0) or find_max(ls, 0).

Why recursively ?
This would work fine and is about 10 times faster than the best recursive algorithm:
def minMax(array): return min(array),max(array)
To avoid having each recursion call itself twice, you could write the function like this:
def minMax(array):
first,*rest = array # first,rest = array[0],array[1:]
if not rest : return first,first
subMin,subMax = minMax(rest)
return min(first,subMin), max(first,subMax)
If you want to avoid the maximum recursion limit (i.e. on large list) you could use a binary approach splitting the array in left and right parts. this will only use log(n) levels of recursion (and also reduce some of the processing overhead):
def minMax(array):
size = len(array)
if size == 1 : return array[0],array[0]
midPoint = size // 2
leftMin,leftMax = minMax(array[:midPoint])
rightMin,rightMax = minMax(array[midPoint:])
return min(leftMin,rightMin), max(leftMin,rightMin)
If you want to reduce the overhead of array creation and function calls, you could pass down the index and avoid min(),max() and len() (but then you're using recursion as a for loop which pretty much defeats the purpose):
def minMax(array, index=None):
index = (index or len(array)) - 1
item = array[index]
if index == 0 : return item,item
subMin,subMax = minMax(array,index)
if item < subMin: return item,subMax
if item > subMax: return subMin,item
return subMin,subMax
You can combine the previous two to reduce overhead and avoid recursion limit, but it is going lose a bit of performance:
def minMax(array, start=0, end=None):
if end is None : end = len(array)-1
if start >= end - 1:
left,right = array[start],array[end]
return (left,right) if left < right else (right,left)
middle = (start + end) >> 1
leftMin,leftMax = minMax(array, start,middle)
rightMin,rightMax = minMax(array, middle+1,end)
return ( leftMin if leftMin < rightMin else rightMin ), \
( leftMax if leftMax > rightMax else rightMax )

Related

Yield in recursion multiplayer

i just wrote this function and got error from the interpreter "RecursionError: maximum recursion depth exceeded in comparison"
is it possible to use yield in this recursion?
def multiplyer(fir, sec):
if(sec==1):
return fir
else:
return fir+multiplyer(fir, sec-1)
print(multiplyer(5, 2983))
There is no need to use yield at all (and as far as I know, it will not work anyway). Your multiplayer function is simply equivalent to:
def multiplayer(fir,sec):
return fir*sec
furthermore yield will not make much difference since it will still result in a recursion error: after all you will still perform calls 2983 deep (which is usually too much for the call stack). Python also does not support tail recursion optimization (TRO).
yield is used when you want use a generator. A generator produces several (it can be zero, one or more) values. Here however you need a single value (and you need it immediately). Say that you however use yield like:
def multiplyer(fir, sec):
if(sec==1):
yield fir
else:
yield fir+next(multiplyer(fir, sec-1))
print(next(multiplyer(5, 2983)))
it will not make any difference: you will still do the recursion and reach the bound.
You run out of stack space since you let the function call itself 2983 times, which means you store that number of return addresses and arguments on the stack, which is just not reasonable.
If your requirement is to use recursion, you can reduce the recursion depth to O(logn) order by doing this:
def multiplyer(fir, sec):
if sec==1:
return fir
elif sec==0:
return 0
else:
return multiplyer(fir, sec//2) + multiplyer(fir, (sec+1)//2)
print(multiplyer(5, 2983))
Or more efficiently, also reducing the number of recursive calls to O(logn) order:
def multiplyer(fir, sec):
if sec==0:
return 0
elif sec%2 == 0:
return 2 * multiplyer(fir, sec//2)
else:
return fir + 2 * multiplyer(fir, sec//2)
print(multiplyer(5, 2983))
When dealing with recursion, you can check the maximum allowed recursion depth like:
import sys
sys.getrecursionlimit()
and the nice thing is you can also set it:
sys.setrecursionlimit(3000)
However, this says nothing about your code or if it is optimal for what you are trying to achieve.
Don't use recursion in Python where iteration will do. Generators optimize for memory usage, nothing else. They are slower than the alternatives, and do not provide any workarounds for the global recursion limit (which is actually a limit on the size of the call stack; non-recursive calls count towards the limit as well).
# Just demonstrating the conversion from recursion to iteration.
# fir * sec would be the most efficient solution.
def multiplyer(fir, sec):
rv = 0
while sec > 0:
rv += fir
sec -= 1
return rv

Simple Merge Sort bug in Python

I'm doing a Merge Sort assignment in Python, but I keep have the error of RuntimeError: maximum recursion depth exceeded
Here's my code:
def merge_sort(list):
left_num = len(list) // 2
left_sorted = merge_sort(list[:left_num])
right_sorted = merge_sort(list[left_num:])
final_sort = merge(left_sorted, right_sorted)
return final_sort
def merge(left_sorted, right_sorted):
final_sort = []
while left_sorted and right_sorted:
if left_sorted[0] <= right_sorted[0]:
final_sort.append(left_sorted[0])
left_sorted.pop(0)
else:
final_sort.append(right_sorted[0])
right_sorted.pop(0)
final_sort = final_sort + left_sorted + right_sorted
return final_sort
if __name__ == "__main__":
list = [4, 2]
print(merge_sort(list))
Can someone tell me why? To make the problem more usable to others, feel free to edit the question to make it make more sense. ^_^
When you write a recursive function, you should be careful about the base case, which decides when the recursion should come to an end.
In your case, the base case is missing. For example, if the list has only one element, then you don't have recursively sort it again. So, that is your base condition.
def merge_sort(list):
if len(list) == 1:
return list
...
...
Note: The variable name list shadows the builtin function list. So better avoid using builtin names.
Since you are doing lot of pop(0)s, its worth noting that it is not efficient on lists. Quoting Python's official documentation,
Though list objects support similar operations, they are optimized for fast fixed-length operations and incur O(n) memory movement costs for pop(0) and insert(0, v) operations which change both the size and position of the underlying data representation.
So, the better alternative would be to use collections.deque, instead of list, if you are popping a lot. The actual popping from a deque is done with popleft method.
>>> from collections import deque
>>> d = deque([4, 2])
>>> d.popleft()
4
>>> d
deque([2])
You don't have an exit point in merge_sort. You need to do something like:
left_num = len(list) // 2
if left_num <= 1:
return list
You always need to have a conditional exit in recursion function: if COND then EXIT else RECURSION_CALL.

What is the big-Oh runtime of two recursive O(logn) calls?

def f(L):
if len(L) < 1 billion:
return L
else:
return f(L[:len(L) // 2]) + f(L[len(L) // 2:])
L is a list of size n
I know that if it was a single recursive call, then it would be O(logn), but there are two recursive calls here.
But it started to exhibit more of a O(n) runtime as I began to run it on a visualizer.
In my opinion it should be O(logn+logn) = O(2logn) = O(logn). Am I correct?
Consider how many calls you're doing. At the first level of the recursion you'll do 2 calls. For each of those you'll do two more calls. Etc ... This means that at level i of the recursion you'll have made a total of O(2^i) function calls.
How many levels of the recursion are there? This is just the height of a binary tree with n elements, which is O(log_2 n).
So by the time you reach all the leaves of the recursion you will have done O(2^(log_2 n)) = O(n) function calls.
--
Another way of looking at it is that you eventually have to piece back together the entire list, so how could you possibly do that in less than O(n) time?
Your algorithm as it stands is going to be O(n) if len(L) is at least 1 billion because you will break the list into two, and then add the two halves back together. Both slicing and adding are O(n) operations.
If you want to test the runtime of the two recursive calls,
1. Pass in a start and end index, and call
f(L, start, start+(end-start)//2) + f(L, start+(end-start)//2, end)
2. Return end-start or some other O(1) value when end-start is less than 1 billion

How to Convert Recursion to Tail Recursion

Is it always possible to convert a recursion into a tail recursive one?
I am having a hard time converting the following Python function into a tail-recursive one.
def BreakWords(glob):
"""Break a string of characters, glob, into a list of words.
Args:
glob: A string of characters to be broken into words if possible.
Returns:
List of words if glob can be broken down. List can be empty if glob is ''.
None if no such break is possible.
"""
# Base case.
if len(glob) == 0:
return []
# Find a partition.
for i in xrange(1, len(glob) + 1):
left = glob[:i]
if IsWord(left):
right = glob[i:]
remaining_words = BreakWords(right)
if remaining_words is not None:
return [left] + remaining_words
return None
I'n not sure if is always the case, but most of recursive functions can be implemented as tail recursives. Besides Tail Recursion is different from Tail Recursion optimization.
Differences Tail Recursion and "Regular" ones
There are two elements that must be present in a recursive function:
The recursive call
A place to keep count of the return values.
A "regular" recursive function keeps (2) in the stack frame.
The return values in regular recursive function are composed of two types of values:
Other return values
Result of the owns function computation
Let's see a example:
def factorial(n):
if n == 1 return 1
return n * factorial(n-1)
The frame f(5) "stores" the result of it's own computation (5) and the value of f(4), for example. If i call factorial(5), just before the stack calls begin to colapse, i have:
[Stack_f(5): return 5 * [Stack_f(4): 4 * [Stack_f(3): 3 * ... [1[1]]
Notice that each stack stores, besides the values i mentioned, the whole scope of the function. So, the memory usage for a recursive function f is O(x), where x is the number of recursive calls i have to made. So, if i needb 1kb of RAM to calculate factorial(1) or factorial(2), i need ~100k to calculate factorial(100), and so on.
A Tail Recursive function put (2) in it's arguments.
In a Tail Recursion, i pass the result of the partial calculations in each recursive frame to the next one using parameters. Let's see our factorial example, Tail Recursive:
def factorial(n):
def tail_helper(n, acc):
if n == 1 or n == 2: return acc
return tail_helper(n-1, acc + n)
return tail_helper(n,0)
Let's look at it's frames in factorial(4):
[Stack f(4, 5): Stack f(3, 20): [Stack f(2,60): [Stack f(1, 120): 120]]]]
See the differences? In "regular" recursive calls the return functions recursively compose the final value. In Tail Recursion they only reference the base case (last one evaluated). We call accumulator the argument that keeps track of the older values.
Recursion Templates
The regular recursive function go as follows:
def regular(n)
base_case
computation
return (result of computation) combined with (regular(n towards base case))
To transform it in a Tail recursion we:
Introduce a helper function that carries the accumulator
run the helper function inside the main function, with the accumulator set to the base case.
Look:
def tail(n):
def helper(n, accumulator):
if n == base case:
return accumulator
computation
accumulator = computation combined with accumulator
return helper(n towards base case, accumulator)
helper(n, base case)
Your example:
I did something like this:
def BreakWords(glob):
def helper(word, glob, acc_1, acc_2):
if len(word) == 0 and len(glob) == 0:
if not acc_1:
return None
return acc
if len(word) == 0:
word = glob.pop[0]
acc_2 = 0
if IsWord(word.substring[:acc_2]):
acc_1.append(word[:acc_2])
return helper(word[acc_2 + 1:], glob, acc_1, acc_2 + 1)
return helper(word[acc_2 + 1:], glob, acc_1, acc_2 + 1)
return helper("", glob, [], 0)
In order to eliminate the for statement you made, i did my recursive helper function with 2 accumulators. One to store the results, and one to store the position i'm currently trying.
Tail Call optimization
Since no state is being stored on the Non-Border-Cases of the Tail Call stacks, they aren't so important. Some languages/interpreters then substitute the old stack with the new one. So, with no stack frames constraining the number of calls, the Tail Calls behave just like a for-loop.
But unfortunately for you Python isn't one of these cases. You'll get a RunTimeError when the stack gets bigger than 1000. Mr. Guido
thinks that the clarity lost to debugging purposes due to Tail Call Optimization (caused by the frames thrown awy) is more important than the feature. That's a shame. Python has so many cool functional stuff, and tail recursion would be great on top of it :/

Halt a recursively called function

I'm trying to halt the for loop below once values (x,y) or (z,2) have been returned so that the value i doesn't keep increasing, and simply halts when the if or elif condition is first
def maxPalindrome(theList):
# students need to put some logic here
maxcomplist = theList[:]
maxcomplist.reverse()
control = len(theList) - 1
# exit if maxPalindrome is True
for i in range(control):
if maxcomplist[:] == theList[:]:
x = 0
y = len(theList)
return (x, y)
break
elif maxcomplist[i:control] == theList[i:control]:
successList = theList[i:control]
z = i
w = len(theList) - z - 1
return (z, w)
How can I accomplish this?
As I wrote in a comment already: that function isn't a recursive one at all.
Recursion means, that a function calls itself to complete it's purpose. This call can be indirect, meaning that the function uses helper function that will call the first function again.
But your code doesn't cover both cases.
A recursive function always have a certain architecture:
the first thing after being called should be to test, if the primitive case (or one primitive case among options) has been reached. if so, it returns.
If not it will compute whatever is needed and pass this results to itself again,
untill the primitive case is reached, and the nested function calls will finish in one after the other.
One well-known usage of recursion is the quicksort algorithm:
def quicksort(alist):
if len(alist) < 2:
return alist # primitive case: a list of size one is ordered
pivotelement = alist.pop()
# compute the 2 lists for the next recursive call
left = [element for element in alist if element < pivotelement]#left = smaller than pivotelemet
right = [element for element in alist if element >= pivotelement]#left = greater than pivotelemet
# call function recursively
return quicksort(left) + [pivotelement] + quicksort(right)
So the "stop" must be the return of a primitive case. This is vital for recursion. You cannot just break out somehow.
I don't understand the question - if I get it right, that what you want already happens. If you return, the function stops running.
Some comments in addition to this answer:
As well, I cannot see where the function is called recursively, nor what
exit if maxPalindrome is True
means. (Is this a comment, maybe?)
Besides, the maxcomplist[:]==theList[:] does not make much sense to me, and seem to be a waste of time and memory, and to have this comparison in each iteration loop doesn't make it faster as well.

Categories

Resources