Metagolf estrellado

25

Starry es un divertido lenguaje de programación esotérico en el que el código consiste solo en +*.,`'donde el comando real representado por cada uno de esos caracteres está determinado por el número de espacios en frente de él. Eso hace que sea complicado incluso para los desafíos de salida fija de golf, porque los diferentes comandos pueden representar cantidades muy diferentes de bytes. En particular, los literales numéricos tienen una representación unaria que hace que sea necesario construir números más grandes al operar con números más pequeños.

Por lo tanto, este desafío se trata de escribir un programa que pueda jugar golf a tales programas Starry.

¿Cómo funciona Starry?

(Algunos detalles se dejan sin especificar en esolangs, así que voy con el comportamiento del intérprete de Ruby ).

Starry es un lenguaje basado en la pila, con una sola pila de valores enteros de precisión arbitraria (que inicialmente está vacía).

Los únicos caracteres significativos son:

+*.,`'

y espacios. Todos los demás personajes son ignorados. Cada secuencia de espacios seguida por uno de esos caracteres que no son espacios representa una sola instrucción. El tipo de instrucción depende del carácter no espacial y el número de espacios.

Las instrucciones son:

Spaces  Symbol  Meaning
0         +     Invalid opcode.
1         +     Duplicate top of stack.
2         +     Swap top 2 stack elements.
3         +     Rotate top 3 stack elements. That is, send the top stack element
                two positions down. [... 1 2 3] becomes [... 3 1 2].
4         +     Pop and discard top of stack.
n ≥ 5     +     Push n − 5 to stack.
0 mod 5   *     Pop y, pop x, push x + y.
1 mod 5   *     Pop y, pop x, push x − y.
2 mod 5   *     Pop y, pop x, push x * y.
3 mod 5   *     Pop y, pop x, push x / y, rounded towards -∞.
4 mod 5   *     Pop y, pop x, push x % y. The sign of the result matches the sign of y.
0 mod 2   .     Pop a value and print it as a decimal number.
1 mod 2   .     Pop a value and print it as an ASCII character. This throws an error
                if the value is not in the range [0, 255].
n         `     Mark label n.
n         '     Pop a value; if non-zero, jump to label n. 

Tenga en cuenta que el intérprete escanea el código fuente de las etiquetas antes de que comience la ejecución, por lo que es posible saltar hacia adelante y hacia atrás.

Por supuesto, Starry también tiene comandos de entrada (que se usan de forma ,análoga a .), pero estos son irrelevantes para este desafío.

El reto

Dada una cadena, genere un programa Starry que no tome entrada e imprima esa cadena exactamente en STDOUT.

Puede escribir un programa o función, tomando la entrada a través de STDIN (o la alternativa más cercana), argumento de línea de comando o argumento de función y generando el resultado a través de STDOUT (o la alternativa más cercana), el valor de retorno de la función o el parámetro de función (out).

Puede suponer que la cadena no tiene más de 128 caracteres y que consistirá solo en caracteres ASCII imprimibles (puntos de código 0x20 a 0x7E).

Su solución debería procesar cualquier entrada de este tipo en menos de 5 minutos en una máquina de escritorio razonable (hay algo de margen para esto; si toma unos minutos más en mi computadora portátil, no me importa, pero si toma 15, descalificaré eso).

Su solución se probará en varias cadenas diferentes que se enumeran a continuación. Su puntaje es el recuento total de bytes de los programas Starry correspondientes. En caso de empate, gana el metagolfer más corto. Es decir, no se moleste en jugar su propio código a menos que haya un empate (lo que creo que solo sucederá en el caso de que sea posible una solución óptima).

No debe optimizar su código para los casos de prueba específicos que se enumeran a continuación. Específicamente, no debe codificar soluciones hechas a mano para ellos. Está bien optimizar hacia clases de cadenas cuya estructura es similar a la de las cadenas dadas. Si sospecho que alguien tiene soluciones de codificación, me reservo el derecho de reemplazar algunos o todos los casos de prueba (con cadenas de estructuras comparables).

Casos de prueba

Cada línea es un caso de prueba separado:

Hello, World!
pneumonoultramicroscopicsilicovolcanoconiosis
.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.
Hickory, dickory, dock. The mouse ran up the clock. The clock struck 1. The mouse ran down. Hickory, dickory, dock.
36912059868043514648560046917066768694455682545071266675083273015450033938555319356951628735735013250100789433961153496780296165
bVZ48121347GLtpYnt76CZSxTpMDs6791EJE808077eySXldY162424ddTB90707UupwlWGb63618542VhA252989453TXrWgqGm85899uHOAY2oAKE198GOVUttvW63
7MYxoWBNt180CDHS5xBGvU70HHVB17bh8jYzIIiU6n6g98Rose1nOe8Svcg56nax20q30kT3Ttb2jHl5q2Iuf1vPbjPxm9cyKXwxc0OUK8pr13b2n7U9Y7RwQTc26A1I
n9}unwxVa}[rj+5em6K#-H@= p^X/:DS]b*Jv/_x4.a5vT/So2R`yKy=in7-15B=g _BD`Bw=Z`Br;UwwF[{q]cS|&i;Gn4)q=`!G]8"eFP`Mn:zt-#mfCV2AL2^fL"A

