Skip to content

Commit 4d03daf

Browse files
BethanyGYrahcaz7
andauthored
[Collatz Conjecture Dig Deeper]: Corrections & Edit (#4250)
* Corrections and edit per issue 4197. * Apply suggestions from code review Suggestions from review. Co-authored-by: Yrahcaz <74512479+Yrahcaz7@users.noreply.github.com> Co-authored-by: BethanyG <BethanyG@users.noreply.github.com> * Per review suggestions, renamed pathological link and reflinked links in recursion note. * Test of GH name casing. --------- Co-authored-by: Yrahcaz <74512479+Yrahcaz7@users.noreply.github.com>
1 parent 4b3f288 commit 4d03daf

6 files changed

Lines changed: 91 additions & 77 deletions

File tree

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,32 @@
11
{
22
"introduction": {
3-
"authors": ["bethanyg", "meatball133"],
4-
"contributors": []
3+
"authors": ["Bethanyg", "meatball133"],
4+
"contributors": ["Yrahcaz7"]
55
},
66
"approaches": [
77
{
88
"uuid": "d92adc98-36fd-49bb-baf5-e4588387841c",
99
"slug": "if-else",
1010
"title": "If/Else",
1111
"blurb": "Use if and else",
12-
"authors": ["bethanyg", "meatball133"]
12+
"authors": ["Bethanyg", "meatball133"],
13+
"contributors": ["Yrahcaz7"]
1314
},
1415
{
1516
"uuid": "d7703aef-1510-4ec8-b6ce-ca608b5b8f70",
1617
"slug": "ternary-operator",
1718
"title": "Ternary operator",
1819
"blurb": "Use a ternary operator",
19-
"authors": ["bethanyg", "meatball133"]
20+
"authors": ["Bethanyg", "meatball133"],
21+
"contributors": ["Yrahcaz7"]
2022
},
2123
{
2224
"uuid": "b1220645-124a-4994-96c4-3b2b710fd562",
2325
"slug": "recursion",
2426
"title": "Recursion",
2527
"blurb": "Use recursion",
26-
"authors": ["bethanyg", "meatball133"]
28+
"authors": ["Bethanyg", "meatball133"],
29+
"contributors": ["Yrahcaz7"]
2730
}
2831
]
2932
}

exercises/practice/collatz-conjecture/.approaches/if-else/content.md

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,16 @@ def steps(number):
1616

1717
This approach starts with checking if the number is less than or equal to zero.
1818
If it is, then it raises a [`ValueError`][value-error].
19-
After that, we declare a counter variable and set it to zero.
20-
Then we start a [`while` loop][while-loop] that will run until the number is equal to one.
21-
Meaning the loop won't run if the number is already one.
19+
After that, we declare a `counter` variable and set it to zero.
20+
Next, we start a [`while loop`][while-loop] that will run until the number is equal to one, at which point it will terminate.
2221

23-
Inside the loop we check if the number is even.
24-
If it is, then we divide it by two.
25-
If it isn't, then we multiply it by three and add one.
26-
After that, we increment the counter by one.
27-
After the loop completes, we return the counter variable.
22+
Inside the `loop`, we check if the number is even, and if it is, we divide it by two.
23+
If the number is odd, we multiply it by three and add one.
24+
After that, we increment the `counter` by one.
25+
When the `loop` completes, we return the `counter` value.
26+
27+
We use a `while loop` here because we don't know exactly how many times the `loop` will run — only that it will run until the number is equal to one.
2828

29-
We use a `while` loop here because we don't know exactly how many times the loop will run.
3029

3130
[value-error]: https://docs.python.org/3/library/exceptions.html#ValueError
3231
[while-loop]: https://realpython.com/python-while-loop/

exercises/practice/collatz-conjecture/.approaches/introduction.md

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
# Introduction
22

3-
There are various approaches to solving the Collatz Conjecture exercise in Python.
4-
You can for example use a while loop or a recursive function.
5-
You can also solve it by using if and else statements or the ternary operator.
3+
There are multiple idiomatic approaches to solving the Collatz Conjecture exercise in Python.
4+
You can, for example, use a `while loop` or a `recursive` function.
5+
You can also solve the exercise by using `if`/`else` statements or the `ternary operator`.
66

77
## General guidance
88

99
The key to this exercise is to check if the number is even or odd and then perform the correct operation.
10-
Under this process you are supposed to count how many steps it takes to get to one.
10+
Under this process (_if the input number doesn't create an excessively [long cycle][collatz-pathological]_), the result will settle at 1.
11+
Your task is to count how many steps it takes to get there.
12+
1113

1214
## Approach: If/Else
1315

1416
This is a good way to solve the exercise, it is easy to understand and it is very readable.
15-
The reason why you might not want to use this approach is because it is longer than the other approaches.
17+
The reason why you might _not_ want to use this approach is because it is more verbose than other approaches.
18+
1619

1720
```python
1821
def steps(number):
@@ -28,12 +31,14 @@ def steps(number):
2831
return counter
2932
```
3033

31-
For more information, check the [if/else approach][approach-if-else].
34+
For more information, check out the [if/else approach][approach-if-else].
35+
3236

3337
## Approach: Ternary operator
3438

35-
In this approach we replace the `if/else` multi-line construction with a [conditional expression][conditional-expression], sometimes called a _ternary operator_.
36-
This syntax allows us to write a one-line `if/ else` check, making the code more concise.
39+
In this approach we replace the `if`/`else` multi-line construction with a [conditional expression][conditional-expression], sometimes called a _ternary operator_.
40+
This syntax allows us to write a one-line `if`/`else` check, making the code more concise:
41+
3742

3843
```python
3944
def steps(number):
@@ -46,15 +51,19 @@ def steps(number):
4651
return counter
4752
```
4853

49-
For more information, check the [Ternary operator approach][approach-ternary-operator].
54+
For more information, read the [Ternary operator approach][approach-ternary-operator].
55+
5056

5157
## Approach: Recursive function
5258

53-
In this approach we use a recursive function.
59+
In this approach we use a `recursive function`.
5460
A recursive function is a function that calls itself.
55-
This approach can be more concise than other approaches, and may also be more readable for some audiences.
61+
This approach can be more concise than other approaches, but may or may not be more readable for some audiences.
62+
63+
You might not want to use this approach due to Python's [`recursion` limit][recursion-limit], which has a default of 1000 stack frames.
64+
While the current tests for this exercise all have input that is easily calculated in under the `recursion` limit, this is not true for arbitrary numbers.
65+
To make this approach work with large numbers of steps, techniques such as memoization may need to be employed.
5666

57-
The reason why you might not want to use this approach is that Python has a [recursion limit][recursion-limit] with a default of 1000.
5867

5968
```python
6069
def steps(number):
@@ -66,16 +75,18 @@ def steps(number):
6675
return 1 + steps(number)
6776
```
6877

69-
For more information, check the [Recursion approach][approach-recursion].
78+
For more information, check out the [`recursion` approach][approach-recursion].
79+
7080

7181
## Benchmarks
7282

73-
To get a better understanding of the performance of the different approaches, we have created benchmarks.
74-
For more information, check the [Performance article][performance-article].
83+
To get a better understanding of the performance of the different approaches, we have created a small benchmarking application.
84+
For more information on timings, check out the [Performance article][performance-article].
7585

7686
[approach-if-else]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/if-else
7787
[approach-recursion]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/recursion
78-
[recursion-limit]: https://docs.python.org/3/library/sys.html#sys.setrecursionlimit
7988
[approach-ternary-operator]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/ternary-operator
89+
[collatz-pathological]: https://www.quantamagazine.org/why-mathematicians-still-cant-solve-the-collatz-conjecture-20200922/
8090
[conditional-expression]: https://docs.python.org/3/reference/expressions.html#conditional-expressions
8191
[performance-article]: https://exercism.org/tracks/python/exercises/collatz-conjecture/articles/performance
92+
[recursion-limit]: https://docs.python.org/3/library/sys.html#sys.setrecursionlimit

exercises/practice/collatz-conjecture/.approaches/recursion/content.md

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,46 +10,47 @@ def steps(number):
1010
return 1 + steps(number)
1111
```
1212

13-
This approach uses [concept:python/recursion]() to solve the problem.
14-
Recursion is a programming technique where a function calls itself.
15-
It is a powerful technique, but can be more tricky to implement than a while loop.
16-
Recursion isn't that common in Python, it is more common in functional programming languages, like: [Elixir][elixir], [Haskell][haskell], and [Clojure][clojure].
13+
This approach uses [concept:python/recursion]() to solve the challenge.
14+
`Recursion` is less common as a strategy in Python than in other "fully-functional" programming languages such as [Elixir][elixir], [Haskell][haskell], and [Clojure][clojure].
15+
While it can be powerful, it can also be trickier to implement than looping constructs.
1716

18-
This approach starts with checking if the number is less than or equal to zero.
19-
If it is, then it raises a [`ValueError`][value-error].
17+
This approach starts with checking if `number <= 0` and raising a [`ValueError`][value-error] if it is.
18+
Next, we `return` zero if `number == 1`.
19+
This is the [`base case`][recursion-base-case].
2020

21-
After that, we check if the number is equal to one.
22-
If it is, then we return zero.
21+
We then assign `number` to the same `conditional expression` as seen in the [`ternary operator`][ternary-operator] approach.
22+
Finally, we `return` one plus the result of calling `steps()`, with the updated `number` value.
23+
This is the [`recursive case`][recursive-case].
2324

24-
We then use the same conditional expression/ternary operator as the [ternary operator][ternary-operator] approach does.
25-
We assign **number** to the result of the conditional expression.
26-
The expression checks if the number is even.
27-
If the number is even, we divide it by two.
28-
If it isn't, we multiply it by three and add one.
25+
Solving this exercise in this way removes the need for a `counter` variable and the creation of a `loop`.
26+
If `number` is not equal to one, we call `1 + steps(number)`.
27+
Then `steps()` can execute the same code again with new values.
28+
This makes a long chain (_or stack_) of `1 + steps(number)` — until `number == 1` and the code adds zero and exits.
29+
That translates to something like: `1 + 1 + 1 + 1 + 0`.
2930

30-
After that, we `return` one plus the result of calling the `steps` function with the new number value.
31-
This is the recursion part.
31+
Python doesn't have [tail call optimization][tail-call], so the stack of `1 + steps(number)` will continue to grow until the `base case` triggers resolution, or the code reaches the `recursion limit`.
3232

33-
Solving this exercise with recursion removes the need for a "counter" variable and the instantiation of a `loop`.
34-
If the number is not equal to one, we call `1 + steps(number)`.
35-
Then the `steps` function can execute the same code again with new values.
36-
Meaning we can get a long chain or stack of `1 + steps(number)` until the number reaches one and the code adds 0.
37-
That translates to something like this: `1 + 1 + 1 + 1 + 0`.
38-
39-
Python doesn't have [tail call optimization][tail-call].
40-
Which means that the stack of `1 + steps(number)` will grow until it reaches the recursion limit.
4133

4234
~~~~exercism/caution
43-
In Python, we can't have a function call itself more than 1000 times by default.
44-
Code that exceeds this recursion limit will throw a [RecursionError](https://docs.python.org/3/library/exceptions.html#RecursionError).
45-
While it is possible to adjust the [recursion limit](https://docs.python.org/3/library/sys.html#sys.setrecursionlimit), doing so risks crashing Python and may also crash your system.
46-
Casually raising the recursion limit is not recommended.
35+
In Python, we can't have a function call itself more than 1000 times by default.
36+
Code that exceeds this `recursion limit` will throw a [RecursionError][recursion-error].
37+
38+
While it is possible to adjust the [`recursion limit`][recursion-limit], doing so risks crashing Python and may also crash your system with a [`stack overflow`][stack-overflow-def].
39+
Casually raising the limit is not recommended and seldom helps the performance situation.
40+
Instead, applying [memoization techniques][memoization] or [dynamic programming strategies][dynamic-programming] is a better path.
41+
42+
[recursion-error]: https://docs.python.org/3/library/exceptions.html#RecursionError
43+
[recursion-limit]: https://docs.python.org/3/library/sys.html#sys.setrecursionlimit
44+
[stack-overflow-def]: https://en.wikipedia.org/wiki/Stack_overflow
45+
[memoization]: https://dbader.org/blog/python-memoization
46+
[dynamic-programming]: https://medium.com/@conniezhou678/mastering-data-algorithms-part-18-dynamic-programming-in-python-3077c01f4a15
4747
~~~~
4848

4949
[clojure]: https://exercism.org/tracks/clojure
5050
[elixir]: https://exercism.org/tracks/elixir
5151
[haskell]: https://exercism.org/tracks/haskell
52-
[recursion]: https://realpython.com/python-thinking-recursively/
5352
[tail-call]: https://en.wikipedia.org/wiki/Tail_call
5453
[ternary-operator]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/ternary-operator
5554
[value-error]: https://docs.python.org/3/library/exceptions.html#ValueError
55+
[recursion-base-case]: https://www.geeksforgeeks.org/dsa/what-is-base-case-in-recursion/
56+
[recursive-case]: https://inventwithpython.com/blog/how-many-recursive-cases-and-base-cases-does-recursive-function-need.html

exercises/practice/collatz-conjecture/.approaches/ternary-operator/content.md

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,19 @@ def steps(number):
1414
This approach starts with checking if the number is less than or equal to zero.
1515
If it is, then a [`ValueError`][value-error] is raised.
1616

17-
After that, a counter variable is assigned to zero.
18-
Then we start a `while` loop that will run until the number is equal to one.
19-
Meaning the loop won't run if the number is already one.
17+
After that, a `counter` variable is assigned to zero.
18+
Then we start a `while loop` that will run until the number is equal to one, at which point it will terminate.
2019

21-
Inside the loop we have a [ternary operator][ternary-operator] or [conditional expression][conditional-expression].
22-
A ternary operator/conditional expression can be viewed as a one-line `if/else` statement.
20+
Inside the `loop`, we have a [ternary operator][ternary-operator] (also called a [conditional expression][conditional-expression]).
21+
A `ternary operator` (_`conditional expression`_) can be viewed as a one-line `if`/`else` statement.
2322
Using a one-line construct can make the code more concise.
2423

25-
We assign the number value to the result of the ternary operator.
26-
The ternary operator/conditional expression checks if the number is even.
27-
If it is, then we divide it by two.
28-
If the number is not even, we multiply by three and add one.
29-
Then the counter is incremented by one.
30-
When the loop completes, we return the counter value.
3124

25+
The first part of the `ternary operator` divides the number by two if it is even.
26+
The `else` part of the expression (_where the number is not even_) multiplies the number by three and adds one.
27+
Then the `counter` is incremented by one.
28+
When the `loop` completes, the `counter` value is returned.
29+
30+
[conditional-expression]: https://docs.python.org/3/reference/expressions.html#conditional-expressions
3231
[ternary-operator]: https://www.pythontutorial.net/python-basics/python-ternary-operator/
3332
[value-error]: https://docs.python.org/3/library/exceptions.html#ValueError
Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
# Performance
22

3-
In this approach, we'll find out how to most efficiently calculate the Collatz Conjecture in Python.
3+
In this article, we'll find out how to most efficiently calculate the Collatz Conjecture in Python.
44

55
The [approaches page][approaches] lists three approaches to this exercise:
66

7-
1. [Using recursion][approach-recursion]
8-
2. [Using the ternary operator][approach-ternary-operator]
9-
3. [Using the if/else][approach-if-else]
7+
1. [Using `recursion`][approach-recursion]
8+
2. [Using the `ternary operator`][approach-ternary-operator]
9+
3. [Using `if`/`else`][approach-if-else]
1010

1111
## Benchmarks
1212

1313
To benchmark the approaches, we wrote a [small benchmark application][benchmark-application] using the [`timeit`][timeit] library.
14-
These tests were run in windows 11, using Python 3.11.1.
14+
These tests were conducted on Windows 11, using Python `3.11.1`.
1515

1616
```
1717
Steps with recursion : 4.1499966755509377e-05
@@ -21,12 +21,13 @@ Steps with if/else : 2.0900042727589607e-05
2121

2222
## Conclusion
2323

24-
The fastest approach is the one using the `if/else` statement, followed by the one using the ternary operator/conditional expression.
25-
The slowest approach is the one using recursion, probably because Python isn't as optimized for recursion as it is for iteration.
24+
The fastest approach is the one using the `if`/`else` statement, followed by the one using the `ternary operator`/`conditional expression`.
25+
The slowest approach is the one using `recursion`, probably due to Python's lack of `tail-call optimization` and focus on efficient `iteration`.
26+
2627

27-
[approaches]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches
2828
[approach-if-else]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/if-else
2929
[approach-recursion]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/recursion
3030
[approach-ternary-operator]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/ternary-operator
31+
[approaches]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches
3132
[benchmark-application]: https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.articles/performance/code/Benchmark.py
3233
[timeit]: https://docs.python.org/3/library/timeit.html

0 commit comments

Comments
 (0)