Project Euler in PL/SQL: Problem 19

  • by

Solving the riddle from problem 19 for Project Euler in PL/SQL.

Problem 19:

Counting Sundays

You are given the following information, but you may prefer to do some research for yourself.

1 Jan 1900 was a Monday.
Thirty days has September,
April, June and November.
All the rest have thirty-one,
Saving February alone,
Which has twenty-eight, rain or shine.
And on leap years, twenty-nine.
A leap year occurs on any year evenly divisible by 4, but not on a century unless it is divisible by 400.

How many Sundays fell on the first of the month during the twentieth century (1 Jan 1901 to 31 Dec 2000)?

It took me a couple re-reads of the problem to understand what they were asking for here. I needed to check for the following conditions – for every month in every year of 1901 to 2000, check to see if the 1st day is a Sunday.

I came up with the below solution without using any of the Oracle date functions so the code could stand on its own. I think it’s pretty self explanatory, though the first_day_of_month code might take a couple read-throughs to understand how or why it works.

DECLARE
day_of_week INTEGER := 2; --1901 starts on a Tuesday
answer INTEGER := 0;
FUNCTION first_day_of_month(month_days_in INTEGER, curr_day_in INTEGER) RETURN INTEGER
AS
    day_tracker INTEGER;
BEGIN
--takes weekday of the first of the previous month and return
--what the first of the next month will be
-- 1 = Mon, 2 = Tues, 3 = Wed, 4 = Thurs, 5 = Fri, 6 = Sat, 7 = Sun

   day_tracker := curr_day_in + MOD(month_days_in, 7);
   
   IF day_tracker > 7 THEN
    day_tracker := day_tracker - 7;
   END IF;
   
   RETURN day_tracker;
END;
BEGIN
    FOR i_year IN 1901..2000 LOOP
        FOR i_month IN 1..12 LOOP
            --check if it's Sunday at the start of each month
            IF day_of_week = 7 THEN
                    answer := answer + 1;
            END IF;
                
            --calculate what day of the week if will be at the start of next month
            IF i_month IN (4,6,9,11) THEN
                --thirty days            
                day_of_week := first_day_of_month(30, day_of_week);
            ELSIF i_month IN (2) THEN
                --feb 28/29            
                IF (REMAINDER(i_year,4) = 0 AND REMAINDER(i_year,100) != 0) OR REMAINDER(i_year, 400) = 0 THEN
                    --leap year
                    day_of_week := first_day_of_month(29, day_of_week);
                ELSE
                    day_of_week := first_day_of_month(28, day_of_week);
                END IF;
            ELSE
                --thirty-one days            
                day_of_week := first_day_of_month(31, day_of_week);
            END IF;
        END LOOP;
    END LOOP;
dbms_output.put_line(answer);
END;

--output: 171

I decided to see how much easier it would be to use built-in Oracle functions, and the result was much, much simpler…

DECLARE
answer INTEGER := 0;
BEGIN
    FOR i_year IN 1901..2000 LOOP
        FOR i_month IN 1..12 LOOP
            --check if it's Sunday at the start of each month
           IF TRIM(TO_CHAR(TO_DATE(i_month||'/1/'||i_year,'MM/DD/YYYY'), 'DAY')) = 'SUNDAY' THEN
                answer := answer + 1;
           END IF;            
        END LOOP;
    END LOOP;
dbms_output.put_line(answer);
END;

The only strange thing was I had to TRIM the TO_CHAR function or else it wasn’t matching in that IF clause. It turns out, TO_CHAR always returns a CHAR(9) here (looking at you, Wednesday) so there were some trailing spaces screwing up the logic.

Questions or comments? Feel free to leave them below or reach out to me on Twitter!

Leave a Reply

Your email address will not be published.