p
\Ai
\&
>(&]&|0
<*&d
&~bN
10
( )/+
/*
Experimente online!
Explicação
Este é de longe o programa mais elaborado (e também o mais longo) que escrevi na Jellyfish até agora. Não tenho idéia se vou conseguir resolver isso de uma maneira compreensível, mas acho que vou ter que tentar.
O Jellyfish fornece um operador de iteração bastante geral \
, que ajuda muito a "encontrar o enésimo item ". Uma de suas semânticas é "iterar uma função em um valor até que uma função de teste separada forneça algo verdadeiro" (de fato, a função de teste recebe o elemento atual e o último, mas só faremos com que ela olhe para o elemento atual) . Podemos usar isso para implementar uma função "próximo número válido". Outra sobrecarga de \
"itera uma função em um valor inicial N vezes". Podemos usar nossa função anterior e iterá-la em 0
N vezes, onde N é a entrada. Tudo isso é configurado de forma bastante concisa com esta parte do código:
p
\Ai
\&
> 0
(As razões pelas quais 0
a entrada real da função resultante é mais complicada e eu não irei abordá-las aqui.)
O problema de tudo isso é que não passaremos o valor atual para a função de teste manualmente. O \
operador fará isso por nós. Então, agora construímos uma única função unária (por meio de composições, ganchos, garfos e curry) que pega um número e nos diz se é um número válido (isto é, um número dividido pela soma dos dígitos e pelo produto de dígitos). Isso é bastante trivial quando você não pode se referir ao argumento. Sempre. É essa beleza:
(&]&|
<*&d
&~bN
10
( )/+
/*
O (
é um gancho unário , o que significa que ele chama a função abaixo ( f
) em sua entrada (o valor atual x
) e passa os dois para a função de teste à direita ( g
), ou seja, calcula g(f(x), x)
.
No nosso caso, f(x)
é outra função composta que obtém um par com o produto de dígitos e a soma de dígitos de x
. Isso significa g
que será uma função que possui todos os três valores para verificar se x
é válido.
Começaremos analisando como f
calcula a soma e o dígito do produto. Isto é f
:
&~b
10
( )/*
/+
&
também é composição (mas o contrário). ~
está currying, portanto, 10~b
fornece uma função que calcula os dígitos decimais de um número e, como estamos passando isso para &
a direita, essa é a primeira coisa que acontecerá com a entrada x
. O restante usa essa lista de dígitos para calcular sua soma e produto.
Para calcular uma soma, podemos dobrar a adição sobre ela, o que é /+
. Da mesma forma, para calcular o produto, dobramos a multiplicação sobre ele /*
. Para combinar esses dois resultados em um par, usamos um par de ganchos, (
e )
. A estrutura disso é:
()g
f
(Onde f
e que g
são produto e soma, respectivamente.) Vamos tentar descobrir por que isso nos dá um par de f(x)
e g(x)
. Observe que o gancho certo )
tem apenas um argumento. Nesse caso, o outro argumento está implícito em ser o ;
que agrupa seus argumentos em um par. Além disso, os ganchos também podem ser usados como funções binárias (o que será o caso aqui), caso em que simplesmente aplicam a função interna apenas a um argumento. Então, )
na verdade, uma única função g
fornece uma função que calcula [x, g(y)]
. Usando isso no gancho esquerdo, junto com f
, obtemos [f(x), g(y)]
. Isso, por sua vez, é usado em um contexto unário, o que significa que na verdade é chamado com x == y
e, portanto, terminamos com [f(x), g(x)]
o necessário. Ufa.
Isso deixa apenas uma coisa, que foi nossa função de teste anterior g
. Lembre-se de que ele será chamado como g([p, s], x)
onde x
ainda é o valor de entrada atual, p
é seu produto de dígitos e s
sua soma de dígitos. Isto é g
:
&]&|
<*&d
N
Para testar a divisibilidade, obviamente usaremos o módulo, que está |
no Jellyfish. De maneira um tanto incomum, ele usa seu operando do lado direito e seu operando do lado esquerdo, o que significa que os argumentos g
já estão na ordem correta (funções aritméticas como essa encadeiam automaticamente as listas, portanto, ele calculará os dois módulos separados gratuitamente) . Nosso número é divisível pelo produto e pela soma, se o resultado for um par de zeros. Para verificar se é esse o caso, tratamos o par como uma lista de dígitos da base 2 ( d
). O resultado disso é zero, somente quando os dois elementos do par são zero, portanto, podemos negar o resultado disso ( N
) para obter um valor verdadeiro para se ambos os valores dividem a entrada. Note-se que |
, d
eN
são simplesmente todos compostos em conjunto com um par de &
s.
Infelizmente, essa não é a história completa. E se o produto com dígitos for zero? Divisão e módulo por zero retornam zero na água-viva. Embora isso possa parecer uma convenção um tanto estranha, na verdade acaba sendo útil (porque não precisamos verificar o zero antes de fazer o módulo). No entanto, também significa que podemos obter um falso positivo, se a soma dos dígitos dividir a entrada, mas o produto do dígito for zero (por exemplo, entrada 10
).
Podemos corrigir isso multiplicando nosso resultado de divisibilidade pelo produto de dígito (portanto, se o produto de dígito for zero, ele também transformará nosso valor de verdade em zero). É mais simples multiplicar o resultado da divisibilidade pelo par de produto e soma e extrair o resultado do produto posteriormente.
Para multiplicar o resultado pelo par, precisamos voltar a um valor anterior (o par). Isso é feito com um garfo ( ]
). Garfos são como ganchos com esteróides. Se você lhes der duas funções f
e g
, elas representam uma função binária que calcula f(a, g(a, b))
. No nosso caso, a
é o par produto / soma, b
é o valor de entrada atual, g
é o nosso teste de divisibilidade e f
é a multiplicação. Então, tudo isso calcula [p, s] * ([p, s] % x == [0, 0])
.
Tudo o que resta agora é extrair o primeiro valor disso, que é o valor final da função de teste usada no iterador. Isso é tão simples quanto compor ( &
) o fork com a função head<
, que retorna o primeiro valor de uma lista.