Осторожно, спойлеры! Не читайте, пока хотите решить задачу самостоятельно.
В этой челлендж-серии статей, начатой с прошлогоднего эвента, попробуем использовать PostgreSQL как среду для решения задач Advent of Code 2025.
Возможно, SQL не самый подходящий для этого язык, зато мы рассмотрим его различные возможности, о которых вы могли и не подозревать.

Оригинальная постановка задачи и ее перевод:
Advent of Code 2025, Day 2: Gift Shop
--- День 2: Сувенирный магазин ---
Вы входите внутрь и поднимаетесь на лифте на единственную оставшуюся остановку: сувенирный магазин. «Спасибо за посещение Северного полюса!» — радостно восклицает табличка рядом. Вы не уверены, кому вообще разрешено посещать Северный полюс, но знаете, что отсюда можно попасть в вестибюль, а оттуда — в остальную часть базы на Северном полюсе.
Пока вы пробираетесь сквозь удивительно обширный ассортимент, один из продавцов узнает вас и просит о помощи.
Оказалось, один из юных эльфов играл на компьютере в сувенирном магазине и умудрился добавить в базу данных целую кучу недействительных идентификаторов товаров! Вам же наверняка не составит труда определить недействительные идентификаторы товаров для него, верно?
Они уже проверили большинство диапазонов идентификаторов продуктов; вам нужно проверить лишь несколько из них (ваши данные для головоломки). Например:
11-22,95-115,998-1012,1188511880-1188511890,222220-222224,
1698522-1698528,446443-446449,38593856-38593862,565653-565659,
824824821-824824827,2121212118-2121212124
(Диапазоны идентификаторов здесь перенесены для удобства чтения; в вашем вводе они отображаются на одной длинной строке.)
Диапазоны разделяются запятыми (,); каждый диапазон имеет свой первый идентификатор и последний идентификатор, разделенные тире (-).
Поскольку юный эльф просто выписывал какие-то глупые узоры, вы можете найти недействительные идентификаторы, поискав любой идентификатор, состоящий только из последовательности цифр, повторённых дважды. Таким образом, 55(5дважды), 6464(64дважды) и 123123(123дважды) будут недействительными идентификаторами.
Ни одно из чисел не содержит начальных нулей; 0101это вообще не идентификатор. ( 101— допустимый идентификатор, который следует игнорировать.)
Ваша задача — найти все недействительные идентификаторы в указанных диапазонах. В приведённом выше примере:
11-22имеет два недействительных идентификатора,11и22.95-115имеет один недействительный идентификатор,99.998-1012имеет один недействительный идентификатор,1010.1188511880-1188511890имеет один недействительный идентификатор,1188511885.222220-222224имеет один недействительный идентификатор,222222.1698522-1698528не содержит недействительных идентификаторов.446443-446449имеет один недействительный идентификатор,446446.38593856-38593862имеет один недействительный идентификатор,38593859.Остальные диапазоны не содержат недействительных идентификаторов.
Сложение всех недействительных идентификаторов в этом примере дает 1227775554.
Что получится, если сложить все недействительные удостоверения личности?
--- Часть вторая ---
Клерк быстро обнаруживает, что в диапазонах вашего списка всё ещё есть недействительные идентификаторы. Может быть, юный эльф вытворял и другие глупости?
Итак, идентификатор недействителен, если он состоит только из некоторой последовательности цифр, повторённой как минимум дважды. Таким образом, 12341234(1234два раза), 123123123(123три раза), 1212121212(12пять раз) и 1111111(1семь раз) — всё это недействительные идентификаторы.
Из того же примера, что и раньше:
11-22по-прежнему имеет два недействительных идентификатора,11и22.95-115теперь имеет два недействительных идентификатора,99и111.998-1012теперь имеет два недействительных идентификатора,999и1010.1188511880-1188511890все еще имеет один недействительный идентификатор,1188511885.222220-222224все еще имеет один недействительный идентификатор,222222.1698522-1698528по-прежнему не содержит недействительных идентификаторов.446443-446449все еще имеет один недействительный идентификатор,446446.38593856-38593862все еще имеет один недействительный идентификатор,38593859.565653-565659теперь имеет один недействительный идентификатор,565656.824824821-824824827теперь имеет один недействительный идентификатор,824824824.2121212118-2121212124теперь имеет один недействительный идентификатор,2121212121.
Сложение всех недействительных идентификаторов в этом примере дает 4174379265.
Что получится, если сложить все недействительные удостоверения личности, используя эти новые правила?
Часть 1
Для решения задачи можно было бы использовать и банальный перебор всех значений в каждом из диапазонов, их приведение в текстовый вид, проверку соответствия половинок...
Мы так делать не будем, а воспользуемся тем фактом, что "недействительный идентификатор состоит из последовательности цифр, повторенной дважды" означает простую вещь - это число делится на 10...01 (назовем это число множителем).
Действительно, достаточно вспомнить умножение "столбиком" из начальной школы, чтобы понять, почему:
aa = 11 * aabab = 101 * ababcabc = 1001 * abc...
Сначала разберем ввод уже знакомой по вчерашней задаче функцией regexp_matches, причем наличие или отсутствие переводов строк нас тут совсем не интересует:
regexp_matches($$
11-22,95-115,998-1012,1188511880-1188511890,222220-222224,
1698522-1698528,446443-446449,38593856-38593862,565653-565659,
824824821-824824827,2121212118-2121212124
$$, '(\d+)-(\d+)', 'g') ids
Затем определим, сколько нулей может быть в множителе: не менее округленной вверх половины длины первого идентификатора, но не более округленной вниз половины длины второго:
ceil(length(ids[1]) * 0.5) <= zeroes <= floor(length(ids[2]) * 0.5)
Сгенерируем все эти значения множителя функцией generate_series, вычислив сразу и min/max границы диапазона, для которого он подойдет - это все числа соответствующей ему [четной] длины:
10 <= a * 11 <= 99
1000 <= ab * 101 <= 9999
100000 <= abc * 1001 <= 999999
...
При этом мы воспользуемся удобной возможностью PostgreSQL легко превращать строковые значения в числовые.
Можно было бы сразу оперировать на уровне чисел со степенями
10, но - зачем?..
Остался последний шаг - непосредственно сгенерировать искомые идентификаторы с шагом множителя в каждом из диапазонов и посчитать их сумму.
Опять же, сумму чисел в интервале, кратных конкретному множителю, можно представить как сумму арифметической прогрессии:
abcabc + abdabd + abeabe + ...= 1001 * (abc + abd + abe + ...) = 1001 * ((abc + 0) + (abc + 1) + (abc + 2) + ...) = 1001 * (abc * N + sum(0, N - 1))Можно, да, но современных мощностей хватает, чтобы обойтись и без этого.
Проверим себя, сгенерировав все подходящие под условия числа, воспользовавшись возможностью указывать "шаг" для generate_series:
SELECT
*
-- sum(num)
FROM
regexp_matches($$
11-22,95-115,998-1012,1188511880-1188511890,222220-222224,
1698522-1698528,446443-446449,38593856-38593862,565653-565659,
824824821-824824827,2121212118-2121212124
$$, '(\d+)-(\d+)', 'g') ids
, LATERAL (
SELECT
('1' || repeat('0', zeroes::integer - 1) || '1')::bigint mul -- множитель
, ('1' || repeat('0', zeroes::integer * 2 - 1))::bigint min -- 10..0
, (repeat('9', zeroes::integer * 2))::bigint max -- 9..99
FROM
generate_series(
ceil(length(ids[1]) * 0.5)
, floor(length(ids[2]) * 0.5)
) zeroes -- сколько нулей может быть в множителе
) T
, generate_series(
ceil(
greatest(ids[1]::double precision, min) -- граница интервала справа
/ mul)::bigint * mul -- первое кратное на интервале
, least(ids[2]::bigint, max) -- граница интервала справа
, mul -- шаг по множителю
) num;
Если все сделано правильно, для исходного примера мы получим вот такую выборку:
ids | mul | min | max | num
{11,22} | 11 | 10 | 99 | 11
{11,22} | 11 | 10 | 99 | 22
{95,115} | 11 | 10 | 99 | 99
{998,1012} | 101 | 1000 | 9999 | 1010
{1188511880,1188511890} | 100001 | 1000000000 | 9999999999 | 1188511885
{222220,222224} | 1001 | 100000 | 999999 | 222222
{446443,446449} | 1001 | 100000 | 999999 | 446446
{38593856,38593862} | 10001 | 10000000 | 99999999 | 38593859
Для решения задачи остается лишь заменить SELECT * на SELECT sum(num).
Часть 2
Во второй части уже требуется найти числа делящиеся не только на совпадающие "половинки", но и на одинаковые трети, четверти, ..., поэтому код выбора множителя и границ диапазона придется немного модифицировать:
SELECT
('1' || repeat('0', digits - 1))::bigint min
, (repeat('9', digits))::bigint max
, mul
FROM
generate_series(
length(ids[1])
, length(ids[2])
) digits -- сколько цифр может быть в идентификаторе
, LATERAL (
SELECT
(repeat(repeat('0', len - 1) || '1', digits / len))::bigint mul
FROM
generate_series(1, digits - 1) len
WHERE
digits % len = 0 -- количество цифр должно делиться нацело на группы по len
) T
Тут мы удачно пользуемся тем фактом, что преобразование строки в число само отбрасывает лидирующие нули: '001001'::bigint -> 1001.
Проверим, что там сгенерировалось:
SELECT
*
-- sum(DISTINCT num)
FROM
regexp_matches($$
11-22,95-115,998-1012,1188511880-1188511890,222220-222224,
1698522-1698528,446443-446449,38593856-38593862,565653-565659,
824824821-824824827,2121212118-2121212124
$$, '(\d+)-(\d+)', 'g') ids
, LATERAL (
SELECT
mul
, ('1' || repeat('0', digits - 1))::bigint min
, (repeat('9', digits))::bigint max
FROM
generate_series(
length(ids[1])
, length(ids[2])
) digits
, LATERAL (
SELECT
(repeat(repeat('0', len - 1) || '1', digits / len))::bigint mul
FROM
generate_series(1, digits - 1) len
WHERE
digits % len = 0
) T
) T
, generate_series(
ceil(greatest(ids[1]::double precision, min) / mul)::bigint * mul
, least(ids[2]::bigint, max)
, mul) num;
ids | mul | min | max | num
{11,22} | 11 | 10 | 99 | 11
{11,22} | 11 | 10 | 99 | 22
{95,115} | 11 | 10 | 99 | 99
{95,115} | 111 | 100 | 999 | 111
{998,1012} | 111 | 100 | 999 | 999
{998,1012} | 101 | 1000 | 9999 | 1010
{1188511880,1188511890} | 100001 | 1000000000 | 9999999999 | 1188511885
{222220,222224} | 111111 | 100000 | 999999 | 222222
{222220,222224} | 10101 | 100000 | 999999 | 222222
{222220,222224} | 1001 | 100000 | 999999 | 222222
{446443,446449} | 1001 | 100000 | 999999 | 446446
{38593856,38593862} | 10001 | 10000000 | 99999999 | 38593859
{565653,565659} | 10101 | 100000 | 999999 | 565656
{824824821,824824827} | 1001001 | 100000000 | 999999999 | 824824824
{2121212118,2121212124} | 101010101 | 1000000000 | 9999999999 | 2121212121
Обратите внимание, что число 222222 попало в нашу выборку аж 3 раза - 6 x '2', 3 x '22', 2 x '222'. Поэтому при суммировании надо не забыть исключить дубли с помощью sum(DISTINCT num).
На сегодня - все!