Mini-Flak, 6851113 cykli
Program (dosłownie)
Wiem, że większość ludzi nie spodziewa się, że quine Mini-Flak będzie używać znaków niedrukowalnych, a nawet znaków wielobajtowych (dzięki czemu kodowanie jest odpowiednie). Jednak ta quine tak, a nie drukowalne, w połączeniu z rozmiarem quine (93919 znaków zakodowanych jako 102646 bajtów UTF-8), utrudniają umieszczenie programu w tym poście.
Jednak program jest bardzo powtarzalny i jako taki kompresuje się naprawdę dobrze. Aby cały program był dostępny dosłownie z Stack Exchange, istnieje xxd
odwracalny gzip
zrzut heksowy skompresowanej wersji pełnej wersji quine ukrytej za składaną poniżej:
00000000: 1f8b 0808 bea3 045c 0203 7175 696e 652e .......\..quine.
00000010: 6d69 6e69 666c 616b 00ed d9db 6a13 4118 miniflak....j.A.
00000020: 0060 2f8b f808 0d64 a1c1 1dc8 4202 c973 .`/....d....B..s
00000030: 4829 4524 0409 22e2 5529 a194 1242 1129 H)E$..".U)...B.)
00000040: d2d7 ca93 f9cf 4c4c d45b 9536 e6db 6967 ......LL.[.6..ig
00000050: 770e 3bc9 ffed eca9 edb7 b1a4 9ad2 6a1d w.;...........j.
00000060: bfab 75db c6c6 6c5f 3d4f a5a6 8da6 dcd8 ..u...l_=O......
00000070: 465b d4a5 5a28 4bd9 719d 727b aa79 f9c9 F[..Z(K.q.r{.y..
00000080: 43b6 b9d7 8b17 cd45 7f79 d3f4 fb65 7519 C......E.y...eu.
00000090: 59ac 9a65 bfdf 8f86 e6b2 69a2 bc5c 4675 Y..e......i..\Fu
000000a0: d4e4 bcd9 5637 17b9 7099 9b73 7dd3 fcb2 ....V7..p..s}...
000000b0: 4773 b9bc e9bd b9ba 3eed 9df7 aeaf 229d Gs......>.....".
000000c0: e6ed 5eae 3aef 9d46 21b2 5e4d bd28 942e ..^.:..F!.^M.(..
000000d0: 6917 d71f a6bf 348c 819f 6260 dfd9 77fe i.....4...b`..w.
000000e0: df86 3e84 74e4 e19b b70e 9af0 111c fa0d ..>.t...........
000000f0: d29c 75ab 21e3 71d7 77f6 9d8f f902 6db2 ..u.!.q.w.....m.
00000100: b8e1 0adf e9e0 9009 1f81 f011 18d8 1b33 ...............3
00000110: 72af 762e aac2 4760 6003 1bd8 698c c043 r.v...G``...i..C
00000120: 8879 6bde 9245 207c 04ae 5ce6 2d02 e1bb .yk..E |..\.-...
00000130: 7291 4540 57f8 fe0d 6546 f89b a70b 8da9 r.E@W...eF......
00000140: f5e7 03ff 8b8f 3ad6 a367 d60b f980 679d ......:..g....g.
00000150: d3d6 1c16 f2ff a767 e608 57c8 c27d c697 .......g..W..}..
00000160: 4207 c140 9e47 9d57 2e50 6e8e c215 b270 B..@.G.W.Pn....p
00000170: bdf6 9926 9e47 9d05 ce02 0ff0 5ea7 109a ...&.G......^...
00000180: 8ba6 b5db 880b 970b 9749 2864 47d8 1b92 .........I(dG...
00000190: 39e7 9aec 8f0e 9e93 117a 6773 b710 ae53 9........zgs...S
000001a0: cd01 17ee b30e d9c1 15e6 6186 7a5c dc26 ..........a.z\.&
000001b0: 9750 1d51 610a d594 10ea f3be 4b7a 2c37 .P.Qa.......Kz,7
000001c0: 2f85 7a14 8fc4 a696 304d 4bdf c143 8db3 /.z.....0MK..C..
000001d0: d785 8a96 3085 2acc 274a a358 c635 8d37 ....0.*.'J.X.5.7
000001e0: 5f37 0f25 8ff5 6854 4a1f f6ad 1fc7 dbba _7.%..hTJ.......
000001f0: 51ed 517b 8da2 4b34 8d77 e5b2 ec46 7a18 Q.Q{..K4.w...Fz.
00000200: ffe8 3ade 6fed b2f2 99a3 bae3 c949 9ab5 ..:.o........I..
00000210: ab75 d897 d53c b258 a555 1b07 63d6 a679 .u...<.X.U..c..y
00000220: 4a51 5ead a23a 6a72 9eb6 d569 960b f3dc JQ^..:jr...i....
00000230: 9ceb 53fa 658f 345f ad07 6f6f efce 06ef ..S.e.4_..oo....
00000240: 0677 b791 cef2 f620 57bd 1b9c 4521 b241 .w..... W...E!.A
00000250: 4d83 2894 2eaf a140 8102 050a 1428 50a0 M.(....@.....(P.
00000260: 4081 0205 0a14 2850 a040 8102 050a 1428 @.....(P.@.....(
00000270: 50a0 4081 0205 0a14 2850 a040 8102 050a P.@.....(P.@....
00000280: 1428 50a0 4081 0205 0a14 2850 a040 8102 .(P.@.....(P.@..
00000290: 050a 1428 50a0 4081 0205 0a14 2850 a040 ...(P.@.....(P.@
000002a0: 8102 050a 1428 50a0 4081 0205 0a14 2850 .....(P.@.....(P
000002b0: a040 8102 050a 1428 50a0 4081 0205 0a14 .@.....(P.@.....
000002c0: 2850 a040 8102 050a 1428 50a0 4081 0205 (P.@.....(P.@...
000002d0: 0a14 2850 a040 8102 050a 1428 50a0 4081 ..(P.@.....(P.@.
000002e0: 0205 0a14 2850 a040 8102 050a 1428 50a0 ....(P.@.....(P.
000002f0: 4081 0205 0a14 2850 a040 8102 050a 1428 @.....(P.@.....(
00000300: 50a0 4081 0205 0a14 2850 a040 8102 050a P.@.....(P.@....
00000310: 1428 50a0 4081 0205 0a14 2850 a040 8102 .(P.@.....(P.@..
00000320: 050a 1428 50a0 4081 0205 0a14 2850 a040 ...(P.@.....(P.@
00000330: 8102 050a 1428 50a0 4081 0205 0a14 2850 .....(P.@.....(P
00000340: a040 8102 050a 1428 50a0 4081 0205 0a14 .@.....(P.@.....
00000350: 2850 a040 8102 050a 1428 50a0 4081 0205 (P.@.....(P.@...
00000360: 0a14 2850 a040 8102 050a 1428 50a0 4081 ..(P.@.....(P.@.
00000370: 0205 0a14 2850 a01c 14ca 7012 cbb4 a6e9 ....(P....p.....
00000380: e6db e6b1 e4b1 9e4c 4ae9 d3be f5f3 745b .......LJ.....t[
00000390: 37a9 3d6a af49 7489 a6e9 ae5c 96dd 488f 7.=j.It....\..H.
000003a0: d31f 5da7 fbad 5d56 3e73 5277 7cf5 aa7b ..]...]V>sRw|..{
000003b0: 3fbc df7c e986 c3ba 5ee4 3c6f 74f7 c3e1 ?..|....^.<ot...
000003c0: 301a bb45 d795 9afb fbdc 1495 65d5 6d9b 0..E........e.m.
000003d0: baf7 a5b4 a87d 4a5b d7fd b667 b788 ec27 .....}J[...g...'
000003e0: c5d8 28bc b96a 9eda 7a50 524d 290a a5cb ..(..j..zPRM)...
000003f0: cbef 38cb c3ad f690 0100 ..8.......
(Tak, jest tak powtarzalny, że można nawet zobaczyć powtórzenia po skompresowaniu).
Pytanie brzmi: „Poleciłbym również nie uruchamiać twojego programu w TIO. TIO jest nie tylko wolniejszy niż interpreter na pulpicie, ale także upłynie limit czasu za około minutę. Byłoby to niezwykle imponujące, gdyby komuś udało się zdobyć wystarczająco niskie wyniki, aby uruchomić ich program przed przekroczeniem limitu czasu TIO. ” Mogę to zrobić! Uruchomienie na TIO zajmuje około 20 sekund przy użyciu interpretera Ruby: Wypróbuj online!
Program (czytelnie)
Teraz podałem wersję programu, którą mogą czytać komputery, wypróbujmy wersję, którą ludzie mogą przeczytać. Przekształciłem bajty tworzące quine na stronę kodową 437 (jeśli mają ustawiony wysoki bit) lub obrazy kontrolne Unicode (jeśli są to kody sterujące ASCII), dodałem białe znaki (wszelkie istniejące wcześniej białe znaki zostały przekonwertowane na obrazy kontrolne ), zakodowane według długości przebiegu przy użyciu składni «string×length»
, a niektóre bity o dużej ilości danych pominęły:
␠
(((()()()()){}))
{{}
(({})[(()()()())])
(({})(
{{}{}((()[()]))}{}
(((((((({})){}){}{})){}{}){}){}())
{
({}(
(␀␀!S␠su! … many more comment characters … oq␝qoqoq)
(«()×35» («()×44» («()×44» («()×44» («()×44» («()×45»
… much more data encoded the same way …
(«()×117»(«()×115»(«()×117»
«000010101011┬â┬ … many more comment characters … ┬â0┬â┬à00␈␈
)[({})(
([({})]({}{}))
{
((()[()]))
}{}
{
{
({}(((({}())[()])))[{}()])
}{}
(({}))
((()[()]))
}{}
)]{}
%Wwy$%Y%ywywy$wy$%%%WwyY%$$wy%$$%$%$%$%%wy%ywywy'×almost 241»
,444454545455┬ç┬ … many more comment characters … -a--┬ü␡┬ü-a␡┬ü
)[{}()])
}{}
{}({}())
)[{}])
(({})(()()()()){})
}{}{}␊
(„Prawie 241” wynika z tego, że 241-sza kopia nie ma końca '
, ale poza tym jest identyczna z pozostałą 240.)
Wyjaśnienie
O komentarzach
Pierwszą rzeczą do wyjaśnienia jest to, co jest z postaciami do drukowania i innymi śmieciami, które nie są poleceniami Mini-Flak? Może ci się wydawać, że dodawanie komentarzy do quine tylko utrudnia pracę, ale jest to konkurencja prędkości (a nie konkurencja wielkościowa), co oznacza, że komentarze nie zaszkodzą szybkości programu. Tymczasem Brain-Flak, a tym samym Mini-Flak, po prostu zrzuca zawartość stosu na standardowe wyjście; gdybyś musiał upewnić się, że stos zawierał tylkopostaci, które składają się na polecenia twojego programu, będziesz musiał spędzić cykle czyszcząc stos. W tej chwili Brain-Flak ignoruje większość postaci, o ile upewnimy się, że elementy stosu śmieci nie są prawidłowymi poleceniami Brain-Flak (co czyni go poliglotą Brain-Flak / Mini-Flak) i nie są negatywne ani zewnętrzne w zakresie Unicode, możemy po prostu zostawić je na stosie, pozwolić im na wyjście i umieścić ten sam znak w naszym programie w tym samym miejscu, aby zachować właściwość quine.
Jest jeden szczególnie ważny sposób, w jaki możemy z tego skorzystać. Quine działa przy użyciu długiego ciągu danych, a zasadniczo całe wyjście z quinu jest wytwarzane przez formatowanie ciągu danych na różne sposoby. Jest tylko jeden ciąg danych, mimo że program ma wiele elementów; więc musimy być w stanie użyć tego samego ciągu danych do wydrukowania różnych części programu. Sztuczka „śmieciowe dane nie mają znaczenia” pozwala nam to zrobić w bardzo prosty sposób; przechowujemy znaki tworzące program w ciągu danych, dodając lub odejmując wartość do lub od kodu ASCII. W szczególności znaki składające się na początek programu są przechowywane jako kod ASCII + 4, znaki tworzą sekcję, która jest powtarzana prawie 241 razy jako kod ASCII - 4,każdy znak ciągu danych z przesunięciem; jeśli, na przykład, wydrukujemy go z 4 dodanymi do każdego kodu znakowego, otrzymamy jedno powtórzenie powtarzanej sekcji z kilkoma komentarzami przed i po. (Te komentarze to po prostu inne sekcje programu, z przesuniętymi kodami znaków, aby nie tworzyły żadnych prawidłowych poleceń Brain-Flak, ponieważ dodano nieprawidłowe przesunięcie. Musimy unikać poleceń Brain-Flak, a nie tylko Mini- Polecenia Flak, aby uniknąć naruszenia części pytania o ograniczonym źródle ; wybór przesunięć został zaprojektowany, aby to zapewnić.)
Z powodu tej sztuczki komentowania, w rzeczywistości musimy być w stanie wyprowadzić ciąg danych sformatowany na dwa różne sposoby: a) zakodowany w taki sam sposób jak w źródle, b) jako kody znaków z określonym przesunięciem dodanym do każdego kodu. To ogromne uproszczenie, które sprawia, że dodatkowa długość jest tego warta.
Struktura programu
Ten program składa się z czterech części: wprowadzenie, ciąg danych, formatator ciągu danych i zakończenie. Intro i outro są w zasadzie odpowiedzialne za uruchamianie ciągu danych i jego formatyzatora w pętli, określając za każdym razem odpowiedni format (tj. Czy kodować czy offsetować i jakiego przesunięcia użyć). Ciąg danych jest tylko danymi i jest jedyną częścią quine, dla której znaki, które go tworzą, nie są dosłownie określone w ciągu danych (robienie tego byłoby oczywiście niemożliwe, ponieważ musiałoby być dłuższe niż on sam); jest więc napisany w sposób, który jest szczególnie łatwy do zregenerowania. Formatyzator ciągu danych składa się z 241 prawie identycznych części, z których każda formatuje określony układ odniesienia z 241 ciągu ciągu danych.
Każda część programu może być utworzona za pomocą ciągu danych i jego formatyzatora w następujący sposób:
- Aby wygenerować wyjście, sformatuj ciąg danych z przesunięciem +8
- Aby utworzyć formatyzator ciągu danych, sformatuj ciąg danych z przesunięciem +4, 241 razy
- Aby utworzyć ciąg danych, sformatuj ciąg danych, kodując go do formatu źródłowego
- Aby utworzyć wprowadzenie, sformatuj ciąg danych z przesunięciem -4
Wystarczy więc spojrzeć na działanie tych części programu.
Ciąg danych
(«()×35» («()×44» («()×44» («()×44» («()×44» («()×45» …
Potrzebujemy prostego kodowania ciągu danych, ponieważ musimy być w stanie odwrócić kodowanie w kodzie Mini-Flak. Nie możesz stać się o wiele prostszy!
Kluczową ideą tej quine (oprócz sztuczki komentowania) jest zauważenie, że w zasadzie jest tylko jedno miejsce, w którym możemy przechowywać duże ilości danych: „sumy wartości zwracanych poleceń” w różnych poziomach zagnieżdżania źródła programu. (Jest to powszechnie znane jako trzeci stos, chociaż Mini-Flak nie ma drugiego stosu, więc „stos roboczy” jest prawdopodobnie lepszą nazwą w kontekście Mini-Flak.) Inne możliwości przechowywania danych to główny / pierwszy stos (który nie działa ponieważ tam właśnie musi iść nasz wynik i nie możemy przenieść wyjścia poza magazyn w zdalnie efektywny sposób) i zakodowane w bignum w jednym elemencie stosu (co jest nieodpowiednie dla tego problemu, ponieważ zajmuje to wykładniczy czas wyodrębnij z niego dane); gdy je wyeliminujesz, działający stos będzie jedyną pozostałą lokalizacją.
Aby „przechowywać” dane na tym stosie, używamy niezbalansowanych poleceń (w tym przypadku pierwszej połowy (…)
polecenia), które zostaną później zrównoważone w formatorze ciągu danych. Za każdym razem, gdy zamkniemy jedno z tych poleceń w formatyzatorze, przesunie on sumę danych pobranych z ciągu danych i zwróci wartości wszystkich poleceń na tym poziomie zagnieżdżenia w formatyzatorze; możemy zapewnić, że ta ostatnia doda zero, więc formatyzator po prostu widzi pojedyncze wartości pobrane z ciągu danych.
Format jest bardzo prosty: (
po nim następuje n kopii ()
, gdzie n to liczba, którą chcemy zapisać. (Uwaga: oznacza to, że możemy przechowywać tylko liczby nieujemne, a ostatni element ciągu danych musi być dodatni.)
Nieco intuicyjny punkt dotyczący ciągu danych to kolejność, w jakiej się znajduje. „Początek” ciągu danych to koniec bliżej początku programu, tj. Najbardziej zewnętrzny poziom zagnieżdżenia; ta część jest formatowana jako ostatnia (ponieważ formater działa od najbardziej wewnętrznego do najbardziej zewnętrznego poziomu zagnieżdżenia). Jednak pomimo sformatowania jako ostatniego, drukowane jest ono jako pierwsze, ponieważ wartości wypychane najpierw na stos są drukowane jako ostatnie przez interpreter Mini-Flak. Ta sama zasada dotyczy całego programu; najpierw musimy sformatować outro, potem formatator ciągu danych, potem ciąg danych, potem wprowadzenie, tj. odwrotność kolejności, w jakiej są przechowywane w programie.
Formater string danych
)[({})(
([({})]({}{}))
{
((()[()]))
}{}
{
{
({}(((({}())[()])))[{}()])
}{}
(({}))
((()[()]))
}{}
)]{}
Formatator ciągu danych składa się z 241 sekcji, z których każda ma identyczny kod (jedna sekcja ma nieznacznie inny komentarz), z których każda formatuje jeden określony znak ciągu danych. (Nie mogliśmy tutaj użyć pętli: potrzebujemy niezrównoważenia, )
aby odczytać ciąg danych poprzez dopasowanie jego niezrównoważonego (
i nie możemy umieścić jednego z nich w {…}
pętli, jedynej istniejącej formie pętli. Zamiast tego „ rozwiń „formatyzator i po prostu uzyskaj wprowadzenie / wyjście, aby wyprowadzić ciąg danych z przesunięciem formatyzatora 241 razy.)
)[({})( … )]{}
Najbardziej zewnętrzna część elementu formatującego odczytuje jeden element ciągu danych; prostota kodowania ciągu danych prowadzi do niewielkiej złożoności jego odczytu. Zaczynamy od zamknięcia niedopasowanego (…)
ciągu danych, a następnie negujemy ( […]
) dwie wartości: dane, które właśnie odczytaliśmy z ciągu danych ( ({})
) i wartość zwracaną przez resztę programu. Kopiujemy wartość zwrotną reszty elementu formattera za pomocą (…)
i dodajemy kopię do negowanej wersji za pomocą {}
. Ostateczny wynik jest taki, że zwracana wartość elementu ciągu danych i elementu formatującego razem to układ odniesienia minus układ odniesienia minus wartość zwrotu plus wartość zwrotu lub 0; jest to konieczne, aby kolejny element ciągu danych wygenerował poprawną wartość.
([({})]({}{}))
Formatyzator używa elementu najwyższego stosu, aby dowiedzieć się, w jakim trybie jest (0 = format w formatowaniu ciągu danych, każda inna wartość = przesunięcie wyjściowe z). Jednak po odczytaniu ciągu danych układ odniesienia znajduje się nad formatem na stosie, a my chcemy je odwrócić. Ten kod jest krótszą odmianą kodu wymiany Brain-Flak, przyjmując wartość powyżej b do b powyżej a + b ; jest nie tylko krótszy, ale także (w tym konkretnym przypadku) bardziej użyteczny, ponieważ efekt uboczny dodania b do a nie jest problematyczny, gdy b wynosi 0, a gdy b nie jest 0, wykonuje dla nas obliczenie przesunięcia.
{
((()[()]))
}{}
{
…
((()[()]))
}{}
Brain-Flak ma tylko jedną strukturę sterowania przepływem, więc jeśli chcemy czegoś innego niż while
pętla, zajmie to trochę pracy. Jest to struktura „negująca”; jeśli na stosie znajduje się 0, usuwa go, w przeciwnym razie umieszcza 0 na stosie. (Działa to po prostu: tak długo, jak na stosie nie ma 0, pchnij 1 - 1 do stosu dwa razy; kiedy skończysz, pop element górnego stosu.)
Możliwe jest umieszczenie kodu w strukturze negacji, jak pokazano tutaj. Kod będzie działał tylko wtedy, gdy górna część stosu była niezerowa; więc jeśli mamy dwie struktury negować, przyjmując wierzchołek stosu dwa elementy nie są zarówno zero, będą one znoszą się nawzajem, ale każdy kod wewnątrz pierwszej struktury będzie działać tylko wtedy, gdy element górny stos było niezerowe, a wewnątrz kodu druga struktura będzie działać tylko wtedy, gdy element najwyższego stosu miał wartość zero. Innymi słowy, jest to odpowiednik instrukcji if-then-else.
W klauzuli „then”, która działa, jeśli format jest niezerowy, w rzeczywistości nie mamy nic do roboty; chcemy przesunąć dane + przesunięcie do głównego stosu (aby można go było wyprowadzić na końcu programu), ale już tam jest. Mamy więc do czynienia tylko z przypadkiem kodowania elementu ciągu danych w postaci źródłowej.
{
({}(((({}())[()])))[{}()])
}{}
(({}))
Oto jak to robimy. {({}( … )[{}()])}{}
Struktura powinny być znane jako pętla z numerem konkretnej iteracji (który działa przesuwając licznik pętli do stosu pracy i trzymając go tam, to będzie bezpieczne od innych kodów, ponieważ dostęp do stosu pracy jest związana z poziom zagnieżdżenia programu). Ciało pętli ((({}())[()]))
tworzy trzy kopie elementu z górnego stosu i dodaje 1 do najniższego. Innymi słowy, przekształca 40 na szczycie stosu w 40 powyżej 40 powyżej 41, lub postrzegane jako ASCII, (
w (()
; wielokrotne wykonywanie tego (
przekształca (()
się (()()
w (()()()
i tak dalej, a zatem jest to prosty sposób na wygenerowanie naszego ciągu danych (zakładając, że (
stos jest już na szczycie stosu).
Gdy skończymy z pętlą, (({}))
duplikuje górę stosu (tak, że teraz się zaczyna, ((()…
a nie (()…
. Wiodąca (
kolejka użyje następnej kopii formatera łańcucha danych w celu sformatowania następnego znaku (rozwinie go w (()(()…
następnie (()()(()…
itd., więc generuje to separację (
w ciągu danych).
%Wwy$%Y%ywywy$wy$%%%WwyY%$$wy%$$%$%$%$%%wy%ywywy'
Ostatnie zainteresowanie budzi formatowanie ciągów danych. OK, więc przeważnie jest to tylko przesunięcie o 4 punkty kodowe w dół; jednak apostrof na końcu może wydawać się nie na miejscu. '
(codepoint 39) zmieni się na +
(codepoint 43), co nie jest poleceniem Brain-Flak, więc być może zgadłeś, że jest tam w innym celu.
Powodem tego jest to, że formatyzator ciągu danych oczekuje, że już (
na stosie jest już znak (nie zawiera dosłownie 40). The'
znajduje się na początku bloku, który jest powtarzany w celu utworzenia formatera łańcucha danych, a nie na końcu, więc po tym, jak znaki formatera łańcucha danych zostały wypchnięte na stos (i kod ma zamiar przejść do drukowania łańcucha danych sam), outro dopasowuje 39 na górze stosu do 40, gotowych dla formatyzatora (tym razem uruchomionego formatyzatora, a nie jego reprezentacji w źródle), aby z niego skorzystać. Dlatego mamy „prawie 241” kopii formatera; w pierwszej kopii brakuje pierwszego znaku. I ten znak, apostrof, jest jednym z trzech znaków w ciągu danych, które nie odpowiadają kodowi Mini-Flak gdzieś w programie; jest tam wyłącznie jako metoda zapewnienia stałej.
Wprowadzenie i zakończenie
(((()()()()){}))
{{}
(({})[(()()()())])
(({})(
{{}{}((()[()]))}{}
(((((((({})){}){}{})){}{}){}){}())
{
({}(
(␀␀!S␠su! … many more comment characters … oq␝qoqoq)
…
)[{}()])
}{}
{}({}())
)[{}])
(({})(()()()()){})
}{}{}␊
Wstęp i zakończenie są koncepcyjnie tą samą częścią programu; Jedynym powodem, dla którego wyróżniamy to, jest to, że wyjście musi być wyprowadzone przed łańcuchem danych i jego formatyzatorem (aby drukował po nich), podczas gdy intro musi być wyprowadzane po nich (drukowanie przed nimi).
(((()()()()){}))
Zaczynamy od umieszczenia dwóch kopii 8 na stosie. Jest to przesunięcie dla pierwszej iteracji. Druga kopia jest spowodowana tym, że główna pętla oczekuje, że na stosie powyżej przesunięcia będzie znajdował się element śmieci, pozostawiony po teście, który decyduje o tym, czy istnieje pętla główna, dlatego musimy umieścić tam element śmieci, aby nie wyrzuca elementu, którego tak naprawdę chcemy; kopia jest najkrótszym (a więc najszybszym wyjściem) sposobem na zrobienie tego.
Istnieją inne przedstawienia liczby 8, które nie są już dłuższe niż ten. Jednak przy wybieraniu najszybszego kodu jest to zdecydowanie najlepsza opcja. Po pierwsze, użycie ()()()()
jest szybsze niż, powiedzmy, (()()){}
ponieważ pomimo tego, że oba mają długość 8 znaków, pierwszy jest szybszy o jeden cykl, ponieważ (…)
jest liczony jako 2 cykle, ale ()
tylko jako jeden. Zapisanie jednego cyklu jest jednak pomijalne w porównaniu do znacznie większej uwagi na quine : (
i )
mają znacznie niższe punkty kodowe niż {
i }
, więc wygenerowanie dla nich fragmentu danych będzie znacznie szybsze (a fragment danych zajmie mniej miejsca w kodzie, zbyt).
{{} … }{}{}
Główna pętla. To nie liczy iteracji (jest to while
pętla, a nie for
pętla i używa testu, aby się wyłamać). Po wyjściu odrzucamy dwa górne elementy stosu; górny element jest nieszkodliwym 0, ale elementem poniżej będzie „format do użycia przy następnej iteracji”, który (będący ujemnym przesunięciem) jest liczbą ujemną, a jeśli na stosie są jakieś liczby ujemne, gdy Mini -Flak kończy działanie, interpreter zawiesza się, próbując je wyprowadzić.
Ponieważ ta pętla używa jawnego testu do wybicia, wynik tego testu zostanie pozostawiony na stosie, więc odrzucamy go jako pierwszą czynność (jego wartość nie jest przydatna).
(({})[(()()()())])
Ten kod popycha 4 i f - 4 powyżej elementu stosu f , pozostawiając ten element na miejscu. Format z wyprzedzeniem obliczamy format następnej iteracji (podczas gdy mamy stałą wartość 4) i jednocześnie ustawiamy stos w odpowiedniej kolejności dla kilku kolejnych części programu: będziemy używać f jako formatu dla ta iteracja i 4 są potrzebne wcześniej.
(({})( … )[{}])
Zapisuje to kopię f - 4 na stosie roboczym, dzięki czemu możemy go użyć do następnej iteracji. (Wartość f nadal będzie w tym momencie obecna, ale będzie w niezręcznym miejscu na stosie, a nawet gdybyśmy mogli manewrować nią w odpowiednie miejsce, musielibyśmy spędzić cykle odejmując od niej 4, i wykonuje cykl drukowania kodu, aby wykonać to odejmowanie. O wiele łatwiej po prostu zapisać go teraz.)
{{}{}((()[()]))}{}
Test sprawdzający, czy przesunięcie wynosi 4 (tzn. F - 4 wynosi 0). Jeśli tak, drukujemy formatyzator ciągu danych, więc musimy uruchomić ciąg danych i jego formator 241 razy, a nie tylko raz z tym przesunięciem. Kod jest dość prosty: jeśli f -4 jest niezerowe, zamień f -4 i samą 4 na parę zer; w obu przypadkach pop element górnego stosu. Na stosie mamy teraz liczbę powyżej f , 4 (jeśli chcemy wydrukować tę iterację 241 razy) lub 0 (jeśli chcemy wydrukować ją tylko raz).
(
((((((({})){}){}{})){}{}){}){}
()
)
Jest to interesujący rodzaj stałej Brain-Flak / Mini-Flak; długa linia tutaj reprezentuje liczbę 60. Możesz być zdezorientowany brakiem ()
, które zwykle są wszędzie w stałych Brain-Flak; nie jest to zwykła liczba, ale liczba kościelna, która interpretuje liczby jako operację powielania. Na przykład, widziana tutaj cyfra Kościoła dla 60, tworzy 60 kopii danych wejściowych i łączy je wszystkie w jedną wartość; w Brain-Flak jedyne, co możemy połączyć, to zwykłe liczby, przez dodanie, więc w rezultacie dodajemy 60 kopii góry stosu, a tym samym mnożąc górę stosu przez 60.
Na marginesie możesz użyć wyszukiwarki liczb niedociążonych , która generuje cyfry kościelne w składni niedociążenia, aby znaleźć odpowiednią liczbę również w Mini-Flak. Liczby niedociążone (inne niż zero) używają operacji „zduplikuj górny element stosu” :
i „połącz dwa górne elementy stosu” *
; obie te operacje występują w Brain-Flak, tak po prostu przełożyć :
do )
, *
do {}
, przedrostek {}
i dodać tyle (
na początek równowagi (ten stosuje dziwną mieszankę głównego stosu i stosu pracy, ale działa).
Ten konkretny fragment kodu używa kościelnej cyfry 60 (w rzeczywistości fragmentu „pomnóż przez 60”), wraz z przyrostem, do wygenerowania wyrażenia 60 x + 1. Więc jeśli mielibyśmy 4 z poprzedniego kroku, to daje nam wartość z 241, lub jeśli mieliśmy 0, otrzymujemy po prostu wartość 1, tj. to poprawnie oblicza liczbę potrzebnych iteracji.
Wybór 241 nie jest przypadkowy; wybrano wartość a) w przybliżeniu długość, przy której program i tak skończyłby, oraz b) 1 więcej niż 4 razy zaokrąglona liczba. Okrągłe liczby, w tym przypadku 60, mają zwykle krótsze reprezentacje jako liczby kościelne, ponieważ masz większą elastyczność w kopiowaniu czynników. Program zawiera później dopełnienie, aby dokładnie zwiększyć długość do 241.
{
({}(
…
)[{}()])
}{}
Jest to pętla for, jak poprzednio, która po prostu uruchamia w niej kod kilka razy równy górnej części głównego stosu (który zużywa; sam licznik pętli jest przechowywany na stosie roboczym, ale widoczność jest to powiązane z poziomem zagnieżdżenia programu, a zatem nie jest możliwe, aby cokolwiek innego niż sama pętla for mogła z nim współdziałać). To faktycznie uruchamia łańcuch danych i jego formatyzator 1 lub 241 razy, a ponieważ podaliśmy teraz wszystkie wartości, których używaliśmy do naszych obliczeń przepływu sterującego z głównego stosu, mamy format do użycia na nim, gotowy do formatyzator do użycia.
(␀␀!S␠su! … many more comment characters … oq␝qoqoq)
Komentarz tutaj nie jest całkowicie pozbawiony zainteresowania. Po pierwsze, istnieje kilka poleceń Brain-Flak; )
na końcu jest naturalnie generowane jako efekt uboczny sposobu przejścia między poszczególnymi segmentami pracy programu, a więc (
na początku było ręcznie dodać do niego wagi (i mimo długości komentarza wewnątrz, kładąc komentarz wnętrze ()
komenda jest jeszcze ()
komenda, więc wszystko robi jest dodać 1 do wartości zwracanej ciągu danych i jej formater, coś co dla pętli całkowicie ignoruje).
Co ważniejsze, te znaki NUL na początku komentarza wyraźnie nie są niczym przesunięte (nawet różnica między +8 a -4 nie wystarcza, aby zamienić a (
w NUL). Są to czyste dopełnienia, dzięki którym 239-elementowy ciąg danych może zawierać maksymalnie 241 elementów (które łatwo się opłacają: wygenerowanie 1 vs. 239 zamiast 1 vs. 241 przy obliczaniu liczby wymaganych iteracji wymagałoby znacznie więcej niż dwóch bajtów ). Jako znak wypełniający zastosowano NUL, ponieważ ma on najniższy możliwy punkt kodowy (co powoduje, że kod źródłowy ciągu danych jest krótszy, a zatem szybszy do wydrukowania).
{}({}())
Upuść element najwyższego stosu (format, którego używamy), dodaj 1 do następnego (ostatni znak, który ma zostać wydrukowany, tj. Pierwszy znak do wydrukowania, z właśnie sformatowanej sekcji programu). Nie potrzebujemy już starego formatu (nowy format ukrywa się na stosie roboczym); a przyrost jest w większości przypadków nieszkodliwy i zmienia '
na jednym końcu źródłową reprezentację formatyzatora łańcucha danych na (
(wymagany na stosie przy następnym uruchomieniu formatyzatora, aby sformatować sam łańcuch danych). Potrzebujemy takiego przekształcenia w outro lub intra, bo zmusza każdy ciąg danych formatyzatora elementu zacząć (
stałaby się nieco bardziej skomplikowana (jak musielibyśmy zamknąć (
, a następnie cofnąć swoje działanie później), imusielibyśmy (
gdzieś wygenerować dodatkową, ponieważ mamy tylko prawie 241 kopii formatera, a nie wszystkie 241 (więc najlepiej, aby taki nieszkodliwy znak '
był tym, którego brakuje).
(({})(()()()()){})
Na koniec test wyjścia z pętli. Obecny szczyt głównego stosu to format, którego potrzebujemy do następnej iteracji (która właśnie wróciła ze stosu roboczego). Spowoduje to skopiowanie go i dodanie 8 do kopii; wynikowa wartość zostanie odrzucona następnym razem w pętli. Jeśli jednak właśnie wydrukowaliśmy wprowadzenie, przesunięcie wynosi -4, więc przesunięcie dla „następnej iteracji” będzie wynosić -8; -8 + 8 wynosi 0, więc pętla zakończy się, a nie będzie kontynuowana na iteracji.