p
\Ai
\&
>(&]&|0
<*&d
&~bN
10
( )/+
/*
Попробуйте онлайн!
объяснение
Это, безусловно, самая сложная (и самая длинная) программа, которую я когда-либо писал на «Медузах». Я понятия не имею, смогу ли я разобрать это понятным образом, но, думаю, мне придется попробовать.
Jellyfish предоставляет довольно общий оператор итерации \
, который очень помогает в «поиске чего-то Nth ». Одна из его семантик - «итерация функции по значению, пока отдельная тестовая функция не даст что-то правдивое» (на самом деле тестовая функция получает как текущий, так и последний элемент, но мы только заставим его взглянуть на текущий элемент) , Мы можем использовать это для реализации функции «следующий действительный номер». Другая перегрузка \
- это «перебрать функцию по начальному значению N раз». Мы можем использовать нашу предыдущую функцию и повторять ее 0
N раз, где N - это ввод. Все это настроено довольно кратко с этой частью кода:
p
\Ai
\&
> 0
(Причины, по которым 0
фактический ввод в результирующую функцию закончился, немного сложны, и я не буду вдаваться в них здесь.)
Проблема со всем этим в том, что мы не будем передавать текущее значение в тестовую функцию вручную. \
Оператор сделает это за нас. Итак, теперь мы должны сконструировать единственную унарную функцию (с помощью композиций, хуков, вилок и карри), которая принимает число и сообщает нам, является ли оно действительным числом (т. Е. Делением на сумму, состоящую из цифр и цифр). Это довольно нетривиально, когда вы не можете ссылаться на аргумент. Когда-либо. Вот эта красота
(&]&|
<*&d
&~bN
10
( )/+
/*
Это (
унарный хук , который означает, что он вызывает функцию ниже ( f
) на своем входе (текущее значение x
), а затем передает их оба в тестовую функцию right ( g
), то есть вычисляет g(f(x), x)
.
В нашем случае f(x)
это еще одна составная функция, которая получает пару с цифрой произведения и цифрой суммы x
. Это означает g
, что функция будет иметь все три значения, чтобы проверить, x
является ли она действительной.
Мы начнем с рассмотрения того, как f
вычисляется сумма и цифра произведения. Это f
:
&~b
10
( )/*
/+
&
тоже композиция (но наоборот). ~
каррирует, поэтому 10~b
дает функцию, которая вычисляет десятичные цифры числа, и, поскольку мы передаем это &
справа, это первое, что произойдет с вводом x
. Остальная часть использует этот список цифр для вычисления их суммы и произведения.
Чтобы вычислить сумму, мы можем сложить поверх нее сложение /+
. Аналогично, чтобы вычислить произведение, мы складываем умножение на него /*
. Чтобы объединить оба этих результата в пару, мы используем пару хуков, (
и )
. Структура этого:
()g
f
(Где f
и g
продукт и сумма, соответственно.) Давайте попробуем выяснить, почему это дает нам пару f(x)
и g(x)
. Обратите внимание, что правый хук )
имеет только один аргумент. В этом случае подразумевается, что другой аргумент;
свои аргументы в пару. Кроме того, хуки могут также использоваться в качестве бинарных функций (что будет иметь место здесь), и в этом случае они просто применяют внутреннюю функцию только к одному аргументу. Так что )
на самом деле на одну функцию g
дает функцию, которая вычисляет [x, g(y)]
. Используя это в левом хуке, вместе с f
, мы получаем [f(x), g(y)]
. Это, в свою очередь, используется в унарном контексте, что означает, что он на самом деле вызывается с, x == y
и поэтому мы получаем в соответствии с [f(x), g(x)]
требованиями. Уф.
Это оставляет только одну вещь, которая была нашей предыдущей тестовой функцией g
. Напомним, что это будет называтьсяg([p, s], x)
, где x
до сих пор текущее значение входного сигнала, p
является цифра продукта и s
является его цифра суммы. Это g
:
&]&|
<*&d
N
Для проверки делимости мы, очевидно, будем использовать модуль по модулю |
в Jellyfish. Несколько необычно, что он берет свой правый операнд по модулю своего левого операнда, что означает, что аргументы g
уже находятся в правильном порядке (такие арифметические функции, как эта, автоматически перебирают списки, так что это вычислит два отдельных модуля бесплатно) , Наше число делится как на произведение, так и на сумму, если в результате получается пара нулей. Чтобы проверить, так ли это, мы рассматриваем пару как список цифр base-2 ( d
). Результат этого равен нулю, только когда оба элемента пары равны нулю, поэтому мы можем отменить результат этого ( N
), чтобы получить истинное значение для того, разделяют ли оба значения входные данные. Следует отметить , что |
, d
иN
просто все составлены вместе с парой &
с.
К сожалению, это не полная история. Что если цифра продукта равна нулю? Деление и по модулю на ноль оба возвращают ноль в медузе. Хотя это может показаться странным соглашением, на самом деле оно оказывается несколько полезным (потому что нам не нужно проверять ноль перед выполнением по модулю). Однако это также означает, что мы можем получить ложный положительный результат, если сумма цифр делит входные данные, но произведение цифр равно нулю (например, ввод 10
).
Мы можем исправить это, умножив наш результат делимости на произведение цифр (поэтому, если произведение цифр равно нулю, оно также превратит наше истинное значение в ноль). Оказывается, проще умножить результат делимости на пару продукта и суммы, а затем извлечь результат из продукта.
Чтобы умножить результат на пару, нам нужно вернуться к более раннему значению (паре). Это делается с помощью fork ( ]
). Вилы как крючки на стероидах. Если вы дадите им две функции f
и g
, они представляют двоичную функцию, которая вычисляет f(a, g(a, b))
. В нашем случае a
это пара продукт / сумма, b
текущее входное значение, g
наш тест делимости и f
умножение. Так что все это вычисляется [p, s] * ([p, s] % x == [0, 0])
.
Осталось только извлечь первое значение этого, которое является окончательным значением тестовой функции, используемой в итераторе. Это так же просто, как composing ( &
) форк с функцией head<
, которая возвращает первое значение списка.