Porady dotyczące gry w golfa w pieprzeniu mózgu

23

Jakie masz ogólne wskazówki na temat gry w golfa? Szukam pomysłów, które można by zastosować do problemów z golfem w kodzie, które są przynajmniej w pewnym stopniu specyficzne dla pieprzenia mózgu (np. „Usuń komentarze” nie jest odpowiedzią). Proszę zamieścić jedną wskazówkę na odpowiedź.

Baran
źródło

Odpowiedzi:

25

Stawianie jednej wskazówki na odpowiedź byłoby zdecydowanie zbyt dużą liczbą odpowiedzi.

  • Naucz się myśleć w Brainfuck. Jest zupełnie inny niż cokolwiek innego. Odczytywanie i pisanie oraz przepisywanie i przepisywanie wielu programów typu „pieprzenie mózgu”. Język nie daje wiele do pracy, dlatego ważne jest, aby korzystać z tego, co daje, elastycznie i wydajnie. Nie pozwól, aby jakieś abstrakcje dostały się między tobą a językiem - wejdź tam i zmagaj się z tym.

  • Zapewnij sobie komfort dzięki nieniszczącej kontroli przepływu. Aby wyjść z pętli decyzyjnej, zamiast zerować początkową komórkę, kopiując ją w inne miejsce, a następnie kopiując z powrotem po opuszczeniu pętli, często lepiej jest przesunąć wskaźnik do wcześniej istniejącego zera w pobliżu. Tak, oznacza to, że wskaźnik będzie w różnych miejscach, w zależności od tego, czy przeszedłeś przez pętlę, ale oznacza to również, że te miejsca prawdopodobnie mają różne układy pobliskich zer i nonzerów, których można użyć do ponownego zsynchronizowania położenia wskaźnika za pomocą innej pętli. Ta technika ma podstawowe znaczenie dla dobrego programowania Brainfuck, a różne jej formy będą stale przydatne.

  • To i fakt, że każdy >lub <koszty oznaczają, że szczegóły układu pamięci są ważne. Wypróbuj tyle wariantów swojego układu, ile masz cierpliwości. I pamiętaj, że układ pamięci nie musi być sztywnym mapowaniem danych do lokalizacji. Może zmieniać się w trakcie wykonywania.

  • Na większą skalę rozważ, a nawet spróbuj wdrożyć wiele różnych algorytmów. Początkowo nie będzie oczywiste, który algorytm będzie najlepszy; może nawet nie być oczywiste, jakie podstawowe podejście będzie najlepsze i prawdopodobnie będzie to coś innego niż to, co byłoby najlepsze w normalnym języku.

  • Jeśli masz do czynienia z dużymi lub zmiennymi rozmiarami danych, sprawdź, czy jest jakiś sposób, aby poradzić sobie z nimi lokalnie, bez konieczności śledzenia, jak duże są twoje dane lub ich lokalizacji numerycznej.

  • Te same dane mogą być dwiema różnymi rzeczami. (Najczęściej liczba lub znak, a także niezerowy znacznik pozycyjny. Ale patrz random.b , gdzie bit licznika podwaja się jako wartość jednej komórki automatu komórkowego).

  • Ten sam kod może robić dwie różne rzeczy i jest o wiele łatwiej to zrobić w języku, w którym kod jest tak ogólny, jak <+<. Uważaj na takie możliwości. W rzeczywistości czasami możesz zauważyć, nawet w dobrze napisanym programie, że istnieją małe części, które można całkowicie usunąć, nic nie dodając, a przypadek nadal działałby bezbłędnie.

  • W większości języków często używasz kompilatora lub tłumacza, aby sprawdzić zachowanie programu. Język Brainfuck wymaga większej kontroli konceptualnej; jeśli potrzebujesz kompilatora, aby powiedzieć ci, co robi twój program, nie masz wystarczającej wiedzy na temat swojego programu i prawdopodobnie musisz się na niego jeszcze trochę gapić - przynajmniej jeśli chcesz mieć wystarczająco wyraźny obraz koncepcyjne halo podobnych programów, aby być dobrym w golfa. Ćwicząc, stworzysz kilkanaście wersji swojego programu, zanim spróbujesz uruchomić jedną, i do tego momentu będziesz mieć 95% pewności, że twoja najkrótsza będzie działać poprawnie.

  • Powodzenia! Bardzo niewiele osób próbuje zwięźle pisać Brainfuck, ale myślę, że to jedyny sposób, w jaki język może usprawiedliwić ciągłą uwagę - jako oszałamiająco niejasna forma sztuki.

