Universeller (regelbiegender) Code-Golf-Löser

14

Code-Golf beinhaltet immer einige Antworten, die die Regeln mehr oder weniger biegen, indem sie Einschränkungen aufheben, die die Herausforderer für selbstverständlich hielten oder einfach nicht bedachten und in den Regeln nicht auflisteten. Eine dieser interessanten Lücken ist die Möglichkeit, mehr auszugeben , als die Herausforderung verlangt, um ein besseres Ergebnis zu erzielen.

Aus diesem Grund können wir einen universellen Code-Golf-Solver schreiben, der die gewünschte Ausgabe ausgibt - wenn Sie sich nicht darum kümmern, dass es Ewigkeiten dauern könnte und viele andere Dinge davor und danach ausgibt.

Alles, was wir ausgeben müssen, ist eine Sequenz, die garantiert jede mögliche Teilsequenz enthält. Für diesen Codegolf ist dies die Ehrenfeucht-Mycielski-Sequenz :

Die Sequenz beginnt mit den drei Bits 010; Jede aufeinanderfolgende Ziffer wird gebildet, indem das längste Suffix der Sequenz gefunden wird, das auch früher in der Sequenz erscheint, und das Bit ergänzt wird, das auf das jüngste frühere Auftreten dieses Suffix folgt.

Jede endliche Teilfolge von Bits tritt zusammenhängend und unendlich oft innerhalb der Folge auf

Die ersten Ziffern der Sequenz sind:

010011010111000100001111 ... (Sequenz A038219 in OEIS ).

Wenn wir 8 Bits der Sequenz zu einem Byte kombinieren, erhalten wir eine ASCII-Ausgabe, die wir auf dem Bildschirm oder in einer Datei ausgeben können und die jede mögliche endliche Ausgabe enthält . Das Programm gibt Teile von pi aus, den Text von „Never gonna give you up“ , ein paar nette ASCII-Grafiken, seinen eigenen Quellcode und alles andere, was Sie ausgeben möchten.

Um die Korrektheit zu testen, sind hier Hashes für die ersten 256 Bytes der Sequenz:

MD5: 5dc589a06e5ca0cd9280a364a456d7a4
SHA-1: 657722ceef206ad22881ceba370d32c0960e267f

Die ersten 8 Bytes der Sequenz in hexadezimaler Schreibweise lauten:

4D 71 0F 65 27 46 0B 7C

Regeln:

  • Ihr Programm muss die Ehrenfeucht-Mycielski-Sequenz ausgeben (sonst nichts) und 8 Bits zu einem Byte / ASCII-Zeichen kombinieren.

  • Das kürzeste Programm (Anzahl der Zeichen) gewinnt. Subtrahieren Sie 512 von Ihrer Zeichenanzahl, wenn Sie es schaffen, die Sequenz in linearer Zeit pro generiertem Byte zu generieren .

schnaader
quelle
Das längste Suffix in 010, das früher in dieser Sequenz auftauchte, ist 0, nicht wahr? Und die jüngste frühere Erscheinung ist nur die zweite 0. Und bis jetzt folgt nichts auf die zweite 0, so dass es nichts gibt, worauf wir eine Ergänzung aufbauen können. Ich bin kein englischer Muttersprachler - vielleicht habe ich es einfach falsch verstanden. Der Wikipedia-Artikel verwendet die gleichen Wörter, hat aber eine längere Sequenz, daher würde ich ihn als "den neuesten ... der einen Anhänger hat" bezeichnen.
Benutzer unbekannt
8
Pedantischer Zwiespalt: pi wird niemals erscheinen - nur jeder endliche String wird in der Ausgabe enthalten sein.
Keith Randall
Ich habe noch eine Frage: Darf sich eine Wiederholung überschneiden? Zum Beispiel in 111 (1 [1) 1]?
Benutzer unbekannt
@KeithRandall: Ich würde eine Sequenz vorziehen, die garantiert nicht "Never gonna give you" und Produktionen ähnlicher Art enthält.
Benutzer unbekannt
2
Es kann erwähnenswert sein, dass das bloße Vorhandensein einer "Antwort", die an einer nicht spezifizierten Stelle in einer unendlichen Zeichenkette eingebettet ist, natürlich nicht als "Ausgeben" dieser Antwort angesehen werden kann. Auch diese bestimmte Sequenz ist nur ein Beispiel für eine disjunktive Sequenz - es gibt unendlich viele Sequenzen wie diese.
Res

