AdventOfCode 2024 Day 15

Part One

推箱子的游戏,而且一次可以推动多个箱子,求最后箱子的分布情况

推箱子,很像之前2023 Day 14的题目,同样是一次可以推动多个箱子。

首先将迷宫保存到表中,后续更新表中的记录

drop table matrix;
create table matrix as 
with origin as (
    SELECT row_number() over () as rn, line
    FROM lance_input
)
SELECT rn as _row, idx as _col, pos
FROM origin, regexp_split_to_table(line, '') with ordinality as x(pos, idx)
WHERE rn < (select rn from origin where line is null);

create index idx_row on matrix(_row);

create index idx_col on matrix(_col);

create index idx_pos on matrix(pos);

先判断机器人的下一步位置,是否可以继续前进。

如果前方空闲,则直接前进;如果前方是墙,则无法前进;如果前方是箱子,则需要判断需要一次推动几个箱子,这里会使用正则来匹配可以连续推动的箱子。

postgres=# select (regexp_matches('OOO.#', '^(O+\.)'))[1];
 regexp_matches 
----------------
 OOO.
(1 row)

完整的推箱子函数如下

CREATE OR REPLACE FUNCTION move_robot(direction VARCHAR)
RETURNS VOID AS $$
DECLARE
    robot_row INTEGER;
    robot_col INTEGER;
    target_row INTEGER;
    target_col INTEGER;
    empty_pos INTEGER;
    next_pos VARCHAR;
    move_pos VARCHAR;
BEGIN

    SELECT _row, _col INTO robot_row, robot_col FROM matrix WHERE pos = '@';

    target_row := robot_row;
    target_col := robot_col;

    IF direction = '>' THEN
        target_col := robot_col + 1;
    END IF;

    IF direction = '<' THEN
        target_col := robot_col - 1;
    END IF;

    IF direction = '^' THEN
        target_row := robot_row - 1;
    END IF;

    IF direction = 'v' THEN
        target_row := robot_row + 1;
    END IF;

    empty_pos := (select count(*) from matrix where _row = target_row AND _col = target_col AND pos = '.');

    IF empty_pos = 1 THEN
        IF direction = '>' THEN
            UPDATE matrix SET _col = (CASE WHEN _col = robot_col THEN _col + 1 ELSE _col - 1 END) 
            WHERE _row = robot_row AND _col IN (robot_col, robot_col + 1);
        END IF;

        IF direction = '<' THEN
            UPDATE matrix SET _col = (CASE WHEN _col = robot_col THEN _col - 1 ELSE _col + 1 END) 
            WHERE _row = robot_row AND _col IN (robot_col, robot_col - 1);
        END IF;

        IF direction = '^' THEN
            UPDATE matrix SET _row = (CASE WHEN _row = robot_row THEN _row - 1 ELSE _row + 1 END) 
            WHERE _row IN (robot_row, robot_row - 1) AND _col = robot_col;
        END IF;

        IF direction = 'v' THEN
            UPDATE matrix SET _row = (CASE WHEN _row = robot_row THEN _row + 1 ELSE _row - 1 END) 
            WHERE _row IN (robot_row, robot_row + 1) AND _col = robot_col;
        END IF;
    ELSE
        IF direction = '>' THEN
            next_pos := (select substring(string_agg(pos, '' order by _col), robot_col + 1) from matrix where _row = robot_row);
        END IF;

        IF direction = '<' THEN
            next_pos := (select reverse(substring(string_agg(pos, '' order by _col), 1, robot_col - 1)) from matrix where _row = robot_row);
        END IF;

        IF direction = '^' THEN
            next_pos := (select reverse(substring(string_agg(pos, '' order by _row), 1, robot_row - 1)) from matrix where _col = robot_col);
        END IF;

        IF direction = 'v' THEN
            next_pos := (select substring(string_agg(pos, '' order by _row), robot_row + 1) from matrix where _col = robot_col);
        END IF;

        move_pos := (select (regexp_matches(next_pos, '^(O+\.)'))[1]);

        IF move_pos IS NULL THEN
            RETURN; 
        END IF;

        IF direction = '>' THEN
            UPDATE matrix set _col = (case when _col = robot_col + length(move_pos) THEN robot_col ELSE _col + 1 END)
            WHERE _row = robot_row
            AND _col between robot_col AND robot_col + length(move_pos);
        END IF;

        IF direction = '<' THEN
            UPDATE matrix set _col = (case when _col = robot_col - length(move_pos) THEN robot_col ELSE _col - 1 END)
            WHERE _row = robot_row
            AND _col between robot_col - length(move_pos) AND robot_col;
        END IF;

        IF direction = '^' THEN
            UPDATE matrix set _row = (case when _row = robot_row - length(move_pos) THEN robot_row ELSE _row - 1 END)
            WHERE _col = robot_col
            AND _row between robot_row - length(move_pos) AND robot_row;
        END IF;

        IF direction = 'v' THEN
            UPDATE matrix set _row = (case when _row = robot_row + length(move_pos) THEN robot_row ELSE _row + 1 END)
            WHERE _col = robot_col
            AND _row between robot_row AND robot_row + length(move_pos);
        END IF;
    END IF;
