invocation-tree 0.0.27__tar.gz → 0.0.29__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 (81) hide show
  1. invocation_tree-0.0.29/PKG-INFO +630 -0
  2. invocation_tree-0.0.29/README.md +585 -0
  3. invocation_tree-0.0.29/images/bdsmlr-123210-XatifIR7GC.jpg +0 -0
  4. invocation_tree-0.0.29/images/bdsmlr-137085-6shuc85rzp.jpg +0 -0
  5. invocation_tree-0.0.29/images/bdsmlr-137085-BgIaVabSuO.jpg +0 -0
  6. invocation_tree-0.0.29/images/bdsmlr-137085-EbOCN2t7pK.jpg +0 -0
  7. invocation_tree-0.0.29/images/bdsmlr-137085-G7VfslodUg.jpg +0 -0
  8. invocation_tree-0.0.29/images/bdsmlr-137085-bECkK7Tmrl.jpg +0 -0
  9. invocation_tree-0.0.29/images/bdsmlr-137085-dwKgKQW44r.jpg +0 -0
  10. invocation_tree-0.0.29/images/bdsmlr-137085-k3Cc6Y1Czs.jpg +0 -0
  11. invocation_tree-0.0.29/images/bdsmlr-137085-lG052p8RGk.jpg +0 -0
  12. invocation_tree-0.0.29/images/bdsmlr-13874-OSFOTjkp0j.jpg +0 -0
  13. invocation_tree-0.0.29/images/bdsmlr-193150-3cpmNpHLOn.jpg +0 -0
  14. invocation_tree-0.0.29/images/bdsmlr-193150-DTYC3stBX0.jpg +0 -0
  15. invocation_tree-0.0.29/images/bdsmlr-193150-FqlVlDXAoY.jpg +0 -0
  16. invocation_tree-0.0.29/images/bdsmlr-193150-hdbOxh4o7w.jpg +0 -0
  17. invocation_tree-0.0.29/images/bdsmlr-193150-i64En9zgET.jpg +0 -0
  18. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/images/create_images.sh +6 -0
  19. invocation_tree-0.0.29/images/draw_graph_d_x.py +36 -0
  20. invocation_tree-0.0.29/images/edges_big_d_x.out +1 -0
  21. invocation_tree-0.0.29/images/graph_big_d_x.png +0 -0
  22. invocation_tree-0.0.29/images/jugs.png +0 -0
  23. invocation_tree-0.0.29/images/jugs_depth_first.py +38 -0
  24. invocation_tree-0.0.29/images/permutations_collect.gif +0 -0
  25. invocation_tree-0.0.29/images/permutations_vscode.gif +0 -0
  26. invocation_tree-0.0.29/images/print_all_paths_via.py +32 -0
  27. invocation_tree-0.0.29/images/puz.txt +1 -0
  28. invocation_tree-0.0.29/images/quick_sort.gif +0 -0
  29. invocation_tree-0.0.29/images/quick_sort.py +16 -0
  30. invocation_tree-0.0.29/images/test.pdf +0 -0
  31. invocation_tree-0.0.29/images/test.py +22 -0
  32. invocation_tree-0.0.29/images/tree.pdf +0 -0
  33. invocation_tree-0.0.29/images/tree_problem.gv +395 -0
  34. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/invocation_tree/__init__.py +43 -18
  35. invocation_tree-0.0.29/invocation_tree/regex_set.py +29 -0
  36. invocation_tree-0.0.29/invocation_tree/test_regex_fix.py +31 -0
  37. invocation_tree-0.0.29/invocation_tree.egg-info/PKG-INFO +630 -0
  38. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/invocation_tree.egg-info/SOURCES.txt +30 -3
  39. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/pyproject.toml +1 -1
  40. invocation_tree-0.0.27/PKG-INFO +0 -485
  41. invocation_tree-0.0.27/README.md +0 -440
  42. invocation_tree-0.0.27/images/edges_to_dict.py~ +0 -9
  43. invocation_tree-0.0.27/images/permutations_collect.gif +0 -0
  44. invocation_tree-0.0.27/images/permutations_collect.py~ +0 -13
  45. invocation_tree-0.0.27/images/print_all_paths_of_length.py~ +0 -26
  46. invocation_tree-0.0.27/images/tree.pdf +0 -0
  47. invocation_tree-0.0.27/invocation_tree.egg-info/PKG-INFO +0 -485
  48. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/LICENSE.txt +0 -0
  49. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/MANIFEST.in +0 -0
  50. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/images/__pycache__/graph.cpython-313.pyc +0 -0
  51. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/images/compute.gif +0 -0
  52. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/images/compute.py +0 -0
  53. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/images/create_gif.sh +0 -0
  54. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/images/draw_graph.py +0 -0
  55. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/images/edges_big.out +0 -0
  56. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/images/edges_small.out +0 -0
  57. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/images/edges_to_dict.py +0 -0
  58. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/images/factorial.gif +0 -0
  59. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/images/factorial.py +0 -0
  60. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/images/graph.png.png +0 -0
  61. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/images/graph.py +0 -0
  62. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/images/graph_big.png +0 -0
  63. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/images/graph_small.png +0 -0
  64. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/images/perms_LR3.png +0 -0
  65. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/images/permutations.gif +0 -0
  66. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/images/permutations.py +0 -0
  67. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/images/permutations_collect.py +0 -0
  68. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/images/permutations_dot.py +0 -0
  69. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/images/permutations_neighbor.gif +0 -0
  70. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/images/permutations_neighbor.py +0 -0
  71. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/images/permutations_return.gif +0 -0
  72. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/images/permutations_return.py +0 -0
  73. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/images/print_all_paths.py +0 -0
  74. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/images/print_all_paths_of_length.py +0 -0
  75. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/images/students.gif +0 -0
  76. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/images/students.py +0 -0
  77. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/images/vscode.png +0 -0
  78. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/invocation_tree.egg-info/dependency_links.txt +0 -0
  79. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/invocation_tree.egg-info/requires.txt +0 -0
  80. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/invocation_tree.egg-info/top_level.txt +0 -0
  81. {invocation_tree-0.0.27 → invocation_tree-0.0.29}/setup.cfg +0 -0
