Why Won't Python Won't Do a Lot of Recursion? - python

I'm doing the Project Euler problems, and I'm on number two. The question is:
Each new term in the Fibonacci sequence is generated by adding the previous two terms. By starting with 1 and 2, the first 10 terms will be:
1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...
By considering the terms in the Fibonacci sequence whose values do not exceed four million, find the sum of the even-valued terms.
I'm trying to solve this in python. I think I have the correct code, but for some reason When I run it with n being anything greater than or equal to 27, it will wait like a minute and just return 0. However, for anything 26 or lower, it runs fine. Here's my code:
def fib_seq(n):
if n == 0:
return n
elif n == 1:
return n
else:
return fib_seq(n-1) + fib_seq(n-2)
def get_fib_sum(n):
x = n
sum = 0
for i in range(n):
if fib_seq(x) > 4000000:
pass
elif fib_seq(x) % 2 == 0:
pass
else:
sum += fib_seq(x)
x = i
return sum
print get_fib_sum(27)
Is there anyway to fix this or at least get it to work? If it makes a difference, I'm using Wing IDE 101 Student Edition.

In your loop, you are using fib_seq(x) and it should be fib_seq(i)
Also, if you want to reduce time a bit more, you can use memoization technique
def fib_seq(n):
if n == 0:
return n
elif n == 1:
return n
else:
return fib_seq(n-1) + fib_seq(n-2)
def memoize(fn, arg):
memo = {}
if arg not in memo:
memo[arg] = fn(arg)
return memo[arg]
fibm = memoize(fib_seq,27)
print fibm

Why are you using recursion? your code is recalculating the ENTIRE fibonnaci sequence over and over and over and over and over... The code just wants the sum of the even terms. There is NO need for recursion. In pseudo-code:
t1 = 1
t2 = 2;
sum = 2;
do {
t3 = t1 + t2;
if (t3 is even) {
sum += t3;
}
t1 = t2;
t2 = t3;
} while (t2 <= 4000000)

Fibonacci sequence is often used as an example of how to write recursive code, which is ridiculous because it has a very straight-forward iterative solution:
def fib(n):
if n < 2:
return n
else:
a, b = 1, 1
for _ in range(2, n): # O(n)
a, b = b, a+b
return b
What is less obvious is that it also has a matrix representation,
F = [[0, 1]] # initial state
T = [[0, 1], # transition matrix
[1, 1]]
fib(n) = (F * T**n)[0][0]
which is extremely useful because T**n can be computed in O(log(n)) steps.
(As an aside, the eigenvector of the log of the transition matrix leads to the analytic solution,
phi = (1 + 5**0.5) / 2 # golden ratio
fib(n) = round(phi**n / 5**0.5, 0)
but that's not where I'm going with this.)
Looking at the terms produced in terms of odd-or-even, you see
n: 0, 1, 2, 3, 4, 5, 6, 7, 8, ...
f(n): 0, 1, 1, 2, 3, 5, 8, 13, 21, ...
e/o: even, odd, odd, even, odd, odd, even, odd, odd, ...
so what you need is fib(0) + fib(3) + fib(6) + ... and computing T**3 gives you the coefficients needed to step directly from term to term.
The rest is left as an exercise for the reader ;-)

It does a lot of recursion, that's why it's taking so long.
The get_fib_sum() will evaluate fib_seq(27) in a loop, which does a lot of recursion and takes a while. Since the result of fib_seq(27) is greater then 4000000 it will then will never add anything to sum, returning 0 in the end.

Related

Faster way to find the biggest prime number less than or equal to the given input

