p
\Ai
\&
>(&]&|0
<*&d
&~bN
10
( )/+
/*
Try it online!
Explanation
This is by far the most elaborate (and also the longest) program I've written in Jellyfish so far. I have no idea whether I'll be able to break this down in an understandable manner, but I guess I'll have to try.
Jellyfish provides a fairly general iteration operator, \
, which helps a lot with "finding the Nth something". One of its semantics is "iterate a function on a value until a separate test function gives something truthy" (in fact, the test function receives both the current and the last element, but we'll only make it look at the current element). We can use this to implement a "next valid number" function. Another overload of \
is "iterate a function on a starting value N times". We can use our previous function and iterate it on 0
N times, where N is the input. All of that is set up fairly concisely with this part of the code:
p
\Ai
\&
> 0
(The reasons why 0
, the actual input to the resulting function, is over there are a bit complicated and I won't go into them here.)
The issue with all of this is, that we won't be passing the current value to the test function manually. The \
operator will do this for us. So we now have construct a single unary function (via compositions, hooks, forks and currying) which takes a number and tells us whether it's a valid number (i.e. one which is divided by its digit sum and digit product). This is fairly non-trivial when you can't refer to the argument. Ever. It's this beauty:
(&]&|
<*&d
&~bN
10
( )/+
/*
The (
is a unary hook, which means that it calls the function below (f
) on its input (the current value x
), and then passes both of them to the test function to the right (g
), that is it computes g(f(x), x)
.
In our case, f(x)
is another composite function which obtains a pair with the digit product and digit sum of x
. That means g
will be a function that has all three values to check if x
is valid.
We'll start by looking at how f
computes the digit sum and digit product. This is f
:
&~b
10
( )/*
/+
&
is also composition (but the other way round). ~
is currying so 10~b
gives function that computes the decimal digits of a number, and since we're passing that to &
from the right, that's the first thing that will happen to the input x
. The remainder uses this list of digits to compute their sum and product.
To compute a sum, we can fold addition over it, which is /+
. Likewise, to compute the product we fold multiplication over it with /*
. To combine both of these results into a pair, we use a pair of hooks, (
and )
. The structure of this is:
()g
f
(Where f
and g
are product and sum, respectively.) Let's try to figure out why this gives us a pair of f(x)
and g(x)
. Note that the right hook )
only has one argument. In this case, the other argument is implied to be ;
which wraps its arguments in a pair. Furthermore, hooks can also be used as binary functions (which will be the case here) in which case they simply apply the inner function only to one argument. So really )
on a single function g
gives a function that computes [x, g(y)]
. Using this in a left hook, together with f
, we obtain [f(x), g(y)]
. This, in turn is used in a unary context, which mean that it's actually called with x == y
and so we end up with [f(x), g(x)]
as required. Phew.
That leaves only one thing, which was our earlier test function g
. Recall that it will be called as g([p, s], x)
where x
is still the current input value, p
is its digit product and s
is its digit sum. This is g
:
&]&|
<*&d
N
To test divisibility, we'll obviously use modulo, which is |
in Jellyfish. Somewhat unusually, it take its right-hand operand modulo its left-hand operand, which means that the arguments to g
are already in the right order (arithmetic functions like this automatically thread over lists, so this will compute the two separate moduli for free). Our number is divisible by both the product and sum, if the result is a pair of zeros. To check whether that's the case, we treat the pair as a list of base-2 digits (d
). The result of this is zero, only when both elements of the pair are zero, so we can negate the result of this (N
) to obtain a truthy value for whether both values divide the input. Note that |
, d
and N
are simply all composed together with a pair of &
s.
Unfortunately, that's not the full story. What if the digit product is zero? Division and modulo by zero both return zero in Jellyfish. While this may seem like a somewhat odd convention, it actually turns out to be somewhat useful (because we don't need to check for zero before doing the modulo). However it also means we can get a false positive, if the digit sum does divide the input, but the digit product is zero (e.g. input 10
).
We can fix this by multiplying our divisibility result by the digit product (so if the digit product is zero it will turn our truthy value into a zero as well). It turns out to be simpler to multiply the divisibility result with the pair of product and sum, and extract the result from the product afterwards.
To multiply the result with the pair, we kinda need to get back at an earlier value (the pair). This is done with a fork (]
). Forks are kinda like hooks on steroids. If you give them two functions f
and g
, they represent a binary function which computes f(a, g(a, b))
. In our case, a
is the product/sum pair, b
is the current input value, g
is our divisibility test, and f
is the multiplication. So all of this computes [p, s] * ([p, s] % x == [0, 0])
.
All that's left now is to extract the first value of this, which is the final value of the test function used in the iterator. This is as simple as composing (&
) the fork with the head function <
, which returns the first value of a list.
0
. \$\endgroup\$