AdventOfCode 2023 Day 14

Part One

将下文中的字符“向北滚动”,直到遇到障碍,计算最终所有O的最终位置之和。

O....#....
O.OO#....#
.....##...

行列转换

先将输入进行行列转换,相当于将图向左旋转90度。

------------
 O....#....
 O.OO#....#
 .....##...
 OO.#O....O
 .O.....O#.
 O.#..O.#.#
 ..O..#O..O
 .......O..
 #....###..
 #OO..#....
(10 rows)

转换为

 id | column_str 
----+------------
 10 | .#.O.#O...
  9 | ....#.....
  8 | ....O#.O#.
  7 | ..#...O.#.
  6 | #.#..O#.##
  5 | .#.O......
  4 | .O.#......
  3 | .O...#O..O
  2 | ...OO....O
  1 | OO.O.O..##
(10 rows)

后续就是字符串正则匹配替换,将…O替换为O…即可,直到无可替换字符串。

正则替换?

期望的是直接正则替换即可,但是regexp_replace函数的第三个参数,无法使用自定义函数。先执行了函数,再执行了replace,无法实现预期目标。

postgres=# SELECT REGEXP_REPLACE('OO.O.O..##','(\.+O)','|\1|', 'g'); 
 regexp_replace 
----------------
 OO|.O||.O|..##
(1 row)

postgres=# SELECT REGEXP_REPLACE('OO.O.O..##','(\.+O)',reverse('|\1|'), 'g'); 
 regexp_replace 
----------------
 OO|1\||1\|..##
(1 row)

经过一番搜索,终于找到了相关解决方案,可以完美实现函数替换。

