How can I count the recursive calls of a function in Python? - python-3.x

I was playing with the recursive Ackermanns function. For certain values my prompt whould not show every calculated output 'cause Python whould exceed its recursive limit so fast that whould freeze the prompt before the "easy" parts whould catch up with it.
So I thought I could add a recursive counter and a quick pause after a full execution of the function. I was getting the anticipated outputs until it reached the values (1,0). After that I got a TypeError: can only concatenate tuple (not "int") to tuple.
My code is as follows:
import time
import sys
sys.setrecursionlimit(3000)
def ackermann(i,j,rec):
output = None
if i==0:
output = j+1
elif j==0:
output = ackermann(i-1,1,rec)
rec=rec+1
else:
output = ackermann(i-1,ackermann(i,j-1,rec),rec)
rec=rec+1
return output,rec
rec=0
for i in range(5):
for j in range(5):
print("(",i,",",j,")= ",ackermann(i,j,rec))
time.sleep(2)
Notice that removing all instances of rec (my recurence counter), the program runs fine. (You can see all outputs for values i,j = 3)
Can someone point out how to correct my code or propose a different method of finding how many times the Ackermann function has calls itself ?
Also, I've noticed that putting a limit of 5000 whould crash my python kernel very fast. Is there an upper limit ?
I use the latest Anaconda.
EDIT
I tried to implement the same function using a list as a parameter with the following data [i,j,output,#recursion]
import time
import sys
sys.setrecursionlimit(3000)
def ackermann(*rec):
rec=list(rec)
print(rec) # see the data as they initialize the function
if rec[0][0]==0:
rec[0][1]=rec[0][1]+1
rec[0][2] = rec[0][1]+1
elif rec[0][1]==0:
rec[0][0]=rec[0][0]-1
rec[0][1]=1
rec = ackermann()
rec[0][3]=rec[0][3]+1
else:
rec[0][0]=rec[0][0]-1
rec[0][1] = ackermann()
rec = ackermann()
rec[0][3]=rec[0][3]+1
return rec
for i in range(5):
for j in range(5):
rec=[i,j,0,0]
print(ackermann(rec))
time.sleep(1)
But this time I get a IndexError: list index out of rangebecause for some unknown reason my list gets emptied
OUTPUT:
[[0, 0, 0, 0]]
[[0, 1, 2, 0]]
[[0, 1, 0, 0]]
[[0, 2, 3, 0]]
[[0, 2, 0, 0]]
[[0, 3, 4, 0]]
[[0, 3, 0, 0]]
[[0, 4, 5, 0]]
[[0, 4, 0, 0]]
[[0, 5, 6, 0]]
[[1, 0, 0, 0]]
[]

The problem with the original implementation is that
return output, rec
will happily create a tuple when output and rec are both numbers, which is true whenever i=0. But once you get to i=1, j=0 the function calls Ackerman on (0,1,rec), which returns a tuple, to which it then cannot add the integer rec, hence the error message. I believe I have worked with that idea, though, almost unchanged, except rather than trying to pass and return rec, I made it global (ugly, I know). I also reformatted the output so I could read it better. Thus:
import time
import sys
sys.setrecursionlimit(3000)
def ackermann(i,j):
global rec
output = None
if i==0:
output = j+1
elif j==0:
output = ackermann(i-1,1)
rec=rec+1
else:
output = ackermann(i-1,ackermann(i,j-1))
rec=rec+1
return output
for i in range(5):
for j in range(5):
rec = 0
print
print("ack("+str(i)+","+str(j)+") = "+str(ackermann(i,j)))
print("rec = "+str(rec))
print
time.sleep(1)
and the output, before erroring out, is,
ack(0,0) = 1
rec = 0
ack(0,1) = 2
rec = 0
ack(0,2) = 3
rec = 0
ack(0,3) = 4
rec = 0
ack(0,4) = 5
rec = 0
ack(1,0) = 2
rec = 1
ack(1,1) = 3
rec = 2
ack(1,2) = 4
rec = 3
ack(1,3) = 5
rec = 4
ack(1,4) = 6
rec = 5
ack(2,0) = 3
rec = 3
ack(2,1) = 5
rec = 8
ack(2,2) = 7
rec = 15
ack(2,3) = 9
rec = 24
ack(2,4) = 11
rec = 35
ack(3,0) = 5
rec = 9
ack(3,1) = 13
rec = 58
ack(3,2) = 29
rec = 283
ack(3,3) = 61
rec = 1244
ack(3,4) = 125
rec = 5213
ack(4,0) = 13
rec = 59
It seems to me there are only one or two other values (it will choke on 4,2 I believe, no matter what, so you would need to get 5, 0 first) you could hope to get out this way, no matter how much you tinker.
I am a little troubled that rec appears to exceed the recursion limit, but I think Python must be interpreting along the way somehow, so that it gets deeper than one might think, or that I don't fully understand sys.recursionlimit (I looked at rec a few times, and at the very least I followed your lead on calculating it; also, as a sanity check I switched the order of incrementing it and the function call and got the same results).
EDIT: I added another parameter to track how deeply any particular call ever recurses. That turns out to be typically less than (and at most one more than) "rec." rec represents (actually 1 less than) how many times the function is called to make the particular calculation, but not all of these need be on the Python interpreter stack simultaneously.
Revised code:
import time
import sys
sys.setrecursionlimit(3000)
def ackermann(i,j,d):
global rec
global maxDepth
if ( d > maxDepth ) : maxDepth = d
output = None
if i==0:
output = j+1
elif j==0:
rec=rec+1
output = ackermann(i-1,1, d+1)
else:
rec=rec+1
output = ackermann(i-1,ackermann(i,j-1, d+1),d+1)
return output
for i in range(5):
for j in range(5):
rec = 0
maxDepth=0
print
print("ack("+str(i)+","+str(j)+") = "+str(ackermann(i,j,1)))
print("rec = "+str(rec))
print("maxDepth = "+str(maxDepth))
print
time.sleep(1)
revised output (before it gives up)
ack(0,0) = 1
rec = 0
maxDepth = 1
ack(0,1) = 2
rec = 0
maxDepth = 1
ack(0,2) = 3
rec = 0
maxDepth = 1
ack(0,3) = 4
rec = 0
maxDepth = 1
ack(0,4) = 5
rec = 0
maxDepth = 1
ack(1,0) = 2
rec = 1
maxDepth = 2
ack(1,1) = 3
rec = 2
maxDepth = 3
ack(1,2) = 4
rec = 3
maxDepth = 4
ack(1,3) = 5
rec = 4
maxDepth = 5
ack(1,4) = 6
rec = 5
maxDepth = 6
ack(2,0) = 3
rec = 3
maxDepth = 4
ack(2,1) = 5
rec = 8
maxDepth = 6
ack(2,2) = 7
rec = 15
maxDepth = 8
ack(2,3) = 9
rec = 24
maxDepth = 10
ack(2,4) = 11
rec = 35
maxDepth = 12
ack(3,0) = 5
rec = 9
maxDepth = 7
ack(3,1) = 13
rec = 58
maxDepth = 15
ack(3,2) = 29
rec = 283
maxDepth = 31
ack(3,3) = 61
rec = 1244
maxDepth = 63
ack(3,4) = 125
rec = 5213
maxDepth = 127
ack(4,0) = 13
rec = 59
maxDepth = 16

In your edited version of the code, you used a *arg in your def for ackerman and made it explicitly a list, and you get eleven output lists containing a four-element list in each until on the twelfth recursion you get an empty list. So, did the first eleven lists contain the expected elements according to the ackermann constraints? Also, on the twelfth recursion, you say the list was "emptied." I wonder for analytical purposes if it might make sense to say instead it wasn't filled in the first place. That is, not that something emptied it but that something didn't fill it as expected on the twelfth time through.

Related

Why is the function returning value of stack as None

I'm not able to figure out why the path variable on last line of the code is being printed out as None. As you can see the second last line of the code is calling the DFS function to find the path between two nodes in a tree (I'm giving a tree as input). I've printed out the value of the stack also before returning it to make sure that it is not None and while being printed inside DFS function it is not None. But I'm not able to understand why it is None when it is returned and stored in path variable. I gave this as input
1
6 1
4 2 1 3 5 2
1 2
2 3
2 4
1 5
5 6
And the out put came as
{0: [1, 4], 1: [0, 2, 3], 2: [1], 3: [1], 4: [0, 5], 5: [4]}
[0, 1, 3]
None
Here is the code for reference
def DFS(adj,x, y,stack,vis):
stack.append(x)
if (x == y):
print(stack)
return stack
vis[x] = 1
if (len(adj[x])>0):
for j in adj[x]:
if (vis[j]==0):
DFS(adj,j,y,stack,vis)
del stack[-1]
T = int(input())
for a in range(T):
N,Q = input().split()
N = int(N)
Q = int(Q)
wt = [int(num) for num in input().split(" ")]
adj = {}
for i in range(N):
adj[i] = []
for b in range(N-1):
u,v = input().split()
u = int(u) - 1
v = int(v) - 1
adj[u].append(v)
adj[v].append(u)
print(adj)
vis = [0]*N
stack = []
path = DFS(adj,0,3,stack,vis)
print(path)
Simple equivalent of your code:
def recursive_func(x):
if x > 0:
return x
else:
x += 1
recursive_func(x)
x = 5
x = recursive_func(x)
print(x)
x = 0
x = recursive_func(x)
print(x)
Output:
5
None
What's happening here?
x, with a value of 5 is sent to recursive_func.
x is greater than 0, so 5 is returned. This is seen in the output.
x, with a value of -5 is sent to recursive_func.
x is not greater than 0, so 1 is added to x.
x, with a value of 1, is then sent to a different recursive_func.
This recursive_func returns 1 because 1 > 0.
This response gets passed to the first recursive_func where the line recursive_func(x) becomes 1, but we don't do anything with it.
recursive_function hits the end of its code, without returning a value. By default None is returned to our main body.
x = recursive_func(x) has become x = None
None is output.
Given this information, why does the following code perform differently?
Simple modification of your code:
def recursive_func_v2(x):
if x > 0:
return x
else:
x += 1
return recursive_func_v2(x)
x = 5
x = recursive_func_v2(x)
print(x)
x = 0
x = recursive_func_v2(x)
print(x)
Output:
5
1

