Pyth, 83 82 байта
=eAQM.^GHQKf%=/H=2;1=gftgT/Q;1HJg~gGHh/H2WtG=*J=gT^2t-K=Kfq1gG^2T1=%*G=^T2Q;hS%_BJ
Тестирование
Эта программа реализует алгоритм Тонелли-Шанкса . Я написал это, внимательно следя за страницей Википедии. Это берет как вход (n, p)
.
Об отсутствии квадратного корня сообщает следующая ошибка:
TypeError: pow() 3rd argument not allowed unless all arguments are integers
Это очень сложный гольф-код, написанный в императивном стиле, в отличие от более распространенного функционального стиля Pyth.
Я использую один тонкий аспект Pyth =
, который, если за ним сразу не следует переменная, ищет в программе следующую переменную, затем присваивает результат следующего выражения этой переменной, а затем возвращает этот результат. На протяжении всего объяснения я буду ссылаться на страницу википедии: алгоритм Тонелли-Шенкса , поскольку именно этот алгоритм я реализую.
Объяснение:
=eAQ
A
принимает 2-кортеж в качестве входных данных, присваивает значения G
и, H
соответственно, и возвращает свои входные данные. Q
это начальный вход. e
возвращает последний элемент последовательности. После этого фрагмента кода, G
это n
и H
и Q
есть p
.
M.^GHQ
M
определяет функцию 2 входов g
, где входы G
и H
. .^
является быстрой модульной функцией возведения в степень Пита. Этот фрагмент определяет, g
чтобы означать мод возведения в степень Q
.
Kf%=/H=2;1
f
определяет цикл повторения до false и возвращает количество итераций, для которых он выполняется, в 1
качестве входных данных. Во время каждой итерации цикла мы делим H
на 2, устанавливаем H
это значение и проверяем, является ли результат нечетным. Как только это произойдет, мы остановимся.K
хранит количество итераций, которые это заняло.
Одна очень хитрая вещь - =2;
бит. =
выполняет поиск следующей переменной, которая T
, следовательно, T
имеет значение 2. Однако T
внутри f
цикла находится счетчик итераций, поэтому мы используем его ;
для получения значения T
из глобальной среды. Это сделано для того, чтобы сэкономить пару байтов пробела, которые в противном случае были бы необходимы для разделения чисел.
После этого фрагмента, K
это S
из википедии статьи (вики), и H
это Q
с вики, и T
является 2
.
=gftgT/Q;1H
Теперь нам нужно найти квадратичный мод без остатка p
. Мы будем грубо форсировать это, используя критерий Эйлера. /Q2
есть (p-1)/2
, так как /
это деление по полам, поэтому ftgT/Q;1
находит первое целое число, T
где T ^ ((p-1)/2) != 1
, как желательно. Напомним, что ;
снова тянет T
из глобальной среды, которая по-прежнему 2. Это результат z
из вики.
Далее, чтобы создать c
из вики, нам нужно z^Q
, поэтому мы оборачиваем вышеупомянутое g ... H
и присваиваем результат T
. Теперь T
это c
из вики.
Jg~gGHh/H2
Давайте выделим это: ~gGH
. ~
похоже =
, но возвращает исходное значение переменной, а не ее новое значение. Таким образом, он возвращает G
, чтоn
из вики.
Это присваивает J
значение n^((Q+1)/2)
, которое R
из вики.
Теперь вступает в силу следующее:
~gGH
Это присваивает G
значение n^Q
, которое t
из вики.
Теперь у нас есть переменные цикла. M, c, t, R
из вики есть K, T, G, J
.
Тело цикла сложное, поэтому я собираюсь представить его с пробелами так, как я написал:
WtG
=*J
=
gT^2
t-
K
=Kfq1gG^2T1
=%*G=^T2Q;
Сначала мы проверяем, G
равен ли 1. Если это так, мы выходим из цикла.
Следующий код, который выполняется:
=Kfq1gG^2T1
Здесь мы ищем первое значение i
, которое G^(2^i) mod Q = 1
начинается с 1. Результат сохраняется в K
.
=gT^2t-K=Kfq1gG^2T1
Здесь мы берем старое значение K
, вычитаем новое значение K
, вычитаем 1, повышаем 2 до этой степени, а затем повышаем T
до этого значения мощности Q
, а затем присваиваем результат T
. Это делает T
равным b
из вики.
Это также строка, которая завершает цикл и завершается ошибкой, если нет решения, потому что в этом случае новое значение K
будет равно старому значению K
, 2 будет повышено до -1
, и модульное возведение в степень вызовет ошибку.
=*J
Затем мы умножаем J
на результат выше и сохраняем его J
, сохраняя в R
курсе.
=^T2
Затем мы возводим в квадрат T
и сохраняем результат обратно T
, устанавливая T
обратно c
из вики.
=%*G=^T2Q
Затем мы умножаем G
на этот результат, берем его мод Q
и сохраняем результат обратно G
.
;
И мы завершаем цикл.
После того, как цикл закончен, J
это квадратный корень из n
мода p
. Чтобы найти самый маленький, мы используем следующий код:
hS%_BJ
_BJ
создает список J
и его отрицание, %
неявно принимает в Q
качестве второго аргумента и использует поведение Pyth по умолчанию для применения % ... Q
к каждому члену последовательности. Затем S
сортирует список и h
принимает его первый член, минимум.