Mini-Flak, 6851113 Zyklen
Das Programm (wörtlich)
Ich weiß, dass die meisten Leute nicht damit rechnen, dass ein Mini-Flak-Quine nicht druckbare Zeichen und sogar Mehrbyte-Zeichen verwendet (was die Codierung relevant macht). Diese Quine macht es jedoch ziemlich schwierig, das Programm in diesem Beitrag zu platzieren, und die Unprintables in Kombination mit der Größe der Quine (93919 Zeichen, codiert als 102646 Bytes UTF-8).
Das Programm ist jedoch sehr repetitiv und wird als solches sehr gut komprimiert . Damit das gesamte Programm buchstäblich von Stack Exchange aus verfügbar ist, gibt es einen xxd
reversiblen Hexdump einer gzip
komprimierten Version des vollständigen Quine, der sich hinter dem reduzierbaren darunter verbirgt:
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.......
(Ja, es ist so repetitiv, dass Sie sogar die Wiederholungen sehen können, nachdem es komprimiert wurde).
In der Frage steht: "Ich würde Ihnen auch wärmstens empfehlen, Ihr Programm nicht in TIO auszuführen. TIO ist nicht nur langsamer als der Desktop-Interpreter, sondern es tritt auch eine Zeitüberschreitung von etwa einer Minute auf. Es wäre äußerst beeindruckend, wenn es jemandem gelingen würde, niedrig genug zu sein, um ausgeführt zu werden ihr Programm, bevor TIO abgelaufen ist. " Ich kann das machen! Mit dem Ruby-Interpreter dauert es ungefähr 20 Sekunden, bis TIO ausgeführt wird: Probieren Sie es online aus!
Das Programm (lesbar)
Jetzt habe ich eine Version des Programms angegeben, die Computer lesen können. Probieren wir eine Version aus, die Menschen lesen können. Ich habe die Bytes, aus denen sich das Quine zusammensetzt, in Codepage 437 (sofern das hohe Bit gesetzt ist) oder Unicode-Steuerbilder (sofern es sich um ASCII-Steuercodes handelt) konvertiert und Leerzeichen hinzugefügt (alle zuvor vorhandenen Leerzeichen wurden in Steuerbilder konvertiert) ), «string×length»
lauflängencodiert mit der Syntax und einige datenintensive Bits beseitigt:
␠
(((()()()()){}))
{{}
(({})[(()()()())])
(({})(
{{}{}((()[()]))}{}
(((((((({})){}){}{})){}{}){}){}())
{
({}(
(␀␀!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␡┬ü
)[{}()])
}{}
{}({}())
)[{}])
(({})(()()()()){})
}{}{}␊
(Die "fast 241" liegt daran, dass in der 241. Kopie das Ende fehlt '
, sie ist jedoch ansonsten identisch mit der anderen 240.)
Erläuterung
Über die Kommentare
Die erste Sache, die erklärt werden muss, ist, was mit den nicht druckbaren Zeichen und anderem Müll los ist, die keine Mini-Flak-Befehle sind. Sie mögen denken, dass das Hinzufügen von Kommentaren zum Quine die Dinge nur schwieriger macht, aber dies ist ein Geschwindigkeitswettbewerb (kein Größenwettbewerb), was bedeutet, dass Kommentare die Geschwindigkeit des Programms nicht beeinträchtigen. Währenddessen wird der Inhalt des Stapels von Brain-Flak und damit von Mini-Flak auf die Standardausgabe ausgegeben. wenn Sie sicherstellen müssten, dass der Stapel enthalten ist nurFür die Zeichen, aus denen die Befehle Ihres Programms bestanden, müssten Sie Zyklen zum Reinigen des Stapels durchführen. So wie es ist, ignoriert Brain-Flak die meisten Zeichen, solange wir sicherstellen, dass Junk-Stack-Elemente keine gültigen Brain-Flak-Befehle sind (was dies zu einem Brain-Flak / Mini-Flak-Polyglot macht) und nicht negativ oder außerhalb sind Im Unicode-Bereich können wir sie einfach auf dem Stapel belassen, ausgeben lassen und dasselbe Zeichen in unser Programm an derselben Stelle einfügen, um die quine-Eigenschaft beizubehalten.
Es gibt eine besonders wichtige Möglichkeit, dies auszunutzen. Das Quine verwendet eine lange Datenzeichenfolge, und im Grunde wird die gesamte Ausgabe des Quine durch Formatieren der Datenzeichenfolge auf verschiedene Arten erzeugt. Es gibt nur einen Datenstring, obwohl das Programm mehrere Teile hat. Daher müssen wir in der Lage sein, denselben Datenstring zum Drucken verschiedener Teile des Programms zu verwenden. Mit dem Trick "Junk-Daten spielen keine Rolle" können wir dies auf sehr einfache Weise tun. Wir speichern die Zeichen, aus denen das Programm besteht, in der Datenfolge, indem wir einen Wert zu oder von ihrem ASCII-Code addieren oder subtrahieren. Insbesondere werden die Zeichen, aus denen der Programmstart besteht, als ASCII-Code + 4 gespeichert, die Zeichen, aus denen der Abschnitt besteht, der fast 241-mal als ASCII-Code - 4 wiederholt wird.jedes Zeichen der Datenkette mit einem Offset; Wenn wir es zum Beispiel mit 4 zu jedem Zeichencode drucken, erhalten wir eine Wiederholung des wiederholten Abschnitts mit einigen Kommentaren davor und danach. (Diese Kommentare sind einfach die anderen Abschnitte des Programms, wobei die Zeichencodes so verschoben sind, dass sie keine gültigen Brain-Flak-Befehle bilden, da der falsche Versatz hinzugefügt wurde. Wir müssen Brain-Flak-Befehlen ausweichen, nicht nur Mini- Flak-Befehle, um Verletzungen der eingeschränkten Quelle zu vermeiden der Frage ; die Auswahl von Offsets wurde entwickelt, um dies zu gewährleisten.)
Aufgrund dieses Kommentar-Tricks müssen wir eigentlich nur in der Lage sein, den auf zwei verschiedene Arten formatierten Datenstring auszugeben: a) auf die gleiche Weise wie in der Quelle codiert, b) als Zeichencodes mit einem festgelegten Versatz, der jedem Code hinzugefügt wird. Das ist eine enorme Vereinfachung, die die zusätzliche Länge absolut wert macht.
Programmstruktur
Dieses Programm besteht aus vier Teilen: dem Intro, dem Datenstring, dem Datenstring-Formatierer und dem Outro. Das Intro und Outro sind grundsätzlich dafür verantwortlich, den Datenstring und seinen Formatierer in einer Schleife auszuführen und dabei jedes Mal das entsprechende Format anzugeben (dh ob codiert oder versetzt werden soll und welcher Offset verwendet werden soll). Der Datenstring ist nur ein Datenstring und der einzige Teil des Quines, für den die Zeichen, aus denen er besteht, nicht wörtlich im Datenstring angegeben sind (dies wäre offensichtlich unmöglich, da es länger sein müsste als er selbst). Es ist so geschrieben, dass es besonders einfach ist, sich von selbst zu regenerieren. Der Datenkettenformatierer besteht aus 241 nahezu identischen Teilen, von denen jeder ein bestimmtes Datum aus den 241 in der Datenkette formatiert.
Jeder Teil des Programms kann wie folgt über den Datenstring und dessen Formatierer erstellt werden:
- Formatieren Sie den Datenstring mit Offset +8, um das Outro zu erzeugen
- Formatieren Sie die Datenzeichenfolge mit Offset +4, 241 mal, um den Datenzeichenfolge-Formatierer zu erstellen
- Formatieren Sie die Datenzeichenfolge, um sie zu erstellen, indem Sie sie in das Quellformat codieren
- Formatieren Sie die Datenzeichenfolge mit Offset -4, um das Intro zu erstellen
Wir müssen uns also nur ansehen, wie diese Teile des Programms funktionieren.
Die Datenzeichenfolge
(«()×35» («()×44» («()×44» («()×44» («()×44» («()×45» …
Wir benötigen eine einfache Codierung für den Datenstring, da wir die Codierung im Mini-Flak-Code umkehren können müssen. Einfacher geht es nicht!
Die Schlüsselidee hinter dieser Quine (abgesehen vom Kommentar-Trick) ist, dass es im Grunde nur einen Ort gibt, an dem wir große Datenmengen speichern können: die "Summen der Befehlsrückgabewerte" in den verschiedenen Verschachtelungsebenen der Programmquelle. (Dies ist allgemein als dritter Stapel bekannt "Arbeitsstapel", obwohl Mini-Flak keinen zweiten Stapel hat. "Arbeitsstapel" wahrscheinlich ein besserer Name im Mini-Flak-Kontext.) Die anderen Möglichkeiten zum Speichern von Daten wären main / first Stack (was nicht funktioniert, weil dort unsere Ausgabe abgelegt werden muss und wir die Ausgabe nicht auf eine remote effiziente Weise am Speicher vorbei verschieben können) und in ein Bignum in einem einzelnen Stack-Element codiert (was hierfür ungeeignet ist) Problem, weil es exponentiell lange dauert, Daten daraus zu extrahieren); wenn Sie diese beseitigen,
Um Daten auf diesem Stapel zu "speichern", verwenden wir unsymmetrische Befehle (in diesem Fall die erste Hälfte eines (…)
Befehls), die später im Formatierer der Datenzeichenfolge verteilt werden. Jedes Mal, wenn wir einen dieser Befehle im Formatierer schließen, wird die Summe eines aus der Datenzeichenfolge entnommenen Datums und die Rückgabewerte aller Befehle auf dieser Verschachtelungsebene im Formatierer übertragen. Wir können sicherstellen, dass letztere zu Null addieren, sodass der Formatierer nur einzelne Werte aus dem Datenstring sieht.
Das Format ist sehr einfach: (
gefolgt von n Kopien von ()
, wobei n die Zahl ist, die wir speichern möchten. (Beachten Sie, dass dies bedeutet, dass wir nur nicht negative Zahlen speichern können und das letzte Element der Datenzeichenfolge positiv sein muss.)
Ein etwas uninteressanter Punkt an der Datenzeichenfolge ist die Reihenfolge, in der sie sich befindet. Der "Anfang" der Datenzeichenfolge ist das Ende, das näher am Programmanfang liegt, dh die äußerste Verschachtelungsebene. Dieser Teil wird zuletzt formatiert (da der Formatierer von der innersten bis zur äußersten Verschachtelungsebene ausgeführt wird). Obwohl es zuletzt formatiert wurde, wird es zuerst gedruckt , da zuerst auf den Stapel geschobene Werte zuletzt vom Mini-Flak-Interpreter gedruckt werden. Dasselbe Prinzip gilt für das gesamte Programm. Wir müssen zuerst das Outro formatieren, dann den Datenstring-Formatierer, dann den Datenstring und dann das Intro, dh in umgekehrter Reihenfolge, in der sie im Programm gespeichert sind.
Der Formatierer der Datenzeichenfolge
)[({})(
([({})]({}{}))
{
((()[()]))
}{}
{
{
({}(((({}())[()])))[{}()])
}{}
(({}))
((()[()]))
}{}
)]{}
Der Datenstring-Formatierer besteht aus 241 Abschnitten mit identischem Code (ein Abschnitt hat einen geringfügig unterschiedlichen Kommentar), die jeweils ein bestimmtes Zeichen des Datenstrings formatieren. (Wir konnten hier keine Schleife verwenden: Wir benötigen eine unsymmetrische Schleife )
, um den Datenstring über den Abgleich mit der unsymmetrischen zu lesen (
, und wir können keine solche {…}
Schleife in eine Schleife einfügen, die einzige Form der Schleife, die existiert. Stattdessen verwenden wir "Entrollen Sie den Formatierer und veranlassen Sie einfach das Intro / Outro, die Datenzeichenfolge mit dem 241-fachen Versatz des Formatierers auszugeben.)
)[({})( … )]{}
Der äußerste Teil eines Formatierungselements liest ein Element der Datenzeichenfolge. Die einfache Codierung des Datenstrings führt zu einer geringen Komplexität beim Lesen. Wir schließen zunächst den nicht übereinstimmenden Wert in der Datenzeichenfolge (…)
und negieren dann ( […]
) zwei Werte: das Datum, das wir gerade aus der Datenzeichenfolge ( ({})
) gelesen haben, und den Rückgabewert des restlichen Programms. Wir kopieren den Rückgabewert des restlichen Formatierungselements mit (…)
und fügen die Kopie der negierten Version mit hinzu {}
. Das Endergebnis ist, dass der Rückgabewert des Datenzeichenfolgenelements und des Formatierungselements zusammen das Datum minus das Datum minus den Rückgabewert plus den Rückgabewert oder 0 ist. Dies ist erforderlich, damit das nächste Datenzeichenfolgenelement den richtigen Wert ergibt.
([({})]({}{}))
Der Formatierer verwendet das oberste Stapelelement, um zu ermitteln, in welchem Modus es sich befindet (0 = Format bei der Formatierung von Datenzeichenfolgen, jeder andere Wert = der Offset, mit dem ausgegeben werden soll). Nachdem Sie jedoch die Datenzeichenfolge gelesen haben, befindet sich das Datum über dem Format auf dem Stapel, und wir möchten, dass sie umgekehrt sind. Dieser Code ist eine kürzere Variante des Brain-Flak-Tauschcodes, wobei a über b nach b über a + b steht . Es ist nicht nur kürzer, sondern (in diesem speziellen Fall) auch nützlicher, da der Nebeneffekt des Hinzufügens von b zu a nicht problematisch ist, wenn b 0 ist, und wenn b nicht 0 ist, wird die Offsetberechnung für uns durchgeführt.
{
((()[()]))
}{}
{
…
((()[()]))
}{}
Brain-Flak hat nur eine Kontrollflussstruktur. Wenn wir also etwas anderes als eine while
Schleife wollen, wird es ein bisschen Arbeit kosten. Dies ist eine "negative" Struktur; Wenn oben auf dem Stapel eine 0 ist, wird diese entfernt, andernfalls wird eine 0 oben auf dem Stapel platziert. (Das funktioniert ganz einfach: Solange sich keine 0 auf dem Stapel befindet, drücken Sie zweimal 1 - 1 auf den Stapel. Wenn Sie fertig sind, platzieren Sie das oberste Stapelelement.)
Es ist möglich, Code in eine Negativstruktur einzufügen, wie hier zu sehen. Der Code wird nur ausgeführt, wenn der Anfang des Stapels ungleich Null war. so , wenn wir zwei negate Strukturen haben, die beiden oberen Stapelelemente unter der Annahme sind nicht beide Nullen, werden sie sich gegenseitig aufheben, aber jeder Code innerhalb der ersten Struktur wird nur ausgeführt , wenn das obere Stapelelement ungleich Null ist, und der Code innerhalb Die zweite Struktur wird nur ausgeführt, wenn das oberste Stapelelement Null war. Mit anderen Worten, dies entspricht einer if-then-else-Anweisung.
In der "then" -Klausel, die ausgeführt wird, wenn das Format nicht Null ist, haben wir eigentlich nichts zu tun. Was wir wollen, ist, die Daten + Offset auf den Hauptstapel zu schieben (damit sie am Ende des Programms ausgegeben werden können), aber es ist bereits da. Wir müssen uns also nur mit dem Codieren des Datenzeichenfolgenelements in Quellform befassen.
{
({}(((({}())[()])))[{}()])
}{}
(({}))
So machen wir das. Die {({}( … )[{}()])}{}
Struktur sollte als Schleife mit einer bestimmten Anzahl von Iterationen bekannt sein (dies funktioniert, indem der Schleifenzähler auf den Arbeitsstapel verschoben und dort gehalten wird; er ist vor jedem anderen Code geschützt, da der Zugriff auf den Arbeitsstapel damit verbunden ist Verschachtelungsebene des Programms). Der Hauptteil der Schleife ist ((({}())[()]))
, der drei Kopien des obersten Stapelelements erstellt und 1 zum niedrigsten addiert. Mit anderen Worten, es wandelt eine 40 auf dem Stapel in 40 über 40 über 41 oder als ASCII betrachtet (
in (()
; Wenn Sie dies wiederholt ausführen, wird dies (
zu einem (()
In- (()()
In (()()()
und so weiter. Dies ist ein einfacher Weg, um unsere Datenzeichenfolge zu generieren (vorausgesetzt, es befindet sich bereits eine (
Oben-Position auf dem Stapel).
Sobald wir mit der Schleife fertig sind, (({}))
duplizieren Sie den oberen Teil des Stapels (so dass er jetzt beginnt ((()…
und nicht mehr (()…
. Der führende (
Teil wird von der nächsten Kopie des Formatierers für Datenzeichenfolgen verwendet, um das nächste Zeichen zu formatieren (es wird erweitert) (()(()…
dann (()()(()…
, und so weiter, so dass dies die Trennung (
in der Datenzeichenfolge erzeugt).
%Wwy$%Y%ywywy$wy$%%%WwyY%$$wy%$$%$%$%$%%wy%ywywy'
Es gibt noch ein letztes interessantes Element im Formatierer der Datenzeichenfolge. OK, also meistens sind es nur die um 4 Codepunkte nach unten verschobenen Outro-Werte. Dieses Apostroph am Ende kann jedoch unangebracht aussehen. '
(Codepunkt 39) verschiebt sich in +
(Codepunkt 43), was kein Brain-Flak-Befehl ist. Vielleicht haben Sie erraten, dass er für einen anderen Zweck da ist.
Der Grund dafür ist, dass der Datenstring-Formatierer erwartet, dass sich bereits ein (
auf dem Stapel befindet (er enthält nirgendwo ein Literal 40). Das'
befindet sich tatsächlich am Anfang des Blocks, der wiederholt wird, um den Formatierer der Datenzeichenfolge zu bilden, und nicht am Ende. Nachdem die Zeichen des Formatierers der Datenzeichenfolge auf den Stapel geschoben wurden (und der Code im Begriff ist, mit dem Drucken der Datenzeichenfolge fortzufahren) selbst), passt das Outro die 39 oben auf dem Stapel in eine 40 an, die bereit ist, damit der Formatierer (diesmal der laufende Formatierer selbst, nicht seine Darstellung in der Quelle) davon Gebrauch macht. Deshalb haben wir "fast 241" Kopien des Formatierers; In der ersten Kopie fehlt das erste Zeichen. Und dieses Zeichen, der Apostroph, ist eines von nur drei Zeichen in der Datenfolge, die nicht dem Mini-Flak-Code irgendwo im Programm entsprechen. Es ist lediglich eine Methode zur Bereitstellung einer Konstanten.
Das Intro und Outro
(((()()()()){}))
{{}
(({})[(()()()())])
(({})(
{{}{}((()[()]))}{}
(((((((({})){}){}{})){}{}){}){}())
{
({}(
(␀␀!S␠su! … many more comment characters … oq␝qoqoq)
…
)[{}()])
}{}
{}({}())
)[{}])
(({})(()()()()){})
}{}{}␊
Intro und Outro sind konzeptionell derselbe Teil des Programms. Der einzige Grund, warum wir einen Unterschied machen, ist, dass das Outro vor dem Datenstring und seinem Formatierer ausgegeben werden muss (damit es nach ihnen gedruckt wird), während das Intro nach ihnen ausgegeben werden muss (vor ihnen gedruckt wird).
(((()()()()){}))
Wir beginnen damit, zwei Kopien von 8 auf den Stapel zu legen. Dies ist der Offset für die erste Iteration. Die zweite Kopie ist, weil die Hauptschleife erwartet, dass sich über dem Versatz ein Junk-Element oben auf dem Stapel befindet, das vom Test übrig bleibt, der entscheidet, ob die Hauptschleife vorhanden ist. Daher müssen wir dort ein Junk-Element platzieren es wirft nicht das Element weg, das wir wirklich wollen; Eine Kopie ist der schnellste Weg, dies zu tun.
Es gibt andere Darstellungen der Zahl 8, die nicht länger als diese sind. Wenn Sie sich jedoch für den schnellsten Code entscheiden, ist dies definitiv die beste Option. Zum einen ist using ()()()()
schneller als zum Beispiel, (()()){}
weil erstere, obwohl beide 8 Zeichen lang sind, ein Zyklus schneller ist, weil (…)
sie als 2 Zyklen gezählt werden, aber ()
nur als einer. Das Speichern eines Zyklus ist jedoch vernachlässigbar, verglichen mit einer viel größeren Überlegung für einen Quine : (
und )
hat viel niedrigere Codepunkte als {
und }
, so dass das Generieren des Datenfragments für sie viel schneller ist (und das Datenfragment weniger Platz im Code einnimmt, auch).
{{} … }{}{}
Die Hauptschleife. Dies zählt keine Iterationen (es ist eine while
Schleife, keine for
Schleife, und verwendet einen Test, um auszubrechen). Sobald es beendet ist, werden die beiden obersten Stapelelemente verworfen. Das oberste Element ist eine harmlose 0, aber das Element darunter ist das "Format, das bei der nächsten Iteration verwendet werden soll", das (als negativer Versatz) eine negative Zahl ist und wenn sich beim Mini negative Zahlen auf dem Stapel befinden -Flak-Programm beendet sich, der Interpreter stürzt ab und versucht, sie auszugeben.
Da diese Schleife einen expliziten Test verwendet, um auszubrechen, verbleibt das Ergebnis dieses Tests auf dem Stapel, sodass wir es als erstes verwerfen (sein Wert ist nicht nützlich).
(({})[(()()()())])
Dieser Code verschiebt 4 und f - 4 über ein Stapelelement f , während dieses Element an Ort und Stelle bleibt. Wir berechnen das Format für die nächste Iteration im Voraus (während wir die Konstante 4 zur Hand haben) und bringen gleichzeitig den Stapel für die nächsten Teile des Programms in die richtige Reihenfolge: Wir verwenden f als Format für Diese Iteration, und die 4 wird davor benötigt.
(({})( … )[{}])
Dadurch wird eine Kopie von f - 4 auf dem Arbeitsstapel gespeichert, damit wir sie für die nächste Iteration verwenden können. (Der Wert von f ist zu diesem Zeitpunkt immer noch vorhanden, befindet sich jedoch an einer ungünstigen Stelle auf dem Stapel, und selbst wenn wir ihn an die richtige Stelle manövrieren könnten, müssten wir Zyklen damit verbringen, 4 von ihm zu subtrahieren. und druckt den Code zyklisch aus, um diese Subtraktion durchzuführen.
{{}{}((()[()]))}{}
Ein Test, um festzustellen, ob der Versatz 4 ist (dh f - 4 ist 0). Wenn dies der Fall ist, drucken wir den Formatierer der Datenzeichenfolge. Daher müssen wir die Datenzeichenfolge und ihren Formatierer 241-mal ausführen und nicht nur einmal an diesem Versatz. Der Code ist recht einfach: Wenn f - 4 ungleich Null ist, ersetzen Sie f - 4 und die 4 selbst durch ein Nullenpaar. In beiden Fällen platzieren Sie das oberste Stapelelement. Wir haben jetzt eine Zahl über f auf dem Stapel, entweder 4 (wenn wir diese Iteration 241 Mal drucken möchten) oder 0 (wenn wir sie nur einmal drucken möchten).
(
((((((({})){}){}{})){}{}){}){}
()
)
Dies ist eine interessante Art von Brain-Flak / Mini-Flak-Konstante; Die lange Linie stellt hier die Zahl 60 dar. Sie können verwirrt sein über das Fehlen von ()
, die normalerweise überall in Brain-Flak-Konstanten vorkommen; Dies ist keine reguläre Zahl, sondern eine Kirchenzahl, die Zahlen als Vervielfältigungsoperation interpretiert. Zum Beispiel erstellt die hier gezeigte Kirchenzahl für 60 60 Kopien ihrer Eingabe und kombiniert sie alle zu einem einzigen Wert. In Brain-Flak können wir nur reguläre Zahlen durch Addition kombinieren, sodass wir am Ende 60 Kopien der Stapeloberseite addieren und somit die Stapeloberseite mit 60 multiplizieren.
Als Randnotiz können Sie einen Unterlast-Nummernfinder verwenden , der Church-Nummern in Unterlast-Syntax generiert, um die entsprechende Nummer auch in Mini-Flak zu finden. Unterlastungszahlen (ungleich Null) verwenden die Operationen "oberstes Stapelelement duplizieren" :
und "oberste zwei Stapelelemente kombinieren" *
; beide diese Operationen existieren in Brain-Flak, so dass Sie nur übersetzen :
zu )
, *
zu {}
, prepend ein {}
, und fügen Sie genug (
zu Beginn zu Balance (das eine seltsame Mischung aus dem Hauptstapel verwendet und Arbeitsstapel, aber es funktioniert).
Dieses bestimmte Codefragment verwendet die Kirchenzahl 60 (effektiv ein "Multiplizieren mit 60" -Schnipsel) zusammen mit einem Inkrement, um den Ausdruck 60 x + 1 zu generieren. Wenn wir also eine 4 aus dem vorherigen Schritt hatten, ergibt dies einen Wert von 241, oder wenn wir eine 0 hatten, erhalten wir nur einen Wert von 1, dh dies berechnet die Anzahl der Iterationen, die wir benötigen, korrekt.
Die Wahl von 241 ist kein Zufall; Es wurde ein Wert gewählt, der a) ungefähr der Länge entspricht, bei der das Programm ohnehin enden würde, und b) 1 mehr als das Vierfache einer runden Zahl. Runde Zahlen, in diesem Fall 60, sind in der Regel kürzer als Zahlen in der Kirche, weil Sie flexibler in Bezug auf die zu kopierenden Faktoren sind. Das Programm enthält später eine Auffüllung, um die Länge genau auf 241 zu bringen.
{
({}(
…
)[{}()])
}{}
Dies ist eine for - Schleife, wie die oben gezeigte, die den Code einfach so oft ausführt, wie er im oberen Bereich des Hauptstapels enthalten ist (den sie verbraucht; der Schleifenzähler selbst ist auf dem Arbeitsstapel gespeichert, aber die Sichtbarkeit von Dies ist an die Verschachtelungsebene des Programms gebunden und daher ist es unmöglich, dass etwas anderes als die for-Schleife selbst damit interagiert. Auf diese Weise werden der Datenstring und sein Formatierer 1 oder 241 Mal ausgeführt. Da wir nun alle Werte, die wir für die Berechnung des Kontrollflusses verwendet haben, vom Hauptstapel abgerufen haben, haben wir das Format, das darauf verwendet werden kann, bereit der zu verwendende Formatierer.
(␀␀!S␠su! … many more comment characters … oq␝qoqoq)
Der Kommentar hier ist nicht ganz ohne Interesse. Zum einen gibt es ein paar Brain-Flak-Befehle; Das )
Ende wird natürlich als Nebeneffekt der Art und Weise erzeugt, in der die Übergänge zwischen den verschiedenen Segmenten des Programms funktionieren. Daher wurde das (
am Anfang manuell hinzugefügt, um es auszugleichen (und trotz der Länge des Kommentars ein Kommentar eingefügt) ()
Da ein Befehl immer noch ein ()
Befehl ist, wird lediglich 1 zum Rückgabewert des Datenstrings und seines Formatierers hinzugefügt (was von der for-Schleife vollständig ignoriert wird).
Insbesondere sind diese NUL-Zeichen zu Beginn des Kommentars eindeutig keine Offsets von irgendetwas (selbst der Unterschied zwischen +8 und -4 reicht nicht aus, um aus (
einer NUL eine NUL zu machen). Das sind reine Auffüllungen, um den 239-Elemente-Datenstring auf 241 Elemente zu bringen (was sich leicht auszahlt: Es würde viel mehr als zwei Bytes erfordern, um 1 vs. 239 statt 1 vs. 241 zu generieren, wenn die Anzahl der erforderlichen Iterationen berechnet wird ). NUL wurde als Füllzeichen verwendet, da es den niedrigstmöglichen Codepunkt aufweist (wodurch der Quellcode für den Datenstring kürzer und damit schneller ausgegeben werden kann).
{}({}())
Löschen Sie das oberste Stapelelement (das von uns verwendete Format) und fügen Sie 1 zum nächsten hinzu (das letzte auszugebende Zeichen, dh das erste zu druckende Zeichen des gerade formatierten Programmabschnitts). Wir brauchen das alte Format nicht mehr (das neue Format versteckt sich auf dem Arbeitsstapel); und das Inkrement ist in den meisten Fällen harmlos und ändert das '
an einem Ende der Quellendarstellung des Datenzeichenfolge-Formatierers in ein (
(was auf dem Stapel für das nächste Ausführen des Formatierers erforderlich ist, um die Datenzeichenfolge selbst zu formatieren). Wir brauchen eine Transformation wie diese im Outro oder Intro, weil das Erzwingen jedes Datenstring-Formatierungselements zu Beginn (
etwas komplexer werden würde (da wir das schließen (
und seinen Effekt später wieder rückgängig machen ), was undIrgendwie müssten (
wir irgendwo ein Extra generieren, weil wir nur fast 241 Kopien des Formatierers haben, nicht alle 241 (also ist es am besten, dass ein harmloser Charakter wie '
derjenige fehlt).
(({})(()()()()){})
Zum Schluss der Loop-Exit-Test. Der aktuelle Anfang des Hauptstapels ist das Format, das wir für die nächste Iteration benötigen (die gerade vom Arbeitsstapel zurückgekehrt ist). Dies kopiert es und fügt der Kopie 8 hinzu. Der resultierende Wert wird beim nächsten Mal in der Schleife verworfen. Wenn wir jedoch nur das Intro gedruckt haben, war der Versatz -4, sodass der Versatz für die "nächste Iteration" -8 ist. -8 + 8 ist 0, daher wird die Schleife beendet, anstatt danach mit der Iteration fortzufahren.