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)))*