Skip to content

Commit

Permalink
boolean operators approach
Browse files Browse the repository at this point in the history
  • Loading branch information
michalporeba committed Jan 11, 2024
1 parent 5eb7841 commit 510c33c
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 11 deletions.
2 changes: 1 addition & 1 deletion exercises/practice/leap/.approaches/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
{
"uuid": "be6d6c6e-8e19-4657-aad5-3382e7ec01db",
"slug": "operators",
"title": "Boolean Operators",
"title": "Boolean operators",
"blurb": "Use boolean operators to combine the checks.",
"authors": [
"michalporeba"
Expand Down
10 changes: 5 additions & 5 deletions exercises/practice/leap/.approaches/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ To solve the Leap problem, we must determine if a year is evenly divisible by a
Such operation in computing is called [modulo][modulo].

Unlike many languages, Elixir does not have [operators][operators] for either integer division or modulo.
Instead, it provides the [`Kernel.rem/2`][rem] function and the [`Integer.mod/2`][mod] function.
Instead, it provides the [`Kernel.rem/2`][rem] and the [`Integer.mod/2`][mod] functions.

The two functions differ in how they work with negative numbers, but since, in this exercise,
all the numbers are non-negative, both could work, depending on the approach.
Expand All @@ -23,7 +23,7 @@ defp divides?(number, divisor), do: rem(number, divisor) == 0
Any approach to the problem will perform this check three times to see if a year is equally divisible by 4, 100 and 400.
What will differ between approaches is what Elixir features we will use to combine the checks.

## Approach: Boolean Operators
## Approach: Boolean operators

The full rules are as follows:
A year is a leap year if
Expand All @@ -39,7 +39,7 @@ divides?(year, 400) or (not(divides?(year, 100))) and divides?(year, 4)
In the [boolean operators appraoch][operators-approach] we discuss the details of the solution.
It includes variations of the operators and their precendence.

## Approach: Multiple clause function
## Approach: multiple clause function

Instead of using boolean operators, we can define multiple `leap_year?/1` function clauses with different guards.
We can use the order of the definitions to ensure correct check.
Expand All @@ -53,7 +53,7 @@ def leap_year?(_), do: false

In the [functions with guards approach][guards-approach] we discuss why in this approach the `Integer.mod/2` function will not work.

## Approach: Using cond
## Approach: using cond

Similarly to the multiple clause function approach, we can also use a `cond` expression.

Expand All @@ -68,7 +68,7 @@ end

We discuss this briefly in the [cond approach][cond-approach]

## Approach: Using case
## Approach: using case

Using `case` is yet another way to check for a leap year.
This time, all the reminders are calculated and put into a tuple, and pattern matching is used to decide the outcome.
Expand Down
93 changes: 92 additions & 1 deletion exercises/practice/leap/.approaches/operators/content.md
Original file line number Diff line number Diff line change
@@ -1 +1,92 @@
# Boolean Operators
# Boolean operators

```elixir
defmodule Year do
@spec divides?(non_neg_integer, non_neg_integer) :: boolean
defp divides?(number, divisor), do: rem(number, divisor) == 0

@spec leap_year?(non_neg_integer) :: boolean
def leap_year?(year) do
divides?(year, 4) and not divides?(year, 100) or divides?(year, 400)
end
end
```

## Short-circuiting

At the core of this approach, three checks are returning three boolean values.
We can use [Boolean logic](https://en.wikipedia.org/wiki/Boolean_algebra) to combine the results.

When using this approach, it is essential to consider short-circuiting of boolean operators.
The expression `left and right` can be only true if both `left` and `right` are *true*.
If `left` is *false*, `right` will not be evaluated. The result will be *false*.
However, if `left` is *true*, `right` has to be evaluated to determin the outcome.

The expression `left or right` can be true if either `left` or `right` is *true*.
If `left` is *true*, `right` will not be evaluated. The result will be *true*.
However, if `left` is *false*, `right` has to be evaluated to determine the outcome.


## Precedence of operators

Another thing to consider when using Boolean operators is their precedence.
```elixir
true or false and false
```
The above evaluates to *true* because in Elixir `and` has higher precedence than `or`.
The above expression is equivalent to:
```elixir
true or (false and false)
```
If `or` should be evaluated first, we must use parenthesis.
```elixir
(true or false) and false
```
which equals to *false*.

The `not` operator is evaluated before `and` and `or`.

## Strict or relaxed?

Elixir offers two sets of Boolean operators: strict and relaxed.
The strict versions `not`, `and`, `or` require the first (left) argument to be of [boolean type][hexdocs-booleans].
The relaxed versions `!`, `&&`, `||` require the first argument to be only [truthy or falsy][hexdocs-truthy].

In the case of this exercise, both types will work equally well.

## Being explicit

The `leap_year?` function could be written like so:
```elixir
def leap_year?(year) do
rem(year, 4) == 0 and not rem(year, 100) == 0 or rem(year, 400) == 0
end
```
Some prefer this form, as it is very direct. We can see what is happening.
We are explicitly checking the reminder, comparing it to zero.

```elixir
def leap_year?(year) do
divides?(year, 4) and not divides?(year, 100) or divides?(year, 400)
end
```
Other might prefer the above form, which requires defining the `devides?` function.
By doing so, we can be explicit about the *intent*.
We want to check if a year can be equally divided into a number.

Yet another approach might be use variables to capture the results of individual checks.

```elixir
def leap_year?(year) do
by4? = divides?(year, 4)
by100? = divides?(year, 100)
by400? = divides?(year, 400)
by4? and not by100? or by400?
end
```

All versions of the code will work. Which one to choose is often a personal or sometimes a team preference. What reads best for you? What will make most sense to you when you look at the code again?

[hexdocs-booleans]: https://hexdocs.pm/elixir/basic-types.html#booleans-and-nil
[hexdocs-truthy]: https://hexdocs.pm/elixir/Kernel.html#module-truthy-and-falsy-values
[exercism-booleans]: https://exercism.org/tracks/elixir/concepts/booleans
8 changes: 4 additions & 4 deletions exercises/practice/leap/.approaches/operators/snippet.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
def leap_year?(year) do
divides?(year, 400)
or (not(divides?(year, 100)))
and divides?(year, 4)
end
divides?(year, 4) and
not divides?(year, 100) or
divides?(year, 400)
end

0 comments on commit 510c33c

Please sign in to comment.