Ceres Search
Day 4 took a different turn, we’re still parsing through text to find special values, but now we need to parse horizontally, vertically, and diagonally through a grid of text looking for “XMAS”, which could appear forwards or backwards (SAMX.)
For this I built a two-dimensional map of the text.
As I read each line, looking for horizontal instances of XMAS was trivial, I just used REGEXP_COUNT to look for XMAS and SMAX. I intentionally did not use a pipe (|) expression to look for both so I wouldn’t accidentally ignore overlapping expressions such as XMASAMX or SAMXMAS, each of which should have a count of 2, not 1.
To check if XMAS was present vertically or diagonally I used a function that would accept 4 characters and see if those characters, when concatenated together were XMAS.
To check for vertical instances, for each column along the X-axis of the grid, I iterate from the the 1st to row to 3 less than the last row. I don’t go all the way to the last row, because I know once I have fewer than 4 rows, I can’t spell XMAS, because I won’t have enough characters. Then, for each of those rows, I take the 4 characters along the Y-axis send them to my function to check if they spell XMAS or not, and increase my counter if they do.
To check for diagonals, I follow the same logic but I get to shorten the X-axis iteration similarly as I did with the Y-axis in the vertical search. I have to search the diagonals twice, once in the upper-left to lower-right (\) direction and again from the upper-right to the lower-left direction(/).
When I’ve done with all of the direction options, my counter has the answer.
DECLARE
TYPE t_maprow IS TABLE OF VARCHAR2(1)
INDEX BY PLS_INTEGER;
TYPE t_map IS TABLE OF t_maprow
INDEX BY PLS_INTEGER;
v_result INTEGER := 0;
v_map t_map;
v_rowlength INTEGER;
v_cnt INTEGER;
FUNCTION is_xmas(a IN VARCHAR2, b IN VARCHAR2, c IN VARCHAR2, d IN VARCHAR2)
RETURN INTEGER
IS
BEGIN
RETURN CASE WHEN a || b || c || d IN ('XMAS', 'SAMX') THEN 1 ELSE 0 END;
END;
BEGIN
FOR t IN (SELECT str, seq y FROM advent.data2rows('advent2024-4sample'))
LOOP
v_rowlength := LENGTH(t.str);
FOR x IN 1 .. v_rowlength
LOOP
v_map(x)(t.y) := SUBSTR(t.str, x, 1);
END LOOP;
-- Counting horizontal is easy
v_result := v_result + REGEXP_COUNT(t.str, 'XMAS');
v_result := v_result + REGEXP_COUNT(t.str, 'SAMX');
END LOOP;
-- DBMS_OUTPUT.put_line('horizontal ' || v_result);
FOR x IN 1 .. v_rowlength
LOOP
v_cnt := 0;
FOR y IN 1 .. v_map.COUNT - 3
LOOP
v_cnt := v_cnt + is_xmas(v_map(x)(y), v_map(x)(y + 1), v_map(x)(y + 2), v_map(x)(y + 3));
END LOOP;
-- dbms_output.put_line('Row ' || X || ' ' || v_cnt);
v_result := v_result + v_cnt;
END LOOP;
-- DBMS_OUTPUT.put_line('horizontal + vertical ' || v_result);
-- Diagonals upper left to lower right
FOR y IN 1 .. v_map.COUNT - 3
LOOP
FOR x IN 1 .. v_rowlength - 3
LOOP
v_result := v_result + is_xmas(v_map(x)(y), v_map(x + 1)(y + 1), v_map(x + 2)(y + 2), v_map(x + 3)(y + 3));
END LOOP;
END LOOP;
-- DBMS_OUTPUT.put_line('horizontal + vertical + \ ' || v_result);
-- Diagonals upper right to lower left
FOR y IN 1 .. v_map.COUNT - 3
LOOP
FOR x IN 4 .. v_rowlength
LOOP
v_result := v_result + is_xmas(v_map(x)(y), v_map(x - 1)(y + 1), v_map(x - 2)(y + 2), v_map(x - 3)(y + 3));
END LOOP;
END LOOP;
DBMS_OUTPUT.put_line('Answer: ' || v_result);
END;
Answer: 18
For part 2, the puzzle actually becomes a little simpler (at least, I thought so anyway.) Instead of needing to search for XMAS in 4 different directions, we need to search for MAS overlapping in a X-formation. As before the MAS can be fowards or backwards. This yields the following 4 possible arrangements.
M S S M M M S S A A A A M S S M S S M M
Similar to my solution for part 1, I used a function that would concatenate characters and check if they matched one of the four forms.
Then, iterating through all of the central characters of each X-formation, I check if any of them are an X-MAS form.
DECLARE
TYPE t_maprow IS TABLE OF VARCHAR2(1)
INDEX BY PLS_INTEGER;
TYPE t_map IS TABLE OF t_maprow
INDEX BY PLS_INTEGER;
v_result INTEGER := 0;
v_map t_map;
v_rowlength INTEGER;
FUNCTION is_xmas(a IN VARCHAR2, b IN VARCHAR2, c IN VARCHAR2, d IN VARCHAR2, e IN VARCHAR2)
RETURN INTEGER
IS
BEGIN
RETURN CASE WHEN a || b || c || d || e IN ('MMASS', 'MSAMS', 'SSAMM', 'SMASM') THEN 1 ELSE 0 END;
END;
BEGIN
FOR t IN (SELECT str, seq y FROM advent.data2rows('advent2024-4sample'))
LOOP
v_rowlength := LENGTH(t.str);
FOR x IN 1 .. v_rowlength
LOOP
v_map(x)(t.y) := SUBSTR(t.str, x, 1);
END LOOP;
END LOOP;
FOR x IN 2 .. v_rowlength - 1
LOOP
FOR y IN 2 .. v_map.COUNT - 1
LOOP
v_result :=
v_result
+ is_xmas(v_map(x - 1)(y - 1),
v_map(x + 1)(y - 1),
v_map(x)(y),
v_map(x - 1)(y + 1),
v_map(x + 1)(y + 1));
END LOOP;
END LOOP;
DBMS_OUTPUT.put_line('Answer: ' || v_result);
END;
Answer: 9
I enjoyed this puzzle as a different type of searching and the first “map” puzzle of the year.