Skip to content
Draft
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
22 changes: 22 additions & 0 deletions exercises/practice/eliuds-eggs/.approaches/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"introduction": {
"authors": ["yrahcaz7"],
"contributors": []
},
"approaches": [
{
"uuid": "27fb20ed-a4c2-4f73-a8fc-86ba384c7b35",
"slug": "parameter-modification",
"title": "Modify the Parameter in a Loop",
"blurb": "Modify the parameter in a while-loop to calculate the number of eggs.",
"authors": ["yrahcaz7"]
},
{
"uuid": "65fd717c-0e50-444b-a4b2-b51862f1f810",
"slug": "no-parameter-modification",
"title": "Loop Without Modifying the Parameter",
"blurb": "Loop over the bits without modifying the parameter to determine the number of eggs.",
"authors": ["yrahcaz7"]
}
]
}
72 changes: 72 additions & 0 deletions exercises/practice/eliuds-eggs/.approaches/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Introduction

There are many Pythonic approaches to solving the Eliud's Eggs exercise:

- Using a `while-loop`, modifying the parameter on each iteration
- Looping over every binary digit _without_ modifying the parameter
- Converting the `int` to a binary string and counting the ones

There are also some approaches that aren't recommended:

- Using the bit-count functionality from the Python standard library, as the instructions forbid it
- Breaking up the proccess into many functions, overcomplicating the solution


## General guidance

The goal of the Eliud's Eggs exercise is to count the number of ones in the binary representation of a number.
In essence, this requires you to loop over each bit (binary digit) of the number in some way.

The approaches below represent categories of the most common ways of accomplishing this.


## Approach: Modifying the Parameter in a `while-loop`

```python
def egg_count(display_value):
eggs = 0
while display_value:
eggs += display_value % 2
display_value //= 2
return eggs
```

This approach uses a `while-loop` to count up all of the ones.
In the loop, we increment `eggs` by `display_value % 2`.
This adds the least significant bit (the rightmost digit in the binary representation) of `display_value` to `eggs`.

Next, we divide `display_value` by `2`, discarding any remainder.
This essentially removes the least significant bit of `display_value`, setting up `display_value` for processing the next bit.

The loop repeats until `display_value` reaches `0` (which indicates that we have no more bits to check), and then we return `eggs`.

To see more variations of this solution, see the [modify the parameter in a loop][approach-parameter-modification] approach.


## Approach: Looping Without Modifying the Parameter

```python
from math import ceil, log2

def egg_count(display_value):
eggs = 0
for bit_position in range(ceil(log2(display_value + 1))):
eggs += (display_value >> bit_position) & 1
return eggs
```

This solution uses a `for-loop` with `range()` to iterate over all of the bits in `display_value`.
To determine how many bits `display_value` has, this solution imports `ceil` and `log2` from the `math` module.
It then feeds this number into `range()` to make the `for-loop` iterate over all the `bit_position`s.

For each `bit_position`, we determine the value of the bit at that position by using the [right-shift operator][right-shift-operator] and the bitwise AND operator.
Once we determine the bit's value, we increment `eggs` by that number.

After the loop ends, we know that we have checked all of the bits in `display_value`, thus we return `eggs`.

For more details and variations, read the [loop without modifying the parameter][approach-no-parameter-modification] approach.


[approach-parameter-modification]: https://exercism.org/tracks/python/exercises/eliuds-eggs/approaches/parameter-modification
[approach-no-parameter-modification]: https://exercism.org/tracks/python/exercises/eliuds-eggs/approaches/no-parameter-modification
[right-shift-operator]: https://www.geeksforgeeks.org/software-engineering/right-shift-operator-in-programming/
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Loop Without Modifying the Parameter

```python
from math import ceil, log2

def egg_count(display_value):
eggs = 0
for bit_position in range(ceil(log2(display_value + 1))):
eggs += (display_value >> bit_position) & 1
return eggs
```

This approach uses a loop with `range()` to iterate over all of the bits in `display_value`.

To determine how many bits `display_value` has, this solution imports `ceil` and `log2` from the `math` module.
We then calculate the base 2 logarithm of `display_value` (plus 1) and round it up.
(Rounding up is neccessary because `range(<stop>)` excludes the value of `<stop>` from its returned values.)

Once we have the bit length of `display_value`, we feed this number into `range()` to make the `for-loop` iterate over all of `display_value`'s `bit_position`s.

For each `bit_position`, we determine the value of the bit at that position by using the [right-shift operator][right-shift-operator] and the bitwise AND operator.
We do this by right-shifting `display_value` by `bit_position`, making the bit at `bit_position` become the least significant bit.
Then we use the bitwise AND operator with `1` to remove all bits that are not the least significant bit.