Los créditos para el segundo caso de prueba van a Dennis . Los créditos para el cuarto caso de prueba van a Sp3000.

Solución de referencia

Aquí hay una solución de referencia realmente básica en CJam:

q{S5*\iS*'+S'.}%

Puede ejecutarlo contra todo el conjunto de pruebas aquí. Los puntajes son:

1233
5240
4223
11110
7735
10497
11524
11392
Total: 62954

Es el enfoque más simple posible: empuje el punto de código de cada personaje como un literal y luego imprímalo. No hace uso de pequeñas diferencias entre caracteres consecutivos, impresión de enteros, partes repetitivas de la cadena, etc. Te dejaré esas cosas.

Creo que hay mucho margen de mejora. Como referencia, el más corto hecho a mano "Hello, World!" tiene solo 169 bytes de longitud.

Martin Ender
fuente

Respuestas:

6

Rubí, 13461 10997

$s = {};
def shortest a,b=nil
    return $s[[a,b]] if $s[[a,b]]
    l = []
    if b
        if a == b
            return $s[[a,b]] = ""
        elsif a > b
            l.push shortest(a-b)+" *"
            l.push " +   *"+shortest(1,b) if a > 1
            l.push " + *"+shortest(0,b) if a > 0
            l.push "    +"+shortest(b)
        elsif a < b
            l.push " +  *"+shortest(a*a,b) if a*a>a && a*a<=b
            l.push " +*"+shortest(a+a,b) if a+a<=b && a+a>a
            l.push shortest(b-a)+"*"
            l.push " +"+shortest(a,b/a)+"  *" if a>2 && b%a == 0
            l.push " +"+shortest(a,b-a)+"*" if a>1 && b>a*2
        end
    else
        l.push ' '*(a+5)+'+' #if a < 6
        (1..a/2).each {|n|
            l.push shortest(n)+shortest(n,a)
        }
    end
    return $s[[a,b]] = l.min_by{|x|x.length}
end

def starry(str)
    arr = str.bytes.map{|b|
        if b>47 && b<58
            b-48# change digets to numbers
        else
            b
        end
    }

    startNum = (1..128).min_by{|x|arr.inject{|s,y|s + [shortest(x,y).length+2,shortest(y).length].min}+shortest(x).length}
    #one number to be put on the stack at the start.

    code = shortest(startNum)
    code += [
        shortest(arr[0]),
        " +"+shortest(startNum, arr[0])
    ].min_by{|x|x.length}

    arr.each_cons(2) do |a|
        pr = a[0]<10?'.':' .'
        code += [
            pr+shortest(a[1]),
            " +"+pr+shortest(a[0], a[1]),
            pr+" +"+shortest(startNum, a[1])
        ].min_by{|x|x.length}
    end
    code += arr[-1]<10?'.':' .'
end

