p
\Ai
\&
>(&]&|0
<*&d
&~bN
10
( )/+
/*
Pruébalo en línea!
Explicación
Este es, con mucho, el programa más elaborado (y también el más largo) que he escrito en Jellyfish hasta ahora. No tengo idea de si podré analizar esto de una manera comprensible, pero supongo que tendré que intentarlo.
Jellyfish proporciona un operador de iteración bastante general \, que ayuda mucho a "encontrar el enésimo algo ". Una de sus semánticas es "iterar una función en un valor hasta que una función de prueba separada proporcione algo verdadero" (de hecho, la función de prueba recibe tanto el elemento actual como el último, pero solo haremos que mire el elemento actual) . Podemos usar esto para implementar una función de "siguiente número válido". Otra sobrecarga de \es "iterar una función en un valor inicial N veces". Podemos usar nuestra función anterior e iterarla en 0N veces, donde N es la entrada. Todo eso está configurado de manera bastante concisa con esta parte del código:
p
\Ai
\&
> 0
(Las razones por las cuales 0la entrada real a la función resultante está allí son un poco complicadas y no las abordaré aquí).
El problema con todo esto es que no pasaremos el valor actual a la función de prueba manualmente. El \operador hará esto por nosotros. Así que ahora hemos construido una única función unaria (a través de composiciones, ganchos, tenedores y curry) que toma un número y nos dice si es un número válido (es decir, uno que se divide por su suma de dígitos y producto de dígitos). Esto es bastante trivial cuando no puede referirse al argumento. Siempre. Es esta belleza:
(&]&|
<*&d
&~bN
10
( )/+
/*
El (es un unario gancho , lo que significa que llama a la función de abajo ( f) en su entrada (el valor actual x) y, a continuación, pasa a ambos a la función de prueba a la derecha ( g), que es lo calcula g(f(x), x).
En nuestro caso, f(x)es otra función compuesta que obtiene un par con el producto de dígitos y la suma de dígitos de x. Eso significa gque será una función que tiene los tres valores para verificar si xes válida.
Comenzaremos observando cómo fcalcula la suma de dígitos y el producto de dígitos. Esto es f:
&~b
10
( )/*
/+
&también es composición (pero al revés). ~es curry, así que 10~bda una función que calcula los dígitos decimales de un número, y dado que lo estamos pasando &desde la derecha, eso es lo primero que sucederá con la entrada x. El resto usa esta lista de dígitos para calcular su suma y producto.
Para calcular una suma, podemos doblar la suma sobre ella, que es /+. Del mismo modo, para calcular el producto doblamos la multiplicación sobre él /*. Para combinar ambos resultados en un par, utilizamos un par de ganchos (y ). La estructura de esto es:
()g
f
(Donde fy gson producto y suma, respectivamente.) Tratemos de descubrir por qué esto nos da un par de f(x)y g(x). Tenga en cuenta que el gancho derecho )solo tiene un argumento. En este caso, se supone que el otro argumento es el ;que envuelve sus argumentos en un par. Además, los ganchos también se pueden usar como funciones binarias (que será el caso aquí) en cuyo caso simplemente aplican la función interna solo a un argumento. Entonces, realmente )en una sola función gda una función que computa [x, g(y)]. Usando esto en un gancho izquierdo, junto con f, obtenemos [f(x), g(y)]. Esto, a su vez, se usa en un contexto unario, lo que significa que en realidad se llama con x == yy así terminamos [f(x), g(x)]según sea necesario. Uf.
Eso deja solo una cosa, que era nuestra función de prueba anterior g. Recuerde que se llamará como g([p, s], x)donde xsigue siendo el valor de entrada actual, pes su producto de dígitos y ses su suma de dígitos. Esto es g:
&]&|
<*&d
N
Para probar la divisibilidad, obviamente usaremos el módulo, que está |en Jellyfish. De manera algo inusual, toma su módulo de operando de la derecha a su operando de la izquierda, lo que significa que los argumentos gya están en el orden correcto (las funciones aritméticas como esta se enhebran automáticamente en las listas, por lo que esto calculará los dos módulos separados de forma gratuita) . Nuestro número es divisible tanto por el producto como por la suma, si el resultado es un par de ceros. Para verificar si ese es el caso, tratamos el par como una lista de dígitos de base-2 ( d). El resultado de esto es cero, solo cuando ambos elementos del par son cero, por lo que podemos negar el resultado de this ( N) para obtener un valor verdadero de si ambos valores dividen la entrada. Tenga en cuenta que |, dyNsimplemente están todos compuestos junto con un par de &s.
Desafortunadamente, esa no es la historia completa. ¿Qué pasa si el producto de dígitos es cero? La división y el módulo por cero devuelven cero en Jellyfish. Si bien esto puede parecer una convención un tanto extraña, en realidad resulta ser algo útil (porque no necesitamos verificar cero antes de hacer el módulo). Sin embargo, también significa que podemos obtener un falso positivo, si la suma de dígitos divide la entrada, pero el producto de dígitos es cero (por ejemplo, entrada 10).
Podemos solucionar esto multiplicando nuestro resultado de divisibilidad por el producto del dígito (por lo tanto, si el producto del dígito es cero, también convertirá nuestro valor verdadero en cero). Resulta más simple multiplicar el resultado de divisibilidad con el par de producto y suma, y extraer el resultado del producto después.
Para multiplicar el resultado con el par, necesitamos volver a un valor anterior (el par). Esto se hace con un tenedor ( ]). Los tenedores son como ganchos de esteroides. Si les da dos funciones fy g, representan una función binaria que computa f(a, g(a, b)). En nuestro caso, aes el par producto / suma, bes el valor de entrada actual, ges nuestra prueba de divisibilidad y fes la multiplicación. Entonces todo esto computa [p, s] * ([p, s] % x == [0, 0]).
Todo lo que queda ahora es extraer el primer valor de esto, que es el valor final de la función de prueba utilizada en el iterador. Esto es tan simple como componer ( &) la bifurcación con la función head< , que devuelve el primer valor de una lista.