END;
$$ LANGUAGE plpgsql;

Day Two

箱子从O变成了占用两个位置的[],且一个箱子可能推动两个箱子,从而继续推动更多的箱子。

这部分最大的困难点,在于箱子在水平方向上变成了两个位置的[]。

对于左右方向上的移动,还是同样的一个高度,其实难度不大。可以将[]替换为O,然后按照第一部分的方式计算好后,再替换回去

next_pos := (select replace(substring(string_agg(pos, '' order by _col), robot_col + 1), '[]', 'O') from matrix where _row = robot_row);
move_pos := (select (regexp_matches(next_pos, '^(O+\.)'))[1]);
move_pos := replace(move_pos, 'O', '[]');

但是对于上下方向上的,难度就大了,主要在于水平方向宽度增加,一个箱子可以推动两个箱子,这两个箱子再推动了更多的箱子,如下图所示。

....[][][][].....
.....[]..[]......
......[][].......
.......[]........

所以,判断上下方向上是否前进是比较困难的,需要通过递归查询来查找这个方向上最后是否可以前进一步

每次找到一个上游后,对这个上游节点打标,0代表前方有墙不可移动,1代表前方还是箱子,2代表前方无障碍,可以前进。

flag=1的时候就需要继续查询了,而只要有一次遇到0,则整体都不可移动。

SELECT _row, _col,
       CASE WHEN up_pos like '%#%' THEN 0
            WHEN up_pos like '%[%' OR up_pos like '%]%' THEN 1
            ELSE 2
        END as flag
FROM (
    SELECT _row, _col,
          (select string_agg(pos, '' order by _col) from matrix where _row = t._row - 1 and _col in (t._col, t._col + 1)) as up_pos
    FROM (
        SELECT _row, case when pos = '[' then _col else _col - 1 end as _col
        FROM matrix 
        WHERE _row = robot_row - 1 
        AND _col = robot_col
    ) t
) t

UNION ALL

SELECT _row, _col,
       CASE WHEN up_pos like '%#%' THEN 0
            WHEN up_pos like '%[%' OR up_pos like '%]%' THEN 1
            ELSE 2
        END as flag
FROM (
    SELECT b._row, b._col,
           (select string_agg(pos, '' order by _col) from matrix where _row = b._row - 1 and _col in (b._col, b._col + 1)) as up_pos
    FROM boxes a
    JOIN matrix b 
    ON b._row = a._row - 1
    AND b.pos = '['
    AND (b._col = a._col OR b._col = a._col - 1 OR b._col = a._col + 1)
    AND flag = 1
) t

那么找到所有需要移动的位置后,则通过一次update,将这些位置进行更新

UPDATE matrix set pos = t.pos
FROM (
    with recursive boxes AS (
        SELECT _row, _col,
               CASE WHEN up_pos like '%#%' THEN 0
                    WHEN up_pos like '%[%' OR up_pos like '%]%' THEN 1
                    ELSE 2
                END as flag
        FROM (
            SELECT _row, _col,
                  (select string_agg(pos, '' order by _col) from matrix where _row = t._row - 1 and _col in (t._col, t._col + 1)) as up_pos
            FROM (
                SELECT _row, case when pos = '[' then _col else _col - 1 end as _col
                FROM matrix 
                WHERE _row = robot_row - 1 
                AND _col = robot_col
            ) t
        ) t

        UNION ALL

        SELECT _row, _col,
               CASE WHEN up_pos like '%#%' THEN 0
                    WHEN up_pos like '%[%' OR up_pos like '%]%' THEN 1
                    ELSE 2
                END as flag
        FROM (
            SELECT b._row, b._col,
                   (select string_agg(pos, '' order by _col) from matrix where _row = b._row - 1 and _col in (b._col, b._col + 1)) as up_pos
            FROM boxes a
            JOIN matrix b 
            ON b._row = a._row - 1
            AND b.pos = '['
            AND (b._col = a._col OR b._col = a._col - 1 OR b._col = a._col + 1)
            AND flag = 1
        ) t
    ), moved_boxes AS (
        SELECT _row, _col, '[' as pos
        FROM boxes b
        WHERE not exists (select * From boxes where flag = 0)

        UNION ALL 

        SELECT _row, _col + 1, ']' as pos
        FROM boxes b
        WHERE not exists (select * From boxes where flag = 0)

        UNION ALL

        SELECT robot_row, robot_col, '@' as pos
        WHERE not exists (select * From boxes where flag = 0)
    )
    SELECT coalesce(a._row, b._row) as _row,
           coalesce(a._col, b._col) as _col,
           coalesce(a.pos, '.') as pos
    FROM (select _row - 1 as _row, _col, pos from moved_boxes) a
    FULL OUTER JOIN moved_boxes b
    ON a._row = b._row
    AND a._col = b._col
    
) t
WHERE matrix._row = t._row
AND matrix._col = t._col;

发表评论