Metagolf estrelado

25

Estrelado é uma linguagem de programação esotérica engraçada, na qual o código consiste apenas em +*.,`'onde o comando real representado por cada um desses caracteres é determinado pelo número de espaços à sua frente. Isso torna complicado até mesmo enfrentar desafios de saída fixa, porque comandos diferentes podem ser responsáveis ​​por números muito diferentes de bytes. Em particular, os literais numéricos têm uma representação unária, o que torna necessário construir números maiores operando em números menores.

Portanto, esse desafio é escrever um programa que possa jogar golfe nesses programas estrelados.

Como funciona o Starry?

(Alguns detalhes são deixados não especificados em esolangs, então eu vou acompanhar o comportamento do intérprete Ruby .)

Estrelado é uma linguagem baseada em pilha, com uma única pilha de valores inteiros de precisão arbitrária (inicialmente vazia).

Os únicos caracteres significativos são:

+*.,`'

e espaços. Todos os outros caracteres são ignorados. Cada sequência de espaços seguida por um desses caracteres não espaciais representa uma única instrução. O tipo de instrução depende do caractere não espacial e do número de espaços.

As instruções são:

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. 

Observe que o intérprete verifica o código-fonte quanto aos rótulos antes do início da execução, portanto é possível pular para frente e para trás.

É claro que Starry também possui comandos de entrada (usando ,analogamente para .), mas esses são irrelevantes para esse desafio.

O desafio

Dada uma sequência, gere um programa Starry que não recebe entrada e imprime essa sequência exatamente em STDOUT.

Você pode escrever um programa ou função, recebendo entrada via STDIN (ou alternativa mais próxima), argumento da linha de comando ou argumento da função e emitindo o resultado via STDOUT (ou alternativa mais próxima), valor de retorno da função ou parâmetro da função (saída).

Você pode supor que a string não tenha mais de 128 caracteres e que consistirá apenas em caracteres ASCII imprimíveis (pontos de código 0x20 a 0x7E).

Sua solução deve processar qualquer entrada desse tipo em menos de 5 minutos em uma máquina de desktop razoável (há alguma margem de manobra; se demorar mais alguns minutos no meu laptop, não me importo, mas se levar 15, desqualificarei isto).

Sua solução será testada em várias seqüências diferentes listadas abaixo. Sua pontuação é a contagem total de bytes dos programas Starry correspondentes. No caso de empate, o menor metagolfer vence. Ou seja, não se preocupe em jogar seu próprio código a menos que haja um empate (o que eu acho que só acontecerá no caso de uma solução ideal ser possível).

Você não deve otimizar seu código para os casos de teste específicos listados abaixo. Especificamente, você não deve codificar soluções artesanais para eles. A otimização para classes de strings cuja estrutura é semelhante à das strings fornecidas é boa. Se eu suspeitar de alguém com soluções codificadas, reservo-me o direito de substituir alguns ou todos os casos de teste (por cadeias de estruturas comparáveis).

Casos de teste

Cada linha é um caso de teste 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

Os créditos para o segundo caso de teste vão para Dennis . Os créditos para o quarto caso de teste vão para o Sp3000.

Solução de referência

Aqui está uma solução de referência realmente básica no CJam:

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

Você pode executá-lo em todo o conjunto de testes aqui. As pontuações são:

1233
5240
4223
11110
7735
10497
11524
11392
Total: 62954

É a abordagem mais simples possível: pressione o ponto de código de cada caractere como um literal e imprima-o. Não faz uso de pequenas diferenças entre caracteres consecutivos, impressão inteira, partes repetitivas da string, etc. Vou deixar essas coisas para você.

Acredito que há muito espaço para melhorias. Para referência, o mais curto artesanal "Olá, Mundo!" tem apenas 169 bytes.

Martin Ender
fonte

Respostas:

6

Ruby, 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}

O método starryresponde à pergunta dada.

Resultados:

230
639
682
1974
1024
1897
2115
2436
Total: 10997

Como funciona

shortesté o algoritmo principal. Ele pega um número e encontra a maneira mais curta de colocá-lo na pilha, ou usa dois números e retorna o código para colocar o segundo na pilha, assumindo que o primeiro já esteja ativado. $sé um Hash para armazenar os resultados dessas operações para uso posterior.

starrypega uma string e a divide em uma matriz de códigos de caracteres (ou números para resumos). Inicia o código com um número na parte inferior da pilha. Em seguida, calcula a maneira mais curta possível de gerar cada número sucessivo, possivelmente copiando o último ou usando o número colocado na pilha no início.

MegaTom
fonte
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)

A função relevante é o nome apropriadamente metagolf .

Os resultados são:

210
676
684
2007
1463
2071
2204
2530
Total: 11845

Você pode encontrar a saída completa aqui .

Breve explicação

Vou manter a explicação breve, pois ainda há muitas coisas a serem melhoradas.

O algoritmo básico apenas analisa pares de caracteres e encontra a maneira ideal de fazer a transição de um caracter para outro via BFS. No momento, os dígitos são enviados e impressos imediatamente, embora isso seja alterado posteriormente.

Também há uma subsequência comum mais longa acontecendo, para deixar alguns elementos na pilha para reutilização mais tarde. Não é tão bom quanto a repetição, mas proporciona uma economia decente.

Sp3000
fonte
Hooray, alguém para batalhar :-) Claro, agora vejo que o meu ainda tem um longo caminho a percorrer ...
ETHproductions
5

JavaScript, 25158 23778

Agora compatível com 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

Um bom começo na minha opinião, mas obviamente não terminou. Em vez de criar cada caractere separadamente, ele adiciona ou subtrai do código de caractere anterior. Vou adicionar uma explicação completa quando terminar o meta-golfe.

ETHproductions
fonte
Sim, ele funciona no Firefox agora, embora o Chrome ainda se queixe charCodeAt.
Martin Ender