Daniel Cristofani
źródło
3
Czytanie tego sprawia, że ​​chcę teraz spróbować programować w Brainfuck ...
Claudiu
Kurwa, myślałem, że rozpoznałem twoje imię. Wielbiciel programów typu „pieprzenie mózgu”, szczególnie twój superkompetentny tłumacz!
Jo King
„czasami możesz zauważyć ... że istnieją małe części, które można całkowicie usunąć, nic nie dodając, a przypadek nadal działałby bezbłędnie”. Jest to prawda, szczególnie dla początkujących. O ile pamiętam, napisałem tylko jedną odpowiedź w BF, ale historia wersji zawiera co najmniej 3 przypadki, w których grałem z programem i przypadkowo powiedziałem: „hej, program nadal działa, jeśli usunę ten bit! „
ETHprodukcje
„Wypróbuj tyle wariantów swojego układu, ile masz cierpliwości” lub możesz użyć siły : P
zastosuj
9

Kilka wskazówek tutaj:

Stałe:

Strona stałych Esolangs zawiera niezwykle przydatną listę najkrótszych sposobów tworzenia określonych wartości. Uważam, że sprawdzam tę stronę co najmniej dwa razy na program.

Początek wszystkiego:

+++[[<+>>++<-]>]

Spowoduje to ustawienie taśmy w formacie 3 * n ^ 2, który wygląda

3 6 12 24 48 96 192 128 0 0 '

Dlaczego to takie ważne?

Zejdźmy na dół listy:

  • 3 i 6 są nudne
  • 12: Blisko 10 (nowa linia) lub 13 (powrót karetki). Może być również użyty do licznika dla 0-9
  • 24: Blisko 26, liczba liter w alfabecie
  • 48: ASCII dla 0
  • 96: Blisko 97, ASCII dla a
  • 196 i 128: 196-128 = 64, blisko 65, ASCII dla A.

Na podstawie tego jednego algorytmu jesteśmy na początku praktycznie każdej sekwencji w zakresie ASCII, wraz z licznikiem dla każdej i nowej linii w zasięgu ręki.

Praktyczny przykład:

Drukowanie wszystkich wielkich i małych liter oraz cyfr.

Z algorytmem:

+++[[<+>>++<-]>]<<[-<->]<<<<++[->>+.>+.<<<]<--[>>.+<<-]

Bez:

+++++++++++++[->+++++++>++>+++++>++++>+<<<<<]>+++++>[-<+.>>.+<]>>---->---[-<.+>]

Większość bajtów spędzamy na inicjalizacji taśmy w drugim przykładzie. Niektóre z nich zostały zrównoważone przez dodatkowe ruchy w pierwszym przykładzie, ale ta metoda ma wyraźnie tę zaletę.

Kilka innych interesujących algorytmów w tym samym stylu:

3 * 2 ^ n + 1:

+++[[<+>>++<-]+>]
Tape: 4 7 13 25 49 65 197 129 1 0'

Powoduje to przesunięcie wartości o 1, co pozwala osiągnąć kilka rzeczy. Powoduje to, że 12 jest znakiem powrotu karetki, 64 to faktyczny początek wielkiej litery, a 24 bliżej 26.

2 ^ n:

+[[<+>>++<-]>]
Tape: 1 2 4 8 16 32 64 128

Ponieważ 64 jest dobre dla wielkich liter, 32 to ASCII dla spacji, a 128 może być użyte jako licznik dla 26 (130/5 = 26). Może to zaoszczędzić bajty w pewnych sytuacjach, w których cyfry i małe litery nie są potrzebne.

