invocation-tree 0.0.33__tar.gz → 0.0.35__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. {invocation_tree-0.0.33/invocation_tree.egg-info → invocation_tree-0.0.35}/PKG-INFO +32 -7
  2. {invocation_tree-0.0.33 → invocation_tree-0.0.35}/README.md +31 -6
  3. {invocation_tree-0.0.33 → invocation_tree-0.0.35}/invocation_tree/__init__.py +108 -9
  4. {invocation_tree-0.0.33 → invocation_tree-0.0.35/invocation_tree.egg-info}/PKG-INFO +32 -7
  5. invocation_tree-0.0.35/invocation_tree.egg-info/SOURCES.txt +10 -0
  6. {invocation_tree-0.0.33 → invocation_tree-0.0.35}/pyproject.toml +1 -1
  7. invocation_tree-0.0.33/MANIFEST.in +0 -1
  8. invocation_tree-0.0.33/images/compute.gif +0 -0
  9. invocation_tree-0.0.33/images/compute.py +0 -22
  10. invocation_tree-0.0.33/images/create_gif.sh +0 -25
  11. invocation_tree-0.0.33/images/create_images.sh +0 -47
  12. invocation_tree-0.0.33/images/draw_graph.py +0 -32
  13. invocation_tree-0.0.33/images/draw_graph_d_x.py +0 -36
  14. invocation_tree-0.0.33/images/edges_big.out +0 -1
  15. invocation_tree-0.0.33/images/edges_big_d_x.out +0 -1
  16. invocation_tree-0.0.33/images/edges_small.out +0 -1
  17. invocation_tree-0.0.33/images/factorial.gif +0 -0
  18. invocation_tree-0.0.33/images/factorial.py +0 -9
  19. invocation_tree-0.0.33/images/graph.py +0 -35
  20. invocation_tree-0.0.33/images/graph_big.png +0 -0
  21. invocation_tree-0.0.33/images/graph_big_d_x.png +0 -0
  22. invocation_tree-0.0.33/images/graph_small.png +0 -0
  23. invocation_tree-0.0.33/images/jugs.png +0 -0
  24. invocation_tree-0.0.33/images/jugs_depth_first.py +0 -38
  25. invocation_tree-0.0.33/images/perms_LR3.png +0 -0
  26. invocation_tree-0.0.33/images/permutations.gif +0 -0
  27. invocation_tree-0.0.33/images/permutations.py +0 -11
  28. invocation_tree-0.0.33/images/permutations_collect.gif +0 -0
  29. invocation_tree-0.0.33/images/permutations_collect.py +0 -13
  30. invocation_tree-0.0.33/images/permutations_dot.py +0 -30
  31. invocation_tree-0.0.33/images/permutations_neighbor.gif +0 -0
  32. invocation_tree-0.0.33/images/permutations_neighbor.py +0 -12
  33. invocation_tree-0.0.33/images/permutations_return.gif +0 -0
  34. invocation_tree-0.0.33/images/permutations_return.py +0 -13
  35. invocation_tree-0.0.33/images/permutations_vscode.gif +0 -0
  36. invocation_tree-0.0.33/images/print_all_paths.py +0 -26
  37. invocation_tree-0.0.33/images/print_all_paths_of_length.py +0 -27
  38. invocation_tree-0.0.33/images/print_all_paths_via.py +0 -32
  39. invocation_tree-0.0.33/images/quick_sort.gif +0 -0
  40. invocation_tree-0.0.33/images/quick_sort.py +0 -16
  41. invocation_tree-0.0.33/images/students.gif +0 -0
  42. invocation_tree-0.0.33/images/students.py +0 -28
  43. invocation_tree-0.0.33/images/vscode.png +0 -0
  44. invocation_tree-0.0.33/invocation_tree.egg-info/SOURCES.txt +0 -47
  45. {invocation_tree-0.0.33 → invocation_tree-0.0.35}/LICENSE.txt +0 -0
  46. {invocation_tree-0.0.33 → invocation_tree-0.0.35}/invocation_tree/regex_set.py +0 -0
  47. {invocation_tree-0.0.33 → invocation_tree-0.0.35}/invocation_tree.egg-info/dependency_links.txt +0 -0
  48. {invocation_tree-0.0.33 → invocation_tree-0.0.35}/invocation_tree.egg-info/requires.txt +0 -0
  49. {invocation_tree-0.0.33 → invocation_tree-0.0.35}/invocation_tree.egg-info/top_level.txt +0 -0
  50. {invocation_tree-0.0.33 → invocation_tree-0.0.35}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: invocation_tree
3
- Version: 0.0.33
3
+ Version: 0.0.35
4
4
  Summary: Generates an invocation tree of functions calls.
5
5
  Author-email: Bas Terwijn <bterwijn@gmail.com>
6
6
  License-Expression: BSD-2-Clause
