Skip to content

Commit d84d81d

Browse files
author
sarangbishal
committed
Upload examples
1 parent 91be44f commit d84d81d

File tree

2 files changed

+159
-1
lines changed

2 files changed

+159
-1
lines changed

README.md

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,55 @@
1-
# Recursion-Visualizer
1+
# Recursion Visualiser
2+
Recursion visualiser is a python tool that draws recursion tree for recursive function with very less code changes.
3+
4+
## Example
5+
Let's draw the recursion tree for fibonacci number.
6+
Here is how the simple code looks like
7+
```python
8+
def fib(n):
9+
if n <= 1:
10+
return n
11+
return fib(n - 1) + fib(n - 2)
12+
13+
print(fib(6))
14+
```
15+
16+
Now we want to draw the recursion tree for this function. It is as simple as adding a decorator
17+
```python
18+
# Author: Bishal Sarang
19+
20+
# Import Visualiser class from module visualiser
21+
from visualiser import Visualiser as vs
22+
23+
# Add decorator
24+
# Decorator accepts arguments: ignore_args and show_argument_name
25+
@vs(ignore_args=['node_num'])
26+
def fib(n, node_num):
27+
if n <= 1:
28+
return n
29+
30+
# Increment decorator class node_count before each function calls
31+
vs.node_count += 1
32+
left = fib(n=n - 1, node_num=vs.node_count)
33+
34+
# node_count is incremented
35+
vs.node_count += 1
36+
right = fib(n=n - 2, node_num=vs.node_count)
37+
return left + right
38+
39+
# Call function
40+
print(fib(n=6, node_num=0))
41+
42+
# Save recursion tree to a file
43+
vs.write_image("fibonacci.png")
44+
```
45+
Here are the changes required:
46+
47+
- Change function signature from `fib(n)` to `fib(n, node_num)`
48+
- Add decorator Visualiser which accepts arguments `ignore_args` and `show_argument_name`
49+
- Before each function calls make sure to increment `node_count` argument of decorator class
50+
- Change every function calls to pass as keyword arguments.
51+
- Write the image
52+
53+
Here is how the recursion tree looks like:
54+
55+

visualiser.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import sys
2+
from functools import wraps
3+
from collections import OrderedDict
4+
import pydot
5+
6+
class Visualiser(object):
7+
node_count = 0
8+
graph = pydot.Dot(graph_type="digraph")
9+
def __init__(self, ignore_args, show_argument_name=True):
10+
self.show_argument_name = show_argument_name
11+
self.ignore_args = ignore_args
12+
13+
@classmethod
14+
def write_image(self, filename="out.png"):
15+
self.graph.write_png(f"{filename}")
16+
17+
def __call__(self, fn):
18+
@wraps(fn)
19+
def wrapper(*args, **kwargs):
20+
# Order all the keyword arguments
21+
kwargs = OrderedDict(sorted(kwargs.items()))
22+
23+
# If show_argument flag is True(default)
24+
# Then argument_string is:
25+
# a=1, b=31, c=0
26+
argument_string = ', '.join(
27+
[repr(a) for a in args] +
28+
[f"{key}={repr(value)}" for key, value in kwargs.items()])
29+
30+
current_function_label_argument_string = ', '.join(
31+
[repr(a) for a in args] +
32+
[f"{key}={repr(value)}" for key, value in kwargs.items() if key not in self.ignore_args])
33+
34+
# If show_argument flag is False
35+
# Then argument_string is:
36+
# 1, 31, 0
37+
if self.show_argument_name == False:
38+
argument_string = ', '.join(
39+
[repr(value) for value in args] +
40+
[f"{repr(value)}" for key, value in kwargs.items()])
41+
42+
current_function_label_argument_string = ', '.join(
43+
[repr(a) for a in args] +
44+
[f"{repr(value)}" for key, value in kwargs.items() if key not in self.ignore_args])
45+
46+
# Details about current function
47+
current_function_name = fn.__name__
48+
current_function_argument_string = argument_string
49+
# Current function signature looks as follows:
50+
# foo(1, 31, 0) or foo(a=1, b=31, c=0)
51+
current_function_signature = f"{current_function_name}({current_function_argument_string})"
52+
current_function_label = f"{current_function_name}({current_function_label_argument_string})"
53+
54+
# Caller Function
55+
caller_function_name = sys._getframe(1).f_code.co_name
56+
# Extract the names of arguments only
57+
caller_function_argument_names = sys._getframe(1).f_code.co_varnames[:fn.__code__.co_argcount]
58+
caller_function_locals = sys._getframe(1).f_locals
59+
60+
# Sort all the locals of caller function
61+
caller_function_locals = OrderedDict(sorted(caller_function_locals.items()))
62+
63+
64+
# Extract only those locals which are in function signature
65+
caller_function_argument_string = ', '.join(
66+
[f"{key}={value}" for key, value in caller_function_locals.items() if (key in caller_function_argument_names)])
67+
caller_function_label_argument_string = ', '.join(
68+
[f"{key}={value}" for key, value in caller_function_locals.items() if
69+
(key in caller_function_argument_names and key not in self.ignore_args)])
70+
71+
if self.show_argument_name == False:
72+
caller_function_argument_string = ', '.join(
73+
[f"{value}" for key, value in caller_function_locals.items() if
74+
(key in caller_function_argument_names)])
75+
caller_function_label_argument_string = ', '.join(
76+
[f"{value}" for key, value in caller_function_locals.items() if
77+
(key in caller_function_argument_names and key not in self.ignore_args)])
78+
79+
caller_func_signature = f"{caller_function_name}({caller_function_argument_string})"
80+
caller_func_label = f"{caller_function_name}({caller_function_label_argument_string})"
81+
82+
if caller_function_name == '<module>':
83+
print(f"Drawing for {current_function_signature}")
84+
85+
result = fn(*args, **kwargs)
86+
87+
# #Child Node
88+
child_label = current_function_label
89+
child_name = current_function_signature
90+
v = pydot.Node(name=child_name, label=child_label)
91+
self.graph.add_node(v)
92+
93+
# Parent Node
94+
u = None
95+
96+
if caller_function_name != '<module>':
97+
print(f"Called {current_function_label} by {caller_func_label}")
98+
u = pydot.Node(name=caller_func_signature, label=caller_func_label)
99+
self.graph.add_node(u)
100+
edge = pydot.Edge(u, v)
101+
self.graph.add_edge(edge)
102+
103+
return result
104+
return wrapper

0 commit comments

Comments
 (0)