Skip to content

Commit 27335b4

Browse files
author
sarangbishal
committed
Version 0.03'
1 parent 097662b commit 27335b4

File tree

7 files changed

+61
-73
lines changed

7 files changed

+61
-73
lines changed

README.md

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -27,38 +27,30 @@ Let's draw the recursion tree for fibonacci number.
2727
Here is how the simple code looks like
2828
```python
2929
def fib(n):
30-
if n <= 1:
31-
return n
32-
return fib(n - 1) + fib(n - 2) print(fib(6))
30+
if n <= 1:
31+
return n
32+
return fib(n - 1) + fib(n - 2)
33+
34+
print(fib(6))
3335
```
3436

3537
Now we want to draw the recursion tree for this function. It is as simple as adding a decorator
3638
```python
37-
# Author: Bishal Sarang
38-
3939
# Import Visualiser class from module visualiser
4040
from visualiser.visualiser import Visualiser as vs
4141

42-
4342
# Add decorator
4443
# Decorator accepts arguments: ignore_args and show_argument_name
45-
@vs(ignore_args=['node_num'])
46-
def fib(n, node_num):
44+
@vs()
45+
def fib(n):
4746
if n <= 1:
4847
return n
49-
# Increment node count
50-
vs.node_count += 1
51-
left = fib(n=n - 1, node_num=vs.node_count)
52-
53-
# Increment node count
54-
vs.node_count += 1
55-
right = fib(n=n - 2, node_num=vs.node_count)
56-
return left + right
48+
return fib(n=n - 1) + fib(n=n - 2)
49+
5750

5851
def main():
5952
# Call function
60-
print(fib(n=6, node_num=0))
61-
53+
print(fib(n=6))
6254
# Save recursion tree to a file
6355
vs.write_image("fibonacci.png")
6456

@@ -67,10 +59,8 @@ if __name__ == "__main__":
6759
main()
6860
```
6961
Here are the changes required:
70-
71-
- Change function signature from `fib(n)` to `fib(n, node_num)`
72-
- Add decorator Visualiser which accepts arguments `ignore_args` and `show_argument_name`
73-
- Before each function calls make sure to increment `node_count` argument of decorator class
62+
63+
- Add decorator Visualiser which accepts optional arguments `ignore_args`, `show_argument_name` and 'show_return_value'
7464
- Change every function calls to pass as keyword arguments.
7565
- Write the image
7666

examples/fibonacci.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,18 @@
33
# Import Visualiser class from module visualiser
44
from visualiser.visualiser import Visualiser as vs
55

6-
76
# Add decorator
87
# Decorator accepts arguments: ignore_args and show_argument_name
9-
@vs(ignore_args=['node_num'])
10-
def fib(n, node_num):
8+
@vs()
9+
def fib(n):
1110
if n <= 1:
1211
return n
13-
vs.node_count += 1
14-
left = fib(n=n - 1, node_num=vs.node_count)
12+
return fib(n=n - 1) + fib(n=n - 2)
1513

16-
vs.node_count += 1
17-
right = fib(n=n - 2, node_num=vs.node_count)
18-
return left + right
1914

2015
def main():
2116
# Call function
22-
print(fib(n=6, node_num=0))
23-
17+
print(fib(n=6))
2418
# Save recursion tree to a file
2519
vs.write_image("fibonacci.png")
2620

examples/make_sum.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919

2020
@vs(ignore_args=['node_num'], show_argument_name=False, show_return_value=False)
21-
def f(sum, ans, node_num):
21+
def f(sum, ans):
2222
# If sum becoms 0 we have found the required list
2323
if sum == 0:
2424
print(ans)
@@ -27,16 +27,13 @@ def f(sum, ans, node_num):
2727
# Number that is included also can be included
2828
for elem in nums:
2929
if sum - elem >= 0:
30-
31-
# Increment vs.node_count before each function call
32-
vs.node_count += 1
33-
f(sum=sum - elem, ans=ans + [elem], node_num=vs.node_count)
30+
f(sum=sum - elem, ans=ans + [elem])
3431

3532

3633
# We want to make the sum from list nums
3734
nums = [2, 3, 7]
3835
sum = 10
3936

4037
# Call solve with sum and an empty list
41-
f(sum=sum, ans=[], node_num=0)
38+
f(sum=sum, ans=[])
4239
vs.write_image("make_sum.png")

