Осторожно, спойлеры! Не читайте, пока хотите решить задачу самостоятельно.
В этой челлендж-серии статей, начатой с прошлогоднего эвента, попробуем использовать 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 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 -- количество цифр должно делиться нацело на группы по 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).
На сегодня - все!
