Métagolf étoilé

25

Starry est un langage de programmation ésotérique amusant dans lequel le code consiste uniquement à déterminer +*.,`'où la commande réelle représentée par chacun de ces caractères est déterminée par le nombre d'espaces devant lui. Cela rend difficile même de relever des défis de sortie fixe, car différentes commandes peuvent représenter un nombre d'octets très différent. En particulier, les littéraux numériques ont une représentation unaire, ce qui oblige à construire de plus grands nombres en opérant sur des plus petits.

Par conséquent, ce défi consiste à écrire un programme qui peut jouer à de tels programmes Starry.

Comment fonctionne Starry?

(Quelques détails ne sont pas spécifiés sur les esolangs, donc je vais avec le comportement de l' interpréteur Ruby .)

Starry est un langage basé sur la pile, avec une seule pile de valeurs entières de précision arbitraire (qui est initialement vide).

Les seuls personnages significatifs sont:

+*.,`'

et les espaces. Tous les autres personnages sont ignorés. Chaque séquence d'espaces suivie d'un de ces caractères non-espace représente une instruction unique. Le type d'instruction dépend du caractère non-espace et du nombre d'espaces.

Les instructions sont les suivantes:

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. 

Notez que l'interpréteur analyse le code source pour les étiquettes avant le début de l'exécution, il est donc possible de sauter en avant comme en arrière.

Bien sûr, Starry a également des commandes d'entrée (à utiliser de ,manière analogue à .), mais celles-ci ne sont pas pertinentes pour ce défi.

Le défi

Étant donné une chaîne, générez un programme Starry qui ne prend aucune entrée et imprime cette chaîne exactement dans STDOUT.

Vous pouvez écrire un programme ou une fonction, en prenant une entrée via STDIN (ou l'alternative la plus proche), un argument de ligne de commande ou un argument de fonction et en sortant le résultat via STDOUT (ou l'alternative la plus proche), la valeur de retour de la fonction ou le paramètre de la fonction (out).

Vous pouvez supposer que la chaîne ne dépasse pas 128 caractères et qu'elle ne sera composée que de caractères ASCII imprimables (points de code 0x20 à 0x7E).

Votre solution devrait traiter une telle entrée en moins de 5 minutes sur une machine de bureau raisonnable (il y a une certaine marge de manœuvre; si cela prend quelques minutes de plus sur mon ordinateur portable, cela ne me dérange pas, mais si cela prend 15, je disqualifierai il).

Votre solution sera testée sur un certain nombre de chaînes différentes répertoriées ci-dessous. Votre score est le nombre total d'octets des programmes Starry correspondants. En cas d'égalité, le métagolfer le plus court l'emporte. Autrement dit, ne vous embêtez pas à jouer à votre propre code à moins qu'il y ait une égalité (ce qui, je pense, ne se produira que dans le cas où une solution optimale est possible).

Vous ne devez pas optimiser votre code vers les cas de test spécifiques répertoriés ci-dessous. Plus précisément, vous ne devez pas coder en dur des solutions artisanales pour eux. L'optimisation vers des classes de chaînes dont la structure est similaire à celle des chaînes données est très bien. Si je soupçonne quelqu'un d'avoir des solutions de codage en dur, je me réserve le droit de remplacer tout ou partie des cas de test (avec des chaînes de structures comparables).

Cas de test

Chaque ligne est un cas de test distinct:

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

Les crédits pour le deuxième cas de test vont à Dennis . Les crédits pour le quatrième cas de test vont au Sp3000.

Solution de référence

Voici une solution de référence vraiment basique dans CJam:

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

Vous pouvez l'exécuter sur l'ensemble de la suite de tests ici. Les scores sont:

1233
5240
4223
11110
7735
10497
11524
11392
Total: 62954

C'est l'approche la plus simple possible: poussez le point de code de chaque caractère comme un littéral, puis imprimez-le. Il n'utilise pas de petites différences entre les caractères consécutifs, l'impression entière, les parties répétitives de la chaîne, etc. Je vous laisse ces choses.

Je pense qu'il y a encore beaucoup à faire. Pour référence, le plus court "Hello, World!" ne fait que 169 octets.

Martin Ender
la source

Réponses:

6

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

La méthode starryrépond à la question donnée.

Résultats:

230
639
682
1974
1024
1897
2115
2436
Total: 10997

Comment ça marche

shortestest l'algorithme principal. Il prend un numéro et trouve le moyen le plus court de le placer sur la pile, ou il prend deux chiffres, et renvoie du code pour placer le second sur la pile en supposant que le premier est déjà allumé. $sest un hachage pour conserver les résultats de ces opérations pour une utilisation ultérieure.

starryprend une chaîne et la divise en un tableau de codes de caractères (ou nombres pour les résumés). Il démarre le code avec un numéro au bas de la pile. Ensuite, il calcule la manière la plus courte de générer chaque numéro successif, en copiant éventuellement le dernier ou en utilisant le numéro placé sur la pile au début.

MegaTom
la source
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 fonction pertinente est celle qui porte bien son nom metagolf.

Les résultats sont:

210
676
684
2007
1463
2071
2204
2530
Total: 11845

Vous pouvez trouver la sortie complète ici .

Brève explication

Je vais garder l'explication brève car il y a encore beaucoup de choses à améliorer.

L'algorithme de base examine simplement les paires de caractères et trouve le moyen optimal de passer d'un caractère à un autre via BFS. Les chiffres sont actuellement poussés et imprimés immédiatement, bien que cela soit modifié ultérieurement.

Il y a aussi une petite sous-séquence commune la plus longue, pour laisser quelques éléments sur la pile pour une réutilisation ultérieure. Ce n'est pas aussi bon que la répétition, mais offre des économies décentes.

Sp3000
la source
Hourra, quelqu'un à combattre :-) Bien sûr, maintenant je vois que le mien a encore un long chemin à parcourir ...
ETHproductions
5

JavaScript, 25158 23778

Maintenant compatible 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;
}

Résultats:

432
949
2465
3996
1805
3551
5205
5375
Total: 23778

Un bon début à mon avis, mais évidemment pas fini. Au lieu de créer chaque caractère séparément, il ajoute ou soustrait du code de caractère précédent. J'ajouterai une explication complète lorsque j'aurai terminé le méta-golf.

ETHproductions
la source
Oui, cela fonctionne maintenant dans Firefox, même si Chrome se plaint toujours charCodeAt.
Martin Ender