AdventOfCode 2024 Day 6

Part One

走迷宫的题,遇到障碍物#则转向,按照顺时针的方向,求出最后走出迷宫的足迹范围

....#.....
.........#
..........
..#.......
.......#..
..........
.#..^.....
........#.
#.........
......#...

首先,简单构造出迷宫

with recursive rows as (
    SELECT row_number() over () as _row, line
    FROM lance_input
), matrix AS (
    SELECT _row :: INTEGER, x.idx :: INTEGER as _col, x.pos
    FROM rows, regexp_split_to_table(line, '') with ordinality as x(pos, idx)
), cols AS (
    SELECT _col, string_agg(pos, '' order by _row) as line
    FROM matrix
    GROUP BY _col
)

使用递归查询的方式,每次求出当前方向上最远能走到的位置。以^作为起始位置,以走出迷宫作为终止条件

route AS (
    SELECT _row, _col, 'N' as direction, 1 as step
    FROM matrix
    WHERE pos = '^'

    UNION ALL

    SELECT _row + row_shift as _row,
           _col + col_shift as _col,
           direction,
           step
    FROM (
        SELECT CASE WHEN direction IN ('N') THEN 1 - position('#' IN reverse(substring(cols.line, 1, route._row - 1)) || '#')
                    WHEN direction IN ('S') THEN position('#' IN substring(cols.line, route._row + 1) || '#') - 1
                    ELSE 0
                END as row_shift,
               CASE WHEN direction IN ('E') THEN position('#' IN substring(rows.line, route._col + 1) || '#') - 1
                    WHEN direction IN ('W') THEN 1 - position('#' IN reverse(substring(rows.line, 1, route._col - 1)) || '#')
                    ELSE 0
                END as col_shift,
               route._row,
               route._col,
               CASE WHEN direction = 'N' THEN 'E'
                    WHEN direction = 'E' THEN 'S'
                    WHEN direction = 'S' THEN 'W'
                    WHEN direction = 'W' THEN 'N'
                END as direction,
               step + 1 as step
        FROM route 
        LEFT JOIN rows
        ON route._row = rows._row
        AND direction IN ('E', 'W')
        LEFT JOIN cols
        ON route._col = cols._col
        AND direction IN ('N', 'S')
        WHERE route._row between 2 and 129
        AND route._col between 2 and 129
    ) t
)

最终,走过的足迹,简单去重即可

path AS (
    SELECT least(a._row, b._row) as start_row, 
           least(a._col, b._col) as start_col,
           greatest(a._row, b._row) as end_row, 
           greatest(a._col, b._col) as end_col
    FROM route a
    JOIN route b
    ON a.step + 1 = b.step
)
SELECT DISTINCT x, y
FROM path, generate_series(1, 130) x, generate_series(1, 130) y
WHERE x.id between start_row AND end_row
AND y.id between start_col AND end_col;

Part Two

在迷宫中新放置一个障碍物,从而让其走不出迷宫,求出这些障碍物的位置有几处?

....#.....
....+---+#
....|...|.
..#.|...|.
....|..#|.
....|...|.
.#.O^---+.
........#.
#.........
......#...

循环路径

让其走不出迷宫,让其绕着一个长方形是最简单的方式,如例子所示。但是,也有场景并不是简单一个长方形,但也是循环,如下图所示。

....#.....
....+---+#
....|...|.
..#.|...|.
..+-+-+#|.
..|.|.|.|.
.#+-^-+-+.
....|.|.#.
#..O+-+...
......#...

障碍物位置候选

分析下出现循环的必要条件,就是障碍物一定要出现在其原有的路径上,从而改变其足迹。所以路径上的所有位置(除去起点,起点不能放置障碍物),都是潜在的候选。

new_pos AS (
    SELECT row_number() over () as rn, _row, _col
    FROM (
        SELECT distinct x as _row, y as _col
        FROM path a, 
             generate_series(1, 130) as x,
             generate_series(1, 130) as y,
             matrix b
        WHERE x between start_row and end_row
        AND y between start_col and end_col
        AND x = b._row
        AND y = b._col
        AND b.pos = '.'
    ) t
)

转向位置判断

之前的判断,是当前方向上遇到的第一个#,然后就转变方向。现在的判断条件,变成了先遇到#,还是先遇到新设的障碍物,判断条件需要更加复杂一些。

row_shift和col_shift是之前的位移量,new_row_shift和new_col_shift是新增障碍物的位移量。

CASE WHEN direction IN ('N') THEN 1 - position('#' IN reverse(substring(cols.line, 1, new_route._row - 1)) || '#')
     WHEN direction IN ('S') THEN position('#' IN substring(cols.line, new_route._row + 1) || '#') - 1
     ELSE 0
END as row_shift,
CASE WHEN direction IN ('E') THEN position('#' IN substring(rows.line, new_route._col + 1) || '#') - 1
     WHEN direction IN ('W') THEN 1 - position('#' IN reverse(substring(rows.line, 1, new_route._col - 1)) || '#')
     ELSE 0
