Introduction
A leap year (in the Gregorian calendar) occurs:
- In every year that is evenly divisible by 4.
- Unless the year is evenly divisible by 100, in which case it’s only a leap year if the year is also evenly divisible by 400.
Some examples:
- 1997 was not a leap year as it’s not divisible by 4.
- 1900 was not a leap year as it’s not divisible by 400.
- 2000 was a leap year!
For a delightful, four-minute explanation of the whole phenomenon of leap years, check out [this YouTube video](https://www.youtube.com/watch?v=xX96xng7sAE).
Instructions
Instructions
Your task is to determine whether a given year is a leap year.
Dig Deeper
Use boolean chain
Using Boolean Chain
module Year
def self.leap?(year : Number) : Bool
year.divisible_by?(4) && (year.divisible_by?(400) || !year.divisible_by?(100))
end
end
The method takes a Number as an argument and returns a Bool.
This method first checks if the number is divisible by 4 using the divisible_by? method.
If the number is divisible by 4 it returns true otherwise false.
Then we repeat the divisible_by? method call with with 400 and 100.
But the result when dividing by 100 is inverted using the ! operator.
Finally, we evaluate the result of the division by 400 using the || operator, the logical OR operator.
If one of the two is true the result is true.
After those checks, the two resulting Boolean values are combined with the && operator, the logical AND operator.
If both are true the result is true, otherwise false.
Then the result is returned.
This solution is also possible to be solved using the % (modulus) operator, but as we are only interested in knowing if a number is divisible by another number, the divisible_by? method is more exemplar.
Use Case
Using case
module Year
def self.leap?(year : Number) : Bool
case year
when .divisible_by? 400
true
when .divisible_by? 100
false
when .divisible_by? 4
true
else
false
end
end
end
The Year.leap? method takes a Number as an argument and returns a Bool.
Then a is a case expression defined with the case keyword, it takes the year argument as its subject.
The case expression has four branches, the first three are when expressions.
Each when has a the method call for the method divisible_by?, which can be seen by the dot (.) before the method name, this is known as implicit invocation.
Each method is feed an argument, which is the number to check if the year is divisible by.
When the condition for each when expression is executed, the method is called on the subject of the case expression, which is the year argument.
If the method returns true the branch is executed, otherwise the next branch is evaluated.
The when branches order is important, if not in the correct order the result will be wrong.
This is because the case expression will evaluate the branches in order and return the first one that matches.
In the leap problem so if a year is divisible by 400, then it is always a leap year.
In the 2nd branch we know the input is not divisible by 400, so then we can check if it is divisible by 100, if it is then it is not a leap year.
In the 3rd branch we know the input is not divisible by 400 or 100, so then we can check if it is divisible by 4, if it is then it is a leap year.
Say we had the 2nd branch as the first branch, then the input would be checked if it is divisible by 100 first, that would mean we would never check if it is divisible by 400, so the result would be wrong.
The last branch is the else branch, it is executed if none of the other branches are executed.
The case expression has implicit return, so the result of the executed branch is returned.
Then the method returns the result of the case expression, also by implicit return.
Use Rules
Using Procs
module Year
RULES = [
p_divisible_by?(4),
p_or(
p_negate(p_divisible_by?(100)),
p_divisible_by?(400)),
]
def self.leap?(year) : Bool
RULES.map(&.call(year)).all?
end
def self.p_divisible_by?(num : Int32) : Proc(Int32, Bool)
->(year : Int32) { year.divisible_by?(num) }
end
def self.p_negate(test : Proc(A, Bool)) : Proc(A, Bool) forall A
->(input : A) { !test.call(input) }
end
def self.p_or(a : Proc(A, Bool), b : Proc(A, Bool)) : Proc(A, Bool) forall A
->(input : A) { a.call(input) || b.call(input) }
end
end
This solution relies heavily on the usage of Procs.
Proc is a datatype that represents a function pointer, it is recommended that you read the concept:crystal/procs-blocks concept page before continuing.
The RULES constant is an array of function pointers (Proc) that represent the rules that must be satisfied for a year to be a leap year.
The first rule is that the year must be divisible by 4, the second rule is that the year must be divisible by 400 or not divisible by 100.
The 2nd rule includes a 3 level deep nested Proc that is created using the p_or and p_negate methods.
The leap? method takes a year and checks if all the rules are satisfied.
It does that by mapping all the function pointer in the RULES array and since all the elements are Proc(Int32, Bool), then we can use the call method to call the function pointer with the year as an argument.
The result of the map method is an array of Bools, since all the Procs return a Bool.
Then we use the all? method to check if all the elements in the array are true.
The p_divisible_by? method takes a number and returns a Proc that takes a year and returns a Bool indicating if the year is divisible by the number.
The forall annotation is used to indicate that the type A is generic and can be any type.
The p_negate method takes a Proc and returns a Proc that negates the result of the given Proc.
The p_or method takes two Procs and returns a Proc that returns true if either of the given Procs return true.
Source: Exercism crystal/leap