SQL HowTo: работа с массивами (Advent of Code 2024, Day 4: Ceres Search)

от автора

В этой челлендж-серии статей попробуем использовать PostgreSQL как среду для решения задач Advent of Code 2024.

Возможно, SQL не самый подходящий для этого язык, зато мы рассмотрим его различные возможности, о которых вы могли и не подозревать.

В этой части немного поработаем с массивами.


Оригинальная постановка задачи и ее перевод:

Advent of Code 2024, Day 4: Ceres Search

— День 4: Поиск Цереры —

«Похоже, Шефа здесь нет. Следующий!» Один из Историков достает устройство и нажимает на нем единственную кнопку. После короткой вспышки вы узнаете интерьер станции мониторинга Цереры !

Пока поиски Шефа продолжаются, маленькая эльфийка, живущая на станции, дергает вас за рубашку; она хотела бы узнать, не могли бы вы помочь ей с поиском слов (вашим вводом в головоломку). Ей нужно найти только одно слово: XMAS.

Этот поиск слов позволяет искать слова по горизонтали, вертикали, по диагонали, написанные задом наперед или даже перекрывающие другие слова. Это немного необычно, хотя, поскольку вам нужно найти не просто один экземпляр XMAS— вам нужно найти их все. Вот несколько способов XMAS, которые могут возникнуть, когда нерелевантные символы были заменены на .:

..X... .SAMX. .A..A. XMAS.S .X....

Фактический поиск слова будет заполнен буквами. Например:

MMMSXXMASM MSAMXMSMSA AMXSXMAAMM MSAMASMSMX XMASAMXAMM XXAMMXXAMA SMSMSASXSS SAXAMASAAA MAMMMXMMMM MXMXAXMASX

В этом поиске слово XMASвстречается всего 18раз; вот тот же поиск слов снова, но буквы, не участвующие ни в одном из XMASзаменены на .:

....XXMAS. .SAMXMS... ...S..A... ..A.A.MS.X XMASAMX.MM X.....XA.A S.S.S.S.SS .A.A.A.A.A ..M.M.M.MM .X.X.XMASX

Взгляните на поиск слова маленьким эльфом. Сколько раз XMASвстречается?

— Часть вторая —

Эльф вопросительно смотрит на тебя. Ты неправильно понял задание?

В поисках инструкций вы переворачиваете поиск слов и обнаруживаете, что это на самом деле не головоломка XMAS; это головоломка X-MAS, в которой вам нужно найти два MASв форме X. Один из способов, как это может выглядеть:

M.S .A. M.S

Нерелевантные символы снова были заменены на .в приведенной выше диаграмме. Внутри Xкаждый MASможет быть написан вперед или назад.

Вот тот же пример, что и раньше, но на этот раз все X-MAS были сохранены:

.M.S...... ..A..MSMS. .M.S.MAA.. ..A.ASMSM. .M.S.M.... .......... S.S.S.S.S. .A.A.A.A.. M.M.M.M.M. ..........

В этом примере символ an X-MASпоявляется 9раз.

Сколько раз встречаетсяX-MAS?

Часть 1

  1. Преобразуем цепочкой regexp_split_to_table -> regexp_split_to_array -> array_agg входную строку в двухмерный массив символов.

  2. Используем уже встречавшуюся нам функцию generate_subscripts для получения всех комбинаций координат в матрице.

  3. Для каждой исходной точки формируем 8 слов искомой длины во всех горизонтальных, вертикальных и диагональных направлениях, сразу «разворачивая» их в строки с помощью unnest(ARRAY[...]).

  4. Считаем количество получившихся искомых слов.

WITH matrix AS(   SELECT     array_agg(regexp_split_to_array(line, '')) m   FROM     regexp_split_to_table($$ MMMSXXMASM MSAMXMSMSA AMXSXMAAMM MSAMASMSMX XMASAMXAMM XXAMMXXAMA SMSMSASXSS SAXAMASAAA MAMMMXMMMM MXMXAXMASX     $$, '[\r\n]+') line   WHERE     btrim(line) <> '' ) SELECT   count(*) FILTER(WHERE word = 'XMAS') -- подсчитываем кол-во слов 'XMAS' FROM   matrix , generate_subscripts(m, 1) x -- первый уровень массива , generate_subscripts(m, 2) y -- второй уровень , LATERAL (     SELECT       unnest(ARRAY[ -- генерируем слова во всех 8 возможных направлениях сразу         string_agg(m[x + i][y], '')       , string_agg(m[x - i][y], '')       , string_agg(m[x][y + i], '')       , string_agg(m[x][y - i], '')       , string_agg(m[x + i][y + i], '')       , string_agg(m[x + i][y - i], '')       , string_agg(m[x - i][y + i], '')       , string_agg(m[x - i][y - i], '')       ]) word     FROM       generate_series(0, length('XMAS') - 1) i -- генерируем смещения по кол-ву букв искомого слова   ) T;

Вполне очевидно, что наш алгоритм сильно неоптимален, поскольку мы сгенерировали заведомо больше слов, чем могло бы подойти (хотя можно было бы проверять совпадение буквы на i-месте). Но даже в таком виде за 22 секунды мы получаем результат:

Часть 2

  1. Немного изменим подход к генерации — будем генерировать диагональные слова, считая точку не началом слова, а центром X.

  2. Очевидно, что перебирать тогда можно уже не [1..x], а только [2..x-1] — для клеток на границе «слово» не сформируется все равно.

  3. Останется лишь подсчитать количество ячеек, где получилось ровно 2 'MAS'.

WITH matrix AS(   SELECT     array_agg(regexp_split_to_array(line, '')) m   FROM     regexp_split_to_table($$ MMMSXXMASM MSAMXMSMSA AMXSXMAAMM MSAMASMSMX XMASAMXAMM XXAMMXXAMA SMSMSASXSS SAXAMASAAA MAMMMXMMMM MXMXAXMASX     $$, '[\r\n]+') line   WHERE     btrim(line) <> '' ) SELECT   count(*) FROM   (     SELECT       x     , y     FROM       matrix     , generate_series(2, array_length(m, 1)) x     , generate_series(2, array_length(m, 2)) y     , LATERAL (         SELECT           unnest(ARRAY[ -- только диагонали             string_agg(m[x + i][y + i], '')           , string_agg(m[x + i][y - i], '')           , string_agg(m[x - i][y + i], '')           , string_agg(m[x - i][y - i], '')           ]) word         FROM           generate_series(-1, 1) i -- [-1, 0, 1]       ) T     WHERE       word = 'MAS'     GROUP BY       1, 2     HAVING       count(*) = 2 -- ровно 2 слова 'MAS' через этот центр   ) T;

Слова короче, перебираем мы меньше, поэтому результат почти втрое быстрее:


ссылка на оригинал статьи https://habr.com/ru/articles/869026/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *