Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Approaches for leap #330

Merged
merged 5 commits into from
Jan 17, 2024
Merged
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
29 changes: 29 additions & 0 deletions exercises/practice/leap/.approaches/boolean-chain/content.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Chaining Boolean expressions

```scheme
(define (leap-year? year)
(and (zero? (modulo year 4))
(or (not (zero? (modulo year 100)))
(zero? (modulo year 400)))))
```

The Boolean expression `(zero? (modulo year 4))` checks the remainder from dividing `year` by 4.
If a year is evenly divisible by 4, the remainder will be zero.
[`(zero? n)`][zero] returns the Boolean value `#t` when `n` is zero.
All leap years are divisible by 4, and this pattern is then repeated whether a year is not divisible by 100 and whether it's divisible by 400.
[The `and` and `or` forms][and-or] can chain the values from these three Boolean expressions to produce a new value.
The `and` form produces `#f` if any expression is `#f` or it returns the value of the last expression.
Any value besides `#f` is equivalent to `#t`.
The `or` form produces `#f` if all expressions are `#f` or it returns the first non-`#f` value.

| year | divisible by 4 | not divisible by 100 | divisible by 400 | result |
| ---- | -------------- | ------------------- | ---------------- | ------------ |
| 2020 | #t | #t | not evaluated | #t |
| 2019 | #f | not evaluated | not evaluated | #f |
| 2000 | #t | #f | #t | #t |
| 1900 | #t | #f | #f | #f |

By situationally skipping some of the tests, we can efficiently calculate the result with fewer operations.

[zero]: https://docs.racket-lang.org/reference/number-types.html#%28def._%28%28quote._~23~25kernel%29._zero~3f%29%29
[and-or]: https://docs.racket-lang.org/guide/conditionals.html#%28part._and%2Bor%29
4 changes: 4 additions & 0 deletions exercises/practice/leap/.approaches/boolean-chain/snippet.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
(define (leap-year? year)
(and (zero? (modulo year 4))
(or (not (zero? (modulo year 100)))
(zero? (modulo year 400)))))
46 changes: 46 additions & 0 deletions exercises/practice/leap/.approaches/cond-form/content.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Cond form

```scheme
(define (leap-year? year)
(cond
[(zero? (modulo year 400)) #t]
[(zero? (modulo year 100)) #f]
[(zero? (modulo year 4)) #t]
[else #f]))
```

[The cond form][cond-form] sequentially evaluates a series of test expressions until one produces a non-`#f` value.
Then the associated body for the test expression is evaluated, producing a value.
At this point, test expression evaluation inside the cond form ends and the value is returned.
If no test expressions are matched, [void][void-constant] is produced so often
the last expression is `#t` or `else` which will always be satisfied, allowing a default value to be set.

The cond form can be visualized as a nested [if form][if-form].

```scheme
(define (leap-year? year)
(if (zero? (modulo year 400))
#t
(if (zero? (modulo year 100))
#f
(if (zero? (modulo year 4))
#t
#f))))
```

To run though how this works, `year` is set to 2024, a leap year.

```scheme
(zero? (modulo year 400)) ; produces #f so ignored
(zero? (modulo year 100)) ; produces #f so ignored
(zero? (modulo year 4)) ; produces #t
```

The third test expression produces the first non-`#f` value so its body gets evaluated.
That body contains `#t` which is then the returned value from the cond.
That value is then returned by the function.