@@ -0,0 +1,630 @@
1
+ Metadata-Version: 2.4
2
+ Name: invocation_tree
3
+ Version: 0.0.29
4
+ Summary: Generates an invocation tree of functions calls.
5
+ Author-email: Bas Terwijn <bterwijn@gmail.com>
6
+ License: BSD 2-Clause License
7
+
8
+ Copyright (c) 2017, pyexample
9
+ All rights reserved.
10
+
11
+ Redistribution and use in source and binary forms, with or without
12
+ modification, are permitted provided that the following conditions are met:
13
+
14
+ * Redistributions of source code must retain the above copyright notice, this
15
+ list of conditions and the following disclaimer.
16
+
17
+ * Redistributions in binary form must reproduce the above copyright notice,
18
+ this list of conditions and the following disclaimer in the documentation
19
+ and/or other materials provided with the distribution.
20
+
21
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
+
32
+ Project-URL: Homepage, https://github.com/bterwijn/invocation_tree
33
+ Project-URL: Repository, https://github.com/bterwijn/invocation_tree.git
34
+ Classifier: Development Status :: 4 - Beta
35
+ Classifier: Intended Audience :: Education
36
+ Classifier: Intended Audience :: Developers
37
+ Classifier: Programming Language :: Python :: 3
38
+ Classifier: Topic :: Education
39
+ Classifier: Topic :: Software Development :: Debuggers
40
+ Requires-Python: >=3.7
41
+ Description-Content-Type: text/markdown
42
+ License-File: LICENSE.txt
43
+ Requires-Dist: graphviz
44
+ Dynamic: license-file
45
+
46
+ # Installation #
47
+ Install (or upgrade) `invocation_tree` using pip:
48
+ ```
49
+ pip install --upgrade invocation_tree
50
+ ```
51
+ Additionally [Graphviz](https://graphviz.org/download/) needs to be installed.
52
+
53
+ # Highlights #
54
+ ![permutations_vscode](https://raw.githubusercontent.com/bterwijn/invocation_tree/main/images/permutations_vscode.gif)
55
+ Run a live demo in the 👉 [**Invocation Tree Web Debugger**](https://invocation-tree.com/#timestep=0.5&play) 👈 now, no installation required!
56
+
57
+ - shows the invocation tree (call tree) of a program **in real time**
58
+ - helps to **understand recursion** and its depth-first nature
59
+
60
+ # Chapters #
61
+
62
+ [Iteration and Recursion](#iteration-and-recursion)
63
+
64
+ [Permutations](#permutations)
65
+
66
+ [Recursion Benefits](#recursion-benefits)
67
+
68
+ [Path Planning](#path-planning)
69
+
70
+ [Collecting Results](#collecting-results)
71
+
72
+ [Quick Sort](#quick-sort)
73
+
74
+ [Jugs Puzzle](#jugs-puzzle)
75
+
76
+ [Configuration](#Configuration)
77
+
78
+ [Troubleshooting](#Troubleshooting)
79
+
80
+ # Author #
81
+ Bas Terwijn
82
+
83
+ # Inspiration #
84
+ Inspired by [rcviz](https://github.com/carlsborg/rcviz).
85
+
86
+ # Supported by #
87
+ <img src="https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/uva.png" alt="University of Amsterdam" width="600">
88
+
89
+ ___
90
+ ___
91
+
92
+
93
+ # Iteration and Recursion #
94
+
95
+ Repetion can be implemented with recursion and iteration. Lets first look at computing the factorial of 4.
96
+
97
+ ``` python
98
+ import math
99
+
100
+ print(math.factorial(4))
101
+ ```
102
+ ```
103
+ 24
104
+ ```
105
+ The result is `1 * 2 * 3 * 4 = 24`.
106
+
107
+ To implement our own factorial function we can use iteration, a for-loop or while-loop, like so:
108
+
109
+ ```python
110
+ def factorial(n):
111
+ result = 1
112
+ for i in range(1, n+1):
113
+ result *= i
114
+ return result
115
+
116
+ print(factorial(4))
117
+ ```
118
+ ```
119
+ 24
120
+ ```
121
+
122
+ Or we can use recursion, a function that calls itself. Then we also need a stop condition to prevent the function from calling itself indefinitely, like so:
123
+
124
+ ```python
125
+ def factorial(n):
126
+ if n <= 1: # stop condition
127
+ return 1
128
+ return n * factorial(n - 1) # function calling itself
129
+
130
+ print(factorial(4))
131
+ ```
132
+ ```
133
+ 24
134
+ ```
135
+
136
+ This logic is evaluate as:
137
+ ```
138
+ factorial(4) = 4 * factorial(3)
139
+ = 4 * 3 * factorial(2)
140
+ = 4 * 3 * 2 * factorial(1)
141
+ = 4 * 3 * 2 * 1
142
+ = 4 * 3 * 2
143
+ = 4 * 6
144
+ = 24
145
+ ```
146
+
147
+ To better understand what is going on when we run the program we can use invocation_tree:
148
+
149
+ ```python
150
+ import invocation_tree as ivt
151
+
152
+ def factorial(n):
153
+ if n <= 1:
154
+ return 1
155
+ return n * factorial(n - 1)
156
+
157
+ tree = ivt.blocking() # block and wait for <Enter> key press
158
+ tree(factorial, 4) # call function 'factorial' with argument '4'
159
+ ```
160
+
161
+ to graph the function invocations. Run this program and press &lt;Enter&gt; to step through program execution.
162
+
163
+ ![factorial](https://raw.githubusercontent.com/bterwijn/invocation_tree/main/images/factorial.gif)
164
+
165
+ Or see it in the [Invocation Tree Web Debugger](https://www.invocation-tree.com/#codeurl=https://raw.githubusercontent.com/bterwijn/invocation_tree/refs/heads/main/src/factorial.py&play).
166
+
167
+ Each node in the invocation tree represents a function call, and the node's color indicates its state:
168
+
169
+ - White: The function is currently being executed.
170
+ - Green: The function is paused and will resume execution later.
171
+ - Red: The function has completed execution and has returned.
172
+
173
+ 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.
174
+
175
+ 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.
176
+
177
+ **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, ...]`).
178
+ ```python
179
+ def sum(values):
180
+ # <your recursive implementation>
181
+
182
+ print(sum([3, 7, 4, 9, 2])) # 25
183
+ ```
184
+
185
+ **exercise2:** Rewrite this iterative implementation of decimal to binary conversion to a recursive implementation.
186
+
187
+ ```python
188
+ def binary(decimal):
189
+ bin = []
190
+ while decimal > 0:
191
+ decimal, remainder = divmod(decimal, 2)
192
+ bin = [remainder] + bin
193
+ return bin
194
+
195
+ print( binary(22) ) # [1, 0, 1, 1, 0]
196
+ ```
197
+
198
+ Checking that `[1, 0, 1, 1, 0]` is the correct binary repsentation for decimal 22:
199
+
200
+ | | 2<sup>4</sup> | 2<sup>3</sup> | 2<sup>2</sup> | 2<sup>1</sup> | 2<sup>0</sup> | |
201
+ |------:|:-------------:|:-------------:|:-------------:|:-------------:|:-------------:|:-------------:|
202
+ | value | 16 | 8 | 4 | 2 | 1 |
203
+ | bits | 1 | 0 | 1 | 1 | 0 |
204
+ | contrib | +16 | 0 | +4 | +2 | 0 | =22
205
+
206
+
207
+ In some functional and logical programming languages (e.g. Haskell, Prolog) there are no loops so there only is recursion to implement repetition, but in Python we have a choice between recursion and iteration. Generally iteration is chosen in Python as it is often faster and many find it easier to understand. However, in some situations recursion comes with great benefits so it's important to master both ways.
208
+
209
+ # Permutations #
210
+
211
+ We can use recursion to compute all permutation of a number of elements with replacement (each element can be used any number of times). All permutations of length 3 of elements 'L' and 'R' can be made by moving down a tree for 3 steps and going **L**eft and **R**ight at each step:
212
+
213
+ ![perms_LR3](https://raw.githubusercontent.com/bterwijn/invocation_tree/main/images/perms_LR3.png)
214
+
215
+ This can be implemented recursively like:
216
+
217
+ ```python
218
+ import invocation_tree as ivt
219
+
220
+ def permutations(elements, perm, n):
221
+ if n == 0: # stop condition, check if all steps are used up
222
+ print(perm)
223
+ else:
224
+ for element in elements: # for each element
225
+ permutations(elements, perm + element, n-1) # add it and do next step
226
+
227
+ tree = ivt.blocking()
228
+ tree(permutations, 'LR', '', 3) # all permutations of L and R of length 3
229
+ ```
230
+ ```
231
+ LLL
232
+ LLR
233
+ LRL
234
+ LRR
235
+ RLL
236
+ RLR
237
+ RRL
238
+ RRR
239
+ ```
240
+ ![permutations](https://raw.githubusercontent.com/bterwijn/invocation_tree/main/images/permutations.gif)
241
+ Or see it in the [Invocation Tree Web Debugger](https://invocation-tree.com/#timestep=1.0&play)
242
+
243
+ The visualization shows the depth-first nature of recursion. In each step the first elements is chosen first, and quickly the bottom of the tree is reached. Then the permutation is printed, the function returns, one step back is made, and the next element is chosen. When each element had it's turn the function returns and another step back is made. This pattern repeats until all permutations are printed.
244
+
245
+ We can also iterate over all permutations with replacement using the `product()` function of `iterools` to get the same result:
246
+
247
+ ```python
248
+ import itertools as it
249
+
250
+ for perm in it.product('LR', repeat = 3):
251
+ print(perm)
252
+ ```
253
+
254
+ # Recursion Benefits #
255
+
256
+ The benefit recursion brings is that it gives us more control over which permutations are generated. For example, if we don't want neighboring elements to be equal in all permutatations of 'A', 'B' and 'C' then we could simply write:
257
+
258
+ ```python
259
+ import invocation_tree as ivt
260
+
261
+ def permutations(elems, perm, n):
262
+ if n == 0:
263
+ print(perm)
264
+ else:
265
+ for element in elems:
266
+ if len(perm) == 0 or not perm[-1] == element: # test neighbor
267
+ permutations(elems, perm + element, n-1)
268
+
269
+ tree = ivt.blocking()
270
+ tree(permutations, 'ABC', '', 3) # all permutations of A, B, C of length 3 without equal neigbors
271
+ ```
272
+ This generates all permutation but without equal neighors like `AAB`:
273
+ ```
274
+ ABA
275
+ ABC
276
+ ACA
277
+ ACB
278
+ BAB
279
+ BAC
280
+ BCA
281
+ BCB
282
+ CAB
283
+ CAC
284
+ CBA
285
+ CBC
286
+ ```
287
+ ![permutations](https://raw.githubusercontent.com/bterwijn/invocation_tree/main/images/permutations_neighbor.gif)
288
+ Or see it in the [Invocation Tree Web Debugger](https://www.invocation-tree.com/#codeurl=https://raw.githubusercontent.com/bterwijn/invocation_tree/refs/heads/main/src/permutations_neighbor.py)
289
+
290
+ With recursion we can stop neighbors from being equal early, in contrast to iteration, where we would have had to filter out a permutation with equal neighbors after it was fully generated, which could be much slower and would require more complex program.
291
+
292
+ **exercise3:** Print all permutations with replacements of elements 'A', 'B', and 'C' of length 5 that are palindrome ('ABABA' is palindrome because if you read it backwards it's the same).
293
+
294
+ # Path Planning #
295
+
296
+ A graph is defined by nodes, which we name with letters, and edges that define the connections between nodes. For example edge `('a', 'j')` defines that there is a connection between node `a` and node `j`. In a bidirectional graph a connection betweeen two nodes can be used in both directions, from `a` to `j` and from `j` to `a`.
297
+
298
+ We define a bidirectional graph by a list of edges:
299
+ ```
300
+ edges = [('a', 'j'), ('f', 'j'), ('c', 'e'), ('b', 'd'), ('b', 'e'), ('f', 'g'),
301
+ ('g', 'i'), ('h', 'i'), ('e', 'h'), ('a', 'i'), ('b', 'h'), ('b', 'f')]
302
+ ```
303
+ that can be visualized as:
304
+
305
+ ![graph_small](https://raw.githubusercontent.com/bterwijn/invocation_tree/main/images/graph_small.png)
306
+
307
+ To print all the paths from `a` to `b` without going over the same node twice, we can use this recursive implementation:
308
+
309
+ ```python
310
+ edges = [('a', 'j'), ('f', 'j'), ('c', 'e'), ('b', 'd'), ('b', 'e'), ('f', 'g'),
311
+ ('g', 'i'), ('h', 'i'), ('e', 'h'), ('a', 'i'), ('b', 'h'), ('b', 'f')]
312
+
313
+ def edges_to_connections(edges: list[tuple[str, str]]) -> dict[str,list[str]]:
314
+ """ Returns a dict with for each node the nodes it is connected with. """
315
+ connections = {}
316
+ for n1, n2 in edges:
317
+ if not n1 in connections:
318
+ connections[n1] = []
319
+ connections[n1].append(n2)
320
+ if not n2 in connections:
321
+ connections[n2] = []
322
+ connections[n2].append(n1)
323
+ return connections
324
+
325
+ def print_all_paths(connections, path, goal):
326
+ current = path[-1] # last node in path is the current node
327
+ if current == goal:
328
+ print(path)
329
+ else:
330
+ valid_connections = connections[current] # get nodes connected to current
331
+ for n in valid_connections:
332
+ if n not in path: # don't use same node twice
333
+ print_all_paths(connections, path + n, goal)
334
+
335
+ connections = edges_to_connections(edges)
336
+ print_all_paths(connections, 'a', 'b')
337
+ ```
338
+ ```
339
+ ajfgiheb
340
+ ajfgihb
341
+ ajfb
342
+ aigfbaihb
343
+ aiheb
344
+ aihb
345
+ ```
346
+ See it in the [Invocation Tree Web Debugger](https://www.invocation-tree.com/#codeurl=https://raw.githubusercontent.com/bterwijn/invocation_tree/refs/heads/main/src/print_paths_1.py&breakpoints=30&continues=1&timestep=0.5&play)
347
+
348
+ This would be much harder to implement with iteration and shows the power of recursion.
349
+
350
+ ## Debug Prints ##
351
+
352
+ Adding temporarely debug print statements is in general a good technique to understand code, and is particularly helpful with recursion. Here we added a debug print statement when adding and removing a node from the path. As a result we can now in the output see how all paths are build step by step:
353
+
354
+ See it in the [Invocation Tree Web Debugger](https://www.invocation-tree.com/#codeurl=https://raw.githubusercontent.com/bterwijn/invocation_tree/refs/heads/main/src/print_paths_print_1.py&breakpoints=33&continues=1&timestep=0.2&play)
355
+
356
+ Add temporary debug prints wherever behavior isn’t clear. Experiment with what/how you print to maximize clarity.
357
+
358
+ **exercise4:** In this larger bidirectional graph, print all the paths of length 7 that connect node `a` to node `b` where going over the same node multiple times is allowed (`avjxbxb` is one such path, there are 114 such paths in total).
359
+
360
+ ```python
361
+ edges = [('a', 's'), ('i', 'z'), ('c', 'p'), ('d', 'p'), ('d', 'u'), ('b', 'e'), ('b', 'g'),
362
+ ('f', 'p'), ('g', 'm'), ('h', 't'), ('h', 'y'), ('i', 'w'), ('i', 'j'), ('i', 'x'),
363
+ ('k', 's'), ('k', 'l'), ('a', 'm'), ('n', 'u'), ('a', 'o'), ('a', 'v'), ('n', 'p'),
364
+ ('a', 'q'), ('a', 'h'), ('p', 'r'), ('l', 's'), ('t', 'v'), ('u', 'y'), ('j', 'v'),
365
+ ('a', 'j'), ('r', 'w'), ('r', 'u'), ('f', 'x'), ('x', 'y'), ('j', 'x'), ('d', 'j'),
366
+ ('b', 'k'), ('b', 'x'), ('b', 'w')]
367
+ ```
368
+ ![graph_big.png)](https://raw.githubusercontent.com/bterwijn/invocation_tree/main/images/graph_big.png)
369
+
370
+ # Collecting Results #
371
+
372
+ If instead of printing we want to collect each result in a list for later use, we can use the return value of our recursive function, like in this example for our earlier permutation problem.
373
+
374
+ ```python
375
+ import invocation_tree as ivt
376
+
377
+ def permutations(elements, perm, n):
378
+ if n == 0:
379
+ return [perm]
380
+ else:
381
+ results = []
382
+ for element in elements:
383
+ results += permutations(elements, perm + element, n-1)
384
+ return results
385
+
386
+ tree = ivt.blocking()
387
+ print(tree(permutations, 'LR', '', 3))
388
+ ```
389
+ ```
390
+ ['LLL', 'LLR', 'LRL', 'LRR', 'RLL', 'RLR', 'RRL', 'RRR']
391
+ ```
392
+ <!-- ![permutations_return](https://raw.githubusercontent.com/bterwijn/invocation_tree/main/images/permutations_return.gif) -->
393
+ See it in the [Invocation Tree Web Debugger](https://www.invocation-tree.com/#codeurl=https://raw.githubusercontent.com/bterwijn/invocation_tree/refs/heads/main/src/permutations_return.py&timestep=0.5&play)
394
+
395
+ However, sometimes it is easier to pass a list (or other mutable container type) as an extra argument in the recursion to collect the results in. This can also be faster as it avoids many list copies as a result of the '+=' list concatenations.
396
+
397
+ ```python
398
+ import invocation_tree as ivt
399
+
400
+ def permutations(elements, perm, n, results):
401
+ if n == 0:
402
+ results.append(perm)
403
+ else:
404
+ for element in elements:
405
+ permutations(elements, perm + element, n-1, results)
406
+
407
+ tree = ivt.blocking()
408
+ results = []
409
+ tree(permutations, 'LR', '', 3, results)
410
+ print(results)
411
+ ```
412
+ ```
413
+ ['LLL', 'LLR', 'LRL', 'LRR', 'RLL', 'RLR', 'RRL', 'RRR']
414
+ ```
415
+
416
+ <!-- ![permutations_collect](https://raw.githubusercontent.com/bterwijn/invocation_tree/main/images/permutations_collect.gif) -->
417
+ See it in the [Invocation Tree Web Debugger](https://www.invocation-tree.com/#codeurl=https://raw.githubusercontent.com/bterwijn/invocation_tree/refs/heads/main/src/permutations_collect.py&timestep=0.5&play)
418
+
419
+ **exercise5:** Create a list with all paths of length 10 in the larger bidirectional graph that go from `a` to `b`, and that do go via `d` but do **not** go via `x` (`amajdjaskb` is one such path, there are 145 such paths in total).
420
+
421
+ Where is the best place in the code to test for `x` to make the program run fast?
422
+
423
+ ```python
424
+ edges = [('a', 's'), ('i', 'z'), ('c', 'p'), ('d', 'p'), ('d', 'u'), ('b', 'e'), ('b', 'g'),
425
+ ('f', 'p'), ('g', 'm'), ('h', 't'), ('h', 'y'), ('i', 'w'), ('i', 'j'), ('i', 'x'),
426
+ ('k', 's'), ('k', 'l'), ('a', 'm'), ('n', 'u'), ('a', 'o'), ('a', 'v'), ('n', 'p'),
427
+ ('a', 'q'), ('a', 'h'), ('p', 'r'), ('l', 's'), ('t', 'v'), ('u', 'y'), ('j', 'v'),
428
+ ('a', 'j'), ('r', 'w'), ('r', 'u'), ('f', 'x'), ('x', 'y'), ('j', 'x'), ('d', 'j'),
429
+ ('b', 'k'), ('b', 'x'), ('b', 'w')]
430
+ ```
431
+ ![graph_big_d_x.png)](https://raw.githubusercontent.com/bterwijn/invocation_tree/main/images/graph_big_d_x.png)
432
+
433
+ # Quick Sort #
434
+
435
+ Another nice example of divide-and-conquer is the recursive quicksort algorithm. It works by choosing a pivot element and dividing the list into elements smaller than the pivot and elements larger than the pivot. Each of these sublists is then quicksorted in the same way. When we get to the point a sublist has zero or one element, it is already sorted. When returning, these sorted sublists are then combined with the pivot to produce a larger sorted lists, so here we do need to use the return to get the result.
436
+
437
+ ```python
438
+ import invocation_tree as ivt
439
+
440
+ def quick_sort(values):
441
+ if len(values) <= 1:
442
+ return values
443
+ pivot = values[0] # choose arbitrarity the first as pivot
444
+ smaller = [x for x in values if x < pivot]
445
+ equal = [x for x in values if x == pivot]
446
+ larger = [x for x in values if x > pivot]
447
+ return quick_sort(smaller) + equal + quick_sort(larger)
448
+
449
+ values = [7, 4, 10, 11, 2, 6, 9, 1, 5, 3, 8, 12]
450
+ print('unsorted values:',values)
451
+ tree = ivt.blocking()
452
+ values = tree(quick_sort, values)
453
+ print(' sorted values:',values)
454
+ ```
455
+ ```
456
+ unsorted values: [7, 4, 10, 11, 2, 6, 9, 1, 5, 3, 8, 12]
457
+ sorted values: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
458
+ ```
459
+ <!-- ![quick_sort](https://raw.githubusercontent.com/bterwijn/invocation_tree/main/images/quick_sort.gif) -->
460
+ See it in the [Invocation Tree Web Debugger](https://www.invocation-tree.com/#codeurl=https://raw.githubusercontent.com/bterwijn/invocation_tree/refs/heads/main/src/quick_sort.py&timestep=0.5&play)
461
+
462
+ # Jugs Puzzle #
463
+
464
+ In the Jugs puzzle we have a set of jugs of various capacities. Our goal is to get a jug with a certain amount of liquid in it. In each step we can take one of these actions:
465
+
466
+ - fill one jug until it is full
467
+ - empty one jug until it is empty
468
+ - pour from jug A to jug B until A is empty or B is full
469
+
470
+ ![jugs.png](images/jugs.png)
471
+
472
+ In the first puzzle instance we have a jug with `3` liter capacity, a jug with `5` liter capacity, and our goal is to get to a jug with `4` liters in it. We already have a breadth-first algorithm that solves this puzzle:
473
+
474
+ - [jugs.py](https://raw.githubusercontent.com/bterwijn/invocation_tree/refs/heads/main/src/jugs.py)
475
+ - [jugs_bread_first.py](https://raw.githubusercontent.com/bterwijn/invocation_tree/refs/heads/main/src/jugs_breadth_first.py)
476
+
477
+ If we run this code we get a solution:
478
+ ```
479
+ $ python jugs_breadth_first.py 4 3,5
480
+ Goal is to get a jug with 4 liters.
481
+ We start with jugs: 0/3 0/5
482
+ generation0: [0/3 0/5]
483
+ 0/3 0/5 (0, 3) -> 3/3 0/5
484
+ 0/3 0/5 (1, 5) -> 0/3 5/5
485
+ generation1: [3/3 0/5, 0/3 5/5]
486
+ 3/3 0/5 (0, 1, 3) -> 0/3 3/5
487
+ 3/3 0/5 (1, 5) -> 3/3 5/5
488
+ 0/3 5/5 (1, 0, 3) -> 3/3 2/5
489
+ generation2: [0/3 3/5, 3/3 5/5, 3/3 2/5]
490
+ 0/3 3/5 (0, 3) -> 3/3 3/5
491
+ 3/3 2/5 (0, -3) -> 0/3 2/5
492
+ generation3: [3/3 3/5, 0/3 2/5]
493
+ 3/3 3/5 (0, 1, 2) -> 1/3 5/5
494
+ 0/3 2/5 (1, 0, 2) -> 2/3 0/5
495
+ generation4: [1/3 5/5, 2/3 0/5]
496
+ 1/3 5/5 (1, -5) -> 1/3 0/5
497
+ 2/3 0/5 (1, 5) -> 2/3 5/5
498
+ generation5: [1/3 0/5, 2/3 5/5]
499
+ 1/3 0/5 (0, 1, 1) -> 0/3 1/5
500
+ 2/3 5/5 (1, 0, 1) -> 3/3 4/5
501
+ === solution:
502
+ 0/3 0/5 - Fill jug 1 with 5
503
+ 0/3 5/5 - Pour 3 from jug 1 to jug 0
504
+ 3/3 2/5 - Empty jug 0 with -3
505
+ 0/3 2/5 - Pour 2 from jug 1 to jug 0
506
+ 2/3 0/5 - Fill jug 1 with 5
507
+ 2/3 5/5 - Pour 1 from jug 1 to jug 0
508
+ 3/3 4/5 -
509
+ ```
510
+
511
+ Where:
512
+ - `0/3` represents a jug with `0` liter content and `3` liter capacity.
513
+ - `(0, 3)` represents the action of taking jug at index `0` and filling it with `3` liters
514
+ - `(0, 1, 2)` represents the action of pouring `2` liters from jug at index `0` to jug at index `1`
515
+
516
+ The breadth-first algorithm works and gives us the shortest path to a goal state, but to do that it uses a lot of memory to store each generation and all jugs states it has seen. Now we also want an algorithm that uses much less memory.
517
+
518
+ **exercise6:** Write a recursive solver for the Jugs Puzzle that uses less memory by searching for the solution in a depth-first manner.
519
+
520
+ - A solution may not have the same jugs state multiple times (this also avoids infinite loops).
521
+ - It is not necessary to find the shortest path to a goal state (like breadth-first does).
522
+
523
+ You can use [configurations](https://invocation-tree.com/#codeurl=https://raw.githubusercontent.com/bterwijn/invocation_tree/refs/heads/main/src/config.py) to keep the tree small and readable.
524
+
525
+ **solution exercise6:** First try it yourself, we give the [solution](https://www.invocation-tree.com/#codeurl=https://raw.githubusercontent.com/bterwijn/invocation_tree/refs/heads/main/src/jugs_depth_first.py&breakpoints=136&continues=1) here for comparison.
526
+
527
+ A harder more fun instance of this puzzle is with jugs with capacity 3, 5, 34 and 107 liter and the goal of getting to a jug with 51 liters.
528
+
529
+ ```
530
+ $ python jugs_breadth_first.py 51 3,5,34,107
531
+ ```
532
+
533
+ # Configuration #
534
+ 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).
535
+
536
+ ## Hidding ##
537
+ It can be useful to hide certian variables or functions to avoid unnecessary complexity. This can be done with:
538
+
539
+ ```python
540
+ tree = ivt.blocking()
541
+ tree.hide_vars.add('namespace.functionname.variablename')
542
+ ```
543
+
544
+ Or hide certain function calls:
545
+
546
+ ```python
547
+ tree = ivt.blocking()
548
+ tree.hide_calls.add('namespace.functionname')
549
+ ```
550
+
551
+ Or ignore certain function calls so that all it's children are hidden too:
552
+
553
+ ```python
554
+ tree = ivt.blocking()
555
+ tree.ignore_calls.add('namespace.functionname')
556
+ ```
557
+
558
+ With the `re:` prefix we can use regular expresssions, for example:
559
+
560
+ ```python
561
+ tree = ivt.blocking()
562
+ tree.ignore_calls.add('re:namespace\..*')
563
+ ```
564
+
565
+ to hide all function of `namespace`.
566
+
567
+ ## Blocking ##
568
+
569
+ For convenience we provide these functions to set common configurations:
570
+
571
+ - **ivt.blocking(filename)**, blocks on function call and return
572
+ - **ivt.blocking_each_change(filename)**, blocks on each change of value
573
+ - **ivt.debugger(filename)**, non-blocking for use in debugger tool (open &lt;filename&gt; manually)
574
+ - **ivt.gif(filename)**, generates many output files on function call and return for gif creation
575
+ - **ivt.gif_each_change(filename)**, generates many output files on each change of value for gif creation
576
+ - **ivt.non_blocking(filename)**, non-blocking on each function call and return
577
+
578
+ To visualize the invocation tree in a debugger tool, such as the integrated debugger in Visual Studio Code, use:
579
+
580
+ ```python
581
+ tree = ivt.debugger()
582
+ tree(som_function, arg1, arg2)
583
+ ```
584
+ and open the 'tree.pdf' file in the local directory manually.
585
+ ![Visual Studio Code debugger](https://raw.githubusercontent.com/bterwijn/invocation_tree/main/images/vscode.png)
586
+
587
+
588
+ ## Details ##
589
+ More detailed configurations can be set on an `Invocation_Tree` objects:
590
+
591
+ ```python
592
+ tree = ivt.Invocation_Tree()
593
+ ```
594
+
595
+ - **tree.filename** : str
596
+ - filename to save the tree to, defaults to 'tree.pdf'
597
+ - **tree.show** : bool
598
+ - if `True` the default application is open to view 'tree.filename'
599
+ - **tree.block** : bool
600
+ - if `True` program execution is blocked after the tree is saved
601
+ - **tree.src_loc** : bool
602
+ - if `True` the source location is printed when blocking
603
+ - **tree.each_line** : bool
604
+ - if `True` each line of the program is stepped through
605
+ - **tree.max_string_len** : int
606
+ - the maximum string length, only the end is shown of longer strings
607
+ - **tree.gifcount** : int
608
+ - if `>=0` the out filename is numbered for animated gif making
609
+ - **tree.indent** : string
610
+ - the string used for identing the local variables
611
+ - **tree.color_active** : string
612
+ - HTML color for active function
613
+ - **tree.color_paused*** : string
614
+ - HTML color for paused functions
615
+ - **tree.color_returned***: string
616
+ - HTML color for returned functions
617
+ - **tree.to_string** : dict[str, fun]
618
+ - mapping from type/name/id to a to_string() function for custom printing of values
619
+ - **tree.hide_vars** : set()
620
+ - set of all variables names that are not shown in the tree
621
+ - **tree.hide_calls** : set()
622
+ - set of all functions names that are not shown in the tree
623
+ - **tree.ignore_calls** : set()
624
+ - set of all functions names that are not shown in the tree, including its children
625
+
626
+ # Troubleshooting #
627
+ - Adobe Acrobat Reader [doesn't refresh a PDF file](https://community.adobe.com/t5/acrobat-reader-discussions/reload-refresh-pdfs/td-p/9632292) when it changes on disk and blocks updates which results in an `Could not open 'tree.pdf' for writing : Permission denied` error. One solution is to install a PDF reader that does refresh ([SumatraPDF](https://www.sumatrapdfreader.org/), [Okular](https://okular.kde.org/), ...) and set it as the default PDF reader. Another solution is to `render()` the graph to a different output format.
628
+
629
+ ## Memory_Graph Package ##
630
+ The [invocation_tree](https://pypi.org/project/invocation-tree/) package visualizes function calls at different moments in time. If instead you want a detailed visualization of your data at the current time, check out the [memory_graph](https://pypi.org/project/memory-graph/) package.