a = ["Hello, World!",
"pneumonoultramicroscopicsilicovolcanoconiosis",
".oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.",
"Hickory, dickory, dock. The mouse ran up the clock. The clock struck 1. The mouse ran down. Hickory, dickory, dock.",
"36912059868043514648560046917066768694455682545071266675083273015450033938555319356951628735735013250100789433961153496780296165",
"bVZ48121347GLtpYnt76CZSxTpMDs6791EJE808077eySXldY162424ddTB90707UupwlWGb63618542VhA252989453TXrWgqGm85899uHOAY2oAKE198GOVUttvW63",
"7MYxoWBNt180CDHS5xBGvU70HHVB17bh8jYzIIiU6n6g98Rose1nOe8Svcg56nax20q30kT3Ttb2jHl5q2Iuf1vPbjPxm9cyKXwxc0OUK8pr13b2n7U9Y7RwQTc26A1I",
"n9}unwxVa}[rj+5em6K#-H@= p^X/:DS]b*Jv/_x4.a5vT/So2R`yKy=in7-15B=g _BD`Bw=Z`Br;UwwF[{q]cS|&i;Gn4)q=`!G]8\"eFP`Mn:zt-#mfCV2AL2^fL\"A"]
c = a.map{
    |s|
    starry(s).length
}
p c.inject(0){|a,b|a+b}

El método starryresponde la pregunta dada.

Resultados:

230
639
682
1974
1024
1897
2115
2436
Total: 10997

Cómo funciona

shortestes el algoritmo principal Toma un número y encuentra la forma más corta de colocarlo en la pila, o toma dos números, y devuelve el código para colocar el segundo en la pila, suponiendo que el primero ya esté encendido. $ses un hash para guardar los resultados de estas operaciones para su uso posterior.

starrytoma una cadena y la divide en una matriz de códigos de caracteres (o números para resúmenes). Comienza el código con un número en la parte inferior de la pila. A continuación, calcula la forma más corta en que puede generar cada número sucesivo, posiblemente copiando el último o usando el número puesto en la pila al principio.

MegaTom
fuente
9

Python 3, 17071 11845

from functools import lru_cache
import heapq
import time

cases = r"""
Hello, World!
pneumonoultramicroscopicsilicovolcanoconiosis
.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.
Hickory, dickory, dock. The mouse ran up the clock. The clock struck 1. The mouse ran down. Hickory, dickory, dock.
36912059868043514648560046917066768694455682545071266675083273015450033938555319356951628735735013250100789433961153496780296165
bVZ48121347GLtpYnt76CZSxTpMDs6791EJE808077eySXldY162424ddTB90707UupwlWGb63618542VhA252989453TXrWgqGm85899uHOAY2oAKE198GOVUttvW63
7MYxoWBNt180CDHS5xBGvU70HHVB17bh8jYzIIiU6n6g98Rose1nOe8Svcg56nax20q30kT3Ttb2jHl5q2Iuf1vPbjPxm9cyKXwxc0OUK8pr13b2n7U9Y7RwQTc26A1I
n9}unwxVa}[rj+5em6K#-H@= p^X/:DS]b*Jv/_x4.a5vT/So2R`yKy=in7-15B=g _BD`Bw=Z`Br;UwwF[{q]cS|&i;Gn4)q=`!G]8"eFP`Mn:zt-#mfCV2AL2^fL"A
""".strip().splitlines()

@lru_cache(maxsize=128)
def shortest_m_to_n(m, n):
    if m is None:
        L = []
    else:
        L = [m]

    to_search = [[0, "", L]]
    seen = set()

    while True:
        length, code, stack = heapq.heappop(to_search)

        if len(stack) == 1 and stack[-1] == n:
            return code

        seen.add(tuple(stack))
        options = []

        for i in range(1, 11):
            new_stack = stack[:] + [i]
            new_code = code + ' '*(i+5) + '+'
            options.append([len(new_code), new_code, new_stack])

        if stack:
            new_stack = stack[:] + [stack[-1]]
            new_code = code + " +"
            options.append([len(new_code), new_code, new_stack])

        if len(stack) >= 2:
            x, y = stack[-2:]

            for i, op in enumerate(['+', '-', '*', '//', '%']):
                try:
                    new_elem = eval("{}{}{}".format(x, op, y))
                    new_stack = stack[:-2] + [new_elem]
                    new_code = code + ' '*i + '*'
                    options.append([len(new_code), new_code, new_stack])

                except ZeroDivisionError:
                    pass

        for op in options:
            if tuple(op[2]) in seen or len(op[2]) > 4 or op[2][-1] > 200:
                continue

            heapq.heappush(to_search, op)