Wybierz implementację, która pasuje do pytania:

  • Komórki ujemne są prawie zawsze przydatne i nie ma powodu, aby ich unikać (chyba że nie zmienia to liczby bajtów)
  • Prawie dokładnie to samo z zawijaniem komórek, tym bardziej, że wiele stałych używa zawijania.
  • Arbitralne rozmiary komórek są przydatne do nieskończonych sekwencji matematycznych, takich jak nieskończone obliczanie sekwencji Fibonacciego ( +[[-<+>>+>+<<]>]) lub przetwarzanie większych / ujemnych liczb. Minusem jest to, że na niektóre typowe metody, takie jak [-]i [->+<]nie można polegać, na wypadek gdyby liczba była ujemna.
  • EOF jako 0, -1 lub bez zmian. Zazwyczaj preferowane jest 0, ponieważ można zapętlać całe dane wejściowe bez dodatkowych kontroli. -1 jest przydatne przy zapętlaniu struktur tablicowych. Nie znalazłem jeszcze zastosowania bez zmian :(.

Śledź, co się dzieje do cholery:

Przez cały czas powinieneś komentować, gdzie powinien znajdować się wskaźnik w stosunku do otaczających go danych, i upewnij się, że znasz zakres możliwych wartości każdej komórki. Jest to szczególnie ważne, gdy podzielisz wskaźnik przed pętlą, ponieważ później będziesz chciał ponownie połączyć dwie możliwości.

W dowolnym momencie mój kod jest zaśmiecony komentarzami w każdej innej linii, które wyglądają tak:

*0 *dat a_1 ?  0' !0 0*
 or
*0 *dat 0' ap1 0  !0 0*

Dodatkową radą jest nadanie symbolom specjalnego znaczenia. W powyższym przykładzie, 'gdzie jest wskaźnik, *oznacza powtórzenie w tym kierunku, ?oznacza komórkę o nieznanej wartości, !0oznacza komórkę niezerową, _jest substytutem -i pjest substytutem +. oroznacza, że ​​taśma może wyglądać jak jedno z przedstawień i musi być traktowana jako taka.

Twój schemat symboli niekoniecznie musi być taki sam jak mój (który ma kilka wad), po prostu musi być spójny. Jest to również niezwykle przydatne podczas debugowania, ponieważ można uruchomić go do tego momentu i porównać rzeczywistą taśmę z tym, co powinieneś mieć, co może wskazać potencjalne wady w kodzie.

Jo King
źródło
5

Moją główną radą byłoby nie.

OK, w porządku, chcesz czegoś bardziej przydatnego. BF jest już bardzo zwięzłym językiem, ale to, co naprawdę cię zabija, to arytmetyka, która skutecznie musi być wykonana jednoargumentowo. Warto przeczytać na stronie stałych w Esolang, aby dokładnie wybrać, jak efektywnie pisać duże liczby i jak najlepiej wykorzystać zawijanie.

Dostęp do pamięci jest również bardzo drogi. Ponieważ czytasz z taśmy, musisz pamiętać, gdzie porusza się głowa w danym momencie. W przeciwieństwie do innych języków, gdzie można po prostu napisać a, b, c, w bf trzeba wyraźnie przesunąć głowę pewna liczba bajtów lewo lub w prawo, więc trzeba mieć na uwadze to, gdzie można przechowywać co. Jestem prawie pewien, że optymalne uporządkowanie pamięci jest trudne, więc powodzenia.

ymbirtt
źródło
5

W tej odpowiedzi będę wielokrotnie odwoływał się do określonej komórki na taśmie. Nie ma znaczenia, która to komórka, ale jest to ta sama komórka w całej odpowiedzi. Na potrzeby tego wpisu nazywam tę komórkę „Todd”.

Próbując ustawić stałą wartość komórki, czasami opłaca się nie kończyć jej natychmiast. Powiedzmy na przykład, że chciałeś, aby Todd zawierał 30. Później w kodzie (który może modyfikować wartość Todda, ale nigdy go nie czyta), wrócisz do Todda. Jeśli wartość Todda wynosi 0, program kończy działanie. W przeciwnym razie wartość Todda jest drukowana na zawsze.

Według strony esolangs.org o stałych błędach w mózgu (które prawdopodobnie mogłyby stanowić sam w sobie wskazówkę!) Najkrótsza droga do uzyskania 30 to >+[--[<]>>+<-]>+. Prowadzenie >to jest tylko po to, aby upewnić się, że nic po lewej stronie wskaźnika nie zostanie zmodyfikowane, ale w tym przypadku założymy, że nas to nie obchodzi i upuszczamy. Za pomocą tego kodu Twój kod wyglądałby mniej więcej tak:

+[--[<]>>+<-]>+(MISC. CODE)(GO TO TODD)[.]

Możesz pomyśleć o pierwszej części kodu w ten sposób:

(SET TODD TO 30)(MISC. CODE)(GO TO TODD)[.]

Ale pamiętaj, dwa ostatnie znaki w tym fragmencie: >+. Tak samo dobrze jest myśleć o tym w ten sposób:

(SET TODD TO 29)(GO TO TODD)(ADD 1 TO TODD)(MISC. CODE)(GO TO TODD)[.]

Zauważ, że (GO TO TODD)dwa razy! Zamiast tego możesz napisać kod w ten sposób:

(SET TODD TO 29)(MISC. CODE)(GO TO TODD)(ADD 1 TO TODD)[.]
+[--[<]>>+<-](MISC. CODE)(GO TO TODD)+[.]

Zakładając, że liczba bajtów, których potrzebuje, (GO TO TODD)jest taka sama, jeden ruch mniej == jeden bajt mniej! Czasami fakt zmiany pozycji początkowej odbiera tę korzyść, ale nie zawsze.

podziemny monorail
źródło
0

Mała wskazówka dotycząca wyzwań bez wkładu. Możesz użyć ,zamiast [-], jeśli chcesz szybko wyczyścić komórkę, ponieważ większość interpreterów (w tym TIO.run jeden) ustawi zawartość komórki na reprezentację EOF równą zero. To sprawia, że ​​programy są trochę nieprzenośne, ale kogo to obchodzi w golfowym kodzie?

Krzysztof Szewczyk
źródło