diff --git a/exercises/practice/collatz-conjecture/.approaches/config.json b/exercises/practice/collatz-conjecture/.approaches/config.json index 5730c94d978..3cf9f2119ff 100644 --- a/exercises/practice/collatz-conjecture/.approaches/config.json +++ b/exercises/practice/collatz-conjecture/.approaches/config.json @@ -1,7 +1,7 @@ { "introduction": { - "authors": ["bethanyg", "meatball133"], - "contributors": [] + "authors": ["Bethanyg", "meatball133"], + "contributors": ["Yrahcaz7"] }, "approaches": [ { @@ -9,21 +9,24 @@ "slug": "if-else", "title": "If/Else", "blurb": "Use if and else", - "authors": ["bethanyg", "meatball133"] + "authors": ["Bethanyg", "meatball133"], + "contributors": ["Yrahcaz7"] }, { "uuid": "d7703aef-1510-4ec8-b6ce-ca608b5b8f70", "slug": "ternary-operator", "title": "Ternary operator", "blurb": "Use a ternary operator", - "authors": ["bethanyg", "meatball133"] + "authors": ["Bethanyg", "meatball133"], + "contributors": ["Yrahcaz7"] }, { "uuid": "b1220645-124a-4994-96c4-3b2b710fd562", "slug": "recursion", "title": "Recursion", "blurb": "Use recursion", - "authors": ["bethanyg", "meatball133"] + "authors": ["Bethanyg", "meatball133"], + "contributors": ["Yrahcaz7"] } ] } diff --git a/exercises/practice/collatz-conjecture/.approaches/if-else/content.md b/exercises/practice/collatz-conjecture/.approaches/if-else/content.md index 56cc8e4257c..9f136f34922 100644 --- a/exercises/practice/collatz-conjecture/.approaches/if-else/content.md +++ b/exercises/practice/collatz-conjecture/.approaches/if-else/content.md @@ -16,17 +16,16 @@ def steps(number): This approach starts with checking if the number is less than or equal to zero. If it is, then it raises a [`ValueError`][value-error]. -After that, we declare a counter variable and set it to zero. -Then we start a [`while` loop][while-loop] that will run until the number is equal to one. -Meaning the loop won't run if the number is already one. +After that, we declare a `counter` variable and set it to zero. +Next, we start a [`while loop`][while-loop] that will run until the number is equal to one, at which point it will terminate. -Inside the loop we check if the number is even. -If it is, then we divide it by two. -If it isn't, then we multiply it by three and add one. -After that, we increment the counter by one. -After the loop completes, we return the counter variable. +Inside the `loop`, we check if the number is even, and if it is, we divide it by two. +If the number is odd, we multiply it by three and add one. +After that, we increment the `counter` by one. +When the `loop` completes, we return the `counter` value. + +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. -We use a `while` loop here because we don't know exactly how many times the loop will run. [value-error]: https://docs.python.org/3/library/exceptions.html#ValueError [while-loop]: https://realpython.com/python-while-loop/ diff --git a/exercises/practice/collatz-conjecture/.approaches/introduction.md b/exercises/practice/collatz-conjecture/.approaches/introduction.md index 58d7a65f38d..a2ab88b197e 100644 --- a/exercises/practice/collatz-conjecture/.approaches/introduction.md +++ b/exercises/practice/collatz-conjecture/.approaches/introduction.md @@ -1,18 +1,21 @@ # Introduction -There are various approaches to solving the Collatz Conjecture exercise in Python. -You can for example use a while loop or a recursive function. -You can also solve it by using if and else statements or the ternary operator. +There are multiple idiomatic approaches to solving the Collatz Conjecture exercise in Python. +You can, for example, use a `while loop` or a `recursive` function. +You can also solve the exercise by using `if`/`else` statements or the `ternary operator`. ## General guidance The key to this exercise is to check if the number is even or odd and then perform the correct operation. -Under this process you are supposed to count how many steps it takes to get to one. +Under this process (_if the input number doesn't create an excessively [long cycle][collatz-pathological]_), the result will settle at 1. +Your task is to count how many steps it takes to get there. + ## Approach: If/Else This is a good way to solve the exercise, it is easy to understand and it is very readable. -The reason why you might not want to use this approach is because it is longer than the other approaches. +The reason why you might _not_ want to use this approach is because it is more verbose than other approaches. + ```python def steps(number): @@ -28,12 +31,14 @@ def steps(number): return counter ``` -For more information, check the [if/else approach][approach-if-else]. +For more information, check out the [if/else approach][approach-if-else]. + ## Approach: Ternary operator -In this approach we replace the `if/else` multi-line construction with a [conditional expression][conditional-expression], sometimes called a _ternary operator_. -This syntax allows us to write a one-line `if/ else` check, making the code more concise. +In this approach we replace the `if`/`else` multi-line construction with a [conditional expression][conditional-expression], sometimes called a _ternary operator_. +This syntax allows us to write a one-line `if`/`else` check, making the code more concise: + ```python def steps(number): @@ -46,15 +51,19 @@ def steps(number): return counter ``` -For more information, check the [Ternary operator approach][approach-ternary-operator]. +For more information, read the [Ternary operator approach][approach-ternary-operator]. + ## Approach: Recursive function -In this approach we use a recursive function. +In this approach we use a `recursive function`. A recursive function is a function that calls itself. -This approach can be more concise than other approaches, and may also be more readable for some audiences. +This approach can be more concise than other approaches, but may or may not be more readable for some audiences. + +You might not want to use this approach due to Python's [`recursion` limit][recursion-limit], which has a default of 1000 stack frames. +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. +To make this approach work with large numbers of steps, techniques such as memoization may need to be employed. -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. ```python def steps(number): @@ -66,16 +75,18 @@ def steps(number): return 1 + steps(number) ``` -For more information, check the [Recursion approach][approach-recursion]. +For more information, check out the [`recursion` approach][approach-recursion]. + ## Benchmarks -To get a better understanding of the performance of the different approaches, we have created benchmarks. -For more information, check the [Performance article][performance-article]. +To get a better understanding of the performance of the different approaches, we have created a small benchmarking application. +For more information on timings, check out the [Performance article][performance-article]. [approach-if-else]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/if-else [approach-recursion]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/recursion -[recursion-limit]: https://docs.python.org/3/library/sys.html#sys.setrecursionlimit [approach-ternary-operator]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/ternary-operator +[collatz-pathological]: https://www.quantamagazine.org/why-mathematicians-still-cant-solve-the-collatz-conjecture-20200922/ [conditional-expression]: https://docs.python.org/3/reference/expressions.html#conditional-expressions [performance-article]: https://exercism.org/tracks/python/exercises/collatz-conjecture/articles/performance +[recursion-limit]: https://docs.python.org/3/library/sys.html#sys.setrecursionlimit diff --git a/exercises/practice/collatz-conjecture/.approaches/recursion/content.md b/exercises/practice/collatz-conjecture/.approaches/recursion/content.md index def361c895b..db6383367b1 100644 --- a/exercises/practice/collatz-conjecture/.approaches/recursion/content.md +++ b/exercises/practice/collatz-conjecture/.approaches/recursion/content.md @@ -10,46 +10,47 @@ def steps(number): return 1 + steps(number) ``` -This approach uses [concept:python/recursion]() to solve the problem. -Recursion is a programming technique where a function calls itself. -It is a powerful technique, but can be more tricky to implement than a while loop. -Recursion isn't that common in Python, it is more common in functional programming languages, like: [Elixir][elixir], [Haskell][haskell], and [Clojure][clojure]. +This approach uses [concept:python/recursion]() to solve the challenge. +`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]. +While it can be powerful, it can also be trickier to implement than looping constructs. -This approach starts with checking if the number is less than or equal to zero. -If it is, then it raises a [`ValueError`][value-error]. +This approach starts with checking if `number <= 0` and raising a [`ValueError`][value-error] if it is. +Next, we `return` zero if `number == 1`. +This is the [`base case`][recursion-base-case]. -After that, we check if the number is equal to one. -If it is, then we return zero. +We then assign `number` to the same `conditional expression` as seen in the [`ternary operator`][ternary-operator] approach. +Finally, we `return` one plus the result of calling `steps()`, with the updated `number` value. +This is the [`recursive case`][recursive-case]. -We then use the same conditional expression/ternary operator as the [ternary operator][ternary-operator] approach does. -We assign **number** to the result of the conditional expression. -The expression checks if the number is even. -If the number is even, we divide it by two. -If it isn't, we multiply it by three and add one. +Solving this exercise in this way removes the need for a `counter` variable and the creation of a `loop`. +If `number` is not equal to one, we call `1 + steps(number)`. +Then `steps()` can execute the same code again with new values. +This makes a long chain (_or stack_) of `1 + steps(number)` — until `number == 1` and the code adds zero and exits. +That translates to something like: `1 + 1 + 1 + 1 + 0`. -After that, we `return` one plus the result of calling the `steps` function with the new number value. -This is the recursion part. +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`. -Solving this exercise with recursion removes the need for a "counter" variable and the instantiation of a `loop`. -If the number is not equal to one, we call `1 + steps(number)`. -Then the `steps` function can execute the same code again with new values. -Meaning we can get a long chain or stack of `1 + steps(number)` until the number reaches one and the code adds 0. -That translates to something like this: `1 + 1 + 1 + 1 + 0`. - -Python doesn't have [tail call optimization][tail-call]. -Which means that the stack of `1 + steps(number)` will grow until it reaches the recursion limit. ~~~~exercism/caution -In Python, we can't have a function call itself more than 1000 times by default. -Code that exceeds this recursion limit will throw a [RecursionError](https://docs.python.org/3/library/exceptions.html#RecursionError). -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. -Casually raising the recursion limit is not recommended. +In Python, we can't have a function call itself more than 1000 times by default. +Code that exceeds this `recursion limit` will throw a [RecursionError][recursion-error]. + +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]. +Casually raising the limit is not recommended and seldom helps the performance situation. +Instead, applying [memoization techniques][memoization] or [dynamic programming strategies][dynamic-programming] is a better path. + +[recursion-error]: https://docs.python.org/3/library/exceptions.html#RecursionError +[recursion-limit]: https://docs.python.org/3/library/sys.html#sys.setrecursionlimit +[stack-overflow-def]: https://en.wikipedia.org/wiki/Stack_overflow +[memoization]: https://dbader.org/blog/python-memoization +[dynamic-programming]: https://medium.com/@conniezhou678/mastering-data-algorithms-part-18-dynamic-programming-in-python-3077c01f4a15 ~~~~ [clojure]: https://exercism.org/tracks/clojure [elixir]: https://exercism.org/tracks/elixir [haskell]: https://exercism.org/tracks/haskell -[recursion]: https://realpython.com/python-thinking-recursively/ [tail-call]: https://en.wikipedia.org/wiki/Tail_call [ternary-operator]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/ternary-operator [value-error]: https://docs.python.org/3/library/exceptions.html#ValueError +[recursion-base-case]: https://www.geeksforgeeks.org/dsa/what-is-base-case-in-recursion/ +[recursive-case]: https://inventwithpython.com/blog/how-many-recursive-cases-and-base-cases-does-recursive-function-need.html diff --git a/exercises/practice/collatz-conjecture/.approaches/ternary-operator/content.md b/exercises/practice/collatz-conjecture/.approaches/ternary-operator/content.md index 7a1368cf6ec..5c8f178fa81 100644 --- a/exercises/practice/collatz-conjecture/.approaches/ternary-operator/content.md +++ b/exercises/practice/collatz-conjecture/.approaches/ternary-operator/content.md @@ -14,20 +14,19 @@ def steps(number): This approach starts with checking if the number is less than or equal to zero. If it is, then a [`ValueError`][value-error] is raised. -After that, a counter variable is assigned to zero. -Then we start a `while` loop that will run until the number is equal to one. -Meaning the loop won't run if the number is already one. +After that, a `counter` variable is assigned to zero. +Then we start a `while loop` that will run until the number is equal to one, at which point it will terminate. -Inside the loop we have a [ternary operator][ternary-operator] or [conditional expression][conditional-expression]. -A ternary operator/conditional expression can be viewed as a one-line `if/else` statement. +Inside the `loop`, we have a [ternary operator][ternary-operator] (also called a [conditional expression][conditional-expression]). +A `ternary operator` (_`conditional expression`_) can be viewed as a one-line `if`/`else` statement. Using a one-line construct can make the code more concise. -We assign the number value to the result of the ternary operator. -The ternary operator/conditional expression checks if the number is even. -If it is, then we divide it by two. -If the number is not even, we multiply by three and add one. -Then the counter is incremented by one. -When the loop completes, we return the counter value. +The first part of the `ternary operator` divides the number by two if it is even. +The `else` part of the expression (_where the number is not even_) multiplies the number by three and adds one. +Then the `counter` is incremented by one. +When the `loop` completes, the `counter` value is returned. + +[conditional-expression]: https://docs.python.org/3/reference/expressions.html#conditional-expressions [ternary-operator]: https://www.pythontutorial.net/python-basics/python-ternary-operator/ [value-error]: https://docs.python.org/3/library/exceptions.html#ValueError diff --git a/exercises/practice/collatz-conjecture/.articles/performance/content.md b/exercises/practice/collatz-conjecture/.articles/performance/content.md index 5b1c3d43fd6..6a0ae13a2a5 100644 --- a/exercises/practice/collatz-conjecture/.articles/performance/content.md +++ b/exercises/practice/collatz-conjecture/.articles/performance/content.md @@ -1,17 +1,17 @@ # Performance -In this approach, we'll find out how to most efficiently calculate the Collatz Conjecture in Python. +In this article, we'll find out how to most efficiently calculate the Collatz Conjecture in Python. The [approaches page][approaches] lists three approaches to this exercise: -1. [Using recursion][approach-recursion] -2. [Using the ternary operator][approach-ternary-operator] -3. [Using the if/else][approach-if-else] +1. [Using `recursion`][approach-recursion] +2. [Using the `ternary operator`][approach-ternary-operator] +3. [Using `if`/`else`][approach-if-else] ## Benchmarks To benchmark the approaches, we wrote a [small benchmark application][benchmark-application] using the [`timeit`][timeit] library. -These tests were run in windows 11, using Python 3.11.1. +These tests were conducted on Windows 11, using Python `3.11.1`. ``` Steps with recursion : 4.1499966755509377e-05 @@ -21,12 +21,13 @@ Steps with if/else : 2.0900042727589607e-05 ## Conclusion -The fastest approach is the one using the `if/else` statement, followed by the one using the ternary operator/conditional expression. -The slowest approach is the one using recursion, probably because Python isn't as optimized for recursion as it is for iteration. +The fastest approach is the one using the `if`/`else` statement, followed by the one using the `ternary operator`/`conditional expression`. +The slowest approach is the one using `recursion`, probably due to Python's lack of `tail-call optimization` and focus on efficient `iteration`. + -[approaches]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches [approach-if-else]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/if-else [approach-recursion]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/recursion [approach-ternary-operator]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/ternary-operator +[approaches]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches [benchmark-application]: https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.articles/performance/code/Benchmark.py [timeit]: https://docs.python.org/3/library/timeit.html