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.