В этой челлендж-серии статей попробуем использовать PostgreSQL как среду для решения задач Advent of Code 2024.
Возможно, SQL не самый подходящий для этого язык, зато мы рассмотрим его различные возможности, о которых вы могли и не подозревать.
В этой части с решением нам помогут логические агрегаты bool_and
/bool_or
.
Оригинальная постановка задачи и ее перевод:
Advent of Code 2024, Day 2: Red-Nosed Reports
— День 2: Отчеты о красноносых —
К счастью, первое место, которое хотят исследовать историки, находится недалеко от офиса главного историка.
В то время как завод ядерного синтеза/деления Red-Nosed Reindeer, похоже, не содержит никаких признаков Главного Историка, инженеры там подбегают к вам, как только видят вас. По-видимому, они все еще говорят о том времени, когда Рудольф был спасен с помощью молекулярного синтеза из одного электрона.
Они быстро добавляют, что — поскольку вы уже здесь — они были бы очень признательны за вашу помощь в анализе необычных данных с реактора Red-Nosed. Вы поворачиваетесь, чтобы проверить, ждут ли вас Историки, но они, похоже, уже разделились на группы, которые сейчас обыскивают каждый уголок объекта. Вы предлагаете помочь с необычными данными.
Необычные данные (ваш ввод головоломки) состоят из множества отчетов, по одному отчету на строку. Каждый отчет представляет собой список чисел, называемых уровнями, которые разделены пробелами. Например:
7 6 4 2 1 1 2 7 8 9 9 7 6 2 1 1 3 2 4 5 8 6 4 4 1 1 3 6 7 9
В этом примере данных содержится шесть отчетов, каждый из которых содержит пять уровней.
Инженеры пытаются выяснить, какие отчеты безопасны. Системы безопасности реактора Red-Nosed могут выдерживать только уровни, которые либо постепенно увеличиваются, либо постепенно уменьшаются. Таким образом, отчет считается безопасным только в том случае, если оба следующих условия верны:
-
Уровни либо все увеличиваются, либо все уменьшаются.
-
Любые два соседних уровня отличаются не менее чем на один и не более чем на три.
В приведенном выше примере отчеты можно считать безопасными или небезопасными, проверив следующие правила:
-
7 6 4 2 1
: Безопасно, поскольку все уровни уменьшаются на 1 или 2. -
1 2 7 8 9
: Небезопасно, так как2 7
увеличение составляет 5. -
9 7 6 2 1
: Небезопасно, так как6 2
это уменьшение на 4. -
1 3 2 4 5
: Небезопасно, так как1 3
увеличивается, но3 2
уменьшается. -
8 6 4 4 1
: Небезопасно, так как4 4
не является ни увеличением, ни уменьшением. -
1 3 6 7 9
: Безопасно, поскольку все уровни увеличиваются на 1, 2 или 3.
Итак, в этом примере 2
отчета безопасны.
Проанализируйте необычные данные от инженеров. Сколько отчетов безопасны?
— Часть вторая —
Инженеры удивлены малым количеством безопасных отчетов, пока не понимают, что забыли рассказать вам о демпфере проблем .
Problem Dampener — это модуль, устанавливаемый на реакторе, который позволяет системам безопасности реактора выдерживать один плохой уровень в том, что в противном случае было бы безопасным отчетом. Как будто плохой уровень никогда не был!
Теперь применяются те же правила, что и раньше, за исключением того, что если удаление одного уровня из небезопасного отчета сделает его безопасным, то отчет вместо этого считается безопасным.
Больше отчетов из приведенного выше примера теперь безопасны:
-
7 6 4 2 1
: Безопасно без удаления какого-либо уровня. -
1 2 7 8 9
: Небезопасно независимо от того, какой уровень удален. -
9 7 6 2 1
: Небезопасно независимо от того, какой уровень удален. -
1 3 2 4 5
: Безопасно, удалив второй уровень,3
. -
8 6 4 4 1
: Безопасно, удалив третий уровень,4
. -
1 3 6 7 9
: Безопасно без удаления какого-либо уровня.
Благодаря Problem Dampener 4
отчета действительно безопасны!
Обновите свой анализ, обрабатывая ситуации, в которых Problem Dampener может удалить один уровень из небезопасных отчетов. Сколько отчетов теперь безопасны?
Часть 1
-
В этот раз воспользуемся
regexp_split_to_table
иbtrim
, чтобы разбить ввод на все непустые строки. -
Каждую строку с помощью
string_to_array
превращаем в массив чисел. -
Для каждого массива перебираем индексы с 1 до предпоследнего и проверяем с выполнение условия «для каждого» агрегатной функцией
bool_and
. -
Остается подсчитать количество записей с подходящим условием.
SELECT count(*) FILTER(WHERE cond) -- количество с выполнившимся условием FROM ( SELECT string_to_array(line, ' ')::numeric[] rpt -- преобразуем строку в массив чисел FROM regexp_split_to_table($$ 7 6 4 2 1 1 2 7 8 9 9 7 6 2 1 1 3 2 4 5 8 6 4 4 1 1 3 6 7 9 $$, '[\r\n]+') line -- разбиение по строкам WHERE btrim(line) <> '' -- фильтрация пустых строк ) T , LATERAL ( SELECT ( bool_and(rpt[i] < rpt[i + 1]) OR -- все возрастают bool_and(rpt[i] > rpt[i + 1]) -- все убывают ) AND bool_and(abs(rpt[i] - rpt[i + 1]) BETWEEN 1 AND 3) cond -- все разницы между 1..3 FROM generate_series(1, array_length(rpt, 1) - 1) i -- перебор без последнего элемента ) cond;
На все вычисления понадобилось всего 74мс:
Часть 2
-
По условию, нас спрашивают, будет ли отчет безопасен при удалении лишь одного уровня — так давайте породим все эти возможные отчеты «без одного элемента» с помощью
generate_subscripts
и «склеивания слайсов». -
Для каждого такого отчета мы уже умеем вычислять условие, а для исходного получим его «по ИЛИ» — через
bool_or
.
SELECT count(*) FILTER(WHERE cond) FROM ( SELECT rptno , bool_or(cond) cond -- хоть один удовлетворяет условию? FROM ( SELECT row_number() OVER() rptno -- нумеруем "исходные" отчеты , string_to_array(line, ' ')::numeric[] rpts FROM regexp_split_to_table($$ 7 6 4 2 1 1 2 7 8 9 9 7 6 2 1 1 3 2 4 5 8 6 4 4 1 1 3 6 7 9 $$, '[\r\n]+') line WHERE btrim(line) <> '' ) T , LATERAL ( -- генерируем все возможные подотчеты SELECT rpts[:i-1] || rpts[i+1:] rpt -- "клеим" массив до/после индекса FROM generate_subscripts(rpts, 1) i -- перебираем все индексы ) rptn , LATERAL ( SELECT ( bool_and(rpt[i] < rpt[i + 1]) OR bool_and(rpt[i] > rpt[i + 1]) ) AND bool_and(abs(rpt[i] - rpt[i + 1]) BETWEEN 1 AND 3) cond FROM generate_series(1, array_length(rpt, 1) - 1) i ) cond GROUP BY 1 ) T;
Итоговый вариант работает около 155мс:
ссылка на оригинал статьи https://habr.com/ru/articles/868982/
Добавить комментарий