Close

Advent of Code 2024 – Day 4

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.

My Advent of Code main page

Leave a Reply