How many ways to get the change?

While practicing the following dynamic programming question on HackerRank, I got the 'timeout' error. The code run successfully on some test examples, but got 'timeout' errors on others. I'm wondering how can I further improve the code.
The question is
Given an amount and the denominations of coins available, determine how many ways change can be made for amount. There is a limitless supply of each coin type.
Example:
n = 3
c = [8, 3, 1, 2]
There are 3 ways to make change for n=3 : {1, 1, 1}, {1, 2}, and {3}.
My current code is
import math
import os
import random
import re
import sys
from functools import lru_cache
#
# Complete the 'getWays' function below.
#
# The function is expected to return a LONG_INTEGER.
# The function accepts following parameters:
# 1. INTEGER n
# 2. LONG_INTEGER_ARRAY c
#
def getWays(n, c):
# Write your code here
#c = sorted(c)
#lru_cache
def get_ways_recursive(n, cur_idx):
cur_denom = c[cur_idx]
n_ways = 0
if n == 0:
return 1
if cur_idx == 0:
return 1 if n % cur_denom == 0 else 0
for k in range(n // cur_denom + 1):
n_ways += get_ways_recursive(n - k * cur_denom,
cur_idx - 1)
return n_ways
return get_ways_recursive(n, len(c) - 1)
if __name__ == '__main__':
fptr = open(os.environ['OUTPUT_PATH'], 'w')
first_multiple_input = input().rstrip().split()
n = int(first_multiple_input[0])
m = int(first_multiple_input[1])
c = list(map(int, input().rstrip().split()))
# Print the number of ways of making change for 'n' units using coins having the values given by 'c'
ways = getWays(n, c)
fptr.write(str(ways) + '\n')
fptr.close()
It timed out on the following test example
166 23 # 23 is the number of coins below.
5 37 8 39 33 17 22 32 13 7 10 35 40 2 43 49 46 19 41 1 12 11 28

Python implementation of BFS to solve 8-puzzle takes too long to find a solution

My implementation of BFS in Python to solve the 8-puzzle is taking at least 21 minutes to find a solution. How can I improve my code in order to achieve a better time?
The way I've implemented is very inefficient. I'd like to know any advice about how can I improve it in a way to solve in an acceptable time.
class Node():
def __init__(self, board=[]):
self.board=board
self.adjacency_list=[]
def get_adjacency_list(self):
return self.adjacency_list
def set_adjacency_list(self, adjacency_list):
self.adjacency_list = adjacency_list
def add_item_to_adjacency_list(self, item):
self.adjacency_list.append(item)
def generate_adjacency_list(self):
'''
Generates the adjancency list
from a given puzzle 8's board.
'''
adj_lists = []
empty_cell = 0
row_empty_cell = col_empty_cell = 0
tmp_array = None
for array in self.board:
if empty_cell in array:
tmp_array = array
break
row_empty_cell = self.board.index(tmp_array)
col_empty_cell = tmp_array.index(empty_cell)
left = (row_empty_cell, col_empty_cell - 1)
right = (row_empty_cell, col_empty_cell + 1)
up = (row_empty_cell - 1, col_empty_cell)
down = (row_empty_cell + 1, col_empty_cell)
max_bound = 3
for direction in [left, up, right, down]:
(row, col) = direction
if row >= 0 and row < max_bound and col >= 0 and col < max_bound:
adj_list = [r[:] for r in self.board]
adj_list[row_empty_cell][col_empty_cell] = adj_list[row][col]
adj_list[row][col] = empty_cell
self.add_item_to_adjacency_list(Node(adj_list))
def bfs(root_node, goal_node):
'''
Implementation of the Breadth
First Search algorithm.
The problem to be solved by this
algorithm is the Puzzle 8 game.
input: root -- the root node where
the search begins.
goal_node -- The objective to reach.
return:
(path, node) -- A tuple with a
dictionary path whose key node
gives the path backwards to the
objective node.
'''
frontier = [root_node]
path = {root_node : None} # The path where a node came from
level = {root_node : 0}
boards = [root_node.get_board()] # List of boards to check a board was already generated
i = 1
while frontier:
next_frontier = []
for node_parent in frontier:
if node_parent.get_board() == goal_node.get_board():
return (path, node_parent)
node_parent.generate_adjacency_list()
for children in node_parent.get_adjacency_list():
if children.get_board() not in boards:
boards.append(children.get_board())
next_frontier.append(children)
level[children] = i
path[children] = node_parent
frontier = next_frontier
print("Level ", i)
print("Number of nodes ", len(frontier))
i += 1
return (path, root_node)
root_node = Node([[2, 6, 0],
[5, 7, 3],
[8, 1, 4]])
goal_node = Node([[1, 2, 3],
[4, 5, 6],
[7, 8, 0]])
import time
start = time.time()
path, node = bfs(root_node, goal_node)
end = time.time()
print(end - start)
I think that the problem is in this line:
if children.get_board() not in boards:
This is a linear search, try to change this to binary search.
Use a heuristic, like A*. This is known to work well and there are many guides on it.

Python confusion toward class instance

[Class of players that accept a list then proceed to find the player with the highest score.]
class Player:
def __init__(self, name, score):
self.name = name
self.score = score
def best_score(list):
i = 0
while i < len(list):
n = list[i] #list[1] = (Bratt, 250) #list[2] = Lisa 150
s = list[i].score #list 1. score = 250 #list[2].score = 150
ace = list[0] #homer 50 #homer 50
hs = 0
if s > hs: #if s(250>0): #if s(list[2].score) > hs(250): nothing suppsoed to happen
ace = n #ace(homer) = n(list1) aka bratt #ace(bratt) != n because above
hs = s #hs(0) = s(list1) = 250 #hs(250) != list[2]150
#hs is now 250
i += 1
return ace
p1 = Player('Homer', 50)
p2 = Player('Bart', 250)
p3 = Player('Lisa', 150)
ls = [p1, p2, p3]
best = Player.best_score(ls)
msg = '{} has the best score, with {} points!'.format(best.name, best.score)
print(msg) # Bart has the best score, with 250 points!
For some reasons, my code does not return the highest player score and name. Instead, it gives me the latest player score and name instead.
I have tried checking it by going through the loop and it still does not make sense where did I go wrong.
We can focus on this part of the code:
while i < len(list):
hs = 0
if s > hs:
ace = n
hs = s
As it's written, s > hs is the same as s > 0, so the condition will be true for all items of the list that have a score greater than zero.
To keep the greatest value, it should be defined once before entering the loop, like this:
hs = 0
while i < len(list):
if s > hs:
ace = n
hs = s
With this change, the value of hs will be keep during iterations of the while loop, and in the end it will keep the best score as hs.

Can't figure out why my program is not creating a list

I need to compare each number in a list back to back, subtract, and add the outcome to a new list.
so
list1[1]-[0] = list2 [0]
list1[2]-[1] = list2 [1]
etc.
But I can't get it to do this.
Here is my block
change = []
index = 0
popyear_up = 1
popyear_low = 0
while index < len(data_numbers):
#for i in range, len(data_numbers:
difference = data_numbers[popyear_up] - data_numbers[popyear_low]
change.append(difference)
popyear_up += 1
popyear_low += 1
index += 1
The uncommented "while" line right now returns
difference = data_numbers[popyear_up] - data_numbers[popyear_low]
IndexError: list index out of range
The commented line only does [1]-[0] and [2]-[1], but does them correctly and appends them to change[]
I have no idea why it only does those 2. But for the first one I feel it has something to do with the length of my main list of data and the length of the list I'm asking it to create.
So two different issues, and I can't for the life of me figure out what I'm missing.
Full code if necessary
file = input('File to open: ')
infile = open(file, 'r')
source_file = infile.readlines()
infile.close()
index = 0
while index < len(source_file):
source_file[index] = source_file[index].rstrip('\n')
index += 1
data_numbers = [int(i) for i in source_file]
change = []
index = 0
popyear_up = 1
popyear_low = 0
while index < len(data_numbers):
#for i in range, len(data_numbers:
difference = data_numbers[popyear_up] - data_numbers[popyear_low]
change.append(difference)
popyear_up += 1
popyear_low += 1
index += 1
#start_year = 1950
#change_sum = float(sum(change))
#change_average = change_sum / len(change)
#max_change = start_year + change.index(max(n)) + 1
#min _change = start_year + change.index(min(n)) + 1
#print('Average Change in Population:',change_average)
#print ('Year with most population increas:',max_change)
#print ('Year with lease population increas:',min_change)
Since your lists are of the same length and one of the indices (popyear_up) is one ahead, it will break. Instead, only go up to index < len(data_numbers) - 1.
Also, just do this:
change = []
popyear_up = 1
popyear_low = 0
for popyear_low in range(len(data_numbers) - 1):
difference = data_numbers[popyear_low + 1] - data_numbers[popyear_low]
change.append(difference)
Also, just do this:
change = [data_numbers[i + 1] - data_numbers[i] for i in range(len(data_numbers) - 1)]
Or if you want (though this is slightly less readable):
change = [y - x for x, y in zip(data_numbers, data_numbers[1:])]

Resources