Golfscript - 12 znaków
{,1\{)*}/}:f
Pierwsze kroki z Golfscript - Factorial krok po kroku
Oto coś dla ludzi, którzy próbują nauczyć się gry w golfa. Warunkiem jest podstawowa znajomość gry w golfa i umiejętność czytania dokumentacji gry w golfa.
Dlatego chcemy wypróbować nasze nowe narzędzie do gry w golfa . Zawsze dobrze jest zacząć od czegoś prostego, więc zaczynamy od silni. Oto wstępna próba, oparta na prostym pseudokodzie imperatywnym:
# pseudocode: f(n){c=1;while(n>1){c*=n;n--};return c}
{:n;1:c;{n 1>}{n c*:c;n 1-:n;}while c}:f
Biała spacja jest bardzo rzadko używana w golfscript. Najłatwiejszym sposobem na pozbycie się białych znaków jest użycie różnych nazw zmiennych. Każdy token może być użyty jako zmienna (patrz strona składni ). Przydatne żetonów do wykorzystania jako zmienne są znaki specjalne, takie jak |
, &
, ?
- generalnie nic nie wykorzystane gdzie indziej w kodzie. Są one zawsze analizowane jako tokeny jednoznakowe. Dla kontrastu, zmienne takie n
będą wymagały spacji do wypchnięcia liczby do stosu po. Liczby są zasadniczo zmiennymi wstępnie zainicjowanymi.
Jak zawsze pojawią się oświadczenia, które możemy zmienić bez wpływu na wynik końcowy. W golfscript, wszystko jest true chyba 0
, []
,""
, i {}
(zobacz to ). Tutaj możemy zmienić warunek wyjścia pętli na prosty {n}
(zapętlamy dodatkowy czas i kończymy, gdy n = 0).
Podobnie jak w golfie w dowolnym języku, pomaga poznać dostępne funkcje. Na szczęście lista jest bardzo krótka jak na golfa. Możemy zmienić 1-
, aby (
zapisać inny charakter. Obecnie kod wygląda następująco: ( gdybyśmy chcieli, moglibyśmy użyć 1
zamiast |
tutaj, co spowodowałoby usunięcie inicjalizacji).
{:n;1:|;{n}{n|*:|;n(:n;}while|}:f
Ważne jest, aby dobrze wykorzystać stos, aby uzyskać najkrótsze rozwiązania (ćwiczyć praktykę). Zasadniczo, jeśli wartości są używane tylko w małym segmencie kodu, może nie być konieczne przechowywanie ich w zmiennych. Usuwając działającą zmienną produktu i po prostu używając stosu, możemy zapisać sporo znaków.
{:n;1{n}{n*n(:n;}while}:f
Oto coś innego do przemyślenia. Usuwamy zmienną n
ze stosu na końcu korpusu pętli, ale następnie wypychamy ją natychmiast po. W rzeczywistości przed rozpoczęciem pętli usuwamy ją również ze stosu. Zamiast tego powinniśmy zostawić go na stosie i możemy pozostawić warunek pętli pusty.
{1\:n{}{n*n(:n}while}:f
Może możemy nawet całkowicie wyeliminować zmienną. Aby to zrobić, będziemy musieli cały czas przechowywać zmienną na stosie. Oznacza to, że potrzebujemy dwóch kopii zmiennej na stosie na końcu sprawdzania warunku, abyśmy nie stracili go po sprawdzeniu. Co oznacza, że będziemy mieli zbędne0
po zakończeniu pętli stos, ale łatwo to naprawić.
To prowadzi nas do naszego optymalnego while
rozwiązania pętli!
{1\{.}{.@*\(}while;}:f
Teraz nadal chcemy to skrócić. Oczywistym celem powinno być słowo while
. Patrząc na dokumentację, istnieją dwie realne alternatywy - rozwinąć i zrobić . Gdy masz do wyboru różne trasy, spróbuj rozważyć zalety obu. Unfold to „prawie czasowa pętla”, więc w przybliżeniu zmniejszymy 5 znaków while
o 4 w /
. Jeśli chodzi o to do
, przecinamy while
o 3 znaki i łączymy dwa bloki, co może uratować kolejny znak lub dwa.
Korzystanie z do
pętli ma dużą wadę . Ponieważ sprawdzanie warunku jest wykonywane po jednokrotnym wykonaniu treści, wartość parametru 0
będzie niepoprawna, więc może być konieczne użycie instrukcji if. Powiem wam teraz, że rozwinięcie jest krótsze (niektóre rozwiązania do
są podane na końcu). Śmiało, spróbuj, kod, który już mamy, wymaga minimalnych zmian.
{1\{}{.@*\(}/;}:f
Wspaniały! Nasze rozwiązanie jest teraz super krótkie i skończyliśmy tutaj, prawda? Nie. To jest 17 znaków, a J ma 12 znaków. Nigdy nie przyznawaj się do porażki!
Teraz myślisz z ... rekurencją
Korzystanie z rekurencji oznacza, że musimy użyć rozgałęzionej struktury. Niestety, ale ponieważ czynnik można tak zwięźle wyrazić rekurencyjnie, wydaje się to realną alternatywą dla iteracji.
# pseudocode: f(n){return n==0?n*f(n-1):1}
{:n{n.(f*}1if}:f # taking advantage of the tokeniser
Cóż, to było łatwe - gdybyśmy wcześniej spróbowali rekurencji, moglibyśmy nawet nie spojrzeć na while
pętlę! Nadal mamy tylko 16 znaków.
Tablice
Tablice są zazwyczaj tworzone na dwa sposoby - za pomocą [
i ]
znaków, lub z ,
funkcji. Jeśli zostanie wykonany z liczbą całkowitą na górze stosu, ,
zwraca tablicę o tej długości z arr [i] = i.
Do iteracji po tablicach mamy trzy opcje:
{block}/
: push, block, push, block, ...
{block}%
: [push, block, push, block, ...] (ma to pewne niuanse, np. wartości pośrednie są usuwane ze stosu przed każdym push)
{block}*
: push, push, block, push, block, ...
Dokumentacja golfscript zawiera przykład użycia {+}*
do zsumowania zawartości tablicy. Sugeruje to, że możemy użyć, {*}*
aby uzyskać iloczyn macierzy.
{,{*}*}:f
Niestety nie jest to takie proste. Wszystkie elementy są wyłączone o jeden ( [0 1 2]
zamiast [1 2 3]
). Możemy użyć {)}%
do rozwiązania tego problemu.
{,{)}%{*}*}:f
Cóż, niezupełnie. To nie obsługuje poprawnie zera. Możemy obliczyć (n + 1)! / (N + 1), aby to naprawić, chociaż kosztuje to zdecydowanie za dużo.
{).,{)}%{*}*\/}:f
Możemy również spróbować obsłużyć n = 0 w tym samym segmencie, co n = 1. Jest to naprawdę bardzo krótkie do zrobienia, spróbuj wypracować najkrótsze, jakie możesz.
Nie jest tak dobry sortowania, po 7 znaków: [1\]$1=
. Pamiętaj, że ta technika sortowania ma użyteczne cele, takie jak narzucanie granic na liczbę (np. „[0 \ 100] $ 1 =)
Oto zwycięzca, posiadający tylko 3 znaki:.! +
Jeśli chcemy, aby przyrost i mnożenie występowały w tym samym bloku, powinniśmy iterować każdy element tablicy. Ponieważ nie budujemy tablicy, oznacza to, że powinniśmy używać {)*}/
, co prowadzi nas do najkrótszej implementacji silnia golfowego! Przy długości 12 znaków jest to powiązane z literą J!
{,1\{)*}/}:f
Rozwiązania premiowe
Zaczynając od prostego if
rozwiązania dla do
pętli:
{.{1\{.@*\(.}do;}{)}if}:f
Możemy wycisnąć z tego kilka dodatkowych. Trochę skomplikowane, więc musisz się przekonać, że te działają. Upewnij się, że rozumiesz je wszystkie.
{1\.!!{{.@*\(.}do}*+}:f
{.!{1\{.@*\(.}do}or+}:f
{.{1\{.@*\(.}do}1if+}:f
Lepszą alternatywą jest obliczenie (n + 1)! / (N + 1), co eliminuje potrzebę if
budowy.
{).1\{.@*\(.}do;\/}:f
Ale najkrótsze do
rozwiązanie wymaga kilku znaków, aby zamapować 0 na 1 i wszystko inne na siebie - więc nie potrzebujemy rozgałęzień. Tego rodzaju optymalizacji bardzo łatwo przeoczyć.
{.!+1\{.@*\(.}do;}:f
Dla wszystkich zainteresowanych podajemy tutaj kilka alternatywnych rozwiązań rekurencyjnych o takiej samej długości jak powyżej:
{.!{.)f*0}or+}:f
{.{.)f*0}1if+}:f
{.{.(f*}{)}if}:f
* Uwaga: W rzeczywistości nie testowałem wielu fragmentów kodu w tym poście, więc zachęcamy do informowania o ewentualnych błędach.