Let’s start with the algorithm, as stated in the Wikipedia’s page:
The rule for leap years is that every year divisible by four is a leap year, except for years that are divisible by 100, except in turn for years also divisible by 400.
Nothing more, nothing less. Time to translate it into Julia’s code.
function isLeap(yr::Int)::Bool
@assert 0 < yr < 4001 "yr must be in range [1-4000]"
divisibleBy4::Bool = yr % 4 == 0
gregorianException::Bool = (yr % 100 == 0) && (yr % 400 != 0)
if !divisibleBy4
return false
else
return !gregorianException
end
end
The powerhouse of our function is is %
(modulo operator) that returns the reminder of the division. If a number x
is evenly divided by a number y
(x % y
) the reminder is equal to zero (==
). Otherwise (when x % y != 0
) the x
cannot be evenly divided by y
. Although not strictly necessary, we added a mnemonic names for the tested conditions (divisibleBy4
and gregorianException
), which should make the code more readable. Anyway, if a year (yr
) is not divisible by 4 (!divisibleBy4
) then it is a common year. Otherwise (else
) if the the year fulfills the exception rule (gregorialException
is true
) it is not a leap year (hence we negate !
gregorianException
, because !true
is false
). If a year (yr
) does not meet the exception criteria (gregorianException
is false
) it is a common year (we negate false
, so we get true
).
Actually, we can get rid of the if
-else
condition by using &&
(logical and) and its short circuiting property.
function isLeap(yr::Int)::Bool
@assert 0 < yr < 4001 "yr must be in range [1-4000]"
divisibleBy4::Bool = yr % 4 == 0
gregorianException::Bool = (yr % 100 == 0) && (yr % 400 != 0)
return divisibleBy4 && !gregorianException
end
When divisibleBy4
is false
the and operator (&&
) skips the evaluation of its second argument (short circuiting) and returns false
. Otherwise, (divisibleBy4
is true
) !gregorianException
is evaluated and it becomes the result of the function. For explanation of !gregorianException
see the explanation a few paragraphs above.
Time for a simple test.
yrs = [1792, 1859, 1900, 1918, 1974, 1985, 2000, 2012]
filter(isLeap, yrs)
[1792, 2000, 2012]
The filter
function applies isLeap
to each element of yrs
and returns only those for which the result is true
.
Since the solution was pretty easy let’s add some more tests to let our fingers cool down slowly. If you read the Wikipedia’s page carefully then you know that for every 400 years period we got 303 regular years and 97 leap years. Let’s see if that rule holds.
function runTestSet()::Int
startYr::Int = 0
endYr::Int = 0
numLeapYrs::Int = 0
for i in 1:3601
startYr = i
endYr = i + 400 - 1
numLeapYrs = filter(isLeap, startYr:endYr) |> length
if numLeapYrs != 97
return 1
end
end
return 0
end
runTestSet()
0
Here we tested different time periods (each spanning 400 years). Notice that last such a period starts in the year 3601
, since our function handles inputs in the range [1 - 4000] and length(3600:4000)
is actually equal to 401. Anyway, for each of the periods, we counted the number of leap years (numLeapYrs
) with filter
and length
. If numLearpYrs
differs from 97
, we return 1
right away (and skip other checks). Otherwise (once all the periods passed the tests) we return 0
. The above is a convention met in C/C++ programming languages (see their main
function). It is sufficient for our simplistic case, however, for more serious applications you should follow the testing advice contained in the docs.