Day of the Programmer

See the original problem on HackerRank.

Solutions

This problem seems convoluted but it can be solved very easily if decomposed in terms of simple predicates:

  • if the year is 1918, we can directly return the day 26.09.1918
  • otherwise we can check if the year is leap. In this case we return 12.09 and the year
  • if the year is not leap, we just return 13.09 and the year

To determine if a year is leap, we have to apply either the Julian - if the year is less than 1918 - or the Gregorian rule - otherwise.

Here is an implementation in C++:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
bool isLeapGregorian(int year)
{
    return year%400 == 0 || 
           (year%4==0 && year%100!=0);
}

bool isLeapJulian(int year)
{
    return year%4==0;
}

bool isLeap(int year)
{
    return year < 1918 ? isLeapJulian(year) : isLeapGregorian(year);
}

string dayOfProgrammer(int year) 
{
    if (year == 1918)
        return "26.09.1918";
    
    if (isLeap(year))
        return string("12.09.") + to_string(year);
    return string("13.09.") + to_string(year);
}

int main()
{
    int year; cin >> year;
    cout << dayOfProgrammer(year);
}

An alternative solution consists in calculating the number of days in February:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// ...other functions are the same as before...

bool is1918(int year)
{
    return year == 1918;
}

int main() 
{
    int y; cin >> y;
    auto daysInFebruary = is1918(y) ? (28-13) : (isLeap(y) ? 29 : 28);
    int day = 256 - 31 - daysInFebruary - 31 - 30 - 31 - 30 - 31 - 31;
    printf("%02d.%02d.%04d", day, 9, y);
}

A step forward with this exercise is making the solution a bit more flexible. What if we want to add more rules?

An idea: we put the rules into a vector and we try them in order. The first successful rule just breaks the loop.

Here is a possible implementation using optional:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
optional<int> asDefault([[maybe_unused]]int year)
{
    return 28;
}

optional<int> asLeap(int year)
{
    return isLeap(year) ? optional{29} : nullopt;
}

optional<int> as1918(int year)
{
    return year == 1918 ? optional{28-13} : nullopt;
}

int main()
{
    int year = 2016;
    vector rules = {as1918, asLeap, asDefault};
    
    auto daysInFebruary = 0;
    for (auto rule : rules)
    {
        if (auto res = rule(year); res)
        {
            daysInFebruary = *res;
            break;
        }
    }
    
    int day = 256 - 31 - daysInFebruary - 31 - 30 - 31 - 30 - 31 - 31;
    printf("%02d.%02d.%04d", day, 9, year);
}

You can split asLeap. For example:

1
2
3
4
5
6
7
8
9
optional<int> asLeapJulian(int year)
{
    return (year < 1918 && year%4 == 0) ? optional{29} : nullopt;
}

optional<int> asLeapGregorian(int year)
{
    return (year > 1918 && (year%400 == 0 || (year%4==0 && year%100!=0))) ? optional{29} : nullopt;
}

The rules are just pointer to functions. Note that thanks to automatic deduction of class templates (since C++17) we can omit tedious details about the type of vector.

We've worked on this challenge in these gyms: modena 
comments powered by Disqus