Universal (flexión de reglas) Code Golf solver

14

El código de golf siempre involucra algunas respuestas que doblan las reglas más o menos al romper las restricciones que los retadores dieron por sentado o simplemente no pensaron y no mencionaron en las reglas. Una de estas lagunas interesantes es la posibilidad de generar más de lo que el desafío requiere para obtener un mejor resultado.

Llevando esto a los extremos, podemos escribir un solucionador de golf de código universal que imprima la salida deseada, si no le importa, podría llevar años y generar muchas otras cosas antes y después.

Todo lo que necesitamos para generar es una secuencia que garantice que contenga todas las subsecuencias posibles. Para este código de golf, esta será la secuencia Ehrenfeucht-Mycielski :

La secuencia comienza con los tres bits 010; cada dígito sucesivo se forma al encontrar el sufijo más largo de la secuencia que también aparece antes dentro de la secuencia y complementar el bit que sigue a la aparición anterior más reciente de ese sufijo.

Cada subsecuencia finita de bits ocurre contiguamente, infinitamente a menudo dentro de la secuencia

Los primeros dígitos de la secuencia son:

010011010111000100001111 ... (secuencia A038219 en OEIS ).

Combinando 8 bits de la secuencia en un byte, obtendremos una salida ASCII que podemos enviar a la pantalla o a un archivo y que contiene todas las salidas finitas posibles . El programa generará partes de pi, la letra de "Nunca te daré por vencido" , un bonito arte ASCII, su propio código fuente y todo lo demás que quieras que salga.

Para probar la corrección, aquí hay hashes para los primeros 256 bytes de la secuencia:

MD5: 5dc589a06e5ca0cd9280a364a456d7a4
SHA-1: 657722ceef206ad22881ceba370d32c0960e267f

Los primeros 8 bytes de la secuencia en notación hexadecimal son:

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

Reglas:

  • Su programa debe generar la secuencia Ehrenfeucht-Mycielski (nada más), combinando 8 bits a un carácter byte / ASCII.

  • El programa más corto (recuento de personajes) gana. Resta 512 de tu recuento de caracteres si logras generar la secuencia en tiempo lineal por byte generado .

Schnaader
fuente
El sufijo más largo en 010 que apareció anteriormente en esa secuencia es 0, ¿no? Y la aparición anterior más reciente es solo el segundo 0. Y hasta ahora, nada sigue al segundo 0, por lo que no hay nada sobre lo que podamos construir un complemento. No soy un hablante nativo de inglés, tal vez me equivoqué. El artículo de wikipedia usa las mismas palabras, pero tiene una secuencia más larga, por lo que lo llamaría "el más reciente ... que tiene un seguidor".
Usuario desconocido
8
Quibble pedante: pi nunca aparecerá, solo cada cadena finita estará contenida en la salida.
Keith Randall
Tengo otra pregunta: ¿se puede superponer una repetición? Por ejemplo en 111, (1 [1) 1]?
Usuario desconocido
@KeithRandall: Preferiría una secuencia que garantice que no contenga 'Nunca te daré por vencido' y producciones de tipo similar.
Usuario desconocido
2
Vale la pena mencionar que la mera presencia de una "respuesta" incrustada en una ubicación no especificada en una cadena infinita no puede considerarse como "salida" de esa respuesta, por supuesto. Además, esta secuencia en particular es solo un ejemplo de una secuencia disyuntiva : hay infinitas secuencias como esta.
res

Respuestas:

7

C, –110 caracteres

Esta versión del programa utiliza el algoritmo de tiempo de ejecución lineal para generar la secuencia. Restar 512 de los 402 caracteres en el programa da un total de 110 negativos.

#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];}}

Según el problema, el programa se ejecuta en un bucle infinito, lo que requiere mucha asignación de memoria, y el uso realloc()para mantener la secuencia contigua puede contribuir a la fragmentación del montón. Puede mejorar el uso de memoria del programa reemplazando calloc(7,8)en la primera línea con calloc(1,sizeof*v). Esto ayudará especialmente en una máquina de 32 bits, donde 56 es probablemente demasiado grande por un factor de dos.

