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 0N 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 0a 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 gque será uma função que possui todos os três valores para verificar se xé válido.
Começaremos analisando como fcalcula a soma e o dígito do produto. Isto é f:
&~b
10
( )/*
/+
&também é composição (mas o contrário). ~está currying, portanto, 10~bfornece 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 fe que gsã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 gfornece 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 == ye, 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 xainda é o valor de entrada atual, pé seu produto de dígitos e ssua 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 gjá 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 |, deNsã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 fe 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.