~~~~exercism/note
You could also calculate the bit's value by using arithmetic operators instead of bitwise ones:
```python
eggs += (display_value // (2 ** bit_position)) % 2
```
~~~~

Once we determine the bit's value, we increment `eggs` by that number.

After the loop ends, we know that we have checked all of the bits in `display_value`, thus we return `eggs`.


## Variation #1: Using an `if` Statement

```python
from math import ceil, log2

def egg_count(display_value):
eggs = 0
for bit_position in range(ceil(log2(display_value + 1))):
if display_value & (1 << bit_position):
eggs += 1
return eggs
```

In this variant, the loop uses an `if` statement to check if the digit at `display_value` is `1`.


## Variation #2: Using `sum()` with a Generator Expression

```python
from math import ceil, log2

def egg_count(display_value):
return sum(
(display_value >> bit_position) & 1
for bit_position in range(ceil(log2(display_value + 1)))
)
```

This variant is actually a one-liner, it is just split up here for readability.
Here, we replace the `for-loop` with a [generator expression][generator-expression] and use `sum()` to collect the values into the result.


## Variation #3: Manually Tracking the Place Value

```python
def egg_count(display_value):
eggs = 0
place_value = 1
while place_value <= display_value:
if display_value & place_value:
eggs += 1
place_value <<= 1
return eggs
```

This variant avoids `import`s by manually tracking the `place_value` of the current bit position.
This way, the `while-loop` can end when `place_value` becomes greater than `display_value`.

The operations in the loop are rather similar to the "using an `if` statement" variant.
The main differences are not having to calculate the `place_value` from the bit position, and having to manually progress the iteration by left-shifting `place_value` by `1` (which is the same as multiplying `place_value` by `2`).


[generator-expression]: https://dbader.org/blog/python-generator-expressions
[right-shift-operator]: https://www.geeksforgeeks.org/software-engineering/right-shift-operator-in-programming/
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from math import ceil, log2

def egg_count(display_value):
eggs = 0
for bit_position in range(ceil(log2(display_value + 1))):
eggs += (display_value >> bit_position) & 1
return eggs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Modify the Parameter in a Loop

```python
def egg_count(display_value):
eggs = 0
while display_value:
eggs += display_value % 2
display_value //= 2
return eggs
```

This approach uses a `while-loop` to count up all of the ones.
In the loop, we increment `eggs` by `display_value % 2`.
This adds the least significant bit (the rightmost digit in the binary representation) of `display_value` to `eggs`.

Next, we divide `display_value` by `2`, discarding any remainder.
This essentially removes the least significant bit of `display_value`, setting up `display_value` for processing the next bit.

The loop repeats until `display_value` reaches `0` (which indicates that we have no more bits to check), and then we return `eggs`.


## Variation #1: Using Boolean Operators

```python
def egg_count(display_value):
eggs = 0
while display_value > 0:
if display_value % 2 == 1:
eggs += 1
display_value //= 2
return eggs
```

This is essentially just a more verbose formulation of the previous version.
Instead of relying on Python converting `int`s to `bool`s, this solution manually compares `display_value` to `0`.
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`.

Even though this variant is more verbose than the others, some may consider it to be more readable.


## Variation #2: Using Bitwise Operators

```python
def egg_count(display_value):
eggs = 0
while display_value > 0:
eggs += display_value & 1
display_value >>= 1
return eggs
```

This variant replaces the modulo (`%`) and floor division (`//`) with [bitwise operators][bitwise-operators].
`&` is the bitwise AND operator, which results in a number whose binary representation only has ones where _both_ of its arguments has ones.
(All other bits are zeros.)

For example, if we used the numbers `3` (`11` in binary) and `1` (`1` in binary), we get `1`:

```python
0b011 & 0b001
#=> 0b001
```

This is because the only bit in both numbers that is `1` is least significant bit.
This property lets us extract the least significant bit of `display_value` by using `display_value & 1`.

For the next step, we use `>>`, the [right-shift operator][right-shift-operator].
The expression `a >> b` shifts all of `a`'s bits to the right by `b` places, and returns the resulting number.

For example, if we used the numbers `5` (`101` in binary) and `1`, we get `2` (`10` in binary):

```python
0b101 >> 1
#=> 0b010
```

You can see how `& 1` and `>>= 1` perform the same function as the `% 2` and `//= 2` used in earier variants.


[bitwise-operators]: https://www.w3schools.com/programming/prog_operators_bitwise.php
[right-shift-operator]: https://www.geeksforgeeks.org/software-engineering/right-shift-operator-in-programming/
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
def egg_count(display_value):
eggs = 0
while display_value:
eggs += display_value % 2
display_value //= 2
return eggs
Loading