Retina , 66 63 45 43 36 байт
^()(\1(?<1>.\1))+(\1(.(?(4).\4)))*$
Несмотря на заголовок «Retina», это просто обычное регулярное выражение .NET, которое принимает унарные представления чисел Лоше.
Входы 999 и 1000 занимают меньше секунды.
Попробуйте онлайн! (Первая строка включает набор тестов, разделенных переводом строки, а следующие две для удобства занимаются преобразованием в унарный.)
объяснение
Решение основано на классификации, согласно которой входные данные могут быть записаны как i*i + j*(i + j)
положительные i
и неотрицательные j
(поскольку нам не нужно обрабатывать ввод 0
), и это n*n
всего лишь сумма первых n
нечетных целых чисел. Игра в гольф была интересным упражнением в прямом обращении.
«Прямая ссылка» - это когда вы помещаете обратную ссылку в группу, к которой она относится. Конечно, это не работает, когда группа используется в первый раз, так как обратная ссылка еще не на что, но если вы поместите это в цикл, то обратная ссылка каждый раз получает перехват предыдущей итерации. Это, в свою очередь, позволит вам создавать больший захват с каждой итерацией. Это может быть использовано для создания очень компактных моделей для таких вещей, как треугольные числа, квадраты и числа Фибоначчи.
В качестве примера, используя тот факт, что квадраты являются просто суммами первых n
нечетных целых чисел, мы можем сопоставить квадратные данные следующим образом:
(^.|..\1)+$
На первой итерации ..\1
не может работать, потому что \1
еще не имеет значения. Итак, мы начнем с ^.
захвата одного персонажа в группу 1
. На последующих итерациях ^.
больше не совпадает из-за привязки, но теперь ..\1
действует. Он соответствует на два символа больше, чем в предыдущей итерации, и обновляет захват. Таким образом, мы сопоставляем увеличивающиеся нечетные числа, получая квадрат после каждой итерации.
К сожалению, сейчас мы не можем использовать эту технику как есть. После сопоставления i*i
нам нужно получить i
также, чтобы мы могли умножить его на j
. Простой (но долгий) способ сделать это состоит в том, чтобы использовать тот факт, что сопоставление i*i
требует i
итераций, поэтому мы собираем данные i
в группе 1
. Теперь мы могли бы использовать балансировочные группы, чтобы извлечь это i
, но, как я сказал, это дорого.
Вместо этого я нашел другой способ записать эту «сумму последовательных нечетных целых чисел», которая также дает i
в конце группу захвата. Конечно, i
нечетное число просто 2i-1
. Это дает нам возможность увеличивать прямую ссылку только на 1 на каждой итерации. Вот эта часть:
^()(\1(?<1>.\1))+
Это ()
просто помещает пустой захват в группу 1
(инициализируя i
в 0
). Это в значительной степени эквивалентно ^.|
приведенному выше простому решению, но использование |
в этом случае будет немного сложнее.
Тогда у нас есть основной цикл (\1(?<1>.\1))
. \1
соответствует предыдущему i
, (?<1>.\1)
затем обновляет группу 1
с помощью i+1
. Что касается нового i
, мы только что подобрали 2i-1
персонажей. Именно то, что нам нужно.
Когда мы закончим, мы сопоставим некоторый квадрат, i*i
и группа 1
все еще содержит i
символы.
Вторая часть ближе к простому квадратному соответствию, которое я показал выше. Давайте 1
пока проигнорируем обратную ссылку на :
(.(?(4).\1))*
Это в основном то же самое (^.|..\4)*
, за исключением того, что мы не можем использовать, ^
потому что мы не в начале строки. Вместо этого мы используем условное, чтобы сопоставить дополнительное .\1
только тогда, когда мы уже использовали группу 4
. Но на самом деле это точно так же. Это дает нам j*j
.
Единственное, чего не хватает, это j*i
термин. Мы комбинируем это с j*j
использованием факта, что j*j
вычисления все еще требуют j
итераций. Таким образом , для каждой итерации мы также продвигать курсор i
с \1
. Нам просто нужно убедиться, что не записать это в группу 4
, потому что это будет мешать совпадению последовательных нечетных чисел. Вот как мы приходим к:
(\1(.(?(4).\1)))*