-
-
Notifications
You must be signed in to change notification settings - Fork 399
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* setting up the structure for leap approaches * first draft of the introduction * adding myself to the exercise contributors * fixing formatting * snippets and headers as placeholders * improving the code with divides? * more explicit parameters in divides? * Include approach snippets in formatting * Update exercises/practice/leap/.approaches/introduction.md Co-authored-by: Angelika Tyborska <[email protected]> * Update exercises/practice/leap/.approaches/introduction.md Co-authored-by: Angelika Tyborska <[email protected]> * Update exercises/practice/leap/.approaches/introduction.md Co-authored-by: Angelika Tyborska <[email protected]> * Update exercises/practice/leap/.approaches/introduction.md Co-authored-by: Angelika Tyborska <[email protected]> * Update exercises/practice/leap/.approaches/introduction.md Co-authored-by: Angelika Tyborska <[email protected]> * Update exercises/practice/leap/.approaches/introduction.md Co-authored-by: Angelika Tyborska <[email protected]> * reformatting * boolean operators approach * rem by default, divides? as an option * Update exercises/practice/leap/.approaches/introduction.md Co-authored-by: Angelika Tyborska <[email protected]> * functions approach * control flow approaches * Update exercises/practice/leap/.approaches/introduction.md Co-authored-by: Angelika Tyborska <[email protected]> * Update exercises/practice/leap/.approaches/flow/content.md Co-authored-by: Angelika Tyborska <[email protected]> * Update exercises/practice/leap/.approaches/flow/content.md Co-authored-by: Angelika Tyborska <[email protected]> * Update exercises/practice/leap/.approaches/functions/content.md Co-authored-by: Angelika Tyborska <[email protected]> * Update exercises/practice/leap/.approaches/introduction.md Co-authored-by: Angelika Tyborska <[email protected]> * Update exercises/practice/leap/.approaches/introduction.md Co-authored-by: Angelika Tyborska <[email protected]> * Case on the tuple is the most idiomatic I have adjusted the text to make it clear that while there are many ways to use a case statement, the case on the tuple is the most idiomatic one. * typo * clauses not functions * the number is a divisor * Reformat code in code blocks * Run through a grammar checker * Format approach snippets --------- Co-authored-by: Angelika Tyborska <[email protected]>
- Loading branch information
1 parent
d7e80b3
commit 841d716
Showing
11 changed files
with
414 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
#!/bin/bash | ||
|
||
# this script is necessary as long as | ||
# the config option approaches.snippet_extension is not supported | ||
# and we're forced to keep snippets in txt files | ||
FILES="exercises/**/**/.approaches/**/snippet.txt" | ||
for file in $FILES | ||
do | ||
txt_file_path=$file | ||
ex_file_path="${file//\.txt/.ex}" | ||
mv $txt_file_path $ex_file_path | ||
mix format $ex_file_path | ||
mv $ex_file_path $txt_file_path | ||
done |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
# Multiple clause function | ||
|
||
```elixir | ||
defmodule Year do | ||
@spec leap_year?(non_neg_integer) :: boolean | ||
def leap_year?(year) when rem(year, 400) == 0, do: true | ||
def leap_year?(year) when rem(year, 100) == 0, do: false | ||
def leap_year?(year) when rem(year, 4) == 0, do: true | ||
def leap_year?(_), do: false | ||
end | ||
``` | ||
|
||
In Elixir, functions can have multiple clauses. | ||
Which one will be executed depends on parameter matching and guards. | ||
When a function with multiple clauses is invoked, the parameters are compared to the definitions in the order in which they were defined, and only the first one matching will be invoked. | ||
|
||
While in the [operators approach][operators-approach], it was possible to reorder expressions as long as the suitable boolean operators were used, in this approach, there is only one correct order of definitions. | ||
|
||
In our case, the three guards in the function clauses are as follows: | ||
|
||
```elixir | ||
when rem(year, 400) == 0 | ||
when rem(year, 100) == 0 | ||
when rem(year, 4) == 0 | ||
``` | ||
|
||
But because of the order they are evaluated in, they are equivalent to: | ||
|
||
```elixir | ||
when rem(year, 400) == 0 | ||
when rem(year, 100) == 0 and not rem(year, 400) == 0 | ||
when rem(year, 4) == 0 and not rem(year, 100) == 0 and not rem(year, 400) == 0 | ||
``` | ||
|
||
The final clause, `def leap_year?(_), do: false`, returns false if previous clauses are not a match. | ||
|
||
## Guards | ||
|
||
The [guards][hexdocs-guards] are part of the pattern-matching mechanism. | ||
They allow for more complex checks of values. | ||
However, because of when they are executed to allow the compiler to perform necessary optimization, | ||
only a minimal subset of operations is permitted. | ||
`Kernel.rem/2` is on this limited list, and `Integer.mod/2` is not. | ||
This is why, in this approach, only the first one will work, and the latter will not. | ||
|
||
In this approach, the boolean operators matter too. Only the strict ones, `not`, `and`, `or` are allowed. | ||
The relaxed `!`, `&&`, `||` will fail to compile. | ||
|
||
[operators-approach]: https://exercism.org/tracks/elixir/exercises/leap/approaches/operators | ||
[hexdocs-guards]: https://hexdocs.pm/elixir/main/patterns-and-guards.html#guards |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
def leap_year?(year) when rem(year, 400) == 0, do: true | ||
def leap_year?(year) when rem(year, 100) == 0, do: false | ||
def leap_year?(year) when rem(year, 4) == 0, do: true | ||
def leap_year?(_), do: false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
{ | ||
"introduction": { | ||
"authors": [ | ||
"michalporeba" | ||
] | ||
}, | ||
"approaches": [ | ||
{ | ||
"uuid": "be6d6c6e-8e19-4657-aad5-3382e7ec01db", | ||
"slug": "operators", | ||
"title": "Boolean operators", | ||
"blurb": "Use boolean operators to combine the checks.", | ||
"authors": [ | ||
"michalporeba" | ||
] | ||
}, | ||
{ | ||
"uuid": "0267853e-9607-4b60-b2f9-e4a34f5316db", | ||
"slug": "clauses", | ||
"title": "Multiple clause functions", | ||
"blurb": "Use a multiple clause function to control the order of checks.", | ||
"authors": [ | ||
"michalporeba" | ||
] | ||
}, | ||
{ | ||
"uuid": "428e3cee-309a-4c45-a6d4-3bff4eb41daa", | ||
"slug": "flow", | ||
"title": "Control flow structures", | ||
"blurb": "Use `if, `case` or `cond`, to control order of checks.", | ||
"authors": [ | ||
"michalporeba" | ||
] | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
# Control flow structures | ||
|
||
```elixir | ||
defmodule Year do | ||
@spec leap_year?(non_neg_integer) :: boolean | ||
def leap_year?(year) do | ||
if rem(year, 100) == 0 do | ||
rem(year, 400) == 0 | ||
else | ||
rem(year, 4) == 0 | ||
end | ||
end | ||
end | ||
``` | ||
|
||
## If | ||
|
||
Elixir provides four [control flow structures][hexdocs-structures]: `case`, `cond`, `if`, and `unless`. | ||
The `if` and `unless` allow to evaluate only one condition. | ||
Unlike in many other languages, there is no `else if` option in Elixir. | ||
|
||
However, in this case, it is not necessary. We can use `if` once to check if the year is divisible by 100. | ||
If it is, then whether it is a leap year or not depends on if it is divisible by 400. | ||
If it is not, then whether it is a leap year or not depends on if it is divisible by 4. | ||
|
||
```elixir | ||
def leap_year?(year) do | ||
if rem(year, 100) == 0 do | ||
rem(year, 400) == 0 | ||
else | ||
rem(year, 4) == 0 | ||
end | ||
end | ||
``` | ||
|
||
## Cond | ||
|
||
Another option is `cond` which allows for evaluating multiple conditions, similar to `else if` in other languages. | ||
|
||
```elixir | ||
def leap_year?(year) do | ||
cond do | ||
rem(year, 400) == 0 -> true | ||
rem(year, 100) == 0 -> false | ||
rem(year, 4) == 0 -> true | ||
true -> false | ||
end | ||
end | ||
``` | ||
|
||
Similarly to the [multiple clause function approach][clause-approach], the order here matters. | ||
The conditions are evaluated in order, and the first that is not `nil` or `false` leads to the result. | ||
|
||
## Case | ||
|
||
`case` allows to compare a value to multiple patterns, but can also replicate what `if` offers. | ||
|
||
```elixir | ||
def leap_year?(year) do | ||
case rem(year, 100) do | ||
0 -> rem(year, 400) == 0 | ||
_ -> rem(year, 4) == 0 | ||
end | ||
end | ||
``` | ||
|
||
It also supports [guards][hexdocs-guards], offering another way to solve the problem. | ||
|
||
```elixir | ||
def leap_year?(year) do | ||
case year do | ||
_ when rem(year, 400) == 0 -> true | ||
_ when rem(year, 100) == 0 -> false | ||
_ when rem(year, 4) == 0 -> true | ||
_ -> false | ||
end | ||
end | ||
``` | ||
|
||
The `case` can be very flexible, so many variations are possible. | ||
Using it with pattern matching on a tuple is considered **the most idiomatic**. | ||
In this case, a tuple is created with all the checks. | ||
Then, pattern matching to tuples is performed. | ||
|
||
```elixir | ||
def leap_year?(year) do | ||
case {rem(year, 400), rem(year, 100), rem(year, 4)} do | ||
{0, _, _} -> true | ||
{_, 0, _} -> false | ||
{_, _, 0} -> true | ||
_ -> false | ||
end | ||
end | ||
``` | ||
|
||
[hexdocs-structures]: https://hexdocs.pm/elixir/case-cond-and-if.html | ||
[hexdocs-guards]: https://hexdocs.pm/elixir/main/patterns-and-guards.html#guards | ||
[clause-approach]: https://exercism.org/tracks/elixir/exercises/leap/approaches/clauses |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
def leap_year?(year) do | ||
if rem(year, 100) == 0 do | ||
rem(year, 400) == 0 | ||
else | ||
rem(year, 4) == 0 | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
# Introduction | ||
|
||
Every fourth year is a leap year (with some exceptions), but let's consider this one condition first. | ||
|
||
To solve the Leap problem, we must determine if a year is evenly divisible by a number or if a reminder of an integer division is zero. | ||
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] and the [`Integer.mod/2`][mod] functions. | ||
|
||
The two functions differ in how they work with negative divisors, but since, in this exercise, | ||
all the divisors are non-negative, both could work, depending on the approach you choose. | ||
|
||
## General solution | ||
|
||
To check if a year is divisible by `n`, we can do `rem(year, n) == 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 | ||
|
||
The full rules are as follows: | ||
A year is a leap year if | ||
* it is divisible by 4 | ||
* but not divisible by 100 | ||
* unless it is divisible by 400 | ||
|
||
We can use [boolean operators][boolean-operators] to combine the checks, for example, like so: | ||
|
||
```elixir | ||
rem(year, 5) == 0 and not rem(year, 100) == 0 or rem(year, 400) == 0 | ||
``` | ||
In the [boolean operators approach][operators-approach] we discuss the details of the solution. | ||
It includes variations of the operators and their precedence. | ||
|
||
## Approach: multiple clause function | ||
|
||
Instead of using boolean operators, we can define multiple `leap_year?/1` function clauses with different guards. | ||
|
||
```elixir | ||
def leap_year?(year) when rem(year, 400) == 0, do: true | ||
def leap_year?(year) when rem(year, 100) == 0, do: false | ||
def leap_year?(year) when rem(year, 4) == 0, do: true | ||
def leap_year?(_), do: false | ||
``` | ||
|
||
In the [multiple clause function approach][clause-approach] we discuss why in this approach the `Integer.mod/2` function will not work. | ||
|
||
## Approach: control flow structures | ||
|
||
In addition to the above two approaches, control flow structures offer a number of solutions. | ||
Here are two examples using `if` and `case`. | ||
|
||
```elixir | ||
if rem(year, 100) == 0 do | ||
rem(year, 400) == 0 | ||
else | ||
rem(year, 4) == 0 | ||
end | ||
``` | ||
|
||
```elixir | ||
case {rem(year, 400), rem(year, 100), rem(year, 4)} do | ||
{0, _, _} -> true | ||
{_, 0, _} -> false | ||
{_, _, 0} -> true | ||
_ -> false | ||
end | ||
``` | ||
|
||
We discuss these and other solutions depending on various control flow structures in the [control flow structures approach][flow-approach]. | ||
|
||
[modulo]: https://en.wikipedia.org/wiki/Modulo | ||
[operators]: https://hexdocs.pm/elixir/operators.html | ||
[rem]: https://hexdocs.pm/elixir/Kernel.html#rem/2 | ||
[mod]: https://hexdocs.pm/elixir/Integer.html#mod/2 | ||
[boolean-operators]: https://hexdocs.pm/elixir/operators.html#general-operators | ||
[operators-approach]: https://exercism.org/tracks/elixir/exercises/leap/approaches/operators | ||
[clause-approach]: https://exercism.org/tracks/elixir/exercises/leap/approaches/clauses | ||
[flow-approach]: https://exercism.org/tracks/elixir/exercises/leap/approaches/cond | ||
|
||
|
Oops, something went wrong.