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?

dtell
  • 2,488
  • 1
  • 14
  • 29
pinseng
  • 301
  • 2
  • 6
  • 11
  • 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. – AAM111 Feb 08 '18 at 22:26
  • Possible duplicate of [Counting python method calls within another method](https://stackoverflow.com/questions/1301735/counting-python-method-calls-within-another-method) – wwii Feb 08 '18 at 23:03
  • [Counting function calls](https://wiki.python.org/moin/PythonDecoratorLibrary#Counting_function_calls) – wwii Feb 08 '18 at 23:06

4 Answers4

6

You could return the number of executions:

def loopBST(root):
    if not root:
        return 1
    return 1 + loopBST(root.left) + loopBST(root.right)
Stefan Pochmann
  • 27,593
  • 8
  • 44
  • 107
  • 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. – pinseng Feb 08 '18 at 23:19
  • Which question is it there? I don't think leetcode ever asked to count function calls... – Stefan Pochmann Feb 08 '18 at 23:21
  • 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)``` – pinseng Feb 08 '18 at 23:33
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)
AAM111
  • 1,178
  • 3
  • 19
  • 39
  • No problem! I missed something too; I forgot to return `times` at the end of the function. Edited. – AAM111 Feb 08 '18 at 22:46
  • 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... – Stefan Pochmann Feb 08 '18 at 22:49
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.)

Thomas Fauskanger
  • 2,536
  • 1
  • 27
  • 42
  • 1
    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 – englealuze Feb 09 '18 at 09:21
  • I see, thanks for follow up, I will update my answer when I get back to my computer. – Thomas Fauskanger Feb 09 '18 at 09:28
-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
Ilija
  • 1,556
  • 1
  • 9
  • 12
  • Encapsuling such simple task like this inside a class doesn't seem to be a very pythonic way. – dtell Feb 08 '18 at 23:53