p
\Ai
\&
>(&]&|0
<*&d
&~bN
10
( )/+
/*
Essayez-le en ligne!
Explication
C'est de loin le programme le plus élaboré (et aussi le plus long) que j'ai écrit dans Jellyfish jusqu'à présent. Je ne sais pas si je serai en mesure de décomposer cela d'une manière compréhensible, mais je suppose que je devrai essayer.
Jellyfish fournit un opérateur d'itération assez général \
, ce qui aide beaucoup à «trouver le Nth quelque chose ». L'une de ses sémantiques est "itérer une fonction sur une valeur jusqu'à ce qu'une fonction de test distincte donne quelque chose de vrai" (en fait, la fonction de test reçoit à la fois le courant et le dernier élément, mais nous ne ferons que regarder l'élément actuel) . Nous pouvons l'utiliser pour implémenter une fonction "prochain numéro valide". Une autre surcharge de \
"itère une fonction sur une valeur de départ N fois". Nous pouvons utiliser notre fonction précédente et l'itérer sur 0
N fois, où N est l'entrée. Tout cela est configuré de manière assez concise avec cette partie du code:
p
\Ai
\&
> 0
(Les raisons pour lesquelles 0
l'entrée réelle de la fonction résultante est là-bas sont un peu compliquées et je ne les aborderai pas ici.)
Le problème avec tout cela est que nous ne transmettrons pas la valeur actuelle à la fonction de test manuellement. L' \
opérateur le fera pour nous. Nous avons donc maintenant construire une seule fonction unaire (via les compositions, les crochets, les fourchettes et le curry) qui prend un nombre et nous dit si c'est un nombre valide (c'est-à-dire qui est divisé par sa somme de chiffres et son produit de chiffres). Ceci est assez non trivial lorsque vous ne pouvez pas vous référer à l'argument. Déjà. C'est cette beauté:
(&]&|
<*&d
&~bN
10
( )/+
/*
Le (
est un crochet unaire , ce qui signifie qu'il appelle la fonction ci-dessous ( f
) sur son entrée (la valeur actuelle x
), puis passe les deux à la fonction de test à droite ( g
), c'est-à-dire qu'il calcule g(f(x), x)
.
Dans notre cas, f(x)
est une autre fonction composite qui obtient une paire avec le produit numérique et la somme numérique de x
. Cela signifie que g
sera une fonction qui a les trois valeurs pour vérifier si elle x
est valide.
Nous allons commencer par regarder comment f
calcule la somme des chiffres et le produit des chiffres. C’est f
:
&~b
10
( )/*
/+
&
c'est aussi la composition (mais l'inverse). ~
currying 10~b
donne donc une fonction qui calcule les chiffres décimaux d'un nombre, et puisque nous passons cela à &
droite, c'est la première chose qui arrivera à l'entrée x
. Le reste utilise cette liste de chiffres pour calculer leur somme et leur produit.
Pour calculer une somme, nous pouvons plier l' addition sur elle, ce qui est /+
. De même, pour calculer le produit, nous multiplions la multiplication par dessus /*
. Pour combiner ces deux résultats en une paire, nous utilisons une paire de crochets, (
et )
. La structure de ceci est:
()g
f
(Où f
et g
sont le produit et la somme, respectivement.) Essayons de comprendre pourquoi cela nous donne une paire de f(x)
et g(x)
. Notez que le crochet droit )
n'a qu'un seul argument. Dans ce cas, l'autre argument est supposé être celui ;
qui encapsule ses arguments dans une paire. De plus, les hooks peuvent également être utilisés comme fonctions binaires (ce qui sera le cas ici), auquel cas ils n'appliquent la fonction interne qu'à un seul argument. Donc vraiment )
sur une seule fonction g
donne une fonction qui calcule [x, g(y)]
. En utilisant cela dans un crochet gauche, avec f
, nous obtenons [f(x), g(y)]
. Ceci, à son tour, est utilisé dans un contexte unaire, ce qui signifie qu'il est en fait appelé avec x == y
et nous nous retrouvons donc [f(x), g(x)]
comme requis. Phew.
Cela ne laisse qu'une seule chose, qui était notre fonction de test précédente g
. Rappelez-vous qu'il sera appelé comme g([p, s], x)
où x
est toujours la valeur d'entrée actuelle, p
est son produit numérique et s
sa somme numérique. C’est g
:
&]&|
<*&d
N
Pour tester la divisibilité, nous utiliserons évidemment le modulo, qui est |
dans Jellyfish. Un peu inhabituellement, il prend son opérande de droite modulo son opérande de gauche, ce qui signifie que les arguments à g
sont déjà dans le bon ordre (les fonctions arithmétiques comme celle- ci passent automatiquement sur les listes, donc cela calculera les deux modules séparés gratuitement) . Notre nombre est divisible par le produit et la somme, si le résultat est une paire de zéros. Pour vérifier si c'est le cas, nous traitons la paire comme une liste de chiffres en base 2 ( d
). Le résultat de ceci est zéro, uniquement lorsque les deux éléments de la paire sont nuls, nous pouvons donc annuler le résultat de this ( N
) pour obtenir une valeur vraie pour savoir si les deux valeurs divisent l'entrée. Notez que |
, d
etN
sont simplement tous composés avec une paire de &
s.
Malheureusement, ce n'est pas toute l'histoire. Et si le produit numérique est nul? La division et le modulo par zéro renvoient tous les deux zéro dans les méduses. Bien que cela puisse sembler une convention quelque peu étrange, cela s'avère en fait quelque peu utile (car nous n'avons pas besoin de vérifier zéro avant de faire le modulo). Cependant, cela signifie également que nous pouvons obtenir un faux positif, si la somme des chiffres divise l'entrée, mais que le produit numérique est zéro (par exemple, l'entrée 10
).
Nous pouvons résoudre ce problème en multipliant notre résultat de divisibilité par le produit numérique (donc si le produit numérique est nul, cela transformera également notre valeur véridique en zéro). Il s'avère plus simple de multiplier le résultat de divisibilité par la paire de produit et de somme, et d'extraire ensuite le résultat du produit.
Pour multiplier le résultat avec la paire, nous devons en quelque sorte revenir à une valeur antérieure (la paire). Cela se fait avec un fork ( ]
). Les fourchettes sont un peu comme des crochets sur les stéroïdes. Si vous leur donnez deux fonctions f
et g
, elles représentent une fonction binaire qui calcule f(a, g(a, b))
. Dans notre cas, a
est la paire produit / somme, b
est la valeur d'entrée actuelle, g
est notre test de divisibilité et f
est la multiplication. Donc, tout cela calcule [p, s] * ([p, s] % x == [0, 0])
.
Il ne reste plus qu'à en extraire la première valeur, qui est la valeur finale de la fonction de test utilisée dans l'itérateur. C'est aussi simple que de composer ( &
) la fourche avec la fonction head<
, qui retourne la première valeur d'une liste.