** Brainfuck Interpreter Contest ** [abgeschlossen]



  • 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. 🙂



    1. 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.



    1. 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))

    1. wenn w==0 wird der normale Modus ausgeführt

    2. 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;

    1. 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.

    2. Gleicher Trick für + und - nachdem j um weitere 17 erniedrigt wurde.

    3. 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 gefallen 🙂

    Klar 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 😉



  • µ, das vierfache =0 bei dir tut mir richtig weh. Als Membervariablen wären das auf einen Schlag 7 Zeichen weniger. Ansonsten: Das Muster (j -= XXX) * j == 1 gefällt mir. Da wär ich wohl auch nicht drauf gekommen.



  • Bashar ich beiße mir gerade selbst in den Arsch dafür. 221 wären es sonst.



  • @Bashar:
    Wobei ich das "Deaktivieren" von Opcodes das unsere beiden Codes gemeinsam haben (zum Sparen des 2. "if" das in 1-Schleifen-Lösungen sonst nötig wäre) auch sehr kuhl finde 🕶



  • Naja, 221 geht ja noch.
    Blöd wäre wenn du auf 219 oder gar 217 gekommen wärst damit 🙂



  • Interessant: Niemand konnte 65536 bzw 1<<16 oder eine größere Zahl mit weniger als 5 Zeichen darstellen. Ich vermute irgendeine geschickte Lösung mit Überlauf.... habe sie aber nicht gefunden.



  • Bashar schrieb:

    Ansonsten: Das Muster (j -= XXX) * j == 1 gefällt mir. Da wär ich wohl auch nicht drauf gekommen.

    Spielt ein kleines wenig "Glück" eine Rolle. Bei hohen char-Werten kommt es zum Überlauf. Aber es gibt keine Zahl in UTF-16 die in diesen Fällen ebenfalls j*j==1 ergeben würde. Sonst hätte ich auf long umsteigen müssen. Bei einer Variante dieses Schemas hatte ich nämlich ein solches Problem.



  • hustbaer schrieb:

    Wobei ich das "Deaktivieren" von Opcodes das unsere beiden Codes gemeinsam haben (zum Sparen des 2. "if" das in 1-Schleifen-Lösungen sonst nötig wäre) auch sehr kuhl finde 🕶

    Ich hätte mir einen (sinnlosen) Rückgabewert bei der S.W() Funktion gewünscht. Dieses Monster war sonst nirgends unterzubringen. Imho ist nur der Upadate-Teil der For-Schleife in der Lage, Ausdrücke ohne Rückgabewert zu verarbeiten.



  • Übrigens ist mir bei diesem Contest aufgefallen, dass C# keine oktalen Literale kennt. Es war eine sehr frühe chancenlose Version mit >400 Zeichen, aber da hätte ich das erste mal seit ... weiß ich nicht ... überhaupt Oktalzahlen gebraucht, und C# hat die einfach nicht. 😮 010 == 10 in C#. 😮 Keine Warnung. 😡



  • Mit long hättest du aber Probleme, da C# keine implizite Konvertierung long -> int erlaubt.
    D.h. beim Aufruf der Write Funktion wäre dann ein "(int)" Cast fällig gewesen = +5 Zeichen.

    Ich hatte mir das mit long nämlich auch schon überlegt, da ich eben erst "i * i < 2" stehen hatte. Dummerweise geht sich das aber nicht mit allen möglichen UTF-16 Zeichen aus, bei 46xxx (sqrt(1 << 31)) ist Schluss - da wird die Zahl nämlich auf einmal negativ.


Anmelden zum Antworten