El código es ilegible y no de una manera interesante; Por eso me disculpo. Francamente, incluso la versión sin golf no es terriblemente clara:

#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];
    }
}

(El código anterior no basado en golf se basa en el código escrito por Grzegorz Herman y Michael Soltys, como se menciona en la descripción del problema, y ​​en la página de inicio de Soltys ).

Gracias a @schnaader y @res por informar un error en la versión inicial.

caja de pan
fuente
¡Agradable! Eso es lo que esperaba con el bono -512.
schnaader
¿Alguna idea de por qué esto provoca bloqueos por sistema? Todas las mallocversiones modificadas , sin golf y modificadas paralizan la salida después de aproximadamente 10000 bytes y continúan asignando memoria, lo que prog > out.datproduce un bloqueo instantáneo con solo ~ 700 KB de uso de memoria. Si inserto printf("\n%i\n", size);después realloc, la mayor salida es 4. Sistema: Windows 7 Prof. 64-Bit, 4 GB RAM, GCC 4.6.1
schnaader
(+1) Me parece que con Ubuntu12.04 / gcc, ambos programas compilan y producen la salida correcta ... Con Win7 / mingw / gcc, ambos programas compilan pero producen fallas de segmentación ... Con Win7 / lcc, el la versión sin golf funciona, pero la versión con golf produce fallas de segmentación.
res
1
Eso suena como el uso de datos no inicializados para mí. Efectivamente, no tengo acceso a una máquina con Windows, pero valgrind muestra el problema. Parece que también reproduje este error de la implementación de referencia original. Afortunadamente es una solución fácil; gracias por informarlo!
breadbox
Genial, funciona como un encanto ahora.
schnaader
6

Ruby, 109 104 101 94 caracteres

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

Implementación en Ruby utilizando expresiones regulares para la búsqueda de sufijos. Como lleva bastante tiempo hasta que se agote la memoria, el usuario debe finalizar el programa.

Editar: Acabo de notar que es suficiente comenzar con la secuencia 0.

Edición 2: la propuesta de res guarda 2 caracteres, algunos otros porque no tenemos que cortar un solo byte antes pack.

Howard
fuente
El uso s=(s[/(.*).*\1/][/.#{$1}/]<?1??1:?0)+ssalvará otros dos caracteres.
res
@res Esto funciona de hecho. Gracias.
Howard
¿Puedes deshacerte de los paréntesis ?C?
Financia la demanda de Mónica el
4

Perl, 95 caracteres

En realidad, al principio tenía una versión bastante decente de esto. Luego, mientras jugaba al golf, cada versión se hizo más lenta. Cada vez más lento.

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

Los primeros tres caracteres ( $|=) no son necesarios, estrictamente hablando ... pero sin eso allí, normalmente tendrías que esperar a que el script termine de generar 4096 bytes completos antes de ver cualquiera de los resultados. Y eso llevaría horas. Quizás siglos; No estoy seguro. ¿Mencioné que el rendimiento de este programa se deteriora con el tiempo? Por eso me sentí obligado a incluirlos en el recuento.

Por otro lado, este script tiene una de las expresiones regulares más feas que he creado, así que creo que estoy orgulloso de ello.

caja de pan
fuente
1
No se preocupe por el rendimiento, el algoritmo es O (N ^ 3) sin optimizaciones. Mi sencillo programa Delphi que escribí tardó aproximadamente 30 segundos para 256 bytes, pero aproximadamente una hora para 1024 bytes, por lo que supongo que 4096 bytes demorarán uno o varios días. Por supuesto, las optimizaciones RegEx y de espacio tienen el potencial de empeorarlo :)
schnaader
Mi script inicial de Perl tardó 10 segundos para 256 bytes. Esta versión lleva 90 segundos. (Tampoco parece ser una desaceleración lineal.)
breadbox