@@ -32,6 +32,8 @@ Run a live demo in the 👉 [**Invocation Tree Web Debugger**](https://invocatio
32
32
  - shows the invocation tree (call tree) of a program **in real time**
33
33
  - helps to **understand recursion** and its depth-first nature
34
34
 
35
+ The new `@ivt.show` [decorator interface](#decorator) now allows to scale to application in **production code**.
36
+
35
37
  # Topics #
36
38
 
37
39
  [Iteration and Recursion](#iteration-and-recursion)
@@ -67,7 +69,7 @@ ___
67
69
 
68
70
  # Iteration and Recursion #
69
71
 
70
- Repetion can be implemented with **iteration** and **recursion**. As an example we compute the factorial of 4.
72
+ Repetition can be implemented with **iteration** and **recursion**. As an example we compute the factorial of 4.
71
73
 
72
74
  ``` python
73
75
  import math
@@ -119,7 +121,7 @@ factorial(4) = 4 * factorial(3)
119
121
  = 24
120
122
  ```
121
123
 
122
- To better understand what is going on when we run the program we can use invocation_tree:
124
+ To better understand what is going on when we run the program, we can use invocation_tree:
123
125
 
124
126
  ```python
125
127
  import invocation_tree as ivt
@@ -133,7 +135,7 @@ tree = ivt.blocking() # block and wait for <Enter> key press
133
135
  tree(factorial, 4) # call function 'factorial' with argument '4'
134
136
  ```
135
137
 
136
- to graph the function invocations. Run this program and press &lt;Enter&gt; to step through program execution.
138
+ to graph the function invocations where function calls happen on the way down, and where results combine on the way up when a function returns. Run this program and press &lt;Enter&gt; to step through program execution.
137
139
 
138
140
  ![factorial](https://raw.githubusercontent.com/bterwijn/invocation_tree/main/images/factorial.gif)
139
141
 
@@ -147,7 +149,7 @@ Each node in the invocation tree represents a function call, and the node's colo
147
149
 
148
150
  For every function call, the package displays its **local variables** and **return value**. Changes to the values of these variables over time are highlighted using bold text and gray shading to make them easier to track.
149
151
 
150
- With recursion we often use a divide and conquer strategy, spliting the problem in subproblems that are easier to solve. With factorial we split `factorial(4)` in a `4` and `factorial(3)` subproblem.
152
+ With recursion we often use a divide and conquer strategy, splitting the problem in subproblems that are easier to solve. With factorial we split `factorial(4)` in a `4` and `factorial(3)` subproblem.
151
153
 
152
154
  **exercise1:** Use recursions to compute the sum of all the values in a list (hint: split for example the list `[1, 2, 3, ...]` in head `1` and tail `[2, 3, ...]`).
153
155
  ```python
@@ -170,7 +172,7 @@ def binary(decimal):
170
172
  print( binary(22) ) # [1, 0, 1, 1, 0]
171
173
  ```
172
174
 
173
- Checking that `[1, 0, 1, 1, 0]` is the correct binary repsentation for decimal 22:
175
+ Checking that `[1, 0, 1, 1, 0]` is the correct binary representation for decimal 22:
174
176
 
175
177
  | | 2<sup>4</sup> | 2<sup>3</sup> | 2<sup>2</sup> | 2<sup>1</sup> | 2<sup>0</sup> | |
176
178
  |------:|:-------------:|:-------------:|:-------------:|:-------------:|:-------------:|:-------------:|
@@ -187,7 +189,7 @@ We can use recursion to compute all permutation of a number of elements with rep
187
189
 
188
190
  ![perms_LR3](https://raw.githubusercontent.com/bterwijn/invocation_tree/main/images/perms_LR3.png)
189
191
 
190
- This can be implemented recursively like:
192
+ This can be implemented recursively, using a divide and conquer strategy, like:
191
193
 
192
194
  ```python
193
195
  import invocation_tree as ivt
@@ -510,6 +512,7 @@ $ python jugs_breadth_first.py 51 3,5,34,107
510
512
  # Configuration #
511
513
  The Invocation Tree Web Debugger gives examples of the [most important configurations](https://invocation-tree.com/#codeurl=https://raw.githubusercontent.com/bterwijn/invocation_tree/refs/heads/main/src/config.py).
512
514
 
515
+
513
516
  ## Hidding ##
514
517
  It can be useful to hide certian variables or functions to avoid unnecessary complexity. This can be done with:
515
518
 
@@ -543,6 +546,28 @@ tree.ignore_calls.add(r're:namespace\..*')
543
546
 
544
547
  ignores all function of `namespace`.
545
548
 
549
+ ## Decorator ##
550
+
551
+ A better way to hide functions is to use the `@ivt.show` decorator on only the functions you want to graph. The decorator uses the global `ivt.decorator_tree` tree.
552
+
553
+ ```python
554
+ import invocation_tree as ivt
555
+ ivt.decorator_tree = ivt.blocking() # set tree used by decorator
556
+ #ivt.decorator_tree = ivt.blocking_each_change() # block at each change, but much slower
557
+
558
+ @ivt.show # use decorator to select which functions to graph
559
+ def permutations(elements, perm, n):
560
+ if n == 0:
561
+ print(perm)
562
+ else:
563
+ for element in elements:
564
+ perm.append(element)
565
+ permutations(elements, perm, n-1)
566
+ perm.pop()
567
+
568
+ permutations( 'LR', [], 3) # all permutations of L and R of length 3
569
+ ```
570
+
546
571
  ## Blocking ##
547
572
 
548
573
  For convenience we provide these functions to set common configurations:
@@ -12,6 +12,8 @@ Run a live demo in the 👉 [**Invocation Tree Web Debugger**](https://invocatio
12
12
  - shows the invocation tree (call tree) of a program **in real time**
13
13
  - helps to **understand recursion** and its depth-first nature
14
14
 
15
+ The new `@ivt.show` [decorator interface](#decorator) now allows to scale to application in **production code**.
16
+
15
17
  # Topics #
16
18
 
17
19
  [Iteration and Recursion](#iteration-and-recursion)
@@ -47,7 +49,7 @@ ___
47
49
 
48
50
  # Iteration and Recursion #
49
51
 
50
- Repetion can be implemented with **iteration** and **recursion**. As an example we compute the factorial of 4.
52
+ Repetition can be implemented with **iteration** and **recursion**. As an example we compute the factorial of 4.
51
53
 
52
54
  ``` python
53
55
  import math
@@ -99,7 +101,7 @@ factorial(4) = 4 * factorial(3)
99
101
  = 24
100
102
  ```
101
103
 
102
- To better understand what is going on when we run the program we can use invocation_tree:
104
+ To better understand what is going on when we run the program, we can use invocation_tree:
103
105
 
104
106
  ```python
105
107
  import invocation_tree as ivt
@@ -113,7 +115,7 @@ tree = ivt.blocking() # block and wait for <Enter> key press
113
115
  tree(factorial, 4) # call function 'factorial' with argument '4'
114
116
  ```
115
117
 
116
- to graph the function invocations. Run this program and press &lt;Enter&gt; to step through program execution.
118
+ to graph the function invocations where function calls happen on the way down, and where results combine on the way up when a function returns. Run this program and press &lt;Enter&gt; to step through program execution.
117
119
 
118
120
  ![factorial](https://raw.githubusercontent.com/bterwijn/invocation_tree/main/images/factorial.gif)
119
121
 
@@ -127,7 +129,7 @@ Each node in the invocation tree represents a function call, and the node's colo
127
129
 
128
130
  For every function call, the package displays its **local variables** and **return value**. Changes to the values of these variables over time are highlighted using bold text and gray shading to make them easier to track.
129
131
 
130
- With recursion we often use a divide and conquer strategy, spliting the problem in subproblems that are easier to solve. With factorial we split `factorial(4)` in a `4` and `factorial(3)` subproblem.
132
+ With recursion we often use a divide and conquer strategy, splitting the problem in subproblems that are easier to solve. With factorial we split `factorial(4)` in a `4` and `factorial(3)` subproblem.
131
133
 
132
134
  **exercise1:** Use recursions to compute the sum of all the values in a list (hint: split for example the list `[1, 2, 3, ...]` in head `1` and tail `[2, 3, ...]`).
133
135
  ```python
@@ -150,7 +152,7 @@ def binary(decimal):
150
152
  print( binary(22) ) # [1, 0, 1, 1, 0]
151
153
  ```
152
154
 
153
- Checking that `[1, 0, 1, 1, 0]` is the correct binary repsentation for decimal 22:
155
+ Checking that `[1, 0, 1, 1, 0]` is the correct binary representation for decimal 22:
154
156
 
155
157
  | | 2<sup>4</sup> | 2<sup>3</sup> | 2<sup>2</sup> | 2<sup>1</sup> | 2<sup>0</sup> | |
156
158
  |------:|:-------------:|:-------------:|:-------------:|:-------------:|:-------------:|:-------------:|
@@ -167,7 +169,7 @@ We can use recursion to compute all permutation of a number of elements with rep
167
169
 
168
170
  ![perms_LR3](https://raw.githubusercontent.com/bterwijn/invocation_tree/main/images/perms_LR3.png)
169
171
 
170
- This can be implemented recursively like:
172
+ This can be implemented recursively, using a divide and conquer strategy, like:
171
173
 
172
174
  ```python
173
175
  import invocation_tree as ivt
@@ -490,6 +492,7 @@ $ python jugs_breadth_first.py 51 3,5,34,107
490
492
  # Configuration #
491
493
  The Invocation Tree Web Debugger gives examples of the [most important configurations](https://invocation-tree.com/#codeurl=https://raw.githubusercontent.com/bterwijn/invocation_tree/refs/heads/main/src/config.py).
492
494
 
495
+
493
496
  ## Hidding ##
494
497
  It can be useful to hide certian variables or functions to avoid unnecessary complexity. This can be done with:
495
498
 
@@ -523,6 +526,28 @@ tree.ignore_calls.add(r're:namespace\..*')
523
526
 
524
527
  ignores all function of `namespace`.
525
528
 
529
+ ## Decorator ##
530
+
531
+ A better way to hide functions is to use the `@ivt.show` decorator on only the functions you want to graph. The decorator uses the global `ivt.decorator_tree` tree.
532
+
533
+ ```python
534
+ import invocation_tree as ivt
535
+ ivt.decorator_tree = ivt.blocking() # set tree used by decorator
536
+ #ivt.decorator_tree = ivt.blocking_each_change() # block at each change, but much slower
537
+
538
+ @ivt.show # use decorator to select which functions to graph
539
+ def permutations(elements, perm, n):
540
+ if n == 0:
541
+ print(perm)
542
+ else:
543
+ for element in elements:
544
+ perm.append(element)
545
+ permutations(elements, perm, n-1)
546
+ perm.pop()
547
+
548
+ permutations( 'LR', [], 3) # all permutations of L and R of length 3
549
+ ```
550
+
526
551
  ## Blocking ##
527
552
 
528
553
  For convenience we provide these functions to set common configurations:
@@ -6,10 +6,11 @@ from graphviz import Digraph
6
6
  import html
7
7
  import sys
8
8
  import difflib
9
+ import functools
9
10
 
10
11
  import invocation_tree.regex_set as regset
11
12
 
12
- __version__ = "0.0.33"
13
+ __version__ = "0.0.35"
13
14
  __author__ = 'Bas Terwijn'
14
15
 
15
16
  def highlight_diff(str1, str2):
@@ -134,7 +135,7 @@ class Invocation_Tree:
134
135
  sys.settrace(self.prev_global_tracer)
135
136
  return result
136
137
 
137
- def value_to_string(self, key, value, use_repr=False):
138
+ def value_to_string(self, key, value):
138
139
  try:
139
140
  if id(value) in self.to_string:
140
141
  val_str = self.to_string[id(value)](value)
@@ -143,18 +144,21 @@ class Invocation_Tree:
143
144
  elif type(value) in self.to_string:
144
145
  val_str = self.to_string[type(value)](value)
145
146
  else:
146
- val_str = repr(value) if use_repr else str(value)
147
+ val_str = str(value)
147
148
  except Exception as e:
148
149
  val_str = '<not-string-convertable>'
149
150
  if len(val_str) > self.max_string_len:
150
151
  val_str = '...'+val_str[-self.max_string_len:]
151
- return html.escape(val_str)
152
-
153
- def get_hightlighted_content(self, tree_node, key, value, use_old_content=False, use_repr=False):
152
+ result = html.escape(val_str)
153
+ if '\n' in result:
154
+ result = '<BR/>' + result.replace('\n', '<BR/>') # use HTML line breaks
155
+ return result
156
+
157
+ def get_hightlighted_content(self, tree_node, key, value, use_old_content=False):
154
158
  if use_old_content and key in tree_node.strings:
155
159
  return tree_node.strings[key]
156
160
  is_highlighted = False
157
- content = self.value_to_string(key, value, use_repr=use_repr)
161
+ content = self.value_to_string(key, value)
158
162
  if key in tree_node.strings:
159
163
  use_old_content = tree_node.strings[key]
160
164
  hightlighted_content, is_highlighted = highlight_diff(use_old_content, content)
@@ -192,14 +196,14 @@ class Invocation_Tree:
192
196
  if filter_variables(var,val) and not self.regset_hide_vars.match(val_name, self.hide_vars):
193
197
  table += '</TR>\n <TR>'
194
198
  hightlighted_var = self.get_hightlighted_content(tree_node, var_name, var, use_old_content)
195
- hightlighted_val = self.get_hightlighted_content(tree_node, val_name, val, use_old_content, use_repr=True)
199
+ hightlighted_val = self.get_hightlighted_content(tree_node, val_name, val, use_old_content)
196
200
  hightlighted_content = self.indent + hightlighted_var + ': ' + hightlighted_val
197
201
  table += '<TD ALIGN="left">'+ hightlighted_content +'</TD>'
198
202
  if is_returned:
199
203
  return_name = class_fun_name+'.return'
200
204
  if not self.regset_hide_vars.match(return_name, self.hide_vars):
201
205
  table += '</TR>\n <TR>'
202
- hightlighted_content = self.get_hightlighted_content(tree_node, return_name, return_value, use_old_content, use_repr=True)
206
+ hightlighted_content = self.get_hightlighted_content(tree_node, return_name, return_value, use_old_content)
203
207
  table += '<TD ALIGN="left">'+ 'return ' + hightlighted_content +'</TD>'
204
208
  table += '</TR>\n</TABLE>>'
205
209
  return table
@@ -349,6 +353,11 @@ class Invocation_Tree:
349
353
  return None # stop tracing if debugger stopped tracing
350
354
  return local_multiplexer
351
355
 
356
+ # Optimize: disable line tracing if not needed
357
+ if not self.each_line:
358
+ frame.f_trace_lines = False
359
+ frame.f_trace_opcodes = False
360
+
352
361
  return local_multiplexer
353
362
 
354
363
  def blocking(filename='tree.pdf'):
@@ -371,3 +380,93 @@ def gif_each_change(filename='tree.png'):
371
380
 
372
381
  def non_blocking(filename='tree.pdf'):
373
382
  return Invocation_Tree(filename=filename, block=False)
383
+
384
+
385
+ # ------ decorator ------
386
+
387
+ decorator_tree = None
388
+
389
+ def show(fun):
390
+ """ decorate a function with @ivt.show to visualize its invocation tree """
391
+ target_code = fun.__code__
392
+
393
+ @functools.wraps(fun)
394
+ def wrapper_ivt_deco(*args, **kwargs):
395
+ global decorator_tree
396
+ if decorator_tree is None:
397
+ return fun(*args, **kwargs)
398
+ prev_tracer = sys.gettrace()
399
+ active_depth = 0 # handles recursion of fun
400
+
401
+ def tracer(frame, event, arg):
402
+ nonlocal active_depth
403
+
404
+ if event == "exception":
405
+ exc_type, exc, tb = arg
406
+ if exc_type is KeyboardInterrupt:
407
+ # ensure we stop tracing before bailing out
408
+ sys.settrace(prev_tracer)
409
+ raise exc.with_traceback(tb)
410
+
411
+ # Check if this is our target function
412
+ if frame.f_code is target_code:
413
+ if event == "call":
414
+ active_depth += 1
415
+ if active_depth == 1:
416
+ decorator_tree.trace(frame, event, None)
417
+ # Optimize: disable line tracing if not needed
418
+ if not getattr(decorator_tree, "each_line", False):
419
+ frame.f_trace_lines = False
420
+ frame.f_trace_opcodes = False
421
+ elif event == "line":
422
+ if active_depth == 1 and getattr(decorator_tree, "each_line", False):
423
+ decorator_tree.trace(frame, event, None)
424
+ elif event == "return":
425
+ if active_depth == 1:
426
+ decorator_tree.trace(frame, event, arg)
427
+ active_depth -= 1
428
+ return tracer
429
+
430
+ # Not our target: chain to previous tracer or return None to stop tracing this frame
431
+ if prev_tracer is not None:
432
+ return prev_tracer(frame, event, arg)
433
+
434
+ return None # Don't trace non-target frames
435
+
436
+ sys.settrace(tracer)
437
+ try:
438
+ return fun(*args, **kwargs)
439
+ finally:
440
+ sys.settrace(prev_tracer)
441
+ return wrapper_ivt_deco
442
+
443
+ # faster decorator but no 'line' events
444
+ def decorate_profile(fun):
445
+ target_code = fun.__code__
446
+
447
+ @functools.wraps(fun)
448
+ def wrapper_ivt_deco(*args, **kwargs):
449
+ global decorator_tree
450
+ if decorator_tree is None:
451
+ return fun(*args, **kwargs)
452
+ prev_profiler = sys.getprofile()
453
+ active_depth = 0 # handles recursion of fun
454
+
455
+ def profiler(frame, event, arg):
456
+ nonlocal active_depth
457
+ if frame.f_code is target_code:
458
+ if event == "call":
459
+ active_depth += 1
460
+ if active_depth == 1:
461
+ decorator_tree.trace(frame, event, None)
462
+ elif event == "return":
463
+ if active_depth == 1:
464
+ decorator_tree.trace(frame, event, arg)
465
+ active_depth -= 1
466
+
467
+ sys.setprofile(profiler)
468
+ try:
469
+ return fun(*args, **kwargs)
470
+ finally:
471
+ sys.setprofile(prev_profiler)
472
+ return wrapper_ivt_deco
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: invocation_tree
3
- Version: 0.0.33
3
+ Version: 0.0.35
4
4
  Summary: Generates an invocation tree of functions calls.
5
5
  Author-email: Bas Terwijn <bterwijn@gmail.com>
6
6
  License-Expression: BSD-2-Clause
@@ -32,6 +32,8 @@ Run a live demo in the 👉 [**Invocation Tree Web Debugger**](https://invocatio
32
32
  - shows the invocation tree (call tree) of a program **in real time**
33
33
  - helps to **understand recursion** and its depth-first nature
34
34
 
35
+ The new `@ivt.show` [decorator interface](#decorator) now allows to scale to application in **production code**.
36
+
35
37
  # Topics #
36
38
 
37
39
  [Iteration and Recursion](#iteration-and-recursion)
@@ -67,7 +69,7 @@ ___
67
69
 
68
70
  # Iteration and Recursion #
69
71
 
70
- Repetion can be implemented with **iteration** and **recursion**. As an example we compute the factorial of 4.
72
+ Repetition can be implemented with **iteration** and **recursion**. As an example we compute the factorial of 4.
71
73
 
72
74
  ``` python
73
75
  import math
@@ -119,7 +121,7 @@ factorial(4) = 4 * factorial(3)
119
121
  = 24
120
122
  ```
121
123
 
122
- To better understand what is going on when we run the program we can use invocation_tree:
124
+ To better understand what is going on when we run the program, we can use invocation_tree:
123
125
 
124
126
  ```python
125
127
  import invocation_tree as ivt
@@ -133,7 +135,7 @@ tree = ivt.blocking() # block and wait for <Enter> key press
133
135
  tree(factorial, 4) # call function 'factorial' with argument '4'
134
136
  ```
135
137
 
136
- to graph the function invocations. Run this program and press &lt;Enter&gt; to step through program execution.
138
+ to graph the function invocations where function calls happen on the way down, and where results combine on the way up when a function returns. Run this program and press &lt;Enter&gt; to step through program execution.
137
139
 
138
140
  ![factorial](https://raw.githubusercontent.com/bterwijn/invocation_tree/main/images/factorial.gif)
139
141
 
@@ -147,7 +149,7 @@ Each node in the invocation tree represents a function call, and the node's colo
147
149
 
148
150
  For every function call, the package displays its **local variables** and **return value**. Changes to the values of these variables over time are highlighted using bold text and gray shading to make them easier to track.
149
151
 
150
- With recursion we often use a divide and conquer strategy, spliting the problem in subproblems that are easier to solve. With factorial we split `factorial(4)` in a `4` and `factorial(3)` subproblem.
152
+ With recursion we often use a divide and conquer strategy, splitting the problem in subproblems that are easier to solve. With factorial we split `factorial(4)` in a `4` and `factorial(3)` subproblem.
151
153
 
152
154
  **exercise1:** Use recursions to compute the sum of all the values in a list (hint: split for example the list `[1, 2, 3, ...]` in head `1` and tail `[2, 3, ...]`).
153
155
  ```python
@@ -170,7 +172,7 @@ def binary(decimal):
170
172
  print( binary(22) ) # [1, 0, 1, 1, 0]
171
173
  ```
172
174
 
173
- Checking that `[1, 0, 1, 1, 0]` is the correct binary repsentation for decimal 22:
175
+ Checking that `[1, 0, 1, 1, 0]` is the correct binary representation for decimal 22:
174
176
 
175
177
  | | 2<sup>4</sup> | 2<sup>3</sup> | 2<sup>2</sup> | 2<sup>1</sup> | 2<sup>0</sup> | |
176
178
  |------:|:-------------:|:-------------:|:-------------:|:-------------:|:-------------:|:-------------:|
@@ -187,7 +189,7 @@ We can use recursion to compute all permutation of a number of elements with rep
187
189
 
188
190
  ![perms_LR3](https://raw.githubusercontent.com/bterwijn/invocation_tree/main/images/perms_LR3.png)
189
191
 
190
- This can be implemented recursively like:
192
+ This can be implemented recursively, using a divide and conquer strategy, like:
191
193
 
192
194
  ```python
193
195
  import invocation_tree as ivt
@@ -510,6 +512,7 @@ $ python jugs_breadth_first.py 51 3,5,34,107
510
512
  # Configuration #
511
513
  The Invocation Tree Web Debugger gives examples of the [most important configurations](https://invocation-tree.com/#codeurl=https://raw.githubusercontent.com/bterwijn/invocation_tree/refs/heads/main/src/config.py).
512
514
 
515
+
513
516
  ## Hidding ##
514
517
  It can be useful to hide certian variables or functions to avoid unnecessary complexity. This can be done with:
515
518
 
@@ -543,6 +546,28 @@ tree.ignore_calls.add(r're:namespace\..*')
543
546
 
544
547
  ignores all function of `namespace`.
545
548
 
549
+ ## Decorator ##
550
+
551
+ A better way to hide functions is to use the `@ivt.show` decorator on only the functions you want to graph. The decorator uses the global `ivt.decorator_tree` tree.
552
+
553
+ ```python
554
+ import invocation_tree as ivt
555
+ ivt.decorator_tree = ivt.blocking() # set tree used by decorator
556
+ #ivt.decorator_tree = ivt.blocking_each_change() # block at each change, but much slower
557
+
558
+ @ivt.show # use decorator to select which functions to graph
559
+ def permutations(elements, perm, n):
560
+ if n == 0:
561
+ print(perm)
562
+ else:
563
+ for element in elements:
564
+ perm.append(element)
565
+ permutations(elements, perm, n-1)
566
+ perm.pop()
567
+
568
+ permutations( 'LR', [], 3) # all permutations of L and R of length 3
569
+ ```
570
+
546
571
  ## Blocking ##
547
572
 
548
573
  For convenience we provide these functions to set common configurations:
@@ -0,0 +1,10 @@
1
+ LICENSE.txt
2
+ README.md
3
+ pyproject.toml
4
+ invocation_tree/__init__.py
5
+ invocation_tree/regex_set.py
6
+ invocation_tree.egg-info/PKG-INFO
7
+ invocation_tree.egg-info/SOURCES.txt
8
+ invocation_tree.egg-info/dependency_links.txt
9
+ invocation_tree.egg-info/requires.txt
10
+ invocation_tree.egg-info/top_level.txt
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "invocation_tree"
7
- version = "0.0.33"
7
+ version = "0.0.35"
8
8
  description = "Generates an invocation tree of functions calls."
9
9
  authors = [
10
10
  {name = "Bas Terwijn", email = "bterwijn@gmail.com"}
@@ -1 +0,0 @@
1
- recursive-include images/ *
Binary file
@@ -1,22 +0,0 @@
1
- import invocation_tree as ivt
2
-
3
- def main():
4
- a = 1
5
- a = expression(a)
6
- return multiply(a, 6)
7
-
8
- def expression(a):
9
- a = subtract(a, 3)
10
- return add(a, 9)
11
-
12
- def subtract(a, b):
13
- return a - b
14
-
15
- def add(a, b):
16
- return a + b
17
-
18
- def multiply(a, b):
19
- return a * b
20
-
21
- tree = ivt.gif('compute.png')
22
- print( tree(main) )
@@ -1,25 +0,0 @@
1
- #!/bin/bash
2
- #
3
- # install:
4
- #
5
- # sudo apt install imagemagick
6
-
7
- name="$1"
8
- files=$(ls -v $name*.png | tr '\n' ' ')
9
- echo "creating gif with:"
10
- echo "$files"
11
-
12
- largest_size=$(identify -format "%H %Wx%H %f\n" $name*.png | sort -nr | head -n1| awk '{print $2}')
13
- echo "largest_size: $largest_size"
14
-
15
- echo "resizing images"
16
- mogrify -resize $largest_size -background white -gravity North -extent $largest_size $files
17
- echo "creating file: $name.gif"
18
- convert -delay 150 -dither None -loop 0 $files $name.gif
19
-
20
- if [ "$2" = "-d" ]; then
21
- echo "deleting: $files"
22
- rm $files
23
- fi
24
-
25
- echo "done"
@@ -1,47 +0,0 @@
1
-
2
- #rm -f compute*.png
3
- #python compute.py
4
- #rm -f compute0.png
5
- #bash create_gif.sh compute -d
6
-
7
- #rm -f students*.png
8
- #python students.py
9
- #rm -f students0.png
10
- #bash create_gif.sh students -d
11
-
12
- rm -f factorial*.png
13
- python factorial.py
14
- rm -f factorial0.png
15
- bash create_gif.sh factorial -d
16
-
17
- python permutations_dot.py perms_LR3.png LR 3
18
-
19
- rm -f permutations*.png
20
- python permutations.py
21
- rm -f permutations0.png
22
- bash create_gif.sh permutations -d
23
-
24
- rm -f permutations_neighbor*.png
25
- python permutations_neighbor.py
26
- rm -f permutations_neighbor0.png
27
- bash create_gif.sh permutations_neighbor -d
28
-
29
- python draw_graph.py 10 2 graph_small 2 > edges_small.out
30
- python draw_graph.py 26 3 graph_big 1 > edges_big.out
31
- python draw_graph_d_x.py 26 3 graph_big_d_x 1 > edges_big_d_x.out
32
-
33
- rm -f permutations_return*.png
34
- python permutations_return.py
35
- rm -f permutations_return0.png
36
- bash create_gif.sh permutations_return -d
37
-
38
- rm -f permutations_collect*.png
39
- python permutations_collect.py
40
- rm -f permutations_collect0.png
41
- bash create_gif.sh permutations_collect -d
42
-
43
- rm -f quick_sort*.png
44
- python quick_sort.py
45
- rm -f quick_sort0.png
46
- bash create_gif.sh quick_sort -d
47
-
@@ -1,32 +0,0 @@
1
- import sys
2
- import graph
3
- import graphviz
4
-
5
- def draw_graph(nodes, edges, filename='graph.png'):
6
- dot = graphviz.Graph(
7
- format='png',
8
- )
9
- for node in nodes:
10
- # Color specific nodes
11
- if node == 'a':
12
- dot.node(node, fillcolor='green', style='filled')
13
- elif node == 'b':
14
- dot.node(node, fillcolor='red', style='filled')
15
- else:
16
- dot.node(node)
17
- for edge in edges:
18
- dot.edge(edge[0], edge[1])
19
- dot.render(filename, cleanup=True)
20
- print('edges = ', edges)
21
-
22
- if __name__ == "__main__":
23
- if len(sys.argv) != 5:
24
- print("Usage: python draw_graph.py <n> <max_edges> <output_file> <seed>")
25
- sys.exit(1)
26
- n = int(sys.argv[1])
27
- max_edges = int(sys.argv[2])
28
- output_file = sys.argv[3]
29
- seed = int(sys.argv[4])
30
-
31
- nodes, edges = graph.generate(n, max_edges, seed)
32
- draw_graph(nodes, edges, output_file)
@@ -1,36 +0,0 @@
1
- import sys
2
- import graph
3
- import graphviz
4
-
5
- def draw_graph(nodes, edges, filename='graph.png'):
6
- dot = graphviz.Graph(
7
- format='png',
8
- )
9
- for node in nodes:
10
- # Color specific nodes
11
- if node == 'a':
12
- dot.node(node, fillcolor='green', style='filled')
13
- elif node == 'b':
14
- dot.node(node, fillcolor='red', style='filled')
15
- elif node == 'd':
16
- dot.node(node, fillcolor='orange', style='filled')
17
- elif node == 'x':
18
- dot.node(node, fillcolor='gray', style='filled')
19
- else:
20
- dot.node(node)
21
- for edge in edges:
22
- dot.edge(edge[0], edge[1])
23
- dot.render(filename, cleanup=True)
24
- print('edges = ', edges)
25
-
26
- if __name__ == "__main__":
27
- if len(sys.argv) != 5:
28
- print("Usage: python draw_graph.py <n> <max_edges> <output_file> <seed>")
29
- sys.exit(1)
30
- n = int(sys.argv[1])
31
- max_edges = int(sys.argv[2])
32
- output_file = sys.argv[3]
33
- seed = int(sys.argv[4])
34
-
35
- nodes, edges = graph.generate(n, max_edges, seed)
36
- draw_graph(nodes, edges, output_file)
@@ -1 +0,0 @@
1
- edges = [('a', 's'), ('i', 'z'), ('c', 'p'), ('d', 'p'), ('d', 'u'), ('b', 'e'), ('b', 'g'), ('f', 'p'), ('g', 'm'), ('h', 't'), ('h', 'y'), ('i', 'w'), ('i', 'j'), ('i', 'x'), ('k', 's'), ('k', 'l'), ('a', 'm'), ('n', 'u'), ('a', 'o'), ('a', 'v'), ('n', 'p'), ('a', 'q'), ('a', 'h'), ('p', 'r'), ('l', 's'), ('t', 'v'), ('u', 'y'), ('j', 'v'), ('a', 'j'), ('r', 'w'), ('r', 'u'), ('f', 'x'), ('x', 'y'), ('j', 'x'), ('d', 'j'), ('b', 'k'), ('b', 'x'), ('b', 'w')]
@@ -1 +0,0 @@
1
- edges = [('a', 's'), ('i', 'z'), ('c', 'p'), ('d', 'p'), ('d', 'u'), ('b', 'e'), ('b', 'g'), ('f', 'p'), ('g', 'm'), ('h', 't'), ('h', 'y'), ('i', 'w'), ('i', 'j'), ('i', 'x'), ('k', 's'), ('k', 'l'), ('a', 'm'), ('n', 'u'), ('a', 'o'), ('a', 'v'), ('n', 'p'), ('a', 'q'), ('a', 'h'), ('p', 'r'), ('l', 's'), ('t', 'v'), ('u', 'y'), ('j', 'v'), ('a', 'j'), ('r', 'w'), ('r', 'u'), ('f', 'x'), ('x', 'y'), ('j', 'x'), ('d', 'j'), ('b', 'k'), ('b', 'x'), ('b', 'w')]
@@ -1 +0,0 @@
1
- edges = [('a', 'j'), ('f', 'j'), ('c', 'e'), ('b', 'd'), ('b', 'e'), ('f', 'g'), ('g', 'i'), ('h', 'i'), ('e', 'h'), ('a', 'i'), ('b', 'h'), ('b', 'f')]
Binary file
@@ -1,9 +0,0 @@
1
- import invocation_tree as ivt
2
-
3
- def factorial(n):
4
- if n <= 1:
5
- return 1
6
- return n * factorial(n - 1)
7
-
8
- tree = ivt.gif('factorial.png')
9
- print(tree(factorial, 4))
@@ -1,35 +0,0 @@
1
- import random
2
- import string
3
-
4
- def get_name(i):
5
- name = ''
6
- while True:
7
- i, remainder = divmod(i, 26)
8
- name = string.ascii_lowercase[remainder] + name
9
- if i == 0:
10
- break
11
- i -= 1
12
- return name
13
-
14
- def generate(n, max_edge, seed=0):
15
- random.seed(seed)
16
- nodes = [get_name(i) for i in range(n)]
17
- if len(nodes) > 2:
18
- nodes[1], nodes[-1] = nodes[-1], nodes[1]
19
- edges = []
20
- for node1 in nodes:
21
- nr_edges = random.randint(1, max_edge)
22
- for _ in range(nr_edges):
23
- node2 = random.choice(nodes)
24
- if node1 != node2:
25
- if node1 > node2:
26
- node1 , node2 = node2, node1
27
- edge = (node1, node2)
28
- if edge not in edges:
29
- edges.append(edge)
30
- return nodes, edges
31
-
32
- if __name__ == '__main__':
33
- nodes, edges = generate(10, 3)
34
- print('nodes =', nodes)
35
- print('edges =', edges)
Binary file
Binary file
@@ -1,38 +0,0 @@
1
- import invocation_tree as ivt
2
- import jugs as jg
3
-
4
- def solver_depth_first(jugs, goal):
5
-
6
- def solver_depth_first_recursive(jugs, goal, jugs_hist, action_hist):
7
- actions = jugs.all_actions()
8
- for action in actions:
9
- goal_reached = jugs.do_action(action, goal)
10
- if jugs not in jugs_hist:
11
- jugs_hist.add(jugs.copy())
12
- action_hist.append(action)
13
- if goal_reached:
14
- return action_hist
15
- result = solver_depth_first_recursive(jugs, goal, jugs_hist, action_hist)
16
- if result:
17
- return result
18
- action_hist.pop()
19
- jugs_hist.remove(jugs)
20
- jugs.undo_action(action)
21
- return None
22
-
23
- jugs_hist = {jugs}
24
- action_hist = []
25
- return solver_depth_first_recursive(jugs.copy(), goal, jugs_hist, action_hist)
26
-
27
- if __name__ == '__main__':
28
- goal = 4
29
- print('Goal is to get a jug with', goal, 'liters')
30
- jugs = jg.Jugs((3, 5))
31
- print('We start with jugs:',jugs)
32
- tree = ivt.blocking()
33
- tree.hide_vars.add('solver_depth_first_recursive.jugs_hist')
34
- tree.hide_vars.add('solver_depth_first_recursive.action_hist')
35
- tree.ignore_calls.add('re:Jugs.*')
36
- solution_actions = tree(solver_depth_first, jugs, goal)
37
- #solution_actions = solver_depth_first( jugs, goal)
38
- jg.print_solution(jugs, solution_actions)
Binary file
@@ -1,11 +0,0 @@
1
- import invocation_tree as ivt
2
-
3
- def permutations(elements, perm, n):
4
- if n == 0:
5
- print(perm)
6
- else:
7
- for element in elements:
8
- permutations(elements, perm + element, n-1)
9
-
10
- tree = ivt.gif('permutations.png')
11
- result = tree(permutations, 'LR', '', 3)
@@ -1,13 +0,0 @@
1
- import invocation_tree as ivt
2
-
3
- def permutations(elements, perm, n, results):
4
- if n == 0:
5
- results.append(perm)
6
- else:
7
- for element in elements:
8
- permutations(elements, perm + element, n-1, results)
9
-
10
- tree = ivt.gif('permutations_collect.png')
11
- results = []
12
- tree(permutations, 'LR', '', 3, results)
13
- print(results)
@@ -1,30 +0,0 @@
1
- import sys
2
-
3
- from graphviz import Digraph
4
-
5
- def add_permute_nodes(dot, elements, perm, n, parent_id=None, node_id=[0]):
6
- if n < 0:
7
- return
8
-
9
- my_id = node_id[0]
10
- node_id[0] += 1
11
- dot.node(str(my_id), perm)
12
-
13
-
14
- if parent_id is not None:
15
- dot.edge(str(parent_id), str(my_id))
16
-
17
- for i, ch in enumerate(elements):
18
- add_permute_nodes(dot, elements, perm + ch, n-1, parent_id=my_id, node_id=node_id)
19
-
20
- def make_permutation_tree(outfile, elements, n):
21
- dot = Digraph()
22
- dot.attr(rankdir='TB') # top to bottom
23
- add_permute_nodes(dot, elements, "", n)
24
- splits = outfile.split('.')
25
- dot.format = splits[-1]
26
- dot.render(filename=''.join(splits[:-1]), cleanup=True)
27
- print(f"Generated graph: {outfile}")
28
-
29
- if __name__ == "__main__":
30
- make_permutation_tree(sys.argv[1], sys.argv[2], int(sys.argv[3]))
@@ -1,12 +0,0 @@
1
- import invocation_tree as ivt
2
-
3
- def permutations(elems, perm, n):
4
- if n == 0:
5
- print(perm)
6
- else:
7
- for element in elems:
8
- if len(perm) == 0 or not perm[-1] == element: # test neighbor
9
- permutations(elems, perm + element, n-1)
10
-
11
- tree = ivt.gif('permutations_neighbor.png')
12
- tree(permutations, 'ABC', '', 3) # permutations of A, B, C of length 3
@@ -1,13 +0,0 @@
1
- import invocation_tree as ivt
2
-
3
- def permutations(elements, perm, n):
4
- if n == 0:
5
- return [perm]
6
- else:
7
- results = []
8
- for element in elements:
9
- results += permutations(elements, perm + element, n-1)
10
- return results
11
-
12
- tree = ivt.gif('permutations_return.png')
13
- print(tree(permutations, 'LR', '', 3))
@@ -1,26 +0,0 @@
1
- edges = [('a', 'j'), ('f', 'j'), ('c', 'e'), ('b', 'd'), ('b', 'e'), ('f', 'g'), ('g', 'i'), ('h', 'i'), ('e', 'h'), ('a', 'i'), ('b', 'h'), ('b', 'f')]
2
-
3
- def edges_to_steps(edges: list[tuple[str, str]]) -> dict[str,list[str]]:
4
- """ Returns a dict with for each node the nodes it is connected with. """
5
- steps = {}
6
- for n1, n2 in edges:
7
- if not n1 in steps:
8
- steps[n1] = []
9
- steps[n1].append(n2)
10
- if not n2 in steps:
11
- steps[n2] = []
12
- steps[n2].append(n1)
13
- return steps
14
-
15
- def print_all_paths(steps, path, goal):
16
- current = path[-1]
17
- if current == goal:
18
- print(path)
19
- else:
20
- valid_steps = steps[current]
21
- for s in valid_steps:
22
- if s not in path:
23
- print_all_paths(steps, path+s, goal)
24
-
25
- steps = edges_to_steps(edges)
26
- print_all_paths(steps, 'a', 'b')
@@ -1,27 +0,0 @@
1
- edges = [('a', 's'), ('i', 'z'), ('c', 'p'), ('d', 'p'), ('d', 'u'), ('b', 'e'), ('b', 'g'), ('f', 'p'), ('g', 'm'), ('h', 't'), ('h', 'y'), ('i', 'w'), ('i', 'j'), ('i', 'x'), ('k', 's'), ('k', 'l'), ('a', 'm'), ('n', 'u'), ('a', 'o'), ('a', 'v'), ('n', 'p'), ('a', 'q'), ('a', 'h'), ('p', 'r'), ('l', 's'), ('t', 'v'), ('u', 'y'), ('j', 'v'), ('a', 'j'), ('r', 'w'), ('r', 'u'), ('f', 'x'), ('x', 'y'), ('j', 'x'), ('d', 'j'), ('b', 'k'), ('b', 'x'), ('b', 'w')]
2
-
3
- def edges_to_steps(edges: list[tuple[str, str]]) -> dict[str,list[str]]:
4
- """ Returns a dict with for each node the nodes it is connected with. """
5
- steps = {}
6
- for n1, n2 in edges:
7
- if not n1 in steps:
8
- steps[n1] = []
9
- steps[n1].append(n2)
10
- if not n2 in steps:
11
- steps[n2] = []
12
- steps[n2].append(n1)
13
- return steps
14
-
15
- def print_all_paths(steps, path, goal, length):
16
- current = path[-1]
17
- length_path = len(path)
18
- if length_path >= length:
19
- if length_path == length and current == goal:
20
- print(path)
21
- else:
22
- valid_steps = steps[current]
23
- for s in valid_steps:
24
- print_all_paths(steps, path+s, goal, length)
25
-
26
- steps = edges_to_steps(edges)
27
- print_all_paths(steps, 'a', 'b', 7)
@@ -1,32 +0,0 @@
1
- edges = [('a', 's'), ('i', 'z'), ('c', 'p'), ('d', 'p'), ('d', 'u'), ('b', 'e'), ('b', 'g'), ('f', 'p'), ('g', 'm'), ('h', 't'), ('h', 'y'), ('i', 'w'), ('i', 'j'), ('i', 'x'), ('k', 's'), ('k', 'l'), ('a', 'm'), ('n', 'u'), ('a', 'o'), ('a', 'v'), ('n', 'p'), ('a', 'q'), ('a', 'h'), ('p', 'r'), ('l', 's'), ('t', 'v'), ('u', 'y'), ('j', 'v'), ('a', 'j'), ('r', 'w'), ('r', 'u'), ('f', 'x'), ('x', 'y'), ('j', 'x'), ('d', 'j'), ('b', 'k'), ('b', 'x'), ('b', 'w')]
2
-
3
- def edges_to_steps(edges: list[tuple[str, str]]) -> dict[str,list[str]]:
4
- """ Returns a dict with for each node the nodes it is connected with. """
5
- steps = {}
6
- for n1, n2 in edges:
7
- if not n1 in steps:
8
- steps[n1] = []
9
- steps[n1].append(n2)
10
- if not n2 in steps:
11
- steps[n2] = []
12
- steps[n2].append(n1)
13
- return steps
14
-
15
- def print_all_paths(steps, path, goal, length, results):
16
- current = path[-1]
17
- length_path = len(path)
18
- if length_path >= length:
19
- if length_path == length and current == goal:
20
- if 'd' in path:
21
- results.append(path)
22
- else:
23
- valid_steps = steps[current]
24
- for s in valid_steps:
25
- if not s == 'x':
26
- print_all_paths(steps, path+s, goal, length, results)
27
-
28
- steps = edges_to_steps(edges)
29
- results = []
30
- print_all_paths(steps, 'a', 'b', 10, results)
31
- print('results:', results)
32
- print(len(results))
@@ -1,16 +0,0 @@
1
- import invocation_tree as ivt
2
-
3
- def quick_sort(values):
4
- if len(values) <= 1:
5
- return values
6
- pivot = values[0]
7
- smaller = [x for x in values if x < pivot]
8
- equal = [x for x in values if x == pivot]
9
- larger = [x for x in values if x > pivot]
10
- return quick_sort(smaller) + equal + quick_sort(larger)
11
-
12
- values = [7, 4, 10, 11, 2, 6, 9, 1, 5, 3, 8, 12]
13
- print('unsorted values:',values)
14
- tree = ivt.gif('quick_sort.png')
15
- values = tree(quick_sort, values)
16
- print(' sorted values:',values)
Binary file
@@ -1,28 +0,0 @@
1
- import invocation_tree as ivt
2
- from decimal import Decimal, ROUND_HALF_UP
3
-
4
- def main():
5
- students = {'Ann':[7.5, 8.0],
6
- 'Bob':[4.5, 6.0],
7
- 'Coy':[7.5, 6.0]}
8
- averages = {student:compute_average(grades)
9
- for student, grades in students.items()}
10
- passing = passing_students(averages)
11
- print(passing)
12
-
13
- def compute_average(grades):
14
- average = sum(grades)/len(grades)
15
- return half_up_round(average, 1)
16
-
17
- def half_up_round(value, digits=0):
18
- """ High-precision half-up rounding of 'value' to a specified number of 'digits'. """
19
- return float(Decimal(str(value)).quantize(Decimal(f"1e-{digits}"),
20
- rounding=ROUND_HALF_UP))
21
-
22
- def passing_students(avg):
23
- return [student
24
- for student, average in avg.items()
25
- if average >= 5.5]
26
-
27
- if __name__ == '__main__':
28
- ivt.gif(filename="students.png")(main)
Binary file
@@ -1,47 +0,0 @@
1
- LICENSE.txt
2
- MANIFEST.in
3
- README.md
4
- pyproject.toml
5
- images/compute.gif
6
- images/compute.py
7
- images/create_gif.sh
8
- images/create_images.sh
9
- images/draw_graph.py
10
- images/draw_graph_d_x.py
11
- images/edges_big.out
12
- images/edges_big_d_x.out
13
- images/edges_small.out
14
- images/factorial.gif
15
- images/factorial.py
16
- images/graph.py
17
- images/graph_big.png
18
- images/graph_big_d_x.png
19
- images/graph_small.png
20
- images/jugs.png
21
- images/jugs_depth_first.py
22
- images/perms_LR3.png
23
- images/permutations.gif
24
- images/permutations.py
25
- images/permutations_collect.gif
26
- images/permutations_collect.py
27
- images/permutations_dot.py
28
- images/permutations_neighbor.gif
29
- images/permutations_neighbor.py
30
- images/permutations_return.gif
31
- images/permutations_return.py
32
- images/permutations_vscode.gif
33
- images/print_all_paths.py
34
- images/print_all_paths_of_length.py
35
- images/print_all_paths_via.py
36
- images/quick_sort.gif
37
- images/quick_sort.py
38
- images/students.gif
39
- images/students.py
40
- images/vscode.png
41
- invocation_tree/__init__.py
42
- invocation_tree/regex_set.py
43
- invocation_tree.egg-info/PKG-INFO
44
- invocation_tree.egg-info/SOURCES.txt
45
- invocation_tree.egg-info/dependency_links.txt
46
- invocation_tree.egg-info/requires.txt
47
- invocation_tree.egg-info/top_level.txt