Given an integer number, I want to find the biggest prime number under it. For example:
input 20 -> output 19
input 100 -> output 97.
I already have the simple program given below, but I'm curious about how to make it faster.
def isPrime(x):
for j in range(2,int(x**0.5)+1):
if x%j==0:
return False
return True
def findPrimeNum(num):
for i in range(num-1,1,-1):
if isPrime(i):
return i
findPrimeNum(600851475143) # -> 600851475067
The best way is probably to mirror what several number theory libraries use to implement an efficient next_prime function. The basic layout is identical to yours: an isPrime(x) function, and a loop counting down. Those provide two areas for optimization.
Optimizing isPrime
The recommended approach is to use a number theory library for Python like gmpy2, and a probabilistic primality test like Miller-Rabin repeated an appropriate number of times. This scales fairly well with larger numbers, and you can even get a deterministic primality test by using Miller-Rabin with enough small bases.
Optimizing iteration over possible primes
There is already a comprehensive guide for optimizing next_prime in practice, which basically involves choosing the first k primes (e.g. 2, 3 and 5) and computing all residues modulo their product that could potentially be a prime. Then, rather than iterating over all smaller numbers, you jump between those residues only. To adapt this to previousPrime, we just jump between residues in the opposite direction.
For 2, 3, and 5, those residues mod 30 are L = [1, 7, 11, 13, 17, 19, 23, 29]. For an integer x > 30, say x = 30*q + r, you would do a binary search over L for the largest element less than r, and iterate backwards in this list:
residues = [1, 7, 11, 13, 17, 19, 23, 29]
small_primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
def prevPrime(num):
if num <= 2:
return 0
q, r = divmod(num, 30)
if q == 0:
return small_primes[bisect_left(small_primes, num) - 1]
num = 30 * q
if r <= 2:
num -= 30
q -= 1
idx = len(residues) - 1
else:
idx = bisect_left(residues, r) - 1
num += residues[idx]
while not (isPrime(num)):
idx -= 1
if idx < 0:
q -= 1
idx = len(residues) - 1
num = 30 * q + residues[idx]
return num

List of Fibonacci Numbers using recursion

I want to create a function that makes a so called super Fibonacci sequence which is a list of numbers, that from the third term onwards, every term is the sum of all the previous terms. It takes 2 arguments: t2 and n. t2 is the second term in the list and n is the number of terms in the list.
For example.
superFibSeq(10,10) >>> [1,10,11,22,44,88,176,352,704,1408]
superFibSeq(4,10) >>> [1, 4, 5, 10, 20, 40, 80, 160, 320, 640]
I've been stuck on this for a bit with no idea where to start. How should I think about this if I want to use only recursion.
Someone above gave negative points for saying recurion in this problem is really strange. You want recursion, sure.
def f(t2, n):
if n == 0:
return []
elif n == 1:
return [1]
elif n == 2:
return [1, t2]
else:
temp = f(t2, n - 1)
return temp + [sum(temp)]
This algorithm is O(n^2) rather than O(n)
It sounds like recursion is a really strange idea for this program.
Your terms are:
1, t2, (1 + t2), 2(1 + t2), 4(1 + t2), 8(1 + t2), .... 2**(n-2)(1 + t2)

Optimization of python code for calculating list of squared divisors