def lcs(s1, s2):
    dp_row = [""]*(len(s2)+1)

    for i, c1 in enumerate(s1):
        new_dp_row = [""]

        for j, c2 in enumerate(s2):
            if c1 == c2 and not c1.isdigit():
                new_dp_row.append(dp_row[j] + c1)
            else:
                new_dp_row.append(max(dp_row[j+1], new_dp_row[-1], key=len))

        dp_row = new_dp_row

    return dp_row[-1]

def metagolf(s):
    keep = ""
    split_index = 0

    for i in range(1, len(s)):
        l = lcs(s[:i], s[i:][::-1])
        if len(l) > len(keep):
            keep = l
            split_index = i

    code = []
    stack = []
    keep_ptr = 0
    i = 0

    while i < len(s):
        c = s[i]
        n = ord(c)

        if c in "0123456789":
            code += [" "*(int(c)+5) + "+."]
            i += 1
            continue

        if stack:
            if stack[-1] == n:
                code += [" +", " ."]
            elif len(stack) >= 2 and stack[-2] == n:
                for j in range(len(code)):
                    if code[~j] == " +":
                        code[~j] = ""
                        break

                code += [" +", " ."]
                stack.pop()
            else:
                code += [shortest_m_to_n(stack[-1], n), " +", " ."]
                stack[-1] = n

        else:
            code += [shortest_m_to_n(None, n), " +", " ."]
            stack.append(n)

        while i < split_index and keep[keep_ptr:][:1] == c:
            code += [" +"]
            keep_ptr += 1
            stack.append(n)

        i += 1

    code = "".join(code)

    if code[-4:] == " + .":
        code = code[:-4] + " ."

    return code

total = 0

for case in cases:
    start_time = time.time()

    s = metagolf(case)
    print(len(s), time.time() - start_time)
    total += len(s)
    print(s)
    print('='*50)

print(total)

La función relevante es el nombre apropiado metagolf.

Los resultados son:

210
676
684
2007
1463
2071
2204
2530
Total: 11845

Puede encontrar la salida completa aquí .

Breve explicacion

Voy a mantener la explicación breve ya que todavía hay muchas cosas por mejorar.

El algoritmo básico solo analiza pares de caracteres y encuentra la forma óptima de pasar de un carácter a otro a través de BFS. Los dígitos se presionan e imprimen inmediatamente, aunque esto se cambiará más adelante.

También hay una pequeña subsecuencia común más larga, para dejar algunos elementos en la pila para su reutilización posterior. No es tan bueno como la repetición, pero proporciona ahorros decentes.

Sp3000
fuente
Hurra, alguien para luchar :-) Por supuesto, ahora veo que el mío tiene un largo camino por recorrer ...
ETHproductions
5

JavaScript, 25158 23778

¡Ahora compatible con ES5!

String.prototype.repeat = String.prototype.repeat || function (n) { return Array(n+1).join(this); }

function starrify(x) {
  function c(x){return x.charCodeAt()}
  var char = x[0], result = ' '.repeat(c(char)+5)+'+ + .';
  x=x.slice(1);
  for(var i in x) {
    if (char < x[i]) {
      result += ' '.repeat(c(x[i])-c(char)+5)+'+* + .';
    } else if (char > x[i]) {
      if(c(char)-c(x[i]) < c(x[i])) {
        result += ' '.repeat(c(char)-c(x[i])+5)+'+ * + .';
      } else {
        result += ' '.repeat(c(x[i])+5)+'+ + .';
      }
    } else {
      result += ' + .';
    }
    char = x[i];
  }
  return result;
}

Resultados:

432
949
2465
3996
1805
3551
5205
5375
Total: 23778

Un buen comienzo en mi opinión, pero obviamente no ha terminado. En lugar de crear cada carácter por separado, agrega o resta del código de carácter anterior. Agregaré una explicación completa cuando termine el meta-golf.

ETHproducciones
fuente
Sí, funciona en Firefox ahora, aunque Chrome todavía se queja charCodeAt.
Martin Ender