In solving this codewars challenge I came across a recursion example I don't understand.
The challenge is to give the next nth numbers, given an initial 3 number seed sequence, where the nth numbers are determined by adding the last three numbers of the sequence.
So for the seed sequence list of [1,2,3] and given n=5, you'd return the following:
1 + 2 + 3 = 6
2 + 3 + 6 = 11
Answer:
[1, 2, 3, 6, 11]
I solved the problem with the following:
def tribonacci(sequence,n):
if n <=3:
return sequence[:n]
else:
for i in range(n-3):
sequence.append(sum(signature[-3:]))
return sequence
In reviewing the other solutions, I came across this very elegant solution that uses recursion:
def tribonacci(sequence,n):
return sequence[:n] if n<=len(sequence) else tribonacci(sequence + [sum(sequence[-3:])],n)
My question is: why doesn't this just run infinitely? I'm having trouble understanding why this terminates with the nth iteration. It doesn't seem like the function of 'n' is stipulated anywhere, except in the if case.
As an experiment, I modified the code to ignore the less-than-or-equal-to-length-of-sequence case, like so:
def tribonacci(sequence,n):
return tribonacci(sequence + [sum(sequence[-3:])],n)
And this does run infinitely and errors out with a runtime error of max recursion depth.
So obviously the case option is what seems to control the termination, but I can't see why. I've used recursion myself in solves (for instance in creating a factoring function), but in that example you subtract n-1 as you iterate so there's a terminating process. I don't see that happening here.
I guess I don't completely understand how the return function works. I was reading it as:
return n-item list if n is less/equal to sequence length
else
rerun the function
Perhaps I should actually be reading it as:
return n-item list if n is less/equal to sequence length
else
return n-item list after iterating through the function enough times
to fill a n-item list
At each level of recursion, the sequence becomes longer because of concatenation (+). Eventually it will become long enough for n to become less than length.
You can rewrite this:
a = b if p else c
as this:
if p:
a = b
else:
a = c
Knowing that, you can see that this:
def tribonacci(sequence,n):
return sequence[:n] if n<=len(sequence) else tribonacci(sequence + [sum(sequence[-3:])],n)
is the same as:
def tribonacci(sequence,n):
if n <= len(sequence):
return sequence[:n]
else:
return tribonacci(sequence + [sum(sequence[-3:])],n)
Now you should be able to see that there's a condition controlling that the recursion doesn't go on for ever.
Related
I was recently trying to write an algorithm to solve a math problem I came up with (long story how I encountered it): basically, I wanted to come up with sets of P distinct integers such that given a number, there is at most one way of selecting G numbers from the set (repetitions allowed) which sum to that number (or put another way, there are not two distinct sets of G integers from the set with the same sum, called a "collision"). For example, with P, G = 3, 3, the set (10, 1, 0) would work, but (2, 1, 0) wouldn't, since 1+1+1=2+1+0.
I came up with an algorithm in Python that can find and generate these sets, but when I tried it, it runs extremely slowly; I'm pretty sure there is a much more optimized way to do this, but I'm not sure how. The current code is also a bit messy because parts were added organically as I figured out what I needed.
The algorithm starts with these two functions:
import numpy
def rec_gen_list(leng, index, nums, func):
if index == leng-1: #list full
func(nums)
else:
nextMax = nums[index-1];
for nextNum in range(nextMax)[::-1]: # nextMax-1 to 0
nums[index] = nextNum;
rec_gen_list(leng, index+1, nums, func)
def gen_all_lists(leng, first, func):
nums = np.zeros(leng, dtype='int')
nums[0] = first
rec_gen_list(leng, 1, nums, func)
Basically, this code generates all possible lists of distinct integers (with maximum of "first" and minimum 0) and applies some function to them. rec_gen_list is the recursive part; given a partial list and an index, it tries every possible next number in the list less than the last one, and sends that to the next recursion. Once it gets to the last iteration (with the list being full), it applies the given function to the completed list. Note that I stop before the last entry in the list, so it always ends with 0; I enforce that because if you have a list that doesn't contain 0, you can subtract the smallest number from each one in the list to get one that does, so I force them to have 0 to avoid duplicates and make other things I'm planning to do more convenient.
gen_all_lists is the wrapper around the recursive function; it sets up the array and first iteration of the process and gets it started. For example, you could display all lists of 4 distinct numbers between 7 and 0 by calling it as gen_all_lists(4, 7, print). The function included is so that once the lists are generated, I can test them for collisions before displaying them.
However, after coming up with these, I had to modify them to fit with the rest of the algorithm. First off, I needed to keep track of if the algorithm had found any lists that worked; this is handled by the foundOne and foundNew variables in the updated versions. This probably could be done with a global variable, but I don't think it's a significant issue with the slowdown.
In addition, I realized that I could use backtracking to significantly optimize this: if the first 3 numbers out of a long list are something like (100, 99, 98...), that already causes a collision, so I can skip checking all the lists generated from that. This is handled by the G variable (described before) and the test_no_colls function (which tests if a list has any collisions for a certain value of G); each time I make a new sublist, I check it for collisions, and skip the recursive call if I find any.
This is the result of these modifications, used in the current algorithm:
import numpy
def rec_test_list(leng, index, nums, G, func, foundOne):
if index == leng - 1: #list full
foundNew = func(nums)
foundOne = foundOne or foundNew
else:
nextMax = nums[index-1];
for nextNum in range(nextMax)[::-1]: # nextMax-1 to 0
nums[index] = nextNum;
# If already a collision, don't bother going down this tree.
if (test_no_colls(nums[:index+1], G)):
foundNew = rec_test_list(leng, index+1, nums, G, func, foundOne)
foundOne = foundOne or foundNew
return foundOne
def test_all_lists(leng, first, G, func):
nums = np.zeros(leng, dtype='int')
nums[0] = first
return rec_test_list(leng, 1, nums, G, func, False)
For the next two functions, test_no_colls takes a list of numbers and a number G, and determines if there are any "collisions" (two distinct sets of G numbers from the list that add to the same total), returning true if there are none. It starts by making a set that contains the possible scores, then generates every possible distinct set of G indices into the list (repetition allowed) and finds their totals. Each one is checked for in the set; if one is found, there are two combinations with the same total.
The combinations are generated with another algorithm I came up with; this probably could be done the same way as generating the initial lists, but I was a bit confused about the variable scope of the set, so I found a non-recursive way to do it. This may be something to optimize.
The second function is just a wrapper for test_no_colls, printing the input array if it passes; this is used in the test_all_lists later on.
def test_no_colls(nums, G):
possiblePoints=set(()) # Set of possible scores.
ranks = np.zeros(G, dtype='int')
ranks[0] = len(nums) - 1 # Lowest possible rank.
curr_ind = 0
while True: # Repeat until break.
if ranks[curr_ind] >= 0: # Copy over to make the start of the rest.
if curr_ind < G - 1:
copy = ranks[curr_ind]
curr_ind += 1
ranks[curr_ind] = copy
else: # Start decrementing, since we're at the end. We also have a complete list, so test it.
# First, get the score for these rankings and test to see if it collides with a previous score.
total_score = 0
for rank in ranks:
total_score += nums[rank]
if total_score in possiblePoints: # Collision found.
return False
# Otherwise, add the new score to the list.
possiblePoints.add(total_score)
#Then backtrack and continue.
ranks[curr_ind] -= 1
else:
# If the current value is less than 0, we've exhausted the possibilities for the rest of the list,
# and need to backtrack if possible and start with the next lowest number.
curr_ind -= 1;
if (curr_ind < 0): # Backtracked from the start, so we're done.
break
else:
ranks[curr_ind] -= 1 # Start with the next lowest number.
# If we broke out of the loop before returning, no collisions were found.
return True
def show_if_no_colls(nums, games):
if test_no_colls(nums, games):
print(nums)
return True
return False
These are the final functions that wrap everything up. find_good_lists wraps up test_all_lists more conveniently; it finds all lists ranging from 0 to maxPts of length P which have no collisions for a certain G. find_lowest_score then uses this to find the smallest possible maximum value of a list that works for a certain P and G (for example, find_lowest_score(6, 3) finds two possible lists with max 45, [45 43 34 19 3 0] and [45 42 26 11 2 0], with nothing that is all below 45); it also shows some timing data about how long each iteration took.
def find_good_lists(maxPts, P, G):
return test_all_lists(P, maxPts, G, lambda nums: show_if_no_colls(nums, G))
from time import perf_counter
def find_lowest_score(P, G):
maxPts = P - 1; # The minimum possible to even generate a scoring table.
foundSet = False;
while not foundSet:
start = perf_counter()
foundSet = find_good_lists(maxPts, P, G)
end = perf_counter()
print("Looked for {}, took {:.5f} s".format(maxPts, end-start))
maxPts += 1;
So, this algorithm does seem to work, but it runs very slowly; when trying to run lowest_score(7, 3), for example, it starts taking minutes per iteration around maxPts in the 70s or so, even on Google Colab. Does anyone have suggestions for optimizing this algorithm to improve its runtime and time complexity, or better ways to solve the problem? I am interested in further exploration of this (such as filtering the lists generated for other qualities), but am concerned about the time it would take with this algorithm.
numbers = (2,3,4)
def product(n):
m = 1
for i in n:
m *= i
return print(numbers[0],'x',numbers[1],'x',numbers[2],'=',m)
product(numbers)
This is what I wrote for this problem. But I don't know how to make the result like "2x3x4=24" exactly. Another question is if I add '5' in the parentheses, it only shows "2x3x4=120", I cannot get "2x3x4x5=120". Could anyone helps me to fix my code??? Thanks.
There are two main issues with your code. The first in that your return statement is executed in the first iteration of your loop, but you want it to happen after the entire loop has finished. This happens because you have too much whitespace to the left of the return statement. The second is that you’re attempting to return the return value of a call to print. To fix this ditch the return or even better return a string then print that:
numbers = (2,3,4)
def product(n):
m = 1
for i in n:
m *= i
return f"{n[0]}x{n[1]}x{n[2]}={m}"
returned = product(numbers)
print(returned)
The answer linked in the comments points out there’s an even easier way to do this:
from math import prod
returned = prod((2,3,4))
print(returned)
And here's a bit of a kick-flip for fun:
from math import prod
numbers = (2,3,4)
print(*numbers, sep="x", end=f"={prod(numbers)}")
I’m trying to create a program that lists all the primes below an inputted number, and I came up with the code:
def primes():
num = 20
numlist = list(range(1,num+1))
i = len(numlist)
for j in numlist[2:]:
ans = divisible(j,i)
if ans:
numlist.remove(j)
print(numlist)
def divisible(m,n):
if m!=n and m%n==0:
return True
elif n == 1:
return False
else:
divisible(m, n-1)
primes()
(I used an in-browser IDE so the num part was a proxy for the input.)
My idea was to create a separate function divisible() that when inputted two ints, m and n, would check if n divides m. I'm not sure if I was right in my recursion, but I wrote divisible(m,n-1) the idea was that it would iterate through all the integers from n downward and it would return True if any n divided m, or False if it reached 1.
In the main code, m iterated through all the numbers in a list, and n is the total number of elements in the same list. I put the print(numlist) inside the if statement as an error check. The problem I’m having is nothing is printing. The code returned literally nothing. Is there something I’ve missed in how recursion works here?
There's a lot wrong here:
You've made a common beginner's recursion error in that you have a recursive function that returns a value, but when you call it recursively, you ignore the returned value. You need to deal with it or pass it along.
It seems like this modulus is backward:
if ... and m%n==0:
Maybe it should be:
if ... and n % m == 0:
You're code doesn't appear to be calculating primes. It's looks like it's calculating relative primes to n.
You start your list of numbers at 1:
numlist = list(range(1,num+1))
But you start testing at index 2:
for j in numlist[2:]:
Which is the number 3 and you never check the divisibility of the number 2.
Even with all these fixes, I don't feel your algorithm will work.
Your divisible function doesn't return anything if it falls into else part. change it as
def divisible(m, n):
if m!=m and m%n==0:
return True
elif n==1 :
return False
else:
return divisible(m,n-1)
This should work
I was trying to write a function largest_number() that can output the maximum value by arranging the orders of a list of given numbers (eg. 21,2 should give 221. 543,5432,1 should give 54354321). This is a homework problem that I have tested using many many cases but didn't get any wrong answer. However the grading system kept telling me that I have the wrong output (without showing the output on their end). I suspect there 's a tiny bit of the code that resulted in wrong output in some special cases, but I coudn't find it.
#Uses python3
#%%
import functools
def greater(str1, str2):
if str1==str2:
return 1
for i in range(min(len(str1),len(str2))):
if str1[i]>str2[i]:
return 1
if str1[i]<str2[i]:
return -1
if len(str1)>len(str2):
if str1[len(str1)-len(str2)-1]>str1[0]:
return 1
else:
return -1
if len(str1)<len(str2):
if str2[len(str2)-len(str1)-1]>str2[0]:
return -1
else:
return 1
def largest_number(a):
a=list(map(str,a))
a_sorted=sorted(a,key=functools.cmp_to_key(greater),reverse=True)
largest=int(''.join(a_sorted))
return(largest)
#test cases:
largest_number([21,2])
largest_number([543,5432,1])
The logic in greater is not entirely correct where it deals with the case that one string is a left-substring of the other:
if len(str1)>len(str2):
if str1[len(str1)-len(str2)-1]>str1[0]:
return 1
else:
return -1
Here you don't deal correctly with the case where str1[len(str1)-len(str2)-1] is equal to str1[0]. In that case you should iterate further, increasing the index in both strings (much like you did in the first part of the algorithm). So change the above code to:
if len(str1)>len(str2):
for i in range(len(str2),len(str1)):
if str1[i]>str1[i-len(str2)]:
return 1
if str1[i]<str1[i-len(str2)]:
return -1
... and do a similar change for the mirrored case.
However, there is a much easier way to approach this. In the greater function you could just concatenate the two strings in the two possible ways, and compare to see which of these two is the greater one:
def cmp(a, b): # This function was included in Python 2.x, but no longer in 3.x
return (a > b) - (a < b)
def greater(str1, str2):
return cmp(str1 + str2, str2 + str1)
You can solve that problem with Itertools Module:
import itertools
import functools
#"my_list" object is a list of string objects.
def largest_number(my_list):
answer = 0
for i in itertools.permutations(my_list):
x = int(functools.reduce(lambda x,y:x+y,i))
if x > answer:
answer = x
return answer
This code works because the "permutations" method of itertools module return all possible cases of combinations of a list.
I built anagram generator. It works, but I don't know for loop for functions works at line 8, why does it works only in
for j in anagram(word[:i] + word[i+1:]):
why not
for j in anagram(word):
Also, I want to know what
for j in anagram(...)
means and doing...
what is j doing in this for loop?
this is my full code
def anagram(word):
n = len(word)
anagrams = []
if n <= 1:
return word
else:
for i in range(n):
for j in anagram(word[:i] + word[i+1:]):
anagrams.append(word[i:i+1] + j)
return anagrams
if __name__ == "__main__":
print(anagram("abc"))
The reason you can't write for i in anagram(word) is that it creates an infinite loop.
So for example if I write the recursive factorial function,
def fact(n):
if n <= 1:
return 1
return n * fact(n - 1)
This works and is not a circular definition because I am giving the computer two separate equations to compute the factorial:
n! = 1
n! = n (n-1)!
and I am telling it when to use each of these: the first one when n is 0 or 1, the second when n is larger than that. The key to its working is that eventually we stop using the second definition, and we instead use the first definition, which is called the “base case.” If I were to instead say another true definition like that n! = n! the computer would follow those instructions but we would never reduce down to the base case and so we would enter an infinite recursive loop. This loop would probably exhaust a resource called the “stack” rapidly, leading to errors about “excessive recursion” or too many “stack frames” or just “stack overflow” (for which this site is named!). And then if you gave it a mathematically invalid expression like n! = n n! it would infinitely loop and also it would be wrong even if it did not infinitely loop.
Factorials and anagrams are closely related, in fact we can say mathematically that
len(anagrams(f)) == fact(len(f))
so solving one means solving the other. In this case we are saying that the anagram of a word which is empty or of length 1 is just [word], the list containing just that word. (Your algorithm messes this case up a little bit, so it's a bug.)
The anagram of any other word must have something to do with anagrams of words of length len(word) - 1. So what we do is we pull each character out of the word and put it at the front of the anagram. So word[:i] + word[i+1:] is the word except it is missing the letter at index i, and word[i:i+1] is the space between these -- in other words it is the letter at index i.
This is NOT an answer but a guide for you to understand the logic by yourself.
Firstly you should understand one thing anagram(word[:i] + word[i+1:]) is not same as anagram(word)
>>> a = 'abcd'
>>> a[:2] + a[(2+1):]
'abd'
You can clearly see the difference.
And for a clearer understanding I would recommend you to print the result of every word in the recursion. put a print(word) statement before the loop starts.