p
\Ai
\&
>(&]&|0
<*&d
&~bN
10
( )/+
/*
Wypróbuj online!
Wyjaśnienie
To zdecydowanie najbardziej rozbudowany (i zarazem najdłuższy) program, jaki do tej pory napisałem w Jellyfish. Nie mam pojęcia, czy będę w stanie to naprawić w zrozumiały sposób, ale chyba będę musiał spróbować.
Meduza zapewnia dość ogólny operator iteracji \
, który bardzo pomaga w „znalezieniu N-tego czegoś ”. Jedną z jego semantyek jest „iteracja funkcji na wartości, dopóki osobna funkcja testowa nie da czegoś prawdziwego” (w rzeczywistości funkcja testowa odbiera zarówno bieżący, jak i ostatni element, ale sprawimy, że spojrzy tylko na bieżący element) . Możemy to wykorzystać do zaimplementowania funkcji „następny ważny numer”. Kolejnym przeciążeniem \
jest „iteracja funkcji na wartości początkowej N razy”. Możemy użyć naszej poprzedniej funkcji i iterować ją 0
N razy, gdzie N jest wejściem. Wszystko to jest skonfigurowane dość zwięźle z tą częścią kodu:
p
\Ai
\&
> 0
(Powody, dla 0
których faktyczne dane wejściowe do wynikowej funkcji są tam, są nieco skomplikowane i nie będę tu wchodził.)
Problem polega na tym, że nie będziemy ręcznie przekazywać bieżącej wartości do funkcji testowej. \
Operator zrobi to za nas. Mamy teraz skonstruowaną pojedynczą funkcję jednoargumentową (poprzez kompozycje, haczyki, widelce i curry), która pobiera liczbę i mówi nam, czy jest to poprawna liczba (tj. Ta, która jest podzielona przez sumę cyfr i iloczyn cyfrowy). Jest to dość nietrywialne, gdy nie można odnieść się do argumentu. Zawsze. To jest to piękno:
(&]&|
<*&d
&~bN
10
( )/+
/*
(
To jednoskładnikowa, hak , co oznacza, że wywołuje funkcję (poniżej f
) na wejściu (wartość bieżąca x
), a następnie przechodzi oboje do funkcji testowej w prawo ( g
), to jest to oblicza g(f(x), x)
.
W naszym przypadku f(x)
jest to kolejna funkcja złożona, która uzyskuje parę z iloczynem cyfr i sumą cyfr x
. Oznacza to, g
że będzie funkcją, która ma wszystkie trzy wartości, aby sprawdzić, czy x
jest poprawna.
Zaczniemy od przyjrzenia się, jak f
oblicza sumę cyfr i iloczyn cyfrowy. To jest f
:
&~b
10
( )/*
/+
&
to także kompozycja (ale na odwrót). ~
jest curry, więc 10~b
daje funkcję, która oblicza cyfry dziesiętne liczby, a ponieważ przekazujemy to &
od prawej, to pierwsza rzecz, która stanie się z danymi wejściowymi x
. Pozostała część wykorzystuje tę listę cyfr do obliczenia ich sumy i produktu.
Aby obliczyć sumę, możemy nad nią dodać wartość dodaną, to znaczy /+
. Podobnie, aby obliczyć iloczyn, składamy na nim mnożenie /*
. Aby połączyć oba te wyniki w parę, używamy pary haczyków (
i )
. Struktura tego jest następująca:
()g
f
(Gdzie f
i g
są odpowiednio iloczynem i sumą.) Spróbujmy dowiedzieć się, dlaczego daje nam to parę f(x)
i g(x)
. Zauważ, że prawy haczyk )
ma tylko jeden argument. W tym przypadku sugerowany jest drugi argument, ;
który owija swoje argumenty w parę. Ponadto haki mogą być również używane jako funkcje binarne (co będzie miało miejsce w tym przypadku), w którym to przypadku po prostu stosują funkcję wewnętrzną tylko do jednego argumentu. Tak naprawdę )
na jednej funkcji g
daje funkcję, która się oblicza [x, g(y)]
. Używając tego w lewym haczyku, wraz z f
, otrzymujemy [f(x), g(y)]
. To z kolei jest używane w kontekście jednoargumentowym, co oznacza, że tak naprawdę jest wywoływane za pomocą, x == y
więc kończymy [f(x), g(x)]
na wymaganym. Uff
Pozostaje tylko jedna rzecz, która była naszą wcześniejszą funkcją testową g
. Przypomnijmy, że będzie on wywoływany, ponieważ g([p, s], x)
gdzie x
wciąż jest aktualna wartość wejściowa, p
jest to iloczyn cyfrowy i s
suma cyfr. To jest g
:
&]&|
<*&d
N
Aby przetestować podzielność, oczywiście użyjemy modulo, które znajduje się |
w meduzach. Niecodziennie bierze swój prawostronny modulo lewostronny, co oznacza, że argumenty g
są już we właściwej kolejności (funkcje arytmetyczne takie jak ten automatycznie przewijają listy, więc to obliczy dwa osobne moduły za darmo) . Nasza liczba jest podzielna przez iloczyn produktu i sumy, jeśli wynikiem jest para zer. Aby sprawdzić, czy tak jest, traktujemy tę parę jako listę cyfr składających się z 2 cyfr ( d
). Wynik tego wynosi zero, tylko gdy oba elementy pary są równe zero, więc możemy zanegować wynik this ( N
), aby uzyskać prawdziwą wartość, czy obie wartości dzielą dane wejściowe. Należy zauważyć, że |
, d
iN
są po prostu złożone razem z parą &
s.
Niestety, to nie jest pełna historia. Co jeśli iloczyn cyfrowy to zero? Dzielenie i modulo przez zero zwracają zero w meduzach. Chociaż może się to wydawać nieco dziwną konwencją, w rzeczywistości okazuje się być nieco przydatne (ponieważ nie musimy sprawdzać zera przed wykonaniem modulo). Oznacza to jednak również, że możemy uzyskać fałszywie dodatni wynik, jeśli suma cyfr dzieli dane wejściowe, ale iloczyn cyfrowy wynosi zero (np. Dane wejściowe 10
).
Możemy to naprawić, mnożąc nasz wynik podzielności przez iloczyn cyfrowy (więc jeśli iloczyn cyfrowy to zero, to zmieni naszą prawdziwą wartość również na zero). Okazuje się, że łatwiej jest pomnożyć wynik podzielności przez parę produktu i sumę, a następnie wyodrębnić wynik z produktu.
Aby pomnożyć wynik przez parę, musimy nieco wrócić do wcześniejszej wartości (pary). Odbywa się to za pomocą fork ( ]
). Widelce są trochę jak haczyki na sterydach. Jeśli dasz im dwie funkcje f
i g
, reprezentują one funkcję binarną, która się oblicza f(a, g(a, b))
. W naszym przypadku a
jest to para produkt / suma, b
jest bieżącą wartością wejściową, g
jest naszym testem podzielności i f
jest pomnożeniem. Wszystko to oblicza[p, s] * ([p, s] % x == [0, 0])
.
Teraz pozostaje tylko wyodrębnić pierwszą wartość tego, która jest końcową wartością funkcji testowej użytej w iteratorze. Jest to tak proste, jak skomponowanie ( &
) rozwidlenia za pomocą funkcji head<
, która zwraca pierwszą wartość listy.