END as col_shift,
CASE WHEN direction IN ('N') AND new_pos._col = new_route._col AND new_pos._row < new_route._row THEN new_pos._row - new_route._row + 1 
     WHEN direction IN ('S') AND new_pos._col = new_route._col AND new_pos._row > new_route._row THEN new_pos._row - new_route._row - 1 
END as new_row_shift,
CASE WHEN direction IN ('E') AND new_pos._row = new_route._row AND new_pos._col > new_route._col THEN new_pos._col - new_route._col - 1 
     WHEN direction IN ('W') AND new_pos._row = new_route._row AND new_pos._col < new_route._col THEN new_pos._col - new_route._col + 1 
END as new_col_shift,

循环判断

为了判断循环,需要记录走过的足迹,从而判断是否有重复的位置信息。但是,需要考虑一种特殊情况,就是当前方向上无路可走,原地转向。虽然足迹重复了,但是不能认为就能形成循环,还要看后续的足迹。

比如下图的场景,在起点的正上方摆放了一个障碍物,向下无法继续,只能向右继续。这里虽然在起点的位置上足迹重复了,但是后续并没有重复,而是成功离开了迷宫。因此这里不能算是循环。

....#.....
.........#
..........
..#.......
.......#..
....O.....
.#..^-----
........#.
#.........
......#...

另一个例子,比如下图所示在(2, 6)放置的障碍物,会导致无法向右前进,从而向下继续,从而形成了循环。

....#.....
....|O...#
....|.....
..#.|.....
....|..#..
....|.....
.#..^.....
........#.
#.........
......#...

最终SQL

注意cycle的判断条件,如果是最后一个足迹重复则忽略,继续前进,再判断是否有重复。

new_route AS (
    SELECT rn, matrix._row, matrix._col, 'N' as direction, array[matrix._row * 1000 + matrix._col] as path, false as cycle
    FROM matrix, new_pos
    WHERE pos = '^'

    UNION ALL

    SELECT rn, _row, _col, 
           CASE WHEN direction = 'N' THEN 'E'
                WHEN direction = 'E' THEN 'S'
                WHEN direction = 'S' THEN 'W'
                WHEN direction = 'W' THEN 'N'
            END as direction,
           CASE WHEN (_row * 1000 + _col) = path[#path] THEN path ELSE path || (_row * 1000 + _col) END as path,
           (_row * 1000 + _col) = ANY(path[1 : (#path) - 1]) as cycle
    FROM (
        SELECT _row + CASE WHEN direction = 'N' THEN greatest(row_shift, new_row_shift)
                           WHEN direction = 'S' THEN least(row_shift, new_row_shift) 
                           ELSE 0
                       END as _row,
               _col + CASE WHEN direction = 'E' THEN least(col_shift, new_col_shift)
                           WHEN direction = 'W' THEN greatest(col_shift, new_col_shift) 
                           ELSE 0
                       END as _col,
               direction, path, rn
        FROM (
            SELECT CASE WHEN direction IN ('N') THEN 1 - position('#' IN reverse(substring(cols.line, 1, new_route._row - 1)) || '#')
                        WHEN direction IN ('S') THEN position('#' IN substring(cols.line, new_route._row + 1) || '#') - 1
                        ELSE 0
                    END as row_shift,
                   CASE WHEN direction IN ('E') THEN position('#' IN substring(rows.line, new_route._col + 1) || '#') - 1
                        WHEN direction IN ('W') THEN 1 - position('#' IN reverse(substring(rows.line, 1, new_route._col - 1)) || '#')
                        ELSE 0
                    END as col_shift,
                   CASE WHEN direction IN ('N') AND new_pos._col = new_route._col AND new_pos._row < new_route._row THEN new_pos._row - new_route._row + 1 
                        WHEN direction IN ('S') AND new_pos._col = new_route._col AND new_pos._row > new_route._row THEN new_pos._row - new_route._row - 1 
                    END as new_row_shift,
                   CASE WHEN direction IN ('E') AND new_pos._row = new_route._row AND new_pos._col > new_route._col THEN new_pos._col - new_route._col - 1 
                        WHEN direction IN ('W') AND new_pos._row = new_route._row AND new_pos._col < new_route._col THEN new_pos._col - new_route._col + 1 
                    END as new_col_shift,
                   new_route._row,
                   new_route._col,
                   new_route.direction,
                   new_route.path,
                   new_route.rn
            FROM new_route 
            JOIN new_pos
            ON new_route.rn = new_pos.rn
            LEFT JOIN rows
            ON new_route._row = rows._row
            AND direction IN ('E', 'W')
            LEFT JOIN cols
            ON new_route._col = cols._col
            AND direction IN ('N', 'S')
            WHERE new_route._row between 2 and 129
            AND new_route._col between 2 and 129
            AND NOT cycle
        ) t
    ) s
)
select rn From new_route where cycle;

(1723 rows)

Time: 13226.939 ms (00:13.227)

发表评论