Skip to content

Commit 787dd20

Browse files
leap: add approaches (#1577)
leap: add approaches Co-authored-by: Erik Schierboom <[email protected]>
1 parent 72fb3b2 commit 787dd20

File tree

16 files changed

+469
-0
lines changed

16 files changed

+469
-0
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# One line of Boolean conditions
2+
3+
```rust
4+
pub fn is_leap_year(year: u64) -> bool {
5+
(year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0))
6+
}
7+
```
8+
9+
The first boolean condition uses the [remainder operator][remainder-operator] to check if the year is evenly divided by `4`.
10+
- If the year is not evenly divisible by `4`, then the chain will "short circuit" due to the next operator being a [logical AND][operators] (`&&`), and will return `false`.
11+
- If the year _is_ evenly divisible by `4`, then the year is checked to _not_ be evenly divisible by `100`.
12+
- If the year is not evenly divisible by `100`, then the expression is `true` and the chain will "short-circuit" to return `true`,
13+
since the next operator is a [logical OR][operators] (`||`).
14+
- If the year _is_ evenly divisible by `100`, then the expression is `false`, and the returned value from the chain will be if the year is evenly divisible by `400`.
15+
16+
| year | year % 4 == 0 | year % 100 != 0 | year % 400 == 0 | is leap year |
17+
| ---- | ------------- | --------------- | --------------- | ------------ |
18+
| 2020 | true | true | not evaluated | true |
19+
| 2019 | false | not evaluated | not evaluated | false |
20+
| 2000 | true | false | true | true |
21+
| 1900 | true | false | false | false |
22+
23+
24+
The chain of boolean expressions is efficient, as it proceeds from testing the most likely to least likely conditions.
25+
26+
[remainder-operator]: https://doc.rust-lang.org/std/ops/trait.Rem.html
27+
[operators]: https://doc.rust-lang.org/book/appendix-02-operators.html
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub fn is_leap_year(year: u64) -> bool {
2+
(year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0))
3+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"introduction": {
3+
"authors": ["bobahop"],
4+
"contributors": []
5+
},
6+
"approaches": [
7+
{
8+
"uuid": "6772ef04-12db-4388-82f2-3e3486998665",
9+
"slug": "boolean-one-line",
10+
"title": "Boolean one line",
11+
"blurb": "Use one line of boolean conditions.",
12+
"authors": ["bobahop"]
13+
},
14+
{
15+
"uuid": "5c072736-3d9a-4a20-877e-8108ccaa59bd",
16+
"slug": "ternary-expression",
17+
"title": "Ternary operator",
18+
"blurb": "Use a ternary expression of boolean conditions.",
19+
"authors": ["bobahop"]
20+
},
21+
{
22+
"uuid": "a66c7016-c697-4b03-b05c-4febaca07dfc",
23+
"slug": "match-on-a-tuple",
24+
"title": "match on a tuple",
25+
"blurb": "Use a match on a tuple made from boolean conditions.",
26+
"authors": ["bobahop"]
27+
},
28+
{
29+
"uuid": "07b68663-d4e4-4ec2-ab0b-9f1366eba06c",
30+
"slug": "date-addition-time",
31+
"title": "Date addition with time crate",
32+
"blurb": "Use time::Date addition.",
33+
"authors": ["bobahop"]
34+
},
35+
{
36+
"uuid": "232280a3-37a4-4e7c-bfcc-ade90fd85586",
37+
"slug": "date-addition-chrono",
38+
"title": "Date addition with chrono crate",
39+
"blurb": "Use chrono::Date addition.",
40+
"authors": ["bobahop"]
41+
}
42+
]
43+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# `chrono::Date` addition
2+
3+
```rust
4+
use chrono::{prelude::*, Duration};
5+
6+
pub fn is_leap_year(year: u64) -> bool {
7+
(Utc.ymd(year as i32, 2, 28) + Duration::days(1)).day() == 29
8+
}
9+
```
10+
11+
```exercism/caution
12+
This approach may be considered a "cheat" for this exercise.
13+
```
14+
By adding a day to February 28th for the year, you can see if the new day is the 29th or the 1st.
15+
If it is the 29th, then the year is a leap year.
16+
This is done by using the [`Duration::days(1)`][day-duration] method to add a day to a [`chrono::Date`][chrono-date] `struct` and comparing it to `29` with the [`day`][day-method] method.
17+
18+
The documentation suggests using [NaiveDate][naive] instead of `Date`, in which case the code would be
19+
20+
```rust
21+
pub fn is_leap_year_naive(year: u64) -> bool {
22+
(NaiveDate::from_ymd(year as i32, 2, 28) + chrono::Duration::days(1)).day() == 29
23+
}
24+
```
25+
26+
Running benchmarks gave similar results for `Date` and `NaiveDate`.
27+
28+
[day-duration]: https://docs.rs/chrono/latest/chrono/struct.Duration.html#method.days
29+
[chrono-date]: https://docs.rs/chrono/latest/chrono/struct.Date.html
30+
[day-method]: https://docs.rs/chrono/latest/chrono/struct.Date.html#method.day
31+
[naive]: https://docs.rs/chrono/latest/chrono/naive/struct.NaiveDate.html
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
use chrono::{prelude::*, Duration};
2+
3+
pub fn is_leap_year(year: u64) -> bool {
4+
(Utc.ymd(year as i32, 2, 28) + Duration::days(1)).day() == 29
5+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# `time::Date` addition
2+
3+
```rust
4+
use time::{Date, Duration, Month};
5+
6+
pub fn is_leap_year(year: u64) -> bool {
7+
(Date::from_calendar_date(year as i32, Month::February, 28).unwrap() + Duration::DAY).day()
8+
== 29
9+
}
10+
```
11+
12+
```exercism/caution
13+
This approach may be considered a "cheat" for this exercise.
14+
```
15+
By adding a day to February 28th for the year, you can see if the new day is the 29th or the 1st.
16+
If it is the 29th, then the year is a leap year.
17+
This is done by adding a [`Duration::DAY`][day-duration] to a [`time::Date`][time-date] `struct` and comparing it to `29` with the [`day`][day-method] method.
18+
19+
[day-duration]: https://docs.rs/time/latest/time/struct.Duration.html#associatedconstant.DAY
20+
[time-date]: https://docs.rs/time/latest/time/struct.Date.html
21+
[day-method]: https://docs.rs/time/latest/time/struct.Date.html#method.day
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
use time::{Date, Duration, Month};
2+
3+
pub fn is_leap_year(year: u64) -> bool {
4+
(Date::from_calendar_date(year as i32, Month::February, 28).unwrap() + Duration::DAY).day()
5+
== 29
6+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Introduction
2+
3+
There are various idiomatic approaches to solve Leap.
4+
You can use one line of boolean expressions to test the conditions.
5+
Or you can use a [ternary expression][ternary-expression].
6+
Another approach you can use is a [match][match] on a [tuple][tuple].
7+
8+
## General guidance
9+
10+
The key to solving Leap is to know if the year is evenly divisible by `4`, `100` and `400`.
11+
For determining that, you will use the [remainder operator][remainder-operator].
12+
13+
## Approach: One line of Boolean conditions
14+
15+
```rust
16+
pub fn is_leap_year(year: u64) -> bool {
17+
(year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0))
18+
}
19+
```
20+
21+
For more information, check the [Boolean one-line approach][approach-boolean-line].
22+
23+
## Approach: Ternary expression of Boolean conditions
24+
25+
```rust
26+
pub fn is_leap_year(year: u64) -> bool {
27+
if year % 100 == 0 {
28+
year % 400 == 0
29+
} else {
30+
year % 4 == 0
31+
}
32+
}
33+
```
34+
35+
For more information, check the [Ternary expression approach][approach-ternary-expression].
36+
37+
## Approach: `match` on a `tuple`
38+
39+
```rust
40+
pub fn is_leap_year(year: u64) -> bool {
41+
match (year % 4, year % 100, year % 400) {
42+
(_, _, 0) => true,
43+
(_, 0, _) => false,
44+
(0, _, _) => true,
45+
_ => false,
46+
}
47+
}
48+
```
49+
50+
For more information, check the [`match` on a `tuple` approach][approach-match-on-a-tuple].
51+
52+
## Other approaches
53+
54+
Besides the aforementioned, idiomatic approaches, you could also approach the exercise as follows:
55+
56+
### Other approach: `time::Date` addition approach:
57+
58+
Add a day to February 28th for the year and see if the new day is the 29th. For more information, see the [`time::Date` addition approach][approach-date-add-time].
59+
60+
### Other approach: `chrono::Date` addition approach:
61+
62+
Add a day to February 28th for the year and see if the new day is the 29th. For more information, see the [`chrono::Date` addition approach][approach-date-add-chrono].
63+
64+
## Which approach to use?
65+
66+
- The one line of boolean conditions is most efficient, as it proceeds from the most likely to least likely conditions.
67+
It has a maximum of three checks.
68+
- The ternary expression has a maximum of only two checks, but it starts from a less likely condition.
69+
- The `match` on a `tuple` may be considered more "functional", but it is more verbose and may be considered less readable.
70+
- Using `Date` addition may be considered "cheats" for the exercise,
71+
and they are less performant than the three main approaches.
72+
73+
For more information, check the [Performance article][article-performance].
74+
75+
[remainder-operator]: https://doc.rust-lang.org/std/ops/trait.Rem.html
76+
[match]: https://doc.rust-lang.org/rust-by-example/flow_control/match.html
77+
[tuple]: https://doc.rust-lang.org/rust-by-example/primitives/tuples.html
78+
[ternary-expression]: (https://doc.rust-lang.org/reference/expressions/if-expr.html)
79+
[approach-boolean-line]: https://exercism.org/tracks/rust/exercises/leap/approaches/boolean-one-line
80+
[approach-ternary-expression]: https://exercism.org/tracks/rust/exercises/leap/approaches/ternary-expression
81+
[approach-match-on-a-tuple]: https://exercism.org/tracks/rust/exercises/leap/approaches/match-on-a-tuple
82+
[approach-date-add-time]: https://exercism.org/tracks/rust/exercises/leap/approaches/date-addition-time
83+
[approach-date-add-chrono]: https://exercism.org/tracks/rust/exercises/leap/approaches/date-addition-chrono
84+
[article-performance]: https://exercism.org/tracks/rust/exercises/leap/articles/performance
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# `match` on a `tuple`
2+
3+
```rust
4+
pub fn is_leap_year(year: u64) -> bool {
5+
match (year % 4, year % 100, year % 400) {
6+
(_, _, 0) => true,
7+
(_, 0, _) => false,
8+
(0, _, _) => true,
9+
_ => false,
10+
}
11+
}
12+
```
13+
14+
A [tuple][tuple] is made from the conditions for the year being evenly divisible by `4`, `100` and `400`.
15+
16+
- The `tuple` is tested in a [match expression][match].
17+
- It tests from top to bottom, so the `(_, _, 0) => true,` arm is tested first.
18+
- It checks the values of the expressions in the `tuple`, and uses the `_` [wildcard pattern][wildcard] to disregard a value inside the tuple.
19+
- If the pattern matches, then the arm returns the value to the right of the `=>`.
20+
- If the pattern does not match, then the pattern in the next arm is tested.
21+
- In the final arm of the `match`, the wildcard pattern is applied to the entire tuple.
22+
This is similar to `default` used in `switch` statements in other languages.
23+
It returns `false` no matter what the values in the tuple are.
24+
25+
| year | year % 4 | year % 100 | year % 400 == 0 | is leap year |
26+
| ---- | -------- | ---------- | --------------- | ------------ |
27+
| 2020 | 0 | 20 | 20 | true |
28+
| 2019 | 3 | 19 | 19 | false |
29+
| 2020 | 0 | 0 | 0 | true |
30+
| 1900 | 0 | 0 | 300 | false |
31+
32+
Although some may consider it to be a more "functional" approach, the `switch` on a `tuple` approach is somewhat more verbose than other approaches,
33+
and may also be considered less readable.
34+
35+
[match]: https://doc.rust-lang.org/rust-by-example/flow_control/match.html
36+
[tuple]: https://doc.rust-lang.org/rust-by-example/primitives/tuples.html
37+
[wildcard]: https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html#ignoring-values-in-a-pattern
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
pub fn is_leap_year(year: u64) -> bool {
2+
match (year % 4, year % 100, year % 400) {
3+
(_, _, 0) => true,
4+
(_, 0, _) => false,
5+
(0, _, _) => true,
6+
_ => false,
7+
}
8+
}

0 commit comments

Comments
 (0)