Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions exercises/practice/collatz-conjecture/.approaches/config.json
Original file line number Diff line number Diff line change
@@ -1,29 +1,32 @@
{
"introduction": {
"authors": ["bethanyg", "meatball133"],
"contributors": []
"authors": ["Bethanyg", "meatball133"],
"contributors": ["Yrahcaz7"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought the usernames had to be in lowercase... Does the casing actually not matter?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure. I've had cases where I swear my name in lowercase didn't count ... but IDK for sure. Maybe keep an eye out and see? We can come back later and change things.

},
"approaches": [
{
"uuid": "d92adc98-36fd-49bb-baf5-e4588387841c",
"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"]
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -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/
43 changes: 27 additions & 16 deletions exercises/practice/collatz-conjecture/.approaches/introduction.md
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -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):
Expand All @@ -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):
Expand All @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Loading