Antworten:

7

C –110 Zeichen

Diese Programmversion verwendet den linearen Laufzeitalgorithmus, um die Sequenz zu generieren. Das Subtrahieren von 512 von den 402 Zeichen im Programm ergibt insgesamt negative 110.

#define C v=calloc(7,8),v->p=p
#define G(F,K)u->F[d[K]]
#define S(F,T)G(f,T)=F,G(t,T)=T,G(n,T)=
struct{int p,f[2],t[2];void*n[2];}r,*u,*v,*w;char*d,c;p,b,h,i,j,k;
main(s){for(;d=++p-s?d:realloc(d,s*=2);){d[i=p]=b;c+=c+b;p%8||putchar(c);
for(u=&r;b=u->p,u->p=p,w=G(n,k=i);S(i,k)v=G(n,k),u=v)for(h=G(f,k),j=G(t,k);j>h;--i,--j)
if(d[i]-d[j]){S(i,k)C;u=v;S(h,j)w;S(0,i)C;b=w->p;goto x;}S(0,i)C;x:b=1-d[b+1];}}

Gemäß dem Problem wird das Programm in einer Endlosschleife ausgeführt, die viel Speicherzuweisung erfordert, und die Verwendung realloc(), um die Sequenz zusammenhängend zu halten, kann zur Heap-Fragmentierung beitragen. Sie können die Speichernutzung des Programms verbessern, indem Sie calloc(7,8)in der ersten Zeile durch ersetzen calloc(1,sizeof*v). Dies ist besonders auf 32-Bit-Computern hilfreich, bei denen 56 wahrscheinlich um den Faktor zwei zu groß ist.

Der Code ist irgendwie unlesbar und nicht auf interessante Weise; dafür entschuldige ich mich. Ehrlich gesagt, auch die ungolfed Version ist nicht besonders klar:

#include <stdio.h>
#include <stdlib.h>

typedef struct branch branch;
typedef struct node node;

struct branch {
    int from, to;
    node *next;
};

struct node {
    int pos;
    branch br[2];
};

static node root = { 0 };

static unsigned char *data = NULL;
static int endpos = 0;
static int size = 1;

static node *mknode(void)
{
    node *n;

    n = calloc(1, sizeof *n);
    n->pos = endpos;
    return n;
}

static branch *getbranch(node *n, int p)
{
    return &n->br[data[p]];
}

static void setbranch(node *n, int from, int to, node *next)
{
    n->br[data[to]].next = next;
    n->br[data[to]].from = from;
    n->br[data[to]].to = to;
}

int main(void)
{
    node *u, *v, *w;
    int follower, from, i, i0, j;
    int out, b;

    out = b = 0;
    for (;;) {
        ++endpos;
        if (endpos == size) {
            size *= 2;
            data = realloc(data, size);
        }
        data[endpos] = b;
        out = (out << 1) | b;
        if (endpos % 8 == 0) {
            putchar(out);
            out = 0;
        }

        i = endpos;
        u = &root;
        for (;;) {
            follower = u->pos + 1;
            u->pos = endpos;
            w = getbranch(u, i)->next;
            if (!w)
                break;
            i0 = i;
            from = getbranch(u, i0)->from;
            for (j = getbranch(u, i0)->to ; j > from ; --j) {
                if (data[i] != data[j]) {
                    /* divide branch */
                    v = mknode();
                    setbranch(u, i, i0, v);
                    u = v;
                    setbranch(u, from, j, w);
                    setbranch(u, 0, i, mknode());
                    follower = w->pos + 1;
                    goto bitfound;
                }
                --i;
            }
            v = getbranch(u, i0)->next;
            setbranch(u, i, i0, v);
            u = v;
        }
        /* extend branch */
        setbranch(u, 0, i, mknode());

      bitfound:
        b = 1 - data[follower];
    }
}

(Der obige ungolfed Code basiert auf dem Code von Grzegorz Herman und Michael Soltys, auf den in der Problembeschreibung und auf der Homepage von Soltys verwiesen wird .)

Vielen Dank an @schnaader und @res für den Hinweis auf einen Fehler in der ursprünglichen Version.

