0

Suppose I have a binary search tree [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]

If I run the following function, I want to know how many times the recursion function executes(in the following example, it is 31)

def loopBST(root):
    if not root:
        return
    loopBST(root.left)
    loopBST(root.right)

I can get this by create a global variable

global ind 
ind = 0
def loopBST(root):
    global ind
    ind += 1
    if not root:
        return
    loopBST(root.left)
    loopBST(root.right)
loopBST(bsttree)

The variable ind will be 31.

The question is, how can I make this indinside the dfs function rather than creating the global variable?

3
  • 1
    Can you please use codeblock formatting? Do four spaces of indentation for every line you have code and it will look a lot cleaner. Commented Feb 8, 2018 at 22:26
  • Possible duplicate of Counting python method calls within another method Commented Feb 8, 2018 at 23:03
  • Counting function calls Commented Feb 8, 2018 at 23:06

4 Answers 4

6

You could return the number of executions:

def loopBST(root):
    if not root:
        return 1
    return 1 + loopBST(root.left) + loopBST(root.right)
Sign up to request clarification or add additional context in comments.

3 Comments

Thanks Stefan. I see you from leetcode. In fact this question is from the question I am practicing in leetcode. I know I can solve this by put the dfs inside a class and set up a global count variable inside the class to finish this job. I am wondering if there is any way I can put that count variable as a parameter of the dfs function. Can I do this in the recursion fuction? I can create a dfs parameter called temp list and append a value to that temp list once there is a recursion run.
Which question is it there? I don't think leetcode ever asked to count function calls...
for example, q230. to return kth smallest of bst. You have fancy way to solve it. I try to start from stupid way: goes to the left lowest leaf, and then goes up, if I goes up k(my i==k), I will get the answer. My stupid code is like def kthsmall2(root, k): if not root: return def dfs(node, i, k): i += 1 if not node: return dfs(node.left, i, k) if i == k: return node.val dfs(node.right, i, k) return i print dfs(root, 0, k)
5

You could use a parameter.

def loopBST(root, times=0):
    times += 1
    if not root:
        return times
    times = loopBST(root.left, times=times)
    times = loopBST(root.right, times=times)
    return times
loopBST(bsttree)

2 Comments

No problem! I missed something too; I forgot to return times at the end of the function. Edited.
Oh, good, so I was somewhat right after all :-P. I wish they had provided usable testing data instead of an unclear list, then none of this would've happened...
2

If you don't want to rewrite your recursive function, you could also decorate it in a function that counts it using a counter of some sort. Example of an implementation:

UPDATE: I've changed the answer, but the old answer is kept at the end of the answer //UPDATE

Assume here that you have some recursion functions in some_module.py:

# From some_module.py
def factorial(x):
    return factorial(x-1)*x if x > 1 else 1

def cumsum(x):
    return cumsum(x-1) + x if x > 1 else 1

def loopBST(root):
    # ... your code

And you want to apply the decorator to count how many recursions ran. Here, the code is performed inside some_function() to show that you don't have to keep the count variable(s) in the global scope. (See comments) (Also, the recursive functions are still in global space)

# Main file:
from functools import wraps, partial
from collections import defaultdict
# import the two recursion functions from some_module.py
from some_module import cumsum, factorial, loopBST


def some_function():
    global factorial, cumsum, loopBST

    def counting_recursion(fn, counter):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            counter[fn.__name__] += 1
            return fn(*args, **kwargs)
        return wrapper

    counters = defaultdict(int)

    my_deco = partial(counting_recursion, counter=counters)
    factorial = my_deco(factorial)
    cumsum = my_deco(cumsum)
    loopBST = my_deco(loopBST)

    print(factorial(3))
    # 6
    print(cumsum(5))
    # 15

    factorial_count = counters[factorial.__name__]
    cumsum_count = counters[cumsum.__name__]
    loopBST_count = counters[loopBST.__name__]  # Equals 0: were not called in my example

    print('The "{}" function ran {} times'.format(factorial.__name__, factorial_count))
    print('The "{}" function ran {} times'.format(cumsum.__name__, cumsum_count))
    # The "factorial" function ran 3 times
    # The "cumsum" function ran 5 times

A few modifications/variations:

Instead of using my_deco = partial(counting_recursion, counter=counters), the recursive functions could be decorated directly:

cumsum = counting_recursion(cumsum, counter=counters)
factorial = counting_recursion(factorial, counter=counters)
loopBST = counting_recursion(loopBST, counter=counters)

Instead of using fn.__name__ to identify the called function, the counting_recursion-function could be rewritten as:

def counting_recursion(fn, counter):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        counter[wrapper] += 1
        return fn(*args, **kwargs)
    return wrapper

Then, to read the number from the counters dictionary:

factorial_count = counters[factorial]
cumsum_count = counters[cumsum]
loopBST_count = counters[loopBST]

If you want to read more about wrapping functions, check out https://stackoverflow.com/a/25827070/1144382 and the docs on wraps

OLD EXAMPLE:

from functools import wraps, partial

class Counter:
    def __init__(self, start_count=0):
        self._counter = start_count

    def __repr__(self):
        return str(self._counter)

    def count(self):
        self._counter += 1

counter = Counter()

def counting_recursion(fn, counter):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        counter.count()
        return fn(*args, **kwargs)
    return wrapper

my_deco = partial(counting_recursion, counter=counter)

@my_deco
def factorial(x):
    return factorial(x-1)*x if x > 1 else 1

print(factorial(3))
# 6

print('The {} function ran {} times'.format(factorial.__name__, counter))
# The factorial function ran 3 times

To implement this in your case, just make some counter and decorate your function:

@my_deco
def loopBST(root):
    # ...

print(counter._counter)
# prints number of counts

Of course, you don't have to make a Counter-class to call counter.count() on, you could also have a dictionary of counters, e.g. counts[loopBST] += 1 or just an array with a single element count_list[0] += 1. (see the code example at top of this answer) (The entire point is to "hide" the value in a reference that is not rewritten when the variable is reassigned, which is why just an integer count count += 1 won't work.)

2 Comments

I like this solution with decorator. It might be a good idea to modify the decorator so that we can avoid the global variable for the empty dictionary and may be importing the partial function
I see, thanks for follow up, I will update my answer when I get back to my computer.
-1

Maybe you can try with instance of a class, this way it probably is cleaner than having keyword argument. Not to mention that you can store additional data in instance.

This is a Python 2.7 class.

class LoopBST(object):

    def __init__(self):
        self.ind = 0

    def __call__(self, root):
        self.ind += 1

        if not root:
            return

        self(root.left)
        self(root.right)

loopBST = LoopBST()

loopBST(bstree)

print loopBST.ind

1 Comment

Encapsuling such simple task like this inside a class doesn't seem to be a very pythonic way.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.