** Brainfuck Interpreter Contest ** [abgeschlossen]
-
Loslos, will Code sehen.
-
Naja, det dauert noch ein paar Stunden...
Nicht dass sich nochwas tun würde bis dahin, aber ich hab Samstag 23:59 geschrieben, daran halte ich mich jetzt auch
-
*hust* *drängel*
-
Sorry, bin grad erst ausm Kino zurückgekommen. Ich muss die Interpreter noch testen und das Ergebnis zusammenschreiben - dauert noch etwas.
-
So. Viele haben nicht eingereicht, aber egal. Hier das
Ergebnis
-
µ - 228
-
KasF - 255
-
Bashar - 260
-
Dummie - 264
-
GPC - 276
-
hustbaer - 254 bzw. 218
Die Sourcen folgen sogleich...
-
-
- µ - 228
class I{public void R(S S){var b=new int[1<<16];for(int p=0,i=0,w=0,n=0,j;i<S.P.Length;i+=w<0?-1:1)if(0==(w+=(j=92-S.P[i])*j==1?j+(w==0?n!=0?-1:1:j):0)){if(j==46)S.W(n);n=b[p-=(j-=31)*j==1?j:0]+=(j-=17)*j==1?j:j==0?S.R()-n:0;}}} class I_Nice { public void R(S S) { var b = new int[1 << 16]; for (int p = 0, i = 0, w = 0, n = 0, j; i < S.P.Length; i += w < 0 ? -1 : 1) if (0 == (w += (j = 92 - S.P[i]) * j == 1 ? j + (w == 0 ? n != 0 ? -1 : 1 : j) : 0)) { if (j == 46) S.W(n); n = b[p -= (j -= 31) * j == 1 ? j : 0] += (j -= 17) * j == 1 ? j : j == 0 ? S.R() - n : 0; } } }
µ hat keine Erklärung dazugeschrieben. Ich müsste den Code auch selbst erst auseinandernehmen, aber das Grundprinzip kommt mir sehr bekannt vor
Im Prinzip fast das selbe wie mein Code, nur fehlen ein paar Tricks.
-
- KasF - 255
class I{public void R(S q){int[]b=new int[1<<16];int z=0,i=0,s=0,a;while(i<q.P.Length){a=q.P[i]-40;s+=a==51&&(b[z]==0||s!=0)?1:a==53&&(b[z]|s)!=0?-1:0;if(s==0){b[z]+=a==3?1:a==5?-1:a==4?q.R()-b[z]:0;z+=a==22?1:a==20?-1:0;if(a==6)q.W(b[z]);}i+=s<0?-1:1;}}} class I_Nice { public void R(S q) { int[] b = new int[1 << 16]; int z = 0, i = 0, s = 0, a; while (i < q.P.Length) { a = q.P[i] - 40; s += a == 51 && (b[z] == 0 || s != 0) ? 1 : a == 53 && (b[z] | s) != 0 ? -1 : 0; if (s == 0) { b[z] += a == 3 ? 1 : a == 5 ? -1 : a == 4 ? q.R() - b[z] : 0; z += a == 22 ? 1 : a == 20 ? -1 : 0; if (a == 6) q.W(b[z]); } i += s < 0 ? -1 : 1; } } }
KasF hat keine Erklärung dazugeschrieben. Ist aber auch eine "klassische" (hihi) 1-Schleifen-Lösung.
-
Gerade nach Hause gekommen. Schön, die Codes treffen ein
Falls Erklärung erwünscht, schreibe ich morgen gerne was dazu.
Und auf die Tricks auf die ich nicht gekommen bin, bin ich natürlich am meisten gespannt
-
- Bashar - 260
class I{int j,p,b,r=1,i,t,k;public void R(S s){var d=new int[65536];for(;j<s.P.Length;j+=r){k=s.P[j];i=">+[.,<-]".IndexOf(s.P[j])%5+(b!=0?6:0);t=(k&2)-1;r=i==8&&(b+=t)==0?1:r;k=d[p+=i==0?t:0];if(i==3)s.W(k);d[p]=i==1?k+t:i==4?s.R():k;b=i==2&&k!=0^t>0?r=t:b;}}} class I_Nice { int j, p, b, r = 1, i, t, k; public void R(S s) { var d = new int[65536]; for (; j < s.P.Length; j += r) { k = s.P[j]; i = ">+[.,<-]".IndexOf(s.P[j]) % 5 + (b != 0 ? 6 : 0); t = (k & 2) - 1; r = i == 8 && (b += t) == 0 ? 1 : r; k = d[p += i == 0 ? t : 0]; if (i == 3) s.W(k); d[p] = i == 1 ? k + t : i == 4 ? s.R() : k; b = i == 2 && k != 0 ^ t > 0 ? r = t : b; } } }
Bashar hat mir nen "kaputten" Code geschickt. Da der Fehler aber lediglich eine überschüssige "}" ganz zum Schluss war, hab ich das mal "übersehen". Erklärt vielleicht auch warum er selbst 261 gezählt hat, wo es doch nur 260 sind
Bashar hat folgende Erklärung dazugeschrieben:
Bashar schrieb:
Die Maschine hat zwei Modi, einen Befehlsausführungsmodus und einen Schleifenmodus. In ersterem (b == 0) werden Befehle ausgeführt, in letzterem (b != 0) werden Klammern gezählt. Beide sind in derselben Schleife untergebracht, dh auch im Schleifenmodus werden Befehle "ausgeführt". Halt anders.
Variablen:
j = Befehlszeiger
p = Datenzeiger
b = Anzahl der offenen Klammerebenen (positiv in Vorwärts- und negativ in Rückwärtsrichtung)
r = Richtung, +1 vorwärts, -1 rückwärts
i = decodierter Befehl, wenn man so will
t = durch den aktuellen Befehl vorgegebene Richtung (z.B. + ist +1, - ist -1, analog bei >< und [])
k = Hilfsvariable für das aktuelle Zeichen und das aktuelle DatumDie Interessanten Sache hier werden allerdings nicht erklärt
Ist zwar auch eine "1-Schleifen-Lösung", aber macht einige Dinge ganz anders als ... die anderen
-
hustbaer schrieb:
Bashar hat mir nen "kaputten" Code geschickt. Da der Fehler aber lediglich eine überschüssige "}" ganz zum Schluss war, hab ich das mal "übersehen". Erklärt vielleicht auch warum er selbst 261 gezählt hat, wo es doch nur 260 sind
Oops, das war die schließende Klammer vom Namespace.
-
- Dummie - 264
class I{int[]m=new int[1<<16];int n=0,z,c;public void R(S s,int p=-1,int r=2){while((z=++p)<s.P.Length&&(c=s.P[p]-40)!=53){if(c==51)if(m[n]!=0)R(s,p--);else{R(s,p,0);p=z;}if(r>0){m[c==20||c==22?n-=21-c:n]+=c==3||c==5?4-c:0;if(c==6)s.W(m[n]);if(c==4)m[n]=s.R();}}}} class I_Nice { int[] m = new int[1 << 16]; int n = 0, z, c; public void R(S s, int p = -1, int r = 2) { while ((z = ++p) < s.P.Length && (c = s.P[p] - 40) != 53) { if (c == 51) if (m[n] != 0) R(s, p--); else { R(s, p, 0); p = z; } if (r > 0) { m[c == 20 || c == 22 ? n -= 21 - c : n] += c == 3 || c == 5 ? 4 - c : 0; if (c == 6) s.W(m[n]); if (c == 4) m[n] = s.R(); } } } }
Dummie hat auch keine Erklärung dazugeschrieben. Erste rekursive Lösung. Ansonsten ziemlich einfach zu verstehen.
-
- GPC - 276
class I{int p=0,i=-1;int[]b=new int[65536];public void R(S s,int x=1){for(int j=i;++i<s.P.Length;){int c=s.P[i];if(c>90|x!=0){if(c==62)++p;if(c==60)--p;if(c==43)++b[p];if(c==45)--b[p];if(c==44)b[p]=s.R();if(c==46)s.W(b[p]);if(c==91)R(s,b[p]);if(c==93){i=x==0?i:j-1;break;}}}}} class I_Nice { int p = 0, i = -1; int[] b = new int[65536]; public void R(S s, int x = 1) { for (int j = i; ++i < s.P.Length; ) { int c = s.P[i]; if (c > 90 | x != 0) { if (c == 62) ++p; if (c == 60) --p; if (c == 43) ++b[p]; if (c == 45) --b[p]; if (c == 44) b[p] = s.R(); if (c == 46) s.W(b[p]); if (c == 91) R(s, b[p]); if (c == 93) { i = x == 0 ? i : j - 1; break; } } } } }
Zweite Rekursive Lösung. Zwar ein paar Zeichen länger als die von Dummie, dafür viel eleganter
GPC hat folgenden Code als Erklärung dazugeschrieben:
class I { int bufferPtr = 0, sourceIndex = -1; int[] buffer = new int[65536]; /// <summary> /// Hauptfunktion /// </summary> /// <param name="s"></param> /// <param name="execute">Wenn execute != 0, dann führe die Befehle bis zum Schleifen-/Programmende aus</param> public void R(S s, int execute=1) { //Zu Beginn den derzeitigen index in initialIndex merken, damit man bei Schleifen zurückspringen kann for (int initialIndex = sourceIndex; ++sourceIndex < s.P.Length; ) { int opCode = s.P[sourceIndex]; //Zum besseren Verständnis ist die Bedingung hier im Vergleich zur kurzen Version negiert if (opCode < 91 && execute == 0) continue; if (opCode == 62) ++bufferPtr; if (opCode == 60) --bufferPtr; if (opCode == 43) ++buffer[bufferPtr]; if (opCode == 45) --buffer[bufferPtr]; if (opCode == 44) buffer[bufferPtr] = s.R(); if (opCode == 46) s.W(buffer[bufferPtr]); if (opCode == 91) R(s, buffer[bufferPtr]); if (opCode == 93) { //Am Ende einer Schleife wird entschieden, ob man zurück zum Anfang springt (index auf initialIndex - 1 zurücksetzen) //oder alle Durchläufe absolviert hat, aus der Funktion zurückkehrt und mit dem nächsten Befehl weitermacht sourceIndex = execute == 0 ? sourceIndex : initialIndex - 1; break; } } } }
Erklärt zwar auch nicht wirklich viel, aber viel muss auch nicht erklärt werden
-
Bashar, dein Code ist echt kaputt
-
0.1) hustbaer - 254
class I{public void R(S z){int p=1<<16,i=-1;var m=new int[2*p];m[93]=-++m[91];while(++i<z.P.Length){int c=z.P[i],n=m[c],d=n;p+=c==62?1:c==60?-1:0;m[p]+=c==43?1:c==45?-1:0;if(c==46)z.W(m[p]);if(c==44)m[p]=z.R();while(n!=0&&m[p]!=0==n<0)n+=m[z.P[i+=d]];}}} class I_Nice { public void R(S z) { int p = 1 << 16, i = -1; var m = new int[2 * p]; m[93] = -++m[91]; while (++i < z.P.Length) { int c = z.P[i], n = m[c], d = n; p += c == 62 ? 1 : c == 60 ? -1 : 0; m[p] += c == 43 ? 1 : c == 45 ? -1 : 0; if (c == 46) z.W(m[p]); if (c == 44) m[p] = z.R(); while (n != 0 && m[p] != 0 == n < 0) n += m[z.P[i += d]]; } } }
Hah, eine "konkurrenzfähige" 2-Schleifen Lösung, da guckt ihr, was?
Das ist der Code den ich quasi zu Beginn des Contest hatte (Interpreter_2_1252.cs).Ein paar kleine Erklärungen: ich hatte in meinen "frühen" Ansätzen einfach SO viele Stellen wo ich mit '[' bzw. ']' verglichen hatte, dass ich mir dachte, da muss ein Lookup-Table her (Lookup-Tables sind auch einfach so kuhl!).
Der Lookup Table ist die erste Hälte von "m", die zweite wird für den Speicher verwendet (der Daten-Zeiger wird dazu einfach initial auf 1 << 16 platziert statt auf 0).
Der Lookup-Table enthält für '[' +1, für ']' -1 und sonst überall 0.
-
hustbaer schrieb:
Die Interessanten Sache hier werden allerdings nicht erklärt
Was ist denn interessant? Ich kommentier mal:
class I_Nice { // TRICK: Membervariablen werden mit 0 initialisiert. int j, p, b, r = 1, i, t, k; public void R(S s) { // TRICK: var ist kürzer als int[]. Und 65536 ist genauso lang wie 1<<16 :p var d = new int[65536]; // TRICK: for ist kürzer als while for (; j < s.P.Length; j += r) { k = s.P[j]; // Das ist das wichtigste: Zusammengehörende Zeichen werden auf ein i abgebildet. // Im Befehlsausführungsmodus: // >< => 0 // +- => 1 // [] => 2 // . => 3 // , => 4 // Im Schleifenmodus wird 6 addiert, so dass wir 6 bis 10 // (und 5 für "nicht gefunden") // haben, alles ausser [] (8) wird aber ignoriert. // Probleme: 1) Ätzend langer Code. 2) Ich hatte erst sowas wie // .IndexOf(...) / 2, aber für "nicht gefunden" gibt IndexOf -1 // zurück, und -1/2 ist auch 0 (:open_mouth:). 3) IndexOf geht nur mit // char, nicht mit int, ich kann also mein k nicht wiederverwenden i = ">+[.,<-]".IndexOf(s.P[j]) % 5 + (b != 0 ? 6 : 0); // Bit 1 des Zeichens gibt bei ><, +-, [] praktischerweise an, ob // nach oben oder nach unten gezählt werden soll. t = (k & 2) - 1; // Schleifenmodus mit Klammern: Zähle die Klammerebenen. Bei 0 // Schleifenmodus beenden und Richtung auf "vorwärts" setzen r = i == 8 && (b += t) == 0 ? 1 : r; // Datenzeiger verändern, falls i==0 (dh ><) k = d[p += i == 0 ? t : 0]; if (i == 3) s.W(k); // Aktuelles Datum verändern, falls +-, bzw. ggf. einlesen d[p] = i == 1 ? k + t : i == 4 ? s.R() : k; // bei [] evtl. in den Schleifenmodus schalten (b auf +1 bzw. -1) // bei [ soll das passieren falls das aktuelle Datum k == 0 ist, // bei ] falls das aktuelle Datum k != 0 ist. Da t für [ +1 und für // ] -1 ist, wird das in der Bedingung k != 0 ^ t > 0 // zusammengefasst. Die Laufrichtung im Schleifenmodus ist // auch durch t festgelegt. b = i == 2 && k != 0 ^ t > 0 ? r = t : b; } } }
-
0.2) hustbaer - 220
class I{int i,p,n;public void R(S z){var m=new int[1<<16];for(;i<z.P.Length;i+=n<0?-1:1){int c=z.P[i]-92,a=m[p];n-=n==0&a==0!=c<0|c*c!=1?0:c;c=c+48^n<<16;m[p+=c>15&c<19?c-17:0]-=c==0?a-z.R():c*c==1?c:0;if(c==2)z.W(a);}}} class I_Nice { int i, p, n; public void R(S z) { var m = new int[1 << 16]; for (; i < z.P.Length; i += n < 0 ? -1 : 1) { int c = z.P[i] - 92, a = m[p]; n -= n == 0 & a == 0 != c < 0 | c * c != 1 ? 0 : c; c = c + 48 ^ n << 16; m[p += c > 15 & c < 19 ? c - 17 : 0] -= c == 0 ? a - z.R() : c * c == 1 ? c : 0; if (c == 2) z.W(a); } } }
Auch eine "klassische" 1-Schleifen-Lösung.
Das ist die letzte Variante für die ich nen Hashcode gepostet hatte (Interpreter_9_1252.cs).
Erklärung folgt gleich in Form der kommentierten 218 Zeichen Version (ist bis auf zwei dumme Oversights genau das selbe).
-
0.3) hustbaer - 218
class I{int i,p,n;public void R(S z){for(var m=new int[1<<16];i<z.P.Length;i+=n<0?-1:1){int c=z.P[i]-92,a=m[p];n-=n==0&a==0!=c<0|c*c!=1?0:c;c+=48^n<<16;m[p+=c>15&c<19?c-17:0]-=c==0?a-z.R():c*c==1?c:0;if(c==2)z.W(a);}}} class I_Nice { // Wir machen Member. Das spart das "=0" das wir bei lokalen Variablen bräuchten. // (Dadurch kann R() zwar nichtmehr ein 2. mal mit einem neuen Programm aufgerufen werden, // aber das war auch nicht gefordert :-) int i, // Der "instruction pointer" p, // Der "data pointer" n; // Der "nesting counter" // Der "nesting counter" wird zum Suchen der passenden [ oder ] verwendet. // "[" werden als +1 gezählt, "]" als -1 // n == 0 => "normal mode" - Normale Programmausführung // n < 0 => "+skip mode" - wir suchen die passende [ zu einer gefundenen ] (rückwärts) // n > 0 => "-skip mode" - wir suchen die passende ] zu einer gefundenen [ (vorwärts) public void R(S z) { for (var m = new int[1 << 16]; // Der Speicher. Wir sparen 1x ";" wenn wir den hier anlegen statt vor der Schleife (das ist das verschenkte Zeichen, hihi) i < z.P.Length; // Sollte klar sein :) i += n < 0 ? -1 : 1 // Anpassen des "instruction pointer": rückwärts wenn wir im "-skip mode" sind, sonst vorwärts ) { int c = z.P[i] - 92, // 92 abziehen, damit wir unten Tricks wie "c * c == 1" verwenden können a = m[p]; // Wert der aktuellen Zelle kopieren, damit wir 3x "a" statt 3x "m[p]" schreiben können // Als erstes passen wir mal den "nesting counter" an. // Dazu müssen wir folgendes machen: // // Im "normal mode": // Wenn eine [ oder ] gefunden wurde UND der Wert der aktuellen Zelle "passt", dann müssen wir // in den "skip mode" wechseln. // (Zu [ "passt" 0 und zu ] "passt" alles ausser 0) // Dazu "zählen" wir einfach die [ bzw. ] ganz normal (siehe oben), dadurch wird n != 0. // Im "skip mode": // Jede [ oder ] muss gezählt werden // Wenn wir dabei n == 0 erzeugen verlassen wir den "skip mode". Das ist gut so, // weil n == 0 bedeutet dass wir das Ziel erreicht haben. // // Die "Zählung" einer [ oder ] erfolgt also immer gleich, wir müssen nur gucken OB wir zählen müssen. // // Momentan ist "c" so eingestellt, dass // '[' c == -1 (91 - 92) - "Wert" dieser Klammer ist "+1" (also "-c") // ']' c == 1 (93 - 92) - "Wert" dieser Klammer ist "-1" (also "-c") // // Um zu testen ob c -1 oder 1 ist können wir c * c == 1 schreiben. // (Lässt sich beweisen dass das für alle 32 Bit Zahlen trotz Überlauf gilt, // da ich für so nen Beweis aber zu doof bin, hab ich's einfach ausprobiert :D) // // Wenn also c * c == 1, dann ist der "Wert" der aktuellen Klammer gleich "-c" (s.o.) // // Um zu entscheiden ob wir zählen müssen verwenden wir folgende Bedingung: // // ( (n != 0) || ((a == 0) == (c < 0)) ) && (c * c == 1) // // Erklärung ("(c * c == 1)" vorgezogen, zur besseren Verständlichkeit): // // (c * c == 1) es ist eine Klammer // && UND // ( ( // (n != 0) wir sind im "skip mode" // || ODER // ((a == 0) == (c < 0)) der Wert der Zelle passt zur Klammer // (das "Null sein" der aktuellen Zelle gleicht dem "[ sein" der Klammer ^^) // ) ) // // Das setzt also die oben beschriebenen Regeln um. // // Blöderweise bräuchten wir da aber zwei Klammern wenn wir das so 1:1 hinschreiben wollen, // da die Operator-Precedence nicht "passt" wenn wir alle Klammern wegnehmen. // Daher formen wir die Bedingung einfach um, nach // A && B => !(!A || !B) // A || B => !(!A && !B) // // So erhalten wir: // // !( ( !(n != 0) && !((a == 0) == (c < 0)) ) || !(c * c == 1) ) // // Bis auf das erste "!" können wir alle loswerden, indem wir == zu != verwandeln (und umgekehrt), und dafür das ! streichen // Und das erste werden wir noch los, indem wir später den "if" und "else" Teil vertauschen: // // ( ( (n == 0) && ((a == 0) != (c < 0)) ) || (c * c != 1) ) // // Jetzt machen wir noch aus && ein einfaches & und aus || ein einfaches |. // ("bool | bool" ist das selbe wie "bool || bool", nur ohne short-circuit Semantik, der 2. Ausdruck wird also immer ausgewertet. // Das selbe nochmal für "bool & bool") // Macht: // ( ( (n == 0) & ((a == 0) != (c < 0)) ) | (c * c != 1) ) // // Und jetzt nehmen wir noch alle Klammern weg, und gucken was passiert: // // n == 0 & a == 0 != c < 0 | c * c != 1 // // TADAA, funktioniert :) // // BTW: das spart genau EIN Zeichen, denn in der Original-Version hätten wir // "a == 0 ^ c < 0" statt "a == 0 != c < 0" schreiben können, und auch nur ein Klammerpaar gebraucht xD n -= n == 0 & a == 0 != c < 0 | c * c != 1 ? 0 : c; // Achja, "-=" statt "+=" machen wir, weil wir so einfach "? 0 : c" statt "? 0 : -c" schreiben können. // Gut, weiter... // Wenn wir im "skip mode" sind, dann wollen wir alles bis auf [ und ] ignorieren. // Wenn wir im "normal mode" sind, dann nicht :) // // Wir könnten nun natürlich "if (n == 0) {}" oder sowas schreiben, aber das geht kürzer! // Und zwar indem wir den Wert von c "kaputtmachen" wenn n != 0. // (d.h. so verändern, dass kein gültiger Opcode mehr erkannt werden kann, egal was das Input-Zeichen war) // // Und es gibt noch weitere "benachbarte" Zeichen die wir ausnutzen können, also "verschieben" // wir "c" auch nochmal. Und zwar um 48. Für einen weiteren "c * c == 1" Trick. // // Das können wir beides in einem Statement machen, und zwar indem wir (48 ^ n << 16) auf c draufaddieren. // // Der (n << 16) Teil ist im "normal mode" immer 0, d.h. da geht nix kaputt. // Und im "skip mode" ist der Betrag von (n << 16) immer ausreichend gross, so dass // sämtliche gültigen Input-Zeichen (inklusive Kommentare) ausreichend weit "verschoben" werden, // dass kein Opcode mehr erkannt wird. // (Das funktioniert allerdings nur bis zu einer Schachteltiefe von ~ 32K, was nach der // Anpassung/Klarstellung der Regeln aber mehr als genug ist, hihi.) c += 48 ^ n << 16; // Bleibt nur noch... // - Den "data pointer" anzupassen // - Den aktuellen Zellenwert anzupassen // - Input lesen // - Output schreiben // Momentan ist "c", nach der neuerlichen Verschiebung, so eingestellt, dass // '<' c == 16 (60 - 92 + 48) // '>' c == 18 (62 - 92 + 48) // '+' c == -1 (43 - 92 + 48) // '-' c == 1 (45 - 92 + 48) // '.' c == 2 (46 - 92 + 48) // ',' c == 0 (44 - 92 + 48) // Bis auf Output schreiben lässt sich das wieder in einem einzigen Statement machen... // // Wenn c im Bereich (15 ... 19) liegt (exklusive), ist der "data pointer" anzupassen, und zwar um "c - 17" // (Spezialfall: c == 17 entspricht keinem Opcode, aber da c - 17 == 0 ist das egal): // // p += c > 15 & c < 19 ? c - 17 : 0 // // Wenn c == 0, dann wollen wir lesen. // Ansonsten, wenn c -1 oder 1 ist, dann wollen wir den Zellenwert anpassen. // // Das können wir nun so zusammenfassen: m[ p += c > 15 & c < 19 ? c - 17 : 0 ] -= c == 0 ? a - z.R() : c * c == 1 ? c : 0; // "a - z.R()" ist ein netter Trick (den ich von µ abgeguckt habe, hihi), um das Lesen auch noch in // diese Zeile reinzupacken. // ("-=" statt "+=" wieder aus dem selben Grund wie beim "nesting counter".) // Und hier nun doch noch ein "if", zum Schreiben: // (k.A. wie ich dieses "if" wegbekommen könnte - alles was mir da sonst einfällt wäre viel länger) if (c == 2) z.W(a); } } }
Die meisten die mitgemacht haben, werden die meisten der hier erklärten Dinge schon kennen bzw. auch ohne Erklärung verstehen. Vielleicht ist es aber für anderen interessant...
-
b = das Band.
p = pointer. Der Zeiger (Index) auf die aktuelle Zelle
i = Index auf das aktuelle Bf Token im Input-Code
n = Inhalt der aktuellen Zelle
j = Hilfvariable die das aktuelle Token (um einen bestimmten Wert verschoben) speichert
w = Zwei Modi. w==0 ist die normale Ausführung. D.h. + - > < . und , werden bearbeitet. w!=0 ist ein Vor- oder Rücklauf über eine Schleife. Dabei ist w>0 ein Lauf vorwärts und w<0 ein Lauf rückwärts. In diesem Zustand speichert w das doppelte (weil kürzerer Code)der Differenz der öffnenden und schließenden Klammern bis der Wert wieder =0 ist und der normale Ausführungsmodus wieder aufgenommen werden kann.class I_Nice { public void R(S S) { var b = new int[1 << 16]; for (int p = 0, i = 0, w = 0, n = 0, j; i < S.P.Length; i += w < 0 ? -1 : 1) if (0 == (w += (j = 92 - S.P[i]) * j == 1 ? j + (w == 0 ? n != 0 ? -1 : 1 : j) : 0)) { if (j == 46) S.W(n); n = b[p -= (j -= 31) * j == 1 ? j : 0] += (j -= 17) * j == 1 ? j : j == 0 ? S.R() - n : 0; } } }
Interessante Punkte bei if (0 == (w += (j = 92 - S.P[i]) * j == 1 ? j + (w == 0 ? n != 0 ? -1 : 1 : j) : 0))
-
wenn w==0 wird der normale Modus ausgeführt
-
Das Token wird zuerst in j = 92 - S.P[i] kodiert. Da '['=91 und ']'=93 ist j bei einem Klammersymbol -1 oder 1. Damit ist j*j == 1. Falls j*j == 1 wird der Schleifentiefezähler um den Summanden j + (w == 0 ? n != 0 ? -1 : 1 : j) angepasst, sonst wird nichts dazugezählt. Dieser Ausdruck führt dazu, dass der Zähler erhöht oder erniedrigt wird, falls ein Lauf bereits im Gange ist (w!=0). Außerdem startet dieser Zähler einen Lauf nach vorne, wenn das aktuelle Bandsymbol ==0 ist und einen Lauf zurück, wenn das aktuelle Bandsymbol !=0 ist. Andere, unwesentliche, Kombinationen führen zu einem Ausdruckswert von 0. Einsetzen aller möglichen Werte schafft Klarheit
Nun if (j == 46) S.W(n);
j hat immer noch den Wert 92 - S.P[i]. Da '.'=46 und 92-46==46 wird der Schreibebefehl bei j==46 ausgeführt.Interessante Punkte bei n = b[p -= (j -= 31) * j == 1 ? j : 0] += (j -= 17) * j == 1 ? j : j == 0 ? S.R() - n : 0;
-
Erst '>'= 62 und '<'=60 behandeln. j, die Hilfsvariable des aktuellen Tokens, wird um 31 erniedrigt und damit gilt j = 61 - S.P[i].
Damit ist j*j==1 genau dann wenn das < oder das > Symbol gefunden wurden. Dann ist -j aber auch genau der Wert, um den sich der Zeiger bewegen muss. -
Gleicher Trick für + und - nachdem j um weitere 17 erniedrigt wurde.
-
Nach dem letzten Schritte ist j == 0 für das Read-Symbol. Da der aktuelle Bandwert um den eingelesenen Wert erhöht wird, subtrahiert man den letzten Bandwert wieder.
Hmmm, noch Fragen?
-
-
Falls jemand meine Erklärung schon gelesen hat (vor dem 2. EDIT), und sich gewundert hat...
Nein, ich bin nicht auf den Kopf gefallenKlar ist "c * c == 1" für c=0 nicht TRUE.
Bloss dass ich erst "c * c < 2" stehen hatte, weil das ein Zeichen kürzer ist. Das wollte mir nicht aus dem Kopf, ist irgendwie stecken geblieben.
-
hustbaer schrieb:
// Wir machen Member. Das spart das "=0" das wir bei lokalen Variablen bräuchten.
NEIN NEIN NEIN WIE KONNTE ICH DAS ÜBERSEHEN ?
EDIT: Den Rest lese ich morgen. Hatte einen Cocktail zuviel heute abend