Solving the riddle from problem 19 for Project Euler in PL/SQL.
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!