I was participating in a python challenge in codewars website. I encountered the following challenge:
Divisors of 42 are : 1, 2, 3, 6, 7, 14, 21, 42. These divisors squared are: 1, 4, 9, 36, 49, 196, 441, 1764. The sum of the squared divisors is 2500 which is 50 * 50, a square!
Given two integers m, n (1 <= m <= n) we want to find all integers between m and n whose sum of squared divisors is itself a square. 42 is such a number.
The result will be an array of arrays, each subarray having two elements, first the number whose squared divisors is a square and then the sum of the squared divisors.
The output should be:
list_squared(1, 250) --> [[1, 1], [42, 2500], [246, 84100]]
list_squared(42, 250) --> [[42, 2500], [246, 84100]]
list_squared(250, 500) --> [[287, 84100]]
I have written following code with two additional functions: one corresponding to determine all factors of a number and other checking if a number is perfect square or not.
Function to determine all factors:
def fact(m):
return [i for i in range(1,m+1) if m%i == 0]
Function to check if a number is perfect square and return 0 if it is not otherwise return square root
def square_root(x):
ans = 0
while ans < x // 2 + 1:
ans = ans + 1
if ans*ans == x:
return ans
break;
return 0
Function where the desired result is calculated
def list_squared(m, n):
# your code
fac=[]
for i in range(m,n+1):
sq_sum = sum(list(map(lambda x: x**2,fact(i))))
if square_root(sq_sum) != 0:
fac.append([i,sq_sum])
return fac
This code gives me the correct result, however it is too long. I was able to pass all the test results but it took me around 6000 ms. When I attempted to submit the code, the web submission returns that the algorithm is inefficient and it took more than 1200 ms which is the maximum.
I would highly appreciate if anyone can point to a better algorithm for this.
There are several optimizations to your code but the biggest one is to stop when ans*ans becomes bigger than x here:
def square_root(x):
ans = 0
while True:
ans += 1
sqans = ans*ans
if sqans == x:
return ans
elif sqans > x:
return 0
The condition in the while can be removed, since now the test is done on the square value.
with that optimization, I drop from 8 seconds to 0.07 seconds with the 250, 500 case.
But that's stil not satisfactory. In general, algorithms containing a condition to break or return are at least O(n) and even if you can save time, the complexity is too high.
You can do better by simply checking the square of the rounded square root:
def square_root(x):
ans = int(x**0.5 + 0.5) # rounded just in case it goes below the actual value (float inaccuracy)
sqans = ans*ans
return 0 if sqans !=x else x
I divide the execution time by further 2 with that (confirmed by Optimized way to find if a number is a perfect square)
Aside (that doesn't speed up that much but worth mentionning):
no need to convert map to list in sum:
sq_sum = sum(map(lambda x: x**2,fact(i)))
Also fact could avoid looping to the max number. Loop to the max number divided by 2 and add the max number to the list is equivalent. No more divisors exist above max number / 2
def fact(m):
return [i for i in range(1,m//2+1) if m%i == 0] + [m]
Final edit: this is still slow because of the list comprehension used in fact. I could cut the time drastically by using a generator instead and add m*m outside it:
def sqfact(m):
return (i*i for i in range(1,m//2+1) if m%i == 0)
final code, now runs so fast I get 0 seconds.
def sqfact(m):
return (i*i for i in range(1,m//2+1) if m%i == 0)
def square_root(x):
ans = int(x**0.5 + 0.5)
return 0 if ans*ans !=x else x
def list_squared(m, n):
# your code
fac=[]
for i in range(m,n+1):
sq_sum = sum(sqfact(i)) + i*i # add i square outside
if square_root(sq_sum):
fac.append([i,sq_sum])
return fac
I have updated the fact function which was very inefficient. Now, rather than iterating to full value of m to find its factors, I am only going up to sqrt(m). This has reduced the run time immensely. The logic behind this is trivial so I am not elaborating. Following is the new code which worked for me.
def fact(m):
#determining the lower factors i.e., smaller than sqrt(m)
fac = [i for i in range(1, int(m**0.5) + 1) if m%i == 0]
#determining the higher factors i.e., larger than sqrt(m)
fac = fac + [m//i for i in range(1, int(m**0.5) + 1) if m%i == 0]
return sorted(list(set(fac))) #in order to get rid of duplicate factors

How to search for combination of digits in number (optimizing for speed)?

I'm trying to look for the number of combinations of 7 digit numbers (or more, actually need it to work for 10, but its faster to test with 7) that have 1,3,5,7 in it. Tried a few different methods like using
combinations = 0
for combination in itertools.product(xrange(10), repeat=7):
if all(x in combination for x in (1,3,5,7)):
combinations += 1
However, this next method worked out to be about 4 times faster as it doesnt look for 3,5,7 if 1 is not in the list.
combinations = 0
for combination in itertools.product(xrange(10), repeat=7):
if 1 in combination:
if 3 in combination:
if 5 in combination:
if 7 in combination:
combinations += 1
I'm sure there is a more cleaver way to achieve this result with numpy or something like that, but I can't figure it out.
Thanks for feedback
The problem is to find k-digit numbers that contain all the digits 1, 3, 5, 7.
This answer contains a number of solutions, increasing in sophistication and algorithmic efficiency. By the end, we'll be able to, in a fraction of a second, count solutions for huge k, for example 10^12, modulo a large prime.
The section at the end includes tests that provide good evidence that all the implementations are correct.
Brute force: O(k10^k) time, O(k) space
We'll use this slow approach to test the more optimized versions of the code:
def contains_1357(i):
i = str(i)
return all(x in i for x in '1357')
def combos_slow(k):
return sum(contains_1357(i) for i in xrange(10 ** k))
Counting: O(k^4) time, O(k) space
The simplest moderately efficient method is to count. One way to do this is to count all k-digit numbers where the first occurrences of the four special digits appear at digits a, b, c, d.
Given such an a, b, c, d, the digits up to a must be 0,2,4,6,8,9, the digit a must be one of [1, 3, 5, 7], the digits between a and b must be either the same as the digit a or any of the safe digits, the digit b must be one of [1, 3, 5, 7] that's different from the digit at a, and so on.
Summing over all possible a, b, c, d gives the result. Like this:
import itertools
def combos0(k):
S = 0
for a, b, c, d in itertools.combinations(range(k), 4):
S += 6 ** a * 4 * 7**(b-a-1) * 3 * 8**(c-b-1) * 2 * 9**(d-c-1) * 10**(k-d-1)
return S
Dynamic programming: O(k) time, O(k) and then O(1) space
You can solve this more efficiently with dynamic programming: let c[j][i] be the number of i-digit numbers which contain exactly j different digits from (1, 3, 5, 7).
Then c satisfies these recurrence relations:
c[0][0] = 1
c[j][0] = 0 for j > 0
c[0][i] = 6 * c[0][i-1] for i > 0
c[j][i] = (6+j)c[j][i-1] + (5-j)c[j-1][i-1] for i, j > 0
The final line of the recurrence relations is the hardest one to understand. The first part (6+j)c[j][i-1] says that you can make an i digit number containing j of the digits 1, 3, 5, 7 from a i-1 digit number containing j of the digits 1, 3, 5, 7, and add an extra digit that's either 0, 2, 4, 6, 8, 9 or any of the digits you've already got. Similarly, the second part (5-j)c[j-1][i-1] says that you can take an i-1 digit number containing j-1 of the digits 1, 3, 5, 7 and make it an i-digit number containing j of the special digits by adding one of the digits you haven't already used. There's 5-j of these.
That leads to this O(k) solution using dynamic programming:
def combos(k):
c = [[0] * (k + 1) for _ in xrange(5)]
c[0][0] = 1
for i in xrange(1, k+1):
c[0][i] = 6 * c[0][i-1]
for j in xrange(1, 5):
c[j][i] = (6 + j) * c[j][i-1] + (5-j) * c[j-1][i-1]
return c[4][k]
We can print combos(10):
print 'combos(10) =', combos(10)
This gives this output:
combos(10) = 1425878520
The solution above is already fast enough to compute combos(10000) in a fraction of a second. But it's possible to optimize the DP solution a little to use O(1) rather than O(k) space by observing that values of c depend only on the previous column in the table. With a bit of care (to make sure that we're not overwriting values before they're used), we can write the code like this:
def combos2(k):
c = [1, 0, 0, 0, 0]
for _ in xrange(k):
for j in xrange(4, 0, -1):
c[j] = (6+j)*c[j] + (5-j)*c[j-1]
c[0] *= 6
return c[4]
Matrix power: O(log k) time, O(1) space.
Ultimately, it's possible to get the result in O(log k) time and O(1) space, by expressing the recurrence relation as a matrix-by-vector multiply, and using exponentiation by squaring. That makes it possible to compute combos(k) modulo X even for massive k (here combos(10^12) modulo 2^31 - 1). That looks like this:
def mat_vec(M, v, X):
return [sum(M[i][j] * v[j] % X for j in xrange(5)) for i in xrange(5)]
def mat_mul(M, N, X):
return [[sum(M[i][j] * N[j][k] for j in xrange(5)) % X for k in xrange(5)] for i in xrange(5)]
def mat_pow(M, k, X):
r = [[i==j for i in xrange(5)] for j in xrange(5)]
while k:
if k % 2:
r = mat_mul(r, M, X)
M = mat_mul(M, M, X)
k //= 2
return r
def combos3(k, X):
M = [[6, 0, 0, 0, 0], [4, 7, 0, 0, 0], [0, 3, 8, 0, 0], [0, 0, 2, 9, 0], [0, 0, 0, 1, 10]]
return mat_vec(mat_pow(M, k, X), [1, 0, 0, 0, 0], X)[4]
print combos3(10**12, (2**31) - 1)
Given that your original code struggled for k=10, this is quite an improvement!
Testing
We can test each of the functions against each other (and combos_slow for small values). Since combos3 has an extra arg, we wrap it in a function that passes a modulo that's guaranteed to be larger than the result.
def combos3p(k):
return combos3(k, 10**k)
for c in [combos0, combos, combos2, combos3p]:
for i in xrange(40 if c == combos0 else 100):
assert c(i) == (combos_slow if i < 7 else combos)(i)
This tests all the implementations against combos_slow for i<7, and against each other for 7 <= i < 100 (except for the less efficient combos0 which stops at 40).

Finding shortest combinations in array/sequence that equals sum

I'm totally stuck and have no idea how to go about solving this. Let's say I've an array
arr = [1, 4, 5, 10]
and a number
n = 8
I need shortest sequence from within arr which equals n. So for example following sequences within arr equals n
c1 = 5,1,1,1
c2 = 4,4
c3= 1,1,1,1,1,1,1,1
So in above case, our answer is c2 because it's shortest sequences in arr that equals sum.
I'm not sure what's the simplest way of finding a solution to above? Any ideas, or help will be really appreciated.
Thanks!
Edited:
Fixed the array
Array will possibly have postive values only.
I'm not sure how subset problem fixes this, probably due to my own ignorance. Does sub-set algorithm always give the shortest sequence that equals sum? For example, will subset problem identify c2 as the answer in above scenario?
As has been pointed before this is the minimum change coin problem, typically solved with dynamic programming. Here's a Python implementation solved in time complexity O(nC) and space complexity O(C), where n is the number of coins and C the required amount of money:
def min_change(V, C):
table, solution = min_change_table(V, C)
num_coins, coins = table[-1], []
if num_coins == float('inf'):
return []
while C > 0:
coins.append(V[solution[C]])
C -= V[solution[C]]
return coins
def min_change_table(V, C):
m, n = C+1, len(V)
table, solution = [0] * m, [0] * m
for i in xrange(1, m):
minNum, minIdx = float('inf'), -1
for j in xrange(n):
if V[j] <= i and 1 + table[i - V[j]] < minNum:
minNum = 1 + table[i - V[j]]
minIdx = j
table[i] = minNum
solution[i] = minIdx
return (table, solution)
In the above functions V is the list of possible coins and C the required amount of money. Now when you call the min_change function the output is as expected:
min_change([1,4,5,10], 8)
> [4, 4]
For the benefit of people who find this question in future -
As Oscar Lopez and Priyank Bhatnagar, have pointed out, this is the coin change (change-giving, change-making) problem.
In general, the dynamic programming solution they have proposed is the optimal solution - both in terms of (provably!) always producing the required sum using the fewest items, and in terms of execution speed. If your basis numbers are arbitrary, then use the dynamic programming solution.
If your basis numbers are "nice", however, a simpler greedy algorithm will do.
For example, the Australian currency system uses denominations of $100, $50, $20, $10, $5, $2, $1, $0.50, $0.20, $0.10, $0.05. Optimal change can be given for any amount by repeatedly giving the largest unit of change possible until the remaining amount is zero (or less than five cents.)
Here's an instructive implementation of the greedy algorithm, illustrating the concept.
def greedy_give_change (denominations, amount):
# Sort from largest to smallest
denominations = sorted(denominations, reverse=True)
# number of each note/coin given
change_given = list()
for d in denominations:
while amount > d:
change_given.append(d)
amount -= d
return change_given
australian_coins = [100, 50, 20, 10, 5, 2, 1, 0.50, 0.20, 0.10, 0.05]
change = greedy_give_change(australian_coins, 313.37)
print (change) # [100, 100, 100, 10, 2, 1, 0.2, 0.1, 0.05]
print (sum(change)) # 313.35
For the specific example in the original post (denominations = [1, 4, 5, 10] and amount = 8) the greedy solution is not optimal - it will give [5, 1, 1, 1]. But the greedy solution is much faster and simpler than the dynamic programming solution, so if you can use it, you should!
This is problem is known as Minimum coin change problem.
You can solve it by using dynamic programming.
Here is the pseudo code :
Set MinCoin[i] equal to Infinity for all of i
MinCoin[0] = 0
For i = 1 to N // The number N
For j = 0 to M - 1 // M denominations given
// Number i is broken into i-Value[j] for which we already know the answer
// And we update if it gives us lesser value than previous known.
If (Value[j] <= i and MinCoin[i-Value[j]]+1 < MinCoin[i])
MinCoin[i] = MinCoin[i-Value[j]]+1
Output MinCoin[N]
This is an variant of subset-sum problem. In your problem, you can pick an item several times. You still can use a similar idea to solve this problem by using the dynamic prorgamming technique. The basic idea is to design a function F(k, j), such that F(k, j) = 1 means that there is a sequence from arr whose sum is j and length is k.
Formally, the base case is that F(k, 1) = 1, if there exists an i, such that arr[i] = k. For inductive case, F(k, j) = 1, if there exists an i, such that arr[i] = m, and F(k-1, j-m) = 1.
The smallest k with F(k, n) = 1 is the length of the shortest sequence you want.
By using the dynamic programming technique, you can compute function F without using recursion.
By tracking additional information for every F(k, j), you also can reconstruct the shortest sequence.
What you're trying to solve is a variant of the coin change problem. Here you're looking for smallest amount of change, or the minimum amount of coins that sum up to a given amount.
Consider a simple case where your array is
c = [1, 2, 3]
you write 5 as a combination of elements from C and want to know what is the shortest such combination. Here C is the set of coin values and 5 is the amount for which you want to get change.
Let's write down all possible combinations:
1 + 1 + 1 + 1 + 1
1 + 1 + 1 + 2
1 + 2 + 2
1 + 1 + 3
2 + 3
Note that two combinations are the same up to re-ordering, so for instance 2 + 3 = 3 + 2.
Here there is an awesome result that's not obvious at first sight but it's very easy to prove. If you have any sequence of coins/values that is a sequence of minimum length that sums up to a given amount, no matter how you split this sequence the two parts will also be sequences of minimum length for the respective amounts.
For instance if c[3] + c[1] + c[2] + c[7] + c[2] + c[3] add up to S and we know that 6 is the minimal length of any sequence of elements from c that add up to S then if you split
|
S = c[3] + c[1] + c[2] + c[7] | + c[2] + c[3]
|
you have that 4 is the minimal length for sequences that add up to c[3] + c[1] + c[2] + c[7] and 2 the minimal length for sequences that add up to c[2] + c[3].
|
S = c[3] + c[1] + c[2] + c[7] | + c[2] + c[3]
|
= S_left + S_right
How to prove this? By contradiction, assume that the length of S_left is not optimal, that is there's a shorter sequence that adds up to S_left. But then we could write S as a sum of this shorter sequence and S_right, thus contradicting the fact that the length of S is minimal. □
Since this is true no matter how you split the sequence, you can use this result to build a recursive algorithm that follows the principles of dynamic programming paradigm (solving smaller problems while possibly skipping computations that won't be used, memoization or keeping track of computed values, and finally combining the results).
Because of this property of maintaining optimality for subproblems, the coins problem is also said to "exhibit optimal substructure".
OK, so in the small example above this is how we would go about solving the problem with a dynamic programming approach: assume we want to find the shortest sequence of elements from c = [1, 2, 3] for writing the sum 5. We solve the subproblems obtained by subtracting one coin: 5 - 1, 5 - 2, and 5 - 3, we take the smallest solution of these subproblems and add 1 (the missing coin).
So we can write something like
shortest_seq_length([1, 2, 3], 5) =
min( shortest_seq_length([1, 2, 3], 5-1),
shortest_seq_length([1, 2, 3], 5-2),
shortest_seq_length([1, 2, 3], 5-3)
) + 1
It is convenient to write the algorithm bottom-up, starting from smaller values of the sums that can be saved and used to form bigger sums. We just solve the problem for all possible values starting from 1 and going up to the desired sum.
Here's the code in Python:
def shortest_seq_length(c, S):
res = {0: 0} # res contains computed results res[i] = shortest_seq_length(c, i)
for i in range(1, S+1):
res[i] = min([res[i-x] for x in c if x<=i]) + 1
return res[S]
Now this works except for the cases when we cannot fill the memoization structure for all values of i. This is the case when we don't have the value 1 in c, so for instance we cannot form the sum 1 if c = [2, 5] and with the above function we get
shortest_seq_length([2, 3], 5)
# ValueError: min() arg is an empty sequence
So to take care of this issue one could for instance use a try/catch:
def shortest_seq_length(c, S):
res = {0: 0} # res contains results for each sum res[i] = shortest_seq_length(c, i)
for i in range(1, S+1):
try:
res[i] = min([res[i-x] for x in c if x<=i and res[i-x] is not None]) +1
except:
res[i] = None # takes care of error when [res[i-x] for x in c if x<=i] is empty
return res[S]
Or without try/catch:
def shortest_seq_length(c, S):
res = {0: 0} # res[i] = shortest_seq_length(c, i)
for i in range(1, S+1):
prev = [res[i-x] for x in c if x<=i and res[i-x] is not None]
if len(prev)>0:
res[i] = min(prev) +1
else:
res[i] = None # takes care of error when [res[i-x] for x in c if x<=i] is empty
return res[S]
Try it out:
print(shortest_seq_length([2, 3], 5))
# 2
print(shortest_seq_length([1, 5, 10, 25], 37))
# 4
print(shortest_seq_length([1, 5, 10], 30))
# 3
print(shortest_seq_length([1, 5, 10], 25))
# 3
print(shortest_seq_length([1, 5, 10], 29))
# 7
print(shortest_seq_length([5, 10], 9))
# None
To show not only the length but also the combinations of coins of minimal length:
from collections import defaultdict
def shortest_seq_length(coins, sum):
combos = defaultdict(list)
combos[0] = [[]]
for i in range(1, sum+1):
for x in coins:
if x<=i and combos[i-x] is not None:
for p in combos[i-x]:
comb = sorted(p + [x])
if comb not in combos[i]:
combos[i].append(comb)
if len(combos[i])>0:
m = (min(map(len,combos[i])))
combos[i] = [combo for i, combo in enumerate(combos[i]) if len(combo) == m]
else:
combos[i] = None
return combos[sum]
total = 9
coin_sizes = [10, 8, 5, 4, 1]
shortest_seq_length(coin_sizes, total)
# [[1, 8], [4, 5]]
To show all sequences remove the minumum computation:
from collections import defaultdict
def all_seq_length(coins, sum):
combos = defaultdict(list)
combos[0] = [[]]
for i in range(1, sum+1):
for x in coins:
if x<=i and combos[i-x] is not None:
for p in combos[i-x]:
comb = sorted(p + [x])
if comb not in combos[i]:
combos[i].append(comb)
if len(combos[i])==0:
combos[i] = None
return combos[sum]
total = 9
coin_sizes = [10, 5, 4, 8, 1]
all_seq_length(coin_sizes, total)
# [[4, 5],
# [1, 1, 1, 1, 5],
# [1, 4, 4],
# [1, 1, 1, 1, 1, 4],
# [1, 8],
# [1, 1, 1, 1, 1, 1, 1, 1, 1]]
One small improvement to the algorithm is to skip the step of computing the minimum when the sum is equal to one of the values/coins, but this can be done better if we write a loop to compute the minimum. This however doesn't improve the overall complexity that's O(mS) where m = len(c).

Categories

Resources