Brot-Box
quelle
Nett! Das habe ich mir mit dem -512-Bonus erhofft.
Schnaader
Irgendeine Idee, warum dies Systemabstürze verursacht? Alle Golf-, Ungolf- und mallocModified-Versionen stoppen die Ausgabe nach etwa 10000 Bytes und weisen weiterhin Speicher zu. Dies prog > out.datführt zu einem sofortigen Absturz mit nur ca. 700 KB Speicherverbrauch. Wenn ich einfügen printf("\n%i\n", size);nach realloc, ist die größte Ausgabe 4. System: Windows 7 Prof. 64-Bit, 4 GB RAM, GCC 4.6.1
schnaader
(+1) Ich finde, dass mit Ubuntu12.04 / gcc beide Programme kompilieren und die richtige Ausgabe erzeugen ... Mit Win7 / mingw / gcc kompilieren beide Programme aber erzeugen Segmentierungsfehler ... Mit Win7 / lcc erzeugen die ungolfed version funktioniert, aber die golfed version erzeugt segmentierungsfehler.
Res
1
Das klingt für mich wie die Verwendung nicht initialisierter Daten. Sicher genug - Ich habe keinen Zugriff auf einen Windows-Computer, aber valgrind zeigt das Problem. Sieht so aus, als hätte ich diesen Fehler auch von der ursprünglichen Referenzimplementierung reproduziert. Zum Glück ist es eine einfache Lösung; Vielen Dank für den Hinweis!
Brotkasten
Großartig, funktioniert jetzt wie ein Zauber.
Schnaader
6

Ruby, 109 104 101 94 Zeichen

s=?0
loop{s=(s[/(.*).*\1/][/.#{$1}/]<?1??1:?0)+s
s.size&7<1&&$><<[s.reverse.to_i(2)].pack(?C)}

Implementierung in Ruby mit regulären Ausdrücken für die Suffixsuche. Da es ziemlich lange dauert, bis der Speicher voll ist, muss das Programm vom Benutzer beendet werden.

Edit: Mir ist gerade aufgefallen, dass es ausreicht, mit der Sequenz zu beginnen 0.

Edit 2: Der Vorschlag von res speichert 2 Zeichen, einige andere, weil wir vorher kein einziges Byte ausschneiden müssen pack.

Howard
quelle
Mit s=(s[/(.*).*\1/][/.#{$1}/]<?1??1:?0)+swerden zwei weitere Zeichen gespeichert.
Res
@res Das funktioniert in der Tat. Vielen Dank.
Howard
Kannst du die Klammern loswerden ?C?
Fund Monica's Lawsuit
4

Perl, 95 Zeichen

Anfangs hatte ich tatsächlich eine halbwegs anständige Version davon. Dann, als ich Golf spielte, wurde jede Version langsamer. Immer langsamer.

$|=$_="010";
y///c%8||print pack"B*",/(.{8})$/while/(.+)$(?(?{m|.*$^N(.)|})(?{$_.=1-$^N})|(?!))/

Die ersten drei Zeichen ( $|=) sind streng genommen nicht erforderlich. Ohne diese Zeichen müssen Sie normalerweise warten, bis das Skript die vollständigen 4096 Byte generiert hat, bevor Sie eine Ausgabe sehen. Und das würde Stunden dauern. Vielleicht Jahrhunderte; Ich bin mir nicht sicher. Habe ich erwähnt, dass sich die Leistung dieses Programms im Laufe der Zeit verschlechtert? Aus diesem Grund fühlte ich mich gezwungen, sie in die Zählung einzubeziehen.

Auf der anderen Seite hat dieses Skript eine der hässlichsten Regexen, die ich je erstellt habe, also denke ich, dass ich stolz darauf bin.

Brot-Box
quelle
1
Mach dir keine Sorgen um die Leistung, der Algorithmus ist O (N ^ 3) ohne Optimierungen. Mein einfaches Delphi-Programm, das ich geschrieben habe, hat ungefähr 30 Sekunden für 256 Bytes, aber ungefähr eine Stunde für 1024 Bytes gedauert, also würde ich davon ausgehen, dass 4096 Bytes einen oder mehrere Tage dauern. Natürlich haben RegEx- und Space-Optimierungen das Potenzial, es noch schlimmer zu machen :)
Schnaader
Mein anfängliches Perl-Skript dauerte 10 Sekunden für 256 Bytes. Diese Version dauert 90 Sekunden. (Es scheint auch keine lineare Verlangsamung zu sein.)
Brotkasten