Memoization Usage/Cache Storing - python

I am writing a program that calculates the Pascal Identity of two variables, hard coded into the program, as I am new into Python and trying out caching and memoization. Here is what I have so far:
counter = 0
call_cacheK = {}
def callTest(n, k):
global counter
if n in call_cacheK:
return call_cacheK[n]
if k == 0:
return 1
elif k == n:
return 1
elif (1 <= k) and (k <= (n-1)):
counter += 1
#call_cacheK[n] = result
result = ((callTest(n-1, k) + callTest(n-1, k-1)))
print(result)
return result
callTest(20, 11)
#167,960
My function will output the final real answer with what it has now, but with a lot of outputted answers. I cannot seem to get how to properly store the values to be used in the cache.
How do I properly use call_cacheK to store the result values I have already used?
Thank you.

Lets see. First, you have a function of two variables, but store result in cache only by one parameter. So callTest(20, 11), callTest(20, 10), callTest(20, 9) will have one result in your cache. Lets rewrite your function a little:
call_cacheK = {}
def callTest(n, k):
if (n, k) in call_cacheK:
return call_cacheK[(n, k)]
if k == 0:
return 1
elif k == n:
return 1
elif (1 <= k) and (k <= (n-1)):
result = ((callTest(n-1, k) + callTest(n-1, k-1)))
call_cacheK[(n, k)] = result
print(result)
return result
Yes there is no counter variable because I didn't realize why do you need it :)
Also, as far as I can judge by the use of print(result), you probably use the Python3.x. If so, you can use standard cache implementing:
from functools import lru_cache
#lru_cache(maxsize=None)
def callTest2(n, k):
if k == 0:
return 1
elif k == n:
return 1
elif (1 <= k) and (k <= (n-1)):
result = ((callTest2(n-1, k) + callTest2(n-1, k-1)))
print(result)
return result
Good luck! :)

Related

Implementing memoization within a Collatz algorithm (Python)