postgres=# SELECT *
postgres-#   FROM ROWS FROM (
postgres(#     regexp_split_to_table('..OO.O###.O..##..O','(\.+O)'),
postgres(#     regexp_matches('..OO.O###.O..##..O','(\.+O)', 'g')
postgres(#   ) AS m(l, u);
  l   |   u   
------+-------
      | {..O}
 O    | {.O}
 ###  | {.O}
 ..## | {..O}
      | 
(5 rows)

postgres=# SELECT string_agg(concat(l, reverse(u[1])), '')
postgres-#   FROM ROWS FROM (
postgres(#     regexp_split_to_table('..OO.O###.O..##..O','(\.+O)'),
postgres(#     regexp_matches('..OO.O###.O..##..O','(\.+O)', 'g')
postgres(#   ) AS m(l, u);
     string_agg     
--------------------
 O..OO.###O...##O..
(1 row)

因此创建个函数,用来向前滚动字符串

CREATE OR REPLACE FUNCTION roll_left(str TEXT)
RETURNS TEXT AS $$
DECLARE
    result VARCHAR;
BEGIN
    result := str;
    WHILE result ~ '\.+O' LOOP
      result := (
        SELECT string_agg(concat(l, reverse(u[1])), '')
        FROM ROWS FROM (
          regexp_split_to_table(result,'(\.+O)'),
          regexp_matches(result,'(\.+O)', 'g')
        ) AS m(l, u)
      );
    END LOOP;
 
    RETURN result;
END;
$$ LANGUAGE plpgsql;

再次行列转换

经过向前滚动后,字符变成了如下所示

 id | column_str 
----+------------
 10 | .#O..#O...
  9 | ....#.....
  8 | O....#O.#.
  7 | ..#O....#.
  6 | #.#O..#.##
  5 | .#O.......
  4 | O..#......
  3 | O....#OO..
  2 | OOO.......
  1 | OOOO....##
(10 rows)

再次行列转换后,向右转动90度,变成了

 id | column_str 
----+------------
  1 | OOOO.#.O..
  2 | OO..#....#
  3 | OO..O##..O
  4 | O..#.OO...
  5 | ........#.
  6 | ..#....#.#
  7 | ..O..#.O.O
  8 | ..O.......
  9 | #....###..
 10 | #....#....
(10 rows)

最终SQL

WITH RECURSIVE origin AS (
  SELECT row_number() over () as _row, line
  FROM lance_input
), seq AS (
  SELECT 1 as id
  UNION ALL
  SELECT id + 1 as id
  FROM seq
), roll_result AS (
  SELECT id, roll_left(column_str) as column_str
  FROM (
    SELECT t.id, string_agg(substring(line from t.id for 1) , '' order by _row) as column_str
    FROM origin
    JOIN (SELECT id FROM seq limit (select length(line) from origin limit 1)) t
    ON 1 = 1
    GROUP BY t.id
  ) t
  ORDER BY id desc
), final_result AS (
  SELECT line_len.cnt - id + 1 as score, str
  FROM (
  SELECT t.id, string_agg(substring(column_str from t.id for 1) , '' order by roll_result.id) as str
  FROM roll_result
  JOIN (SELECT id FROM seq limit (select length(column_str) from roll_result limit 1)) t
  ON 1 = 1
  GROUP BY t.id
  ) t, (select length(column_str) as cnt from roll_result limit 1) line_len
) 
SELECT SUM((length(str) - length(replace(str, 'O', ''))) * score)
FROM final_result;

Part Two

第二关更加复杂,按照北西南东的方向各滚动一次,算做是一个循环。经过10亿次循环后的结果是?

Each cycle tilts the platform four times so that the rounded rocks roll north, then west, then south, then east.

一次循环

首先先写出北、西、南、东一次循环的SQL,通过roll_left和roll_right两个函数来实现

SELECT t.id, roll_right(string_agg(substring(str from t.id for 1) , '' order by south.id)) as str
  FROM (
    SELECT t.id, roll_right(string_agg(substring(str from t.id for 1) , '' order by west.id)) as str
    FROM (
      SELECT t.id, roll_left(string_agg(substring(str from t.id for 1) , '' order by north.id)) as str
      FROM (
        SELECT t.id, roll_left(string_agg(substring(str from t.id for 1) , '' order by origin.id)) as str
        FROM origin
        JOIN (SELECT id FROM seq limit 10) t ON 1 = 1
        GROUP BY t.id
        ORDER BY 1 DESC
      ) north
      JOIN (SELECT id FROM seq limit 10) t ON 1 = 1
      GROUP BY t.id
    ) west 
    JOIN (SELECT id FROM seq limit 10) t ON 1 = 1
    GROUP by t.id
    ORDER BY 1 DESC
  ) south
  JOIN (SELECT id FROM seq limit 10) t ON 1 = 1
  GROUP by t.id

十亿次循环?

当然不可能真的循环十亿次,有限时间内根本计算不出。先尝试循环若干次看看结果,因此CTE中无法对递归表使用aggregate function,因此只能在自定义function中循环插入到表中。

CREATE OR REPLACE FUNCTION trans(cnt INTEGER)
RETURNS INTEGER AS $$
BEGIN
    DELETE FROM input;
    INSERT INTO input 
    SELECT row_number() over () as _row, line
    FROM lance_input;
    FOR i IN 1..CNT LOOP
      WITH recursive seq AS (
        SELECT 1 as id
        UNION ALL
        SELECT id + 1 as id
        FROM seq
      )
      INSERT INTO input
      SELECT t.id, roll_right(string_agg(substring(str from t.id for 1) , '' order by south.id)) as str
      FROM (
        SELECT t.id, roll_right(string_agg(substring(str from t.id for 1) , '' order by west.id)) as str
        FROM (
          SELECT t.id, roll_left(string_agg(substring(str from t.id for 1) , '' order by north.id)) as str
          FROM (
            SELECT t.id, roll_left(string_agg(substring(str from t.id for 1) , '' order by input_bak.id)) as str
            FROM input_bak
            JOIN (SELECT id FROM seq limit 100) t ON 1 = 1
            GROUP BY t.id
            ORDER BY 1 DESC
          ) north
          JOIN (SELECT id FROM seq limit 100) t ON 1 = 1
          GROUP BY t.id
        ) west 
        JOIN (SELECT id FROM seq limit 100) t ON 1 = 1
        GROUP by t.id
        ORDER BY 1 DESC
      ) south
      JOIN (SELECT id FROM seq limit 100) t ON 1 = 1
      GROUP by t.id;
    END LOOP;
 
    RETURN 0;
END;
$$ LANGUAGE plpgsql;

期望是循环若干次后,这个结果能够最终保持稳定,就像例子中的那样,但是结果却大不相同。无论多少次迭代后,总会有8条记录不一致,而且还不是固定的8条记录。

postgres=# select a.* from input a JOIN input_bak b on a.id = b.id and a.str != b.str;
 id |                                                 str                                                  
----+------------------------------------------------------------------------------------------------------
 38 | .....OO#.O#.....OO#....OO#.....O#.........#...O#.#.#.....OOO#......O#.......O#...#..#..#....#.O#....
 41 | #..#.....O#...#......OO#....#........OOO#.....OOOO#...#O#O#....................OO#.##OO#.O#......OOO
 57 | ......OOOOO#........##...O#........O#...#...OO#.....OOO#O#......OO#....#...#.............OO#....O#.#
 59 | .OOO#....OO#.......#.#.......OOO#..#........#........#..#...#..#.O#.......#..OO#.......#..##......O#
 63 | .OO#O#....#....................OOOOOOOOOO#...........O#...........#..#...O#............#.......O##..
 65 | .#.......................OOOO##..OOOOO#....O#.........OOOO#............OO##..#..OO#...###.#...#....O
 77 | .....#....OO#........O#....O#.....#.O#..#.O#....#.......O##...#............OOOOOO#...#..#....O#OOO#.
 89 | ##.O#.#..O#...OO#.......OO#..#..##.OOOO#..O#......OOOO#...O#....O####........OO#..O#.....OOO#..#....
(8 rows)

找规律

因此,有理由相信,这个循环肯定是有规律可循。因此,先迭代200次,并记录下每次循环后前后的结果都有哪些差异。

  10 | {6,7,8,9,10,11,15,16,19,20,25,27,32,33,35,37,38,42,44,46,47,51,53,57,58,63,72,73,74,84,85,86,89,93,97,98,99}
  11 | {5,6,11,12,13,15,19,22,25,27,28,31,33,35,41,43,44,46,51,53,54,55,62,63,70,75,78,79,84,86,89,92,95,97,100}
  12 | {2,5,7,10,11,15,18,24,25,28,34,35,37,40,42,44,45,51,52,54,55,56,57,66,67,69,75,80,83,85,87,88,90,92,95,96,98}
  13 | {2,5,8,10,13,14,15,20,21,25,27,40,43,44,45,46,51,54,57,63,64,65,70,72,75,80,85,91,96,97}
  14 | {1,9,10,14,16,17,22,24,35,37,42,43,44,46,52,55,60,63,64,65,70,73,76,80,83,88,92,97}
  15 | {8,10,13,14,15,20,26,27,37,41,42,44,45,56,61,64,67,70,73,79,85,89,94,96}
  16 | {1,10,12,13,20,25,27,31,35,44,45,46,61,64,65,69,70,73,81,84,86,89,90,91}
  17 | {6,8,13,15,18,24,34,35,37,43,44,46,58,63,69,72,73,75,79,82,92}
  18 | {6,12,18,21,27,28,37,44,45,47,61,65,73,74,75,76,81,96,100}
  19 | {9,15,20,24,25,26,27,34,35,45,46,67,73,75,82,91,99,100}

一开始的差异还是非常大的,随着更多的循环,差异也逐渐收敛到8条记录

 161 | {22,24,57,59,63,65,89,94}
 162 | {15,36,57,59,63,65,82,91}
 163 | {24,38,57,59,63,65,77,94}
 164 | {36,38,57,59,63,65,76,82}
 165 | {38,41,57,59,63,65,77,89}
 166 | {38,45,57,59,63,65,76,91}
 167 | {41,47,57,59,63,65,89,94}

再分析下是否有规律可循,果然看到了规律

postgres=# select diff, array_agg(id order by id) from (select * From record limit 100 offset 100) t group by diff having count(*) = 2;
           diff            | array_agg 
---------------------------+-----------
 {15,26,57,59,63,65,77,94} | {121,199}
 {15,26,57,59,63,65,82,91} | {108,186}
 {15,36,57,59,63,65,76,82} | {110,188}
 {22,24,57,59,63,65,76,82} | {122,200}
 {22,24,57,59,63,65,77,94} | {109,187}
 {22,25,57,59,63,65,82,91} | {120,198}
 {22,25,57,59,63,65,89,94} | {107,185}
 {24,38,57,59,63,65,77,89} | {111,189}
 {25,35,57,59,63,65,76,91} | {118,196}
 {25,35,57,59,63,65,77,89} | {105,183}
 {26,33,57,59,63,65,76,91} | {106,184}
 {26,33,57,59,63,65,89,94} | {119,197}
 {33,47,57,59,63,65,76,82} | {104,182}
 {33,47,57,59,63,65,77,89} | {117,195}
 {35,45,57,59,63,65,76,82} | {116,194}
 {35,45,57,59,63,65,77,94} | {103,181}
 {36,38,57,59,63,65,76,91} | {112,190}
 {38,41,57,59,63,65,89,94} | {113,191}
 {38,45,57,59,63,65,82,91} | {114,192}
 {38,45,57,59,63,65,89,94} | {101,179}
 {41,47,57,59,63,65,77,94} | {115,193}
 {41,47,57,59,63,65,82,91} | {102,180}

因此,每78轮循环后就会回到原点,因此十亿次后的状态等同于142次后的状态。

发表评论