Skip to content

Commit cc74d5c

Browse files
Yrahcaz7BethanyG
andauthored
[Eliud’s Eggs] Add approaches (#4232)
* add first approach * add second approach * add third approach * add two more variations to the first approach * add fourth approach * add fifth approach * add another variation to the third approach also add more links to the docs for built-in functions * ternary operator -> conditional expression use "conditional expression" as the main name, and only mention "ternary operator" as another name for a "conditional expression" * Add "bad idea" variant to `parameter-modification` Courtesy of [this conversation in issue 4227][#4227 (comment)] * Renamed parameter files to argument. * Suggestions, additions, and fixes for approaches. * Suggestions, additions, and fixes for approaches. * Suggestions, additions, and fixes for approaches. * Suggestions, additions, and fixes for approaches. * Suggestions, additions, and fixes for approaches. * Deletions for files that were renamed Directories and file links using 'Parameter' were renamed to 'Argument'. This is because parameters are when you write a function or class. Arguments are used when a function is *called*. * minor spelling, grammar, and formatting fixes also revert most additions to `convert-to-binary-string` variation 3, as agreed upon * add a missing word & remove trailing spaces --------- Co-authored-by: BethanyG <BethanyG@users.noreply.github.com>
1 parent 15f8d2d commit cc74d5c

12 files changed

Lines changed: 785 additions & 0 deletions

File tree

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
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
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
def egg_count(display_value):
2+
eggs = 0
3+
while display_value:
4+
eggs += display_value % 2
5+
display_value //= 2
6+
return eggs
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Use the Built-In Bit-Count Functionality
2+
3+
~~~~exercism/caution
4+
This approach does _not_ follow the instructions, as it uses the bit-count functionality from the standard library.
5+
It is only described here to show what an idiomatic way of counting bits in a _different context_ would be.
6+
~~~~
7+
8+
```python
9+
def egg_count(display_value):
10+
return display_value.bit_count()
11+
```
12+
13+
This approach uses [`int.bit_count()`][int-bit_count] from the Python standard library to count the number of ones in the binary representation of `display_value`.
14+
15+
This works because Python does _not_ have separate types for binary, octal, or hexadecimal numbers.
16+
Instead, all of these are considered _representations_ or display formats of `int`.
17+
Even if a binary literal is declared with the `0b` prefix, Python stores it internally as type `int`:
18+
19+
```python
20+
>>> 0b110101 # <- 53 in binary
21+
53
22+
>>> type(0b110101)
23+
<class 'int'>
24+
25+
>>> 0o65 # <- 53 in octal
26+
53
27+
>>> type(0o65)
28+
<class 'int'>
29+
```
30+
31+
Due to this, all methods that work with binary numbers in the standard library actually operate on `int`s.
32+
33+
34+
## Variation #1: Using `str.count()`
35+
36+
```python
37+
def egg_count(display_value):
38+
return bin(display_value).count("1")
39+
```
40+
41+
This variant uses [`bin()`][bin-built-in] (_or any other method discussed in the [convert to a binary string][approach-convert-to-binary-string] approach_) to convert `display_value` to a `binary string`.
42+
Then, [`str.count()`][sequence-count] is used to count how many times "1" appears in the string.
43+
44+
Though one could argue that this _technically_ doesn't use the built-in bit-count functionality, the solution still defeats the purpose of the exercise.
45+
46+
47+
## Variation #2: Using Function Aliasing
48+
49+
```python
50+
egg_count = int.bit_count
51+
```
52+
53+
This solution is the shortest of them all, but it can also be rather confusing (_and it does not follow the instructions_).
54+
It also can throw what appears to be an unrelated error:
55+
56+
```python
57+
>>> egg_count(3.5)
58+
Traceback (most recent call last):
59+
File "<python-input-77>", line 1, in <module>
60+
egg_count(3.5)
61+
~~~~~~~~~^^^^^
62+
TypeError: descriptor 'bit_count' for 'int' objects doesn't apply to a 'float' object
63+
```
64+
65+
This variant makes clever use of [function/method aliasing][function-aliasing], which creates a new name that refers to the existing `int.bit_count()` method.
66+
This means that when `egg_count(display_value)` is called, `int.bit_count(display_value)` is being used.
67+
However, you should be careful when using function aliasing, as it often makes code _less_ readable (_as with the error shown above_), and it doesn't have many practical applications outside of backward compatibility.
68+
69+
This solution works because `int.bit_count(<integer_variable>)` is just another way of saying `<integer_variable>.bit_count()`, as instance methods of a class (_the class here being `int`_) have `self` implicitly passed as the first argument when called on an instance (_but not when called directly on the class!_).
70+
See the [Classes Concept in the Syllabus][concept-classes: methods] or the [Official Python Classes Tutorial][class-method-objects-tutorial] for more detail on how `self` works.
71+
72+
73+
[approach-convert-to-binary-string]: https://exercism.org/tracks/python/exercises/eliuds-eggs/approaches/convert-to-binary-string
74+
[bin-built-in]: https://docs.python.org/3/library/functions.html#bin
75+
[class-method-objects-tutorial]: https://docs.python.org/3/tutorial/classes.html#method-objects
76+
[concept-classes: methods]: https://exercism.org/tracks/python/concepts/classes#h-methods
77+
[function-aliasing]: https://tutorialreference.com/python/examples/faq/python-how-to-use-function-aliasing
78+
[int-bit_count]: https://docs.python.org/3/library/stdtypes.html#int.bit_count
79+
[sequence-count]: https://docs.python.org/3/library/stdtypes.html#sequence.count
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def egg_count(display_value):
2+
return display_value.bit_count()
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{
2+
"introduction": {
3+
"authors": ["yrahcaz7"],
4+
"contributors": []
5+
},
6+
"approaches": [
7+
{
8+
"uuid": "27fb20ed-a4c2-4f73-a8fc-86ba384c7b35",
9+
"slug": "argument-modification",
10+
"title": "Modify the Argument in a Loop",
11+
"blurb": "Modify the argument in a while-loop to determine the number of eggs.",
12+
"authors": ["yrahcaz7"]
13+
},
14+
{
15+
"uuid": "65fd717c-0e50-444b-a4b2-b51862f1f810",
16+
"slug": "no-argument-modification",
17+
"title": "Loop Without Modifying the Argument",
18+
"blurb": "Loop over the bits without modifying the argument to calculate the number of eggs.",
19+
"authors": ["yrahcaz7"]
20+
},
21+
{
22+
"uuid": "df202fa9-3757-4808-a416-7ec3ee1e9680",
23+
"slug": "convert-to-binary-string",
24+
"title": "Convert to a Binary String",
25+
"blurb": "Convert the argument to a binary string and count its ones to determine the number of eggs.",
26+
"authors": ["yrahcaz7"],
27+
"contributors": ["BethanyG"]
28+
},
29+
{
30+
"uuid": "97b06094-7ce8-4874-b66a-af7391adc853",
31+
"slug": "built-in-bit-count",
32+
"title": "Use the Built-In Bit-Count Functionality",
33+
"blurb": "Use Python's built-in bit-count functionality to calculate the number of eggs.",
34+
"authors": ["yrahcaz7"],
35+
"contributors": ["BethanyG"]
36+
},
37+
{
38+
"uuid": "618491ca-480e-4e32-bf91-4940affb48a8",
39+
"slug": "helper-functions",
40+
"title": "Use Helper Functions",
41+
"blurb": "Split the problem into multiple helper functions to calculate the number of eggs.",
42+
"authors": ["yrahcaz7"]
43+
}
44+
]
45+
}

0 commit comments

Comments
 (0)