You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/python/parsimonious.md
+1-250Lines changed: 1 addition & 250 deletions
Original file line number
Diff line number
Diff line change
@@ -19,256 +19,7 @@ tags:
19
19
20
20
## Introduction
21
21
22
-
I have to admit, I used to struggle with recursion. A lot of people do. It can be a bit mind-bending. But it's a pretty simple concept and can be very useful.
23
-
24
-
In short: **a recursive function is a function that calls itself.** Thus, the code that defines the function will include a call to the same function.
25
-
26
-
As an anology, take a look at examples of recursive acronyms. See how the acronym definition includes the acronym itself!
27
-
28
-
|Acronym|Definition|
29
-
|-------|----------|
30
-
|GNU| = GNU's not Linux|
31
-
|LAME| = LAME Ain't an MP3 Encoder|
32
-
|YAML| YAML Ain't Markup Lanugage|
33
-
34
-
## When Do You Use It?
35
-
36
-
Typical use cases include:
37
-
38
-
- Any time you need to calculate the _next_ value of something, based on the _current_ value of something.
39
-
- To traverse some sort of tree or nested structure.
40
-
41
-
We'll look at example of these in a bit.
42
-
43
-
## The Rules
44
-
45
-
When creating a recursive function, there are only two rules you need to know:
46
-
47
-
1. The function must have an _exit condition_, called the _base case_. You need this, otherwise your recursive function will never end!
48
-
1. Each recursive call should move us closer to the base case.
49
-
50
-
## Examples
51
-
52
-
### Countdown
53
-
54
-
We want to create recursive function that counts down from an arbitary number `n` to 0. We can do it like this:
55
-
56
-
```python
57
-
defcountdown(n):
58
-
print(n)
59
-
if n ==0:
60
-
return# Terminate recursion
61
-
else:
62
-
countdown(n -1) # Recursive call, one closer to the base case
63
-
```
64
-
65
-
As per the rules:
66
-
67
-
- We've defined a base condition. I.e. when we get to 0, we exit.
68
-
- We've defined a condition that always moves us closer to the base condition. In this case, by decrementing the value of `n` each time by 1.
69
-
70
-
We can simplify this code:
71
-
72
-
```python
73
-
defcountdown(n):
74
-
print(n)
75
-
if n >0:
76
-
countdown(n -1) # Recursive call, one closer to the base case
77
-
```
78
-
79
-
Let's try it. I've added the above code to a file called scratch.py, in my snippets folder. I'll now execute it from the Python REPL:
80
-
81
-
```python
82
-
>>>from snippets.scratch import*
83
-
>>> countdown(5)
84
-
5
85
-
4
86
-
3
87
-
2
88
-
1
89
-
0
90
-
```
91
-
92
-
### Factorial
93
-
94
-
Recall the defition of factorial:
95
-
96
-
\\(k! = k * (k-1)\\)
97
-
98
-
This is slightly tricker than the previous example, since we're not just printing a value with each iteration. Instead, we're always multiplying the current iteration by the result of the previous iteration.
99
-
100
-
So we can code it like this:
101
-
102
-
```python
103
-
deffactorial(n):
104
-
return1if n <=1else n * factorial(n -1)
105
-
```
106
-
107
-
- The base condition is when `n == 1`. In this situation, `factorial` should always return 1.
108
-
- If not the base condition, we always multiply by a recursive call where `n` is decremented by 1.
109
-
110
-
Note that it's common for any recusive function that calculates a _product_ to have an exit condition that returns 1.
111
-
112
-
We can see how function works by adding some debugging statements:
Note how each `return` is the product of `n` and the previous return value.
139
-
140
-
### Counting Leaf Items in a Nested List
141
-
142
-
Here we create a recursive function that counts all the individual elements in a list. If the list is nested, the function recurses into each _sub_ list, adding the elements of that list to the overall count.
143
-
144
-
```python
145
-
defcount_leaf_items(item_list):
146
-
"""Recursively counts and returns the number of leaf items
147
-
in a (potentially nested) list. """
148
-
count =0
149
-
for item in item_list:
150
-
ifisinstance(item, list): # if the element is itself a list, recurse...
151
-
count += count_leaf_items(item)
152
-
else: # count the item
153
-
# this is the exit condition, i.e. when we've reached a leaf (element) rather than a nested list
154
-
count +=1
155
-
156
-
return count
157
-
```
158
-
159
-
Let's try this...
160
-
161
-
```python
162
-
nested_list = [2, [3,5], [[10,20],30]]
163
-
print(nested_list)
164
-
165
-
res = count_leaf_items(nested_list)
166
-
print(res)
167
-
```
168
-
169
-
Output:
170
-
171
-
```text
172
-
[2, [3, 5], [[10, 20], 30]]
173
-
6
174
-
```
175
-
176
-
### Fibonacci
177
-
178
-
The Fibonacci sequence is an infinite sequence that generates the next number by adding the two preceding numbers.
179
-
180
-
`1, 1, 2, 3, 5, 8, 13, 21...`
181
-
182
-
I.e. to determine the `nth` value in the sequence:
183
-
184
-
\\(f(n) = f(n-2) + f(n-1)\\)
185
-
186
-
The base case is where `n` is `1`, which returns a value of `1`.
187
-
188
-
```python
189
-
deffib(num: int) -> int:
190
-
""" Recursive function to determine nth value of Fibonacci sequence.
191
-
I.e. 1, 1, 2, 3, 5, 8, 13, 21...
192
-
fib(n) = fib(n-2) + fib(n-1)
193
-
194
-
Args:
195
-
num (int): value of n, i.e. to determine nth value
196
-
197
-
Returns:
198
-
int: The nth value of the Fibonacci sequence
199
-
"""
200
-
if num >2:
201
-
return fib(num-2) + fib(num-1)
202
-
else:
203
-
return1
204
-
205
-
whileTrue:
206
-
try:
207
-
input_val =input("Enter the value of n, or q to quit: ")
208
-
if input_val.upper() =="Q":
209
-
break
210
-
211
-
print(fib(int(input_val)))
212
-
exceptValueErroras err:
213
-
print("Invalid input")
214
-
```
215
-
216
-
Note: this isn't a particularly efficient function. It doesn't scale well!
217
-
218
-
## Calculating the Next Term in an Nth-Degree Arithmetic Progression
219
-
220
-
An arithmetic progression (AP) is a sequence of numbers in which the difference of any two successive members is a constant. This difference is commonly referred to as the _"common difference"_. For example:
221
-
222
-
```text
223
-
Progression: 0 3 6 9 12 15 18
224
-
Common diff: 3 3 3 3 3 3
225
-
```
226
-
227
-
A second-degree arithmetic progression is one in which the differences between terms is growing, but growing by a constant amount. Thus, _differences of differences_ are common:
228
-
229
-
Triangle numbers are a common example:
230
-
231
-
```text
232
-
Progression: 1 3 6 10 15 21
233
-
First diff: 2 3 4 5 6
234
-
Second (common) diff: 1 1 1 1
235
-
```
236
-
237
-
We can extrapolate this to the Nth Degree. I.e. the number of times you have to determine differences, before the differences are common. If you determine the number of degrees after which the differences are common, you can bubble the results back up to the top, in order to determine the next term in the sequence.
238
-
239
-
So this is a good candidate for a recursive function:
Calculate the next value in a numeric sequence based on the pattern of differences.
245
-
246
-
Recursively analyses the differences between consecutive elements of the sequence. Recurses until the differences remain constant. It then calculates the next value in the sequence based on this constant difference.
247
-
248
-
Parameters:
249
-
sequence (np.ndarray): A NumPy array representing the sequence.
250
-
forwards (bool, optional): A flag to determine the direction of progression.
251
-
If True (default), the function calculates the next value.
252
-
If False, it calculates the previous value in the sequence.
253
-
254
-
Returns:
255
-
int: The next (or previous) value in the sequence
256
-
"""
257
-
diffs = np.diff(sequence)
258
-
259
-
op = operator.add if forwards else operator.sub
260
-
term = sequence[-1] if forwards else sequence[0]
261
-
262
-
# Check if all the diffs are constant
263
-
# If they are, we've reached the deepest point in our recursion, and we know the constant diff
264
-
if np.all(diffs == diffs[0]):
265
-
next_val = op(term, diffs[0])
266
-
else: # if the diffs are not constant, then we need to recurse
0 commit comments