[cond-form]: https://docs.racket-lang.org/guide/conditionals.html#%28part._cond%29
[void-constant]: https://docs.racket-lang.org/reference/void.html#%28def._%28%28quote._~23~25kernel%29._void%29%29
[if-form]: https://docs.racket-lang.org/reference/if.html
6 changes: 6 additions & 0 deletions exercises/practice/leap/.approaches/cond-form/snippet.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
(define (leap-year? year)
(cond
[(zero? (modulo year 400)) #t]
[(zero? (modulo year 100)) #f]
[(zero? (modulo year 4)) #t]
[else #f]))
36 changes: 36 additions & 0 deletions exercises/practice/leap/.approaches/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"introduction": {
"authors": [
"BNAndras"
]
},
"approaches": [
{
"uuid": "b163b6d5-92ed-4073-8169-03e2a1b09fab",
"slug": "boolean-chain",
"title": "Boolean chain",
"blurb": "Use operators to check boolean values in a chain",
"authors": [
"BNAndras"
]
},
{
"uuid": "22229478-54ce-48ec-867f-1adcf04c42ac",
"slug": "cond-form",
"title": "Cond",
"blurb": "Use the cond form to check boolean values sequentially.",
"authors": [
"BNAndras"
]
},
{
"uuid": "45f28b96-4803-424c-9973-f2b4445f7227",
"slug": "pattern-matching",
"title": "Pattern matching",
"blurb": "Use the match form to check a list of boolean values at once.",
"authors": [
"BNAndras"
]
}
]
}
51 changes: 51 additions & 0 deletions exercises/practice/leap/.approaches/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Introduction

There are several idiomatic ways to solve this exercise.

## General guidance

Regardless of the approach chosen, this exercise requires students to use Boolean logic to decide if a given year is a leap year.

## Approach: Boolean chaining

```scheme
(define (leap-year? year)
(and (zero? (modulo year 4))
(or (not (zero? (modulo year 100)))
(zero? (modulo year 400)))))
```

For more information, check the [Boolean chain approach][approach-boolean-chain].

## Approach: Cond form

```scheme
(define (leap-year? year)
(cond
[(zero? (modulo year 400)) #t]
[(zero? (modulo year 100)) #f]
[(zero? (modulo year 4)) #t]
[else #f]))
```

For more information, check the [cond form approach][approach-cond-form].

## Approach: Pattern matching

```scheme
(define (leap-year? year)
(define divisible-by-4 (zero? (modulo year 4)))
(define divisible-by-100 (zero? (modulo year 100)))
(define divisible-by-400 (zero? (modulo year 400)))
(match (list divisible-by-4 divisible-by-100 divisible-by-400)
[(list _ _ #t) #t]
[(list _ #t _) #f]
[(list #t _ _) #t]
[_ #f]))
```

For more information, check the [pattern matching approach][approach-pattern-matching].

[approach-boolean-chain]: https://exercism.org/tracks/racket/exercises/leap/approaches/boolean-chain
[approach-cond-form]: https://exercism.org/tracks/racket/exercises/leap/cond-form
[approach-pattern-matching]: https://exercism.org/tracks/racket/exercises/leap/approaches/pattern-matching
51 changes: 51 additions & 0 deletions exercises/practice/leap/.approaches/pattern-matching/content.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Pattern matching

```scheme
(define (leap-year? year)
(define divisible-by-4 (zero? (modulo year 4)))
(define divisible-by-100 (zero? (modulo year 100)))
(define divisible-by-400 (zero? (modulo year 400)))
(match (list divisible-by-4 divisible-by-100 divisible-by-400)
[(list _ _ #t) #t]
[(list _ #t _) #f]
[(list #t _ _) #t]
[_ #f]))
```

[The match form][match-form] compares an expression sequentially to a series of clauses that contain patterns describing the contents of that expression and values returned when a given pattern is matched.
For this exercise, this takes the form of creating a list containing three Boolean values representing whether a year is divisible by 4, 100, or 400.
As an example, let's run through this code assuming `year` is set to 1993.
`(divisible-by-4 1999)` is `#f`, `(divisible-by-100 1999)` is `#f`, and `(divisible-by-400 1999)` is `#f`.
Filling in these results below, we have four patterns underneath it:

```scheme
(match (list #t #f #f) ; assuming the year is 1999
[(list _ _ #t) #t]
[(list _ #t _) #f]
[(list #t _ _) #t]
[_ #f])
```

The first three expect a list of three elements (`_` matching any value), but they differ on which value in the pattern can only be the literal value `#t`.

The first clause `[(list _ _ #t) #t]` describes a series of checks where the year must at least be divisible by 400.
A year divisible by 400 is also divisible by 4 and 100 so we can safely assume all three checks returned `#t` and there's no need to specify the result.
If this pattern is matched, `#t` is returned.
1999 isn't divisible by 400 since the `#f` in the third position means the pattern isn't matched.

The second clause `[(list _ #t _) #f]` describes a series of checks where the year must at least be divisible by 100.
A year divisible by 100 is also divisible by 4 so we can be sure that other check returned true and there's no need to specify it here.
We can also assume a year divisible by 100 wouldn't be divisible by 400 because if it were, the previous pattern would have already matched.
If this pattern is matched, `#f` is returned.
1999 isn't divisible by 100 since `#f` in the second position means the pattern isn't matched.

The third clause `[(list #t _ _) #t]` describes a series of checks where the year must at least be divisible by 4.
If this pattern is matched, `#t` is returned.
1999 isn't divisible by 4 since `#f` in the first position means the pattern isn't matched.

The fourth clause `[_ #f]` features a pattern with a single `_`, which means any value at all can match this pattern.
Without such a clause, an exception will occur if none of the clauses' patterns matched.
1999 isn't divisible by 4, 100, or 400, but it matches this catch-all pattern after not matching the previous patterns.
As a result, `#f` is returned by the code.

[match-form]: https://docs.racket-lang.org/guide/match.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
(define (leap-year? year)
(match (list (zero? (modulo year 4)) (zero? (modulo year 100)) (zero? (modulo year 400)))
[(list _ _ #t) #t]
[(list _ #t _) #f]
[(list #t _ _) #t]
[_ #f]))