I am trying to perform a Collatz algorithm on the following code. It works fine when I use a range of 1-10 etc... However, if the range is for example 1-500,000 it's too slow and won't ever show me the output of the longest sequence.
numberArray = []
s=int(1)
f=int(10)
def collatz(n):
global count
if n == 1:
count += 1
numberArray.append(count)
return True
elif (n % 2) == 0:
count += 1
collatz(n/2)
else:
count += 1
collatz(3*n+1)
for i in range (s, f+1):
count = 0
ourNumber = i
collatz(i)
print(max(numberArray))
Stef means something like this, which uses a dictionary to memorise the values that have already been counted:
s = 1
f = 10000000
def collatz(n):
if n in collatz.memory:
return collatz.memory[n]
if (n % 2) == 0:
count = collatz(n//2)+1
else:
count = collatz((3*n+1)//2)+2
collatz.memory[n] = count
return count
collatz.memory = {1:0}
highest = max(collatz(i) for i in range(s, f+1))
highest_n = max(collatz.memory, key=collatz.memory.get)
print(f"collatz({highest_n}) is {highest}")
Output:
collatz(8400511) is 685
Use lru_cache decorator. Its function to memorize (cache) the returned value of function called with specific argument.
Also read how to write clean code in python
The next code solves your problem
from functools import lru_cache
number_array = []
s = 1
f = 500000
#lru_cache
def collatz(n: int):
if n == 1:
return 1
elif n % 2 == 0:
return 1 + collatz(n // 2)
else:
return 1 + collatz(3 * n + 1)
for i in range(s, f + 1):
number_array.append(collatz(i))
print(number_array)

Python: memoization for the recursion

Suppose we have the following recursion formula
I wrote the following code based on that recursion,
def f(k):
if(k <= 2):
return k
elif(k % 2 ==0):
return 5*f(k/2) + 1
else:
return f((k-1)/2) + 2
Is there a way to make this implementation faster and more efficient?
something like this
storage = dict()
def f(k):
if(k <= 2):
return k
elif k in storage:
return storage[k]
elif(k % 2 ==0):
storage[k] = 5*f(k/2) + 1
return storage[k]
else:
storage[k] = f((k-1)/2) + 2
return storage[k]
You should use integer divisions because results will always be integers. Also, you don't need the parentheses around conditions.
There is a decorator in the functools module to automatically cache (memoize) your function:
from functools import lru_cache
#lru_cache()
def f(k):
if k <= 2:
return k
elif k % 2 == 0:
return 5 * f(k//2) + 1
else:
return f((k-1)//2) + 2
Note that this function converges extremely rapidly (exponentially?). The cache may not improve it much.

In Python, how do I return a value from a function when an `if` statement is executed?

In this function, I want to return a value when the if statement is executed.
Since Python always returns something, it returns None.
def Persistence(number, counter):
numArr = [int(i) for i in str(number)]
result = 1
data = None
for j in numArr:
result *= j
if len(str(number)) == 1:
data = str(f'Done. {counter} steps taken')
print(data)
# return data
else:
counter = counter + 1
Persistence(result, counter)
# return data
print(Persistence(333,0))
Maybe I put the return keyword in the wrong place (I checked by putting it in two different places, marked as a comment) or something else, but I couldn't figure it out.
Please help me out. Also, if there is another way to count the recursion steps besides my technique, please let me know.
Maybe try this :
def Persistence(number, counter):
numArr = [int(i) for i in str(number)]
result = 1
for j in numArr:
result *= j
if len(str(number)) == 1:
return str(f'Done. {counter} steps taken')
counter = counter + 1
return Persistence(result, counter)
print(Persistence(333,0))
Hope it helps
The issue is that you're not setting the value from the else call to persistence to anything. The following code return the data value for me:
def Persistence(number, counter):
numArr = [int(i) for i in str(number)]
result = 1
data = None
for j in numArr:
result *= j
if len(str(number)) == 1:
data = str(f'Done. {counter} steps taken')
print(data)
return data
else:
counter = counter + 1
data = Persistence(result, counter)
return data
x = Persistence(333, 0)
Then if we print x:
print(x)
# Done. 3 steps taken
Your logic to count the recursion steps is basically correct, you just need to place the return statements for both the:
1)Base case
2)The recursive call itself
The following modification to your code will do the trick of what you are asking:
def Persistence(number, counter):
numArr = [int(i) for i in str(number)]
result = 1
data = None
for j in numArr:
result *= j
if len(str(number)) == 1:
data = str(counter)
return data
else:
counter = counter + 1
return Persistence(result, counter)
print(Persistence(333,0))
The above code will return the output of:
3
Please note that the reason you were getting "None" as the output in your original code is because you were not making a return at the actual recursive call itself: **return** Persistence(result, counter)
So when you ran print(Persistence(333,0)) it was returning nothing resulting in the None.
This question is better to learn about recursion than you may think. But we won't muddy the waters by mixing recursion (functional style) with statements and side effects (imperative style).
It looks like you're trying to calculate multiplicative root and persistence. Instead of putting all concerns of the computation into a single function, break it down into sensible parts -
def digits (n = 0):
if n < 10:
return [ n ]
else:
return digits (n // 10) + [ n % 10 ]
def product (n = 0, *more):
if not more:
return n
else:
return n * product (*more)
def mult_root (n = 0):
if n < 10:
return [ n ]
else:
return [ n ] + mult_root (product (*digits (n)))
def mult_persistence (n = 0):
return len (mult_root (n)) - 1
print (mult_persistence (333))
# 3
print (mult_root (333))
# [ 333, 27, 14, 4 ]

Error: Unsupported Operand Types

I'm trying to use recursion to return the dot product of two lists, and I'm trying to account for the situation in which I get two lists of different length: I return 0. However, when I try to check for that condition, I get the error: unsupported operand type(s) for &: 'list' and 'list'. Why can't I use the '&' operand for two lists in Python?
def dot(L, K):
if L+K == []:
return 0
elif L == [] & K != []:
return 0
elif K == [] & L != []:
return 0
else:
return L[-1] * K[-1] + dot(L[:-1], K[:-1])
I would probably do something like this:
def dot(L, K):
if L + K == [] or len(L) != len(K): # this only needs to be checked once
return 0
return dot_recurse(L, K)
def dot_recurse(L, K):
if len(L) > 0:
return L[-1] * K[-1] + dot_recurse(L[:-1], K[:-1])
else:
return 0;
def dot(L, K):
if len(L)!=len(K): # return 0 before the first recursion
return 0
elif not L: # test if L is [] - previous test implies K is [] so no need to retest
return 0
else:
return L[-1] * K[-1] + dot(L[:-1], K[:-1])
Your code is a bit more complicated than it really needs to be. It is not possible to take the dot product of two vectors which are not the same size. There are a couple of ways to deal with receiving vectors of different sizes.
1) Lop off the remaining unused numbers from the larger vector. Below is a modified version of your function. I changed it to only require one check for if either of the vectors is empty (there is no need to check this in multiple ways), and also changed it to start from the beginning of the vectors instead of the end. Was there a particular reason you started from the end?
def dot(L, K):
if(L == [] or K == []):
return 0
else:
return L[0] + K[0] + dot(L[1:], K[1:])
While this option works, it does not give the user any indication that they made a mistake in attempting to dot product two different sized vectors.
2) Give the user an error upon receiving two different sized vectors.
def dot(L, K):
if(len(L) != len(K)):
print('Vector sizes do not match, please pass two same-sized vectors')
return 0 #not sure exactly how you are wanting to do error handling here.
elif(L == [] or K == []):
return 0
else:
return L[0] + K[0] + dot(L[1:], K[1:])
If you check out python's Operator Precedence you will see that & has lower precedence than == and and
This means you are doing the following:
if (L == ([] & K)) != []:
...
As suggested by Tuan333 you should be using and.
def dot(L, K):
if L+K == []:
return 0
elif L == [] and K != []:
return 0
elif K == [] and L != []:
return 0
else:
return L[-1] * K[-1] + dot(L[:-1], K[:-1])
However if you wanted to use & (which is the Binary AND, and isn't the same thing) you could just use () to force precedence
def dot(L, K):
if L+K == []:
return 0
elif (L == []) & (K != []):
return 0
elif (K == []) & (L != []):
return 0
else:
return L[-1] * K[-1] + dot(L[:-1], K[:-1])
If you're curious why & is likely not what you want read on:
AND takes two values, converts them to Booleans (True or False) and check that both are True
Binary AND (&) takes two values, converts them to a Number-like value, then performs an operation on their bits
Here is how I would implement this function
def dot(L, K):
if len(L) != len(K):
# Ensure the lists are the same length
raise ValueError('Can not perform dot product on two differently sized lists')
elif len(L) + len(K) == 0:
# See if we've reached the base case
return 0
else:
# Recurse doing dot product
return L[-1] * K[-1] + dot(L[:-1], K[:-1])
print(dot([6, 2, 6], [5, 1]))

Alternating counters in Python (Fibonacci Plot)

I've been assigned a project in my computing class to do a report on some area of mathematics in LaTeX, using Python 2.7 code - I chose the Fibonacci sequence.
As part of my project I wanted to include a plot of the Fibonacci 'spiral' which is actually comprised of a series of quarter-circles of increasing radii. As such, I've tried to define a function to give a loop that returns the centres of these quarter-circles so I can create a plot. Using pen and paper I have found the centres of each quarter-circle and noticed that with each new quarter-circle there's an exchange of coordinates - ie. if n is even, the x-coordinate of the previous centre remains the x-coordinate for the nth centre; similarly, when n is odd, the y-coordinate remains the same.
My problem arises with the other coordinate. They work on an alternating pattern of + or - the (n-2)th Fibonacci number to the y-coordinate (for even n) or x-coordinate (for odd) of the previous centre.
I've created the following loop in SageMathCloud, but I think I've deduced that my counters aren't incrementing when I wanted them to:
def centrecoords(n):
k = 0
l = 1
if fib(n) == 1:
return tuple((0,-1))
elif n % 2 == 0 and k % 2 == 0:
return tuple((centrecoords(n-1)[0], centrecoords(n-1)[1] + ((-1) ** k) * fib(n - 2)))
k += 1
elif n % 2 == 0:
return tuple((centrecoords(n-1)[0], centrecoords(n-1)[1] + ((-1) ** k) * fib(n - 2)))
elif n % 2 != 0 and l % 2 == 0:
return tuple((centrecoords(n-1)[0] + ((-1) ** l) * fib(n - 2), centrecoords(n-1)[1]))
l += 1
else:
return tuple((centrecoords(n-1)[0] + ((-1) ** l) * fib(n - 2), centrecoords(n-1)[1]))
cen_coords = []
for i in range(0, 21):
cen_coords.append(centrecoords(i))
cen_coords
Any help in making the k counter increment with its if statement only, and the same with the l counter would be greatly appreciated.
Your problem is that k and l are local variables. As such they are lost every time the function exits, and re-start at zero and one respectively when is called again (yes, even when it's called from itself).
Nick's code aims to store a single instance each of k and l in the top-level function, sharing them with the recursive calls.
Another reasonable approach might be to rewrite your recursion as a loop, and yield the sequence. This makes it trivial to keep the state of k and l, as your locals are preserved.
Or, you could re-write your function as a class method, and make k and l instance variables. This behaves similarly, with the instance storing your intermediate state between calls to centrecoords.
Apart from all of these, your code looks like it requires each call to centrecoords to receive the next value of n. So, even if you fix the state problem, this is a poor design.
I'd suggest going the generator route, and taking a single argument, the maximum value of n. Then you can iterate over range(n), yielding each result in turn. Note also that your only recursive call is for n-1, which is just your previous iteration, so you can simply remember it.
Quick demo: I haven't tested this, or checked the corner cases ...
def fib(n):
if n < 2:
return 1
return fib(n-1) + fib(n-2)
def centrecoords(max_n):
# initial values
k = 0
l = 1
result=(0,-1)
# note fib(0) == fib(1) == 1
for n in range(2,max_n):
if n % 2 == 0:
result = (result[0], result[1] + ((-1) ** k) * fib(n - 2))
yield result
if k % 2 == 0:
k += 1
else:
result = (result[0] + ((-1) ** l) * fib(n - 2), result[1])
yield result
if l % 2 == 0:
l += 1
cen_coords = list(centrecoords(21))
Expanding on my comment. Your code could look something like the one below. But please not that you might need to adjust starting values of k and l to -1 and 0 correspondingly, because k and l are incremented before recursion calls (opposite to your code which implied that first a recursion is called and only then k and l are increased).
I also deleted tuple, it is unnecessary in python and hard to read, to create a tuple use comma syntax, e.g.: 1, 2.
Also n == 0 (fib(n) == 0) should be considered as special case, or you program will enter infinite recursion and crash when centrecoords called with n=0.
I have no account on SageMathCloud to test it, but it at least should fix counters increment.
def centrecoords(n, k=0, l=1):
if n == 0:
return 0, 0 # this is pure guess and most likely incorrect, but n == 0 (or fib(n) == 0 should be handled separatly)
if fib(n) == 1:
return 0, -1
elif n % 2 == 0 and k % 2 == 0:
k += 1
return centrecoords(n-1, k, l)[0], centrecoords(n-1, k, l)[1] + ((-1) ** k) * fib(n - 2)
elif n % 2 == 0:
return centrecoords(n-1, k, l)[0], centrecoords(n-1, k, l)[1] + ((-1) ** k) * fib(n - 2)
elif n % 2 != 0 and l % 2 == 0:
l += 1
return centrecoords(n-1, k, l)[0] + ((-1) ** l) * fib(n - 2), centrecoords(n-1, k, l)[1]
else:
return centrecoords(n-1, k, l)[0] + ((-1) ** l) * fib(n - 2), centrecoords(n-1, k, l)[1]
cen_coords = []
for i in range(0, 21):
cen_coords.append(centrecoords(i))
cen_coords

Categories

Resources