10.2 Solution

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.



CC BY-NC-SA 4.0 Bartlomiej Lukaszuk