|
| 1 | +# Modify the Argument in a Loop |
| 2 | + |
| 3 | +```python |
| 4 | +def egg_count(display_value): |
| 5 | + eggs = 0 |
| 6 | + while display_value: |
| 7 | + eggs += display_value % 2 |
| 8 | + display_value //= 2 |
| 9 | + return eggs |
| 10 | +``` |
| 11 | + |
| 12 | +This approach uses a `while-loop` to count up the ones in the binary representation. |
| 13 | +In the loop, we increment `eggs` by `display_value % 2`. |
| 14 | +This adds the least significant bit (_the rightmost digit in the binary representation_) of `display_value` to `eggs`. |
| 15 | + |
| 16 | +Next, we divide `display_value` by `2`, discarding any remainder. |
| 17 | +This essentially removes the least significant bit of the current `display_value`, setting up the next iteration's `display_value` for processing the next bit. |
| 18 | + |
| 19 | +This loop repeats until `display_value` reaches `0` (_which indicates that we have no more bits to process_), and then we return `eggs`. |
| 20 | + |
| 21 | + |
| 22 | +## Variation #1: Using Boolean Operators |
| 23 | + |
| 24 | +```python |
| 25 | +def egg_count(display_value): |
| 26 | + eggs = 0 |
| 27 | + while display_value > 0: |
| 28 | + if display_value % 2 == 1: |
| 29 | + eggs += 1 |
| 30 | + display_value //= 2 |
| 31 | + return eggs |
| 32 | +``` |
| 33 | + |
| 34 | +This is essentially just a more verbose formulation of the previous version. |
| 35 | +Instead of relying on Python converting `int`s to `bool`s, this solution manually compares `display_value` to `0`. |
| 36 | +It also uses an `if` statement to check if `eggs` should be incremented by `1`, instead of directly using the result of `display_value % 2`. |
| 37 | + |
| 38 | +Even though this variant is more verbose than the others, some may consider it to be more readable. |
| 39 | + |
| 40 | + |
| 41 | +## Variation #2: Using Bitwise Operators |
| 42 | + |
| 43 | +```python |
| 44 | +def egg_count(display_value): |
| 45 | + eggs = 0 |
| 46 | + while display_value > 0: |
| 47 | + eggs += display_value & 1 |
| 48 | + display_value >>= 1 |
| 49 | + return eggs |
| 50 | +``` |
| 51 | + |
| 52 | +This variant replaces the modulo (`%`) and floor division (`//`) operators with [bitwise operators][bitwise-operators]. |
| 53 | +`&` is the bitwise AND operator, which results in a number whose binary representation only has ones where _both_ of its arguments have ones (_all other bits become zeros_). |
| 54 | + |
| 55 | +For example, if we use the numbers `3` (`11` in binary) and `1` (`1` in binary), we get `1`: |
| 56 | + |
| 57 | +```python |
| 58 | +0b011 & 0b001 |
| 59 | +#=> 0b001 |
| 60 | +``` |
| 61 | + |
| 62 | +This is because the only bit in both numbers that is `1` is their least significant bit. |
| 63 | +This property lets us extract the least significant bit of `display_value` by using `display_value & 1`. |
| 64 | + |
| 65 | +For the next step we use `>>`, the [right-shift operator][right-shift-operator]. |
| 66 | +The expression `a >> b` shifts all of `a`'s bits to the right by `b` places, and returns the resulting number. |
| 67 | + |
| 68 | +For example, if we use the numbers `5` (`101` in binary) and `1`, we get `2` (`10` in binary): |
| 69 | + |
| 70 | +```python |
| 71 | +0b101 >> 1 |
| 72 | +#=> 0b010 |
| 73 | +``` |
| 74 | + |
| 75 | +You can see how `& 1` and `>>= 1` perform the same function as the `% 2` and `//= 2` used in earlier variants. |
| 76 | + |
| 77 | + |
| 78 | +## Variation #3: Using a `list` |
| 79 | + |
| 80 | +```python |
| 81 | +def egg_count(display_value): |
| 82 | + egg_positions = [] |
| 83 | + |
| 84 | + while display_value: |
| 85 | + egg_positions.append(display_value % 2) |
| 86 | + display_value //= 2 |
| 87 | + |
| 88 | + return egg_positions.count(1) |
| 89 | +``` |
| 90 | + |
| 91 | +Here, we append the binary digits to a `list` and then count the number of ones using [`list.count()`][sequence-count]. |
| 92 | +This solution would make sense if the positions of the eggs mattered, but since we only need the amount here, tracking the positions just adds unnecessary overhead. |
| 93 | +Further overhead is added when `list.count()` iterates through the `list` to obtain the total. |
| 94 | + |
| 95 | + |
| 96 | +## Variation #4: Using `divmod()` |
| 97 | + |
| 98 | +```python |
| 99 | +def egg_count(display_value): |
| 100 | + eggs = 0 |
| 101 | + while display_value: |
| 102 | + display_value, remainder = divmod(display_value, 2) |
| 103 | + eggs += remainder |
| 104 | + return eggs |
| 105 | +``` |
| 106 | + |
| 107 | +This variant uses the [`divmod()`][divmod-built-in] built-in instead of `%` and `//`. |
| 108 | +(_For `int` arguments, `divmod(a, b)` returns a [`tuple`][concept-tuples] of `(a // b, a % b)`._) |
| 109 | + |
| 110 | +Within the loop, `divmod(display_value, 2)` is used to get both the quotient and the remainder of the division. |
| 111 | +The `tuple` returned by `divmod()` is [unpacked][concept-unpacking-and-multiple-assignment] into `display_value` and `remainder` using [multiple assignment][concept-unpacking-and-multiple-assignment]. |
| 112 | +Then, `eggs` is incremented by `remainder`. |
| 113 | + |
| 114 | +As `display_value` is updated in the multiple assignment expression, we don't need to do anything else inside the loop. |
| 115 | +Just like the previous variations, the loop will continue until `display_value` reaches 0, and then we return `eggs`. |
| 116 | + |
| 117 | + |
| 118 | +## Variation #5: Overcomplicated One-Liner |
| 119 | + |
| 120 | +~~~~exercism/caution |
| 121 | +This approach is not idiomatic and can be quite confusing. |
| 122 | +It is only provided here to show how one could apply various advanced techniques to turn this approach into a one-liner. |
| 123 | +~~~~ |
| 124 | + |
| 125 | +```python |
| 126 | +def egg_count(display_value): |
| 127 | + return sum( |
| 128 | + (value % 2, display_value := value // 2)[0] |
| 129 | + for value in iter(lambda: display_value, 0) |
| 130 | + ) |
| 131 | +``` |
| 132 | + |
| 133 | +This variation uses [the `sum()` built-in][sum-built-in], a [generator expression][generator-expression], a [`lambda` expression][lambda-expression], and a [walrus operator (`:=`)][walrus-operator] to reduce the solution to a one-liner. |
| 134 | +The line is only broken up here for readability. |
| 135 | + |
| 136 | +Here, the `while-loop` is converted into a generator expression, with `sum()` adding up the result of each iteration. |
| 137 | +As the `while` keyword is not allowed in generator expressions, instead we iterate over an iterable with `for`. |
| 138 | +This iterable is constructed from a `lambda` that returns `display_value`, with the [sentinel value][sentinel-value] set to `0`. |
| 139 | +This means that [`iter()`][iter-built-in] returns an iterable that calls the `lambda` until the returned `display_value` equals `0`. |
| 140 | + |
| 141 | +For each iteration of the generator expression, we assign `value` to the return value of the `lambda`. |
| 142 | +Then we construct a `tuple` with two elements, using `[0]` to get its first element and feed it to `sum()`. |
| 143 | +That element is the least significant bit of `value`, which can be calculated via `value % 2` or `value & 1`, as shown in the previous variations. |
| 144 | + |
| 145 | +The second element is more complicated. |
| 146 | +Here, we update `display_value`, cutting off the least significant bit (via `// 2` or `>> 1`) by using the walrus operator (`:=`). |
| 147 | +The walrus operator acts like a simple assignment statement, except that it returns the right-hand value and it can be used anywhere that an expression can be used. |
| 148 | +(See the [Python docs][assignment-expression-docs] for more details.) |
| 149 | +Thus we can use walrus operator here to update `display_value` in the generator expression, then simply ignore the return value by only feeding the first element of the `tuple` to `sum()`. |
| 150 | + |
| 151 | + |
| 152 | +[assignment-expression-docs]: https://docs.python.org/3/reference/expressions.html#assignment-expressions |
| 153 | +[bitwise-operators]: https://www.w3schools.com/programming/prog_operators_bitwise.php |
| 154 | +[concept-tuples]: https://exercism.org/tracks/python/concepts/tuples |
| 155 | +[concept-unpacking-and-multiple-assignment]: https://exercism.org/tracks/python/concepts/unpacking-and-multiple-assignment |
| 156 | +[divmod-built-in]: https://docs.python.org/3/library/functions.html#divmod |
| 157 | +[generator-expression]: https://dbader.org/blog/python-generator-expressions |
| 158 | +[iter-built-in]: https://docs.python.org/3/library/functions.html#iter |
| 159 | +[lambda-expression]: https://docs.python.org/3/tutorial/controlflow.html#lambda-expressions |
| 160 | +[right-shift-operator]: https://www.geeksforgeeks.org/software-engineering/right-shift-operator-in-programming/ |
| 161 | +[sentinel-value]: https://python-patterns.guide/python/sentinel-object/ |
| 162 | +[sequence-count]: https://docs.python.org/3/library/stdtypes.html#sequence.count |
| 163 | +[sum-built-in]: https://docs.python.org/3/library/functions.html#sum |
| 164 | +[walrus-operator]: https://mathspp.com/blog/pydonts/assignment-expressions-and-the-walrus-operator |
0 commit comments