-
-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: Isaac Good <[email protected]>
- Loading branch information
Showing
12 changed files
with
321 additions
and
0 deletions.
There are no files selected for viewing
23 changes: 23 additions & 0 deletions
23
exercises/practice/leap/.approaches/boolean-chain/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,23 @@ | ||
# Chaining Boolean expressions | ||
|
||
```tcl | ||
expr {$year % 4 == 0 && ($year % 100 != 0 || $year % 400 == 0)} | ||
``` | ||
|
||
The Boolean expression `$year % 4 == 0` checks the remainder from dividing `$year` by 4. | ||
If a year is evenly divisible by 4, the remainder will be 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 is divisible by 400. | ||
|
||
Parentheses are used to control the [order of precedence][order-of-precedence]: | ||
logical AND `&&` has a higher precedence than logical OR `||`. | ||
|
||
| year | divisible by 4 | not divisible by 100 | divisible by 400 | result | | ||
| ---- | -------------- | ------------------- | ---------------- | ------------ | | ||
| 2020 | true | true | not evaluated | true | | ||
| 2019 | false | not evaluated | not evaluated | false | | ||
| 2000 | true | false | true | true | | ||
| 1900 | true | false | false | false | | ||
|
||
By situationally skipping some of the tests, we can efficiently calculate the result with fewer operations. | ||
|
||
[order-of-precedence]: https://tcl.tk/man/tcl8.6/TclCmd/expr.htm#M6 |
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 @@ | ||
expr {$year % 4 == 0 && ($year % 100 != 0 || $year % 400 == 0)} |
23 changes: 23 additions & 0 deletions
23
exercises/practice/leap/.approaches/clock-command/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,23 @@ | ||
# Using the `clock` command | ||
|
||
Using [the `clock` command][tcl-clock] approach may be considered a "cheat" for this exercise. | ||
|
||
```tcl | ||
set timestamp [clock scan "$year-02-28" -format {%Y-%m-%d}] | ||
set next_day [clock add $timestamp 1 day] | ||
set day [clock format $next_day -format {%d}] | ||
expr {$day == 29} | ||
``` | ||
|
||
By adding a day to February 28th for the year, you can see if the new day is the 29th or the 1st. | ||
If it is the 29th, then the year is a leap year. | ||
|
||
Reference: [`clock` manual page](https://tcl.tk/man/tcl8.6/TclCmd/clock.htm) | ||
|
||
~~~~exercism/note | ||
[Under the hood][tcl-src-leap], Tcl does have an internal helper function to test for leap years. | ||
[tcl-src-leap]: https://github.com/tcltk/tcl/blob/37176a333aa886595daaddbdf14ae7cacd1f06b0/generic/tclClock.c#L1561 | ||
~~~~ | ||
|
||
[tcl-clock]: https://tcl.tk/man/tcl8.6/TclCmd/clock.htm |
4 changes: 4 additions & 0 deletions
4
exercises/practice/leap/.approaches/clock-command/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 @@ | ||
set timestamp [clock scan "$year-02-28" -format {%Y-%m-%d}] | ||
set next_day [clock add $timestamp 1 day] | ||
set day [clock format $next_day -format {%d}] | ||
expr {$day == 29} |
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": [ | ||
"glennj" | ||
] | ||
}, | ||
"approaches": [ | ||
{ | ||
"uuid": "bb72a01e-74f1-4d8e-9df1-625718ced974", | ||
"slug": "boolean-chain", | ||
"title": "Boolean chain", | ||
"blurb": "Use a chain of boolean expressions.", | ||
"authors": [ | ||
"glennj" | ||
] | ||
}, | ||
{ | ||
"uuid": "d4e35a2e-8394-4eee-8f98-8d5c7a680596", | ||
"slug": "ternary-operator", | ||
"title": "Ternary operator", | ||
"blurb": "Use a ternary operator of Boolean expressions.", | ||
"authors": [ | ||
"glennj" | ||
] | ||
}, | ||
{ | ||
"uuid": "c0b494ab-50f7-47dd-9394-a355317be233", | ||
"slug": "clock-command", | ||
"title": "Clock command", | ||
"blurb": "Use the clock command to do date addition.", | ||
"authors": [ | ||
"glennj" | ||
] | ||
} | ||
] | ||
} |
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,59 @@ | ||
# Introduction | ||
|
||
There are various idiomatic approaches to solve Leap. | ||
|
||
## General guidance | ||
|
||
The key to solving Leap is to know if the year is evenly divisible by `4`, `100` and `400`. | ||
To determine that, you will use the [modulo operator][modulo-operator]. | ||
|
||
Recall that Tcl commands all look like `cmd arg arg ...`. | ||
You can't just do `set z $x + $y` because `+` would be treated as just an argument, not an operation to add two numbers. | ||
[The `expr` command][expr-command] is needed to perform arithmetic. | ||
It essentially implements arithmetic as a domain-specific language, parsing its arguments as an arithmetic expression. | ||
|
||
## Approach: Arithmetic expression: chain of Boolean expressions | ||
|
||
```tcl | ||
expr {$year % 4 == 0 && ($year % 100 != 0 || $year % 400 == 0)} | ||
``` | ||
|
||
For more information, check the [Boolean chain approach][approach-boolean-chain]. | ||
|
||
## Approach: Arithmetic expression Ternary operator of Boolean expressions | ||
|
||
```tcl | ||
expr {$year % 100 == 0 ? $year % 400 == 0 : $year % 4 == 0} | ||
``` | ||
|
||
For more information, check the [Ternary operator approach][approach-ternary-operator]. | ||
|
||
## Approach: Using the `clock` command | ||
|
||
Add a day to February 28th in the given year and see if the new day is the 29th. | ||
|
||
```tcl | ||
set timestamp [clock scan "$year-02-28" -format {%Y-%m-%d}] | ||
set next_day [clock add $timestamp 1 day] | ||
set day [clock format $next_day -format {%d}] | ||
expr {$day == 29} | ||
``` | ||
|
||
For more information, see the [`clock` command approach][approach-clock-command]. | ||
|
||
## Which approach to use? | ||
|
||
- The chain of Boolean expressions should be the most efficient, as it proceeds from the most likely to least likely conditions. | ||
It has a maximum of three checks. | ||
It is the most efficient approach when testing a year that is not evenly divisible by `100` and is not a leap year, since the most likely outcome is eliminated first. | ||
- The ternary operator has a maximum of only two checks, but it starts from a less likely condition. | ||
- Using the `clock` command to do datetime arithmetic will be slower than the other approaches, just because Tcl has much more work to do under the hood. | ||
|
||
See [the Performance article][article-perf]. | ||
|
||
[modulo-operator]: https://tcl.tk/man/tcl8.6/TclCmd/expr.htm#M9 | ||
[expr-command]: https://tcl.tk/man/tcl8.6/TclCmd/expr.htm | ||
[approach-boolean-chain]: /tracks/tcl/exercises/leap/approaches/boolean-chain | ||
[approach-ternary-operator]: /tracks/tcl/exercises/leap/approaches/ternary-operator | ||
[approach-clock-command]: /tracks/tcl/exercises/leap/approaches/clock-command | ||
[article-perf]: /tracks/tcl/exercises/leap/articles/performance |
25 changes: 25 additions & 0 deletions
25
exercises/practice/leap/.approaches/ternary-operator/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,25 @@ | ||
# Ternary operator | ||
|
||
```tcl | ||
expr {$year % 100 == 0 ? $year % 400 == 0 : $year % 4 == 0} | ||
``` | ||
|
||
A [conditional operator][ternary-operator], also known as a "ternary conditional operator", or just "ternary operator", is a very condensed "if-then-else" operator. | ||
This structure uses a maximum of two checks to determine if a year is a leap year. | ||
|
||
It starts by testing the outlier condition of the year being evenly divisible by `100`. | ||
It does this by using the [remainder operator][remainder-operator]: `year % 100 == 0`. | ||
If the year is evenly divisible by `100`, then the expression is `true`, and the ternary operator returns the result of testing if the year is evenly divisible by `400`. | ||
If the year is _not_ evenly divisible by `100`, then the expression is `false`, and the ternary operator returns the result of testing if the year is evenly divisible by `4`. | ||
|
||
| year | divisible by 4 | not divisible by 100 | divisible by 400 | result | | ||
| ---- | -------------- | -------------------- | ---------------- | ------------ | | ||
| 2020 | false | not evaluated | true | true | | ||
| 2019 | false | not evaluated | false | false | | ||
| 2000 | true | true | not evaluated | true | | ||
| 1900 | true | false | not evaluated | false | | ||
|
||
Although it uses a maximum of two checks, the ternary operator tests an outlier condition first, making it less efficient than another approach that would first test if the year is evenly divisible by `4`, which is more likely than the year being evenly divisible by `100`. | ||
|
||
[ternary-operator]: https://tcl.tk/man/tcl8.6/TclCmd/expr.htm#M21 | ||
[remainder-operator]: https://tcl.tk/man/tcl8.6/TclCmd/expr.htm#M9 |
1 change: 1 addition & 0 deletions
1
exercises/practice/leap/.approaches/ternary-operator/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 @@ | ||
expr {$year % 100 == 0 ? $year % 400 == 0 : $year % 4 == 0} |
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,13 @@ | ||
{ | ||
"articles": [ | ||
{ | ||
"uuid": "2d4c0b08-4830-4bd9-a985-1c0d3bf71980", | ||
"slug": "performance", | ||
"title": "Performance demonstration", | ||
"blurb": "Compare the performances of the various leap year approaches.", | ||
"authors": [ | ||
"glennj" | ||
] | ||
} | ||
] | ||
} |
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,49 @@ | ||
proc leap_bool {year} { | ||
return [expr {$year % 4 == 0 && ($year % 100 != 0 || $year % 400 == 0)}] | ||
} | ||
|
||
proc leap_ternary {year} { | ||
return [expr {$year % 100 == 0 ? $year % 400 == 0 : $year % 4 == 0}] | ||
} | ||
|
||
proc leap_clock {year} { | ||
set timestamp [clock scan "$year-02-28" -format {%Y-%m-%d}] | ||
set next_day [clock add $timestamp 1 day] | ||
set day [clock format $next_day -format {%d}] | ||
return [expr {$day == 29}] | ||
} | ||
|
||
proc time_it {procname} { | ||
foreach year {2023 2024 1900 2000} { | ||
puts [format {%d - %d - %s} \ | ||
$year \ | ||
[$procname $year] \ | ||
[time {$procname $year} 1000]] | ||
} | ||
} | ||
|
||
foreach procname {leap_bool leap_ternary leap_clock} { | ||
puts $procname | ||
time_it $procname | ||
puts "" | ||
} | ||
|
||
set output { | ||
leap_bool | ||
2023 - 0 - 0.378 microseconds per iteration | ||
2024 - 1 - 0.472 microseconds per iteration | ||
1900 - 0 - 0.525 microseconds per iteration | ||
2000 - 1 - 0.533 microseconds per iteration | ||
|
||
leap_ternary | ||
2023 - 0 - 0.428 microseconds per iteration | ||
2024 - 1 - 0.425 microseconds per iteration | ||
1900 - 0 - 0.425 microseconds per iteration | ||
2000 - 1 - 0.434 microseconds per iteration | ||
|
||
leap_clock | ||
2023 - 0 - 65.716 microseconds per iteration | ||
2024 - 1 - 70.092 microseconds per iteration | ||
1900 - 0 - 59.396 microseconds per iteration | ||
2000 - 1 - 72.496 microseconds per iteration | ||
} |
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,80 @@ | ||
# Performance | ||
|
||
This article demonstrates the use of [the `time` command][time-command] to compare the various approaches: | ||
|
||
* [Boolean chain][approach-boolean-chain] | ||
* [Ternary operator][approach-ternary-operator] | ||
* [`clock` command][approach-clock-command] | ||
|
||
```tcl | ||
proc leap_bool {year} { | ||
return [expr {$year % 4 == 0 && ($year % 100 != 0 || $year % 400 == 0)}] | ||
} | ||
proc leap_ternary {year} { | ||
return [expr {$year % 100 == 0 ? $year % 400 == 0 : $year % 4 == 0}] | ||
} | ||
proc leap_clock {year} { | ||
set timestamp [clock scan "$year-02-28" -format {%Y-%m-%d}] | ||
set next_day [clock add $timestamp 1 day] | ||
set day [clock format $next_day -format {%d}] | ||
return [expr {$day == 29}] | ||
} | ||
proc time_it {procname} { | ||
foreach year {2023 2024 1900 2000} { | ||
puts [format {%d - %d - %s} \ | ||
$year \ | ||
[$procname $year] \ | ||
[time {$procname $year} 1000]] | ||
} | ||
} | ||
foreach procname {leap_bool leap_ternary leap_clock} { | ||
puts $procname | ||
time_it $procname | ||
puts "" | ||
} | ||
``` | ||
|
||
This outputs: | ||
|
||
```none | ||
leap_bool | ||
2023 - 0 - 0.378 microseconds per iteration | ||
2024 - 1 - 0.472 microseconds per iteration | ||
1900 - 0 - 0.525 microseconds per iteration | ||
2000 - 1 - 0.533 microseconds per iteration | ||
leap_ternary | ||
2023 - 0 - 0.428 microseconds per iteration | ||
2024 - 1 - 0.425 microseconds per iteration | ||
1900 - 0 - 0.425 microseconds per iteration | ||
2000 - 1 - 0.434 microseconds per iteration | ||
leap_clock | ||
2023 - 0 - 65.716 microseconds per iteration | ||
2024 - 1 - 70.092 microseconds per iteration | ||
1900 - 0 - 59.396 microseconds per iteration | ||
2000 - 1 - 72.496 microseconds per iteration``` | ||
## Observations | ||
1. Boolean chain | ||
We can see that the non leap year is the quickest to return, only having to execute one comparison. | ||
Even 100 and 400, the most expensive years to compute, execute in under one microsecond. | ||
1. Ternary operator | ||
All the test years take the same amount of time to run. | ||
That is expected since every year passed in has to perform two comparisons. | ||
1. `clock` command | ||
Unsurprisingly, this is much more expensive to run than the purely arithmetic approaches. | ||
[approach-boolean-chain]: /tracks/tcl/exercises/leap/approaches/boolean-chain | ||
[approach-ternary-operator]: /tracks/tcl/exercises/leap/approaches/ternary-operator | ||
[approach-clock-command]: /tracks/tcl/exercises/leap/approaches/clock-command |
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 @@ | ||
```none | ||
leap_bool | ||
2023 - 0 - 0.378 microseconds per iteration | ||
2024 - 1 - 0.472 microseconds per iteration | ||
1900 - 0 - 0.525 microseconds per iteration | ||
2000 - 1 - 0.533 microseconds per iteration | ||
``` |