-
-
Notifications
You must be signed in to change notification settings - Fork 159
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* setting structure for leap approaches * first draft of the introduction * review of the general introduction updates reflecting what I have learnt while refining a similar introduction in the Elixir track * boolean approach * full function in the snippet * control flow structures approach * boolean operations rather than just boolean * a bonus approach - data shapes * adding configuration for the data shapes approach * removing unnecessary trailing spaces
- Loading branch information
1 parent
f883deb
commit bf84d4d
Showing
9 changed files
with
288 additions
and
0 deletions.
There are no files selected for viewing
45 changes: 45 additions & 0 deletions
45
exercises/practice/leap/.approaches/boolean-operations/content.md
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,45 @@ | ||
# Boolean operations | ||
|
||
```clojure | ||
(defn- divides? [number divisor] | ||
(zero? (mod number divisor))) | ||
|
||
(defn leap-year? [year] | ||
(and (divides? year 4) | ||
(or (not (divides? year 100)) | ||
(divides? year 400)))) | ||
``` | ||
|
||
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. Multiple variations are possible. One is shown above, but the below is also possible. | ||
|
||
```clojure | ||
(defn leap-year? [year] | ||
(or (divides? year 400) | ||
(and (not (divides? year 100)) | ||
(divides? year 4)))) | ||
``` | ||
|
||
## Being explicit | ||
|
||
The above examples use a private function `divides?` to be more explicit, to show what logical check is done rather than what operation is performed. | ||
This is common in Clojure code, but it is also possible to do the checks directly. | ||
|
||
```clojure | ||
(defn leap-year? [year] | ||
(and (zero? year 4) | ||
(or (pos? year 100) | ||
(zero? year 400)))) | ||
``` | ||
|
||
In this example, in addition, instead of negating the second check, we check if the reminder from the division is greater than zero. | ||
|
||
If the concern is that defining `divides?` adds confusion, because it is not known in the only place where it is used, `letfn` can be used. Here is another example. | ||
|
||
```clojure | ||
(defn leap-year? [year] | ||
(letfn [(is-multiple-of? [n] (zero? (mod year n)))] | ||
(and (is-multiple-of? 4) | ||
(or (not (is-multiple-of? 100)) | ||
(is-multiple-of? 400))))) | ||
``` |
4 changes: 4 additions & 0 deletions
4
exercises/practice/leap/.approaches/boolean-operations/snippet.txt
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 @@ | ||
(defn leap-year? [year] | ||
(and (divides? year 4) | ||
(or (not (divides? year 100)) | ||
(divides? year 400)))) |
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": "83290a7b-3e87-46a1-9b82-10b029716ad1", | ||
"slug": "boolean-operations", | ||
"title": "Boolean operations", | ||
"blurb": "Solving Leap by using boolean operations `and`, `or` and `not`.", | ||
"authors": [ | ||
"michalporeba" | ||
] | ||
}, | ||
{ | ||
"uuid": "a1927e29-b262-4a83-a66d-0e30699e6c21", | ||
"slug": "flow-control", | ||
"title": "Flow control expressions", | ||
"blurb": "Solving Leap by using conditional branching with `if` and `cond`.", | ||
"authors": [ | ||
"michalporeba" | ||
] | ||
}, | ||
{ | ||
"uuid": "bebc4e3d-2ba4-4c70-8f99-b6bdd1994be8", | ||
"slug": "data-shapes", | ||
"title": "Data shapes", | ||
"blurb": "Solving Leap by using data shapes.", | ||
"authors": [ | ||
"michalporeba" | ||
] | ||
} | ||
] | ||
} |
50 changes: 50 additions & 0 deletions
50
exercises/practice/leap/.approaches/data-shapes/content.md
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 @@ | ||
# Data shapes | ||
|
||
```clojure | ||
(def leap-shapes | ||
#{{:by-4 true :by-100 true :by-400 true} | ||
{:by-4 true :by-100 false :by-400 false}}) | ||
|
||
(defn- to-shape [year] | ||
{:by-4 (zero? (mod year 4)) | ||
:by-100 (zero? (mod year 100)) | ||
:by-400 (zero? (mod year 400))}) | ||
|
||
(defn leap-year? [year] | ||
(->> year | ||
to-shape | ||
leap-shapes | ||
some?)) | ||
``` | ||
|
||
We can define a data structure that describes the properties of a number. | ||
In this case, the number is a year, and the properties are whether it is divisible by 4, 100 and 400. Let's call any instance of such data structure the shape of a number. | ||
|
||
While there are many numbers, there are fewer shapes, so we can define all shapes corresponding to all leap years. There are only two. | ||
|
||
```clojure | ||
(def leap-shapes | ||
#{{:by-4 true :by-100 true :by-400 true} | ||
{:by-4 true :by-100 false :by-400 false}}) | ||
``` | ||
|
||
We now need a function to convert any number to its shape. | ||
|
||
```clojure | ||
(defn- to-shape [year] | ||
{:by-4 (zero? (mod year 4)) | ||
:by-100 (zero? (mod year 100)) | ||
:by-400 (zero? (mod year 400))}) | ||
``` | ||
|
||
With the above, we can now take a year number and check if its shape is one of the leap shapes. | ||
|
||
```clojure | ||
(defn leap-year? [year] | ||
(->> year | ||
to-shape | ||
leap-shapes | ||
some?)) | ||
``` | ||
|
||
In Clojure, if a value is passed to a set, the set acts like a function checking if the value exists in that set. This is how `leap-shapes` acts as a predicate when combined with `some?`. |
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,5 @@ | ||
(defn leap-year? [year] | ||
(->> year | ||
to-shape | ||
leap-shapes | ||
some?)) |
69 changes: 69 additions & 0 deletions
69
exercises/practice/leap/.approaches/flow-control/content.md
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,69 @@ | ||
# Flow control expressions | ||
|
||
```clojure | ||
(defn- divides? [number divisor] | ||
(zero? (mod number divisor))) | ||
|
||
(defn leap-year? [year] | ||
(if (divides? year 100) | ||
(divides? year 400) | ||
(divides? year 4))) | ||
``` | ||
|
||
## If | ||
|
||
Clojure provides `if`, `cond`, `condp` and `case` expressions to control the flow of the program. | ||
|
||
The `if` doesn't have an `else if` option, but it is not necessary here. | ||
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 if it is divisible by 400. | ||
If it is not, then whether it is a leap year or not depends if it is divisible by 4. | ||
|
||
```clojure | ||
(defn leap-year? [year] | ||
(if (divides? year 100) | ||
(divides? year 400) | ||
(divides? year 4))) | ||
``` | ||
|
||
## Cond | ||
|
||
Another option is `cond` which allows for evaluating multiple conditions, similar to `else if` in other languages. | ||
|
||
```clojure | ||
(defn leap-year? [year] | ||
(cond | ||
(divides? year 400) true | ||
(divides? year 100) false | ||
(divides? year 4) true | ||
:else false)) | ||
``` | ||
|
||
A very similar alternative is to use the `condp` macro, which takes a predicate and applies it to a series of test value and expected result pairs. | ||
|
||
```clojure | ||
(defn leap-year? [year] | ||
(condp #(zero? (mod %2 %1)) year | ||
400 true | ||
100 false | ||
4 true | ||
false)) | ||
``` | ||
|
||
When using both `cond` and `condp,` the other matters as the first true condition stops evaluating the list and determines the result. | ||
|
||
## Case | ||
|
||
Finally, it is also possible to use `case`, but this solution is not popular. | ||
|
||
```clojure | ||
(defn leap-year? [year] | ||
(case [(divides? year 400) | ||
(divides? year 100) | ||
(divides? year 4) | ||
] | ||
[true true true] true | ||
[false true true] false | ||
[false false true] true | ||
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,4 @@ | ||
(defn leap-year? [year] | ||
(if (divides? year 100) | ||
(divides? year 400) | ||
(divides? year 4))) |
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,74 @@ | ||
# Introduction | ||
|
||
Every fourth year is a leap year (with some exceptions), but let's focus initially on the primary condition. | ||
|
||
In the Leap problem, we need to determine if a year is evenly divisible by a number, | ||
which means checking if the remainder of an integer division is zero. | ||
This operation in computing is known as [modulo][modulo]. | ||
|
||
|
||
Clojure has two functions we can use: `mod` and `rem`. | ||
The two functions differ in how they work with negative divisors, but since, in this exercise, | ||
all the divisors are non-negative, both will work. | ||
|
||
## General Solution | ||
|
||
To check if a year is divisible by `n` in Clojure, we can do `(zero? (mod year n))`. To make the intent clearer, we can define a private function `divides?`. | ||
|
||
```clojure | ||
(defn- divides? [number divisor] | ||
(zero? (mod number divisor))) | ||
``` | ||
|
||
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 which Clojure features we will use to combine the checks. | ||
|
||
## Approach: boolean operations | ||
|
||
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 `and`, `or` and `not` functions to encode the rules, for example, like so: | ||
|
||
```clojure | ||
(and (divides? year 4) | ||
(or (not (divides? year 100)) | ||
(divides? year 400)))) | ||
``` | ||
|
||
Explore the details in the [boolean operations approach][boolean-approach]. | ||
|
||
## Approach: conditional branching | ||
|
||
Instead of combining the logical expressions, we can use conditional branching with functions like `if` or `cond` to perform the necessary checks and determin if a given years is a leap one. | ||
|
||
```clojure | ||
(if (divides? year 100) | ||
(divides? year 400) | ||
(divides? year 4))) | ||
``` | ||
|
||
In the [flow control expressions approach][flow-control-approach], we discuss the options comparing `if` and `cond` functions. | ||
|
||
## Approach: data shapes | ||
|
||
This is a little less straight forward approach, but with a little bit of setup we can get to a `leap-year?` function that looks like so: | ||
|
||
```clojure | ||
(defn leap-year? [year] | ||
(->> year | ||
to-shape | ||
leap-shapes | ||
some?)) | ||
``` | ||
|
||
In the [data shapes approach][data-shapes-approach], we explore how it works. | ||
|
||
[modulo]: https://en.wikipedia.org/wiki/Modulo | ||
[boolean-approach]: https://exercism.org/tracks/clojure/exercises/leap/approaches/boolean | ||
[flow-control-approach]: https://exercism.org/tracks/clojure/exercises/leap/approaches/flow-control | ||
[data-shapes-approach]: https://exercism.org/tracks/clojure/exercises/leap/approaches/data-shapes |
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 |
---|---|---|
|
@@ -10,6 +10,7 @@ | |
"jgwhite", | ||
"kytrinyx", | ||
"mathias", | ||
"michalporeba", | ||
"ovidiu141", | ||
"sjwarner-bp", | ||
"verdammelt", | ||
|