examples/subset.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,19 @@
1212
"""
1313

1414
subsets = []
15-
@vs(ignore_args=["node_num", "nums"], show_return_value=False, show_argument_name=False)
16-
def f(nums, i, current_subset, node_num):
15+
@vs(ignore_args=["nums"], show_return_value=False, show_argument_name=False)
16+
def f(nums, i, current_subset):
1717
# If no more elements left
1818
if i == 0:
1919
subsets.append(current_subset)
2020
return
2121
# Exclude Current element
22-
vs.node_count += 1
23-
f(nums=nums, i=i - 1, current_subset=current_subset, node_num=vs.node_count)
22+
f(nums=nums, i=i - 1, current_subset=current_subset)
2423

2524
# Include current element
26-
vs.node_count += 1
27-
f(nums=nums, i=i - 1, current_subset=current_subset + [nums[i - 1]], node_num=vs.node_count)
25+
f(nums=nums, i=i - 1, current_subset=current_subset + [nums[i - 1]])
2826

2927
if __name__ == "__main__":
3028
nums = [1, 2, 3]
31-
f(nums=nums, i = len(nums), current_subset=[], node_num=0)
29+
f(nums=nums, i = len(nums), current_subset=[])
3230
vs.write_image("subset.png")

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
setuptools.setup(
77
name="recursion-visualiser", # Replace with your own username
8-
version="0.0.2",
8+
version="0.0.4",
99
author="Bishal Sarangkoti",
1010
author_email="sarangbishal@gmail.com",
1111
description="A small python package to visualise recursive function on Python. It draws recursion tree",

visualiser/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11

2-
# Version of the realpython-reader package
3-
__version__ = "0.0.2"
2+
# Version of the recursion-visualiser
3+
__version__ = "0.0.4"

visualiser/visualiser.py

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,24 @@
77

88

99
class Visualiser(object):
10+
# Total number of nodes
1011
node_count = 0
1112
graph = pydot.Dot(graph_type="digraph")
13+
# To track function call numbers
14+
stack = []
1215

13-
def __init__(self, ignore_args, show_argument_name=True, show_return_value=True):
16+
def __init__(self, ignore_args=None, show_argument_name=True, show_return_value=True):
17+
#If enabled shows keyword arguments ordered by keys
1418
self.show_argument_name = show_argument_name
19+
# If enables shows the return value at every nodes
1520
self.show_return_value = show_return_value
16-
self.ignore_args = ignore_args
21+
22+
# Argument string that are to be ignored in diagram
23+
if ignore_args is None:
24+
self.ignore_args = ['node_num']
25+
else:
26+
self.ignore_args = ['node_num'] + ignore_args
27+
1728

1829
@classmethod
1930
def write_image(cls, filename="out.png"):
@@ -57,9 +68,16 @@ def extract_signature_label_arg_string(self, *args, **kwargs):
5768
def __call__(self, fn):
5869
@wraps(fn)
5970
def wrapper(*args, **kwargs):
71+
72+
# Increment total number of nodes when a call is made
73+
self.node_count += 1
74+
75+
# Update kwargs by adding dummy keyword node_num which helps to uniquely identify each node
76+
kwargs.update({'node_num': self.node_count})
6077
# Order all the keyword arguments
6178
kwargs = OrderedDict(sorted(kwargs.items()))
6279

80+
6381
"""Details about current Function"""
6482
# Get signature and label arguments strings for current function
6583
current_function_argument_string, current_function_label_argument_string = self.extract_signature_label_arg_string(*args, **kwargs)
@@ -85,6 +103,11 @@ def wrapper(*args, **kwargs):
85103
# Extract only those locals that are in arguments
86104
caller_function_kwargs = {key: value for key, value in caller_function_locals.items() if key in caller_function_argument_names}
87105

106+
# If the nodes has parent node get node_num from parent node
107+
if self.stack:
108+
caller_function_kwargs.update({'node_num': self.stack[-1]})
109+
caller_function_kwargs = OrderedDict(sorted(caller_function_kwargs.items()))
110+
88111
caller_function_argument_string, caller_function_label_argument_string = self.extract_signature_label_arg_string(**caller_function_kwargs)
89112

90113
# Caller Function
@@ -98,8 +121,16 @@ def wrapper(*args, **kwargs):
98121
if caller_function_name == '<module>':
99122
print(f"Drawing for {current_function_signature}")
100123

124+
# Push node_count to stack
125+
self.stack.append(self.node_count)
126+
# Before actual function call delete keyword 'node_num' from kwargs
127+
del kwargs['node_num']
128+
# Return after function call
101129
result = fn(*args, **kwargs)
102130

131+
# Pop from tha stack after returning
132+
self.stack.pop()
133+
103134
# If show_return_value flag is set, display the result
104135
if self.show_return_value:
105136
current_function_label += f" => {result}"
@@ -116,26 +147,4 @@ def wrapper(*args, **kwargs):
116147
edge = pydot.Edge(parent_node, child_node)
117148
self.graph.add_edge(edge)
118149
return result
119-
return wrapper
120-
121-
@Visualiser(ignore_args=["node_num"])
122-
def fib(n, node_num):
123-
if n <= 1:
124-
return n
125-
Visualiser.node_count += 1
126-
left = fib(n=n - 1, node_num=Visualiser.node_count)
127-
128-
Visualiser.node_count += 1
129-
right = fib(n=n - 2, node_num=Visualiser.node_count)
130-
return left + right
131-
132-
def main():
133-
# Call function
134-
print(fib(n=6, node_num=0))
135-
136-
# Save recursion tree to a file
137-
Visualiser.write_image("fibonacci.png")
138-
139-
140-
if __name__ == "__main__":
141-
main()
150+
return wrapper

0 commit comments

Comments
 (0)