Sternenklarer Metagolf

25

Starry ist eine witzige esoterische Programmiersprache, in der Code nur besteht, +*.,`'wenn der tatsächliche Befehl, der durch jedes dieser Zeichen dargestellt wird, durch die Anzahl der Leerzeichen davor bestimmt wird. Das macht es sogar schwierig, Herausforderungen mit fester Ausgabe zu meistern, da unterschiedliche Befehle eine sehr unterschiedliche Anzahl von Bytes ausmachen können. Insbesondere haben Zahlenliterale eine unäre Repräsentation, die es erforderlich macht, größere Zahlen aufzubauen, indem kleinere verwendet werden.

Daher geht es bei dieser Herausforderung darum, ein Programm zu schreiben, mit dem solche Starry-Programme gespielt werden können.

Wie arbeitet Starry?

(Einige Details sind bei Esolangs nicht angegeben, daher gehe ich zum Verhalten des Ruby-Interpreters über .)

Sternenhimmel ist eine stapelbasierte Sprache mit einem einzelnen Stapel von Ganzzahlwerten mit willkürlicher Genauigkeit (der anfangs leer ist).

Die einzigen bedeutungsvollen Zeichen sind:

+*.,`'

und Räume. Alle anderen Zeichen werden ignoriert. Jede Folge von Leerzeichen, gefolgt von einem dieser Nicht-Leerzeichen, repräsentiert eine einzelne Anweisung. Die Art der Anweisung hängt vom Nicht-Leerzeichen und der Anzahl der Leerzeichen ab.

Die Anweisungen sind:

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. 

Beachten Sie, dass der Interpreter den Quellcode vor Beginn der Ausführung nach den Labels durchsucht, sodass sowohl vorwärts als auch rückwärts gesprungen werden kann.

Natürlich hat Starry auch Eingabebefehle ( ,analog zu .), aber diese sind für diese Herausforderung irrelevant.

Die Herausforderung

Generieren Sie mit einer gegebenen Zeichenfolge ein Starry-Programm, das keine Eingabe annimmt und die Zeichenfolge genau an STDOUT ausgibt.

Sie können ein Programm oder eine Funktion schreiben, indem Sie eine Eingabe über STDIN (oder die nächstgelegene Alternative), ein Befehlszeilenargument oder ein Funktionsargument vornehmen und das Ergebnis über STDOUT (oder die nächstgelegene Alternative), einen Funktionsrückgabewert oder einen Funktionsparameter (out) ausgeben.

Sie können davon ausgehen, dass die Zeichenfolge nicht länger als 128 Zeichen ist und nur aus druckbaren ASCII-Zeichen besteht (Codepunkte 0x20 bis 0x7E).

Ihre Lösung sollte solche Eingaben auf einem vernünftigen Desktop-Computer in weniger als 5 Minuten verarbeiten (es gibt einen gewissen Spielraum dafür; wenn es auf meinem Laptop ein paar Minuten länger dauert, macht es mir nichts aus, aber wenn es 15 Minuten dauert, werde ich disqualifizieren es).

Ihre Lösung wird anhand einer Reihe von unten aufgeführten Zeichenfolgen getestet. Ihre Punktzahl ist die Gesamtbytezahl der entsprechenden Starry-Programme. Bei einem Gleichstand gewinnt der kürzeste Metagolfer. Das heißt, spielen Sie nicht Ihren eigenen Code, es sei denn, es gibt ein Unentschieden (was meines Erachtens nur passieren wird, wenn eine optimale Lösung möglich ist).

Sie dürfen Ihren Code nicht für die unten aufgeführten spezifischen Testfälle optimieren. Insbesondere sollten Sie keine handgefertigten Lösungen für sie fest programmieren. Die Optimierung für Zeichenfolgenklassen, deren Struktur der der angegebenen Zeichenfolgen ähnelt, ist in Ordnung. Bei Verdacht auf Hardcoding-Lösungen behalte ich mir das Recht vor, einige oder alle Testfälle (durch Zeichenfolgen vergleichbarer Strukturen) zu ersetzen.

Testfälle

Jede Zeile ist ein eigener Testfall:

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

Credits für den zweiten Testfall gehen an Dennis . Credits für den vierten Testfall gehen an Sp3000.

Referenzlösung

Hier ist eine wirklich grundlegende Referenzlösung in CJam:

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

Sie können es hier für die gesamte Testsuite ausführen. Die Ergebnisse sind:

1233
5240
4223
11110
7735
10497
11524
11392
Total: 62954

Dies ist die einfachste Methode: Verschieben Sie den Codepunkt jedes Zeichens als Literal und drucken Sie ihn dann aus. Es werden keine kleinen Unterschiede zwischen aufeinanderfolgenden Zeichen, Ganzzahldrucken, sich wiederholenden Teilen der Zeichenfolge usw. verwendet. Diese Dinge überlasse ich Ihnen.

Ich glaube, es gibt viel Raum für Verbesserungen. Als Referenz die kürzeste handgefertigte "Hallo, Welt!" ist nur 169 Bytes lang.

Martin Ender
quelle

Antworten:

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}

Die Methode starrybeantwortet die gegebene Frage.

Ergebnisse:

230
639
682
1974
1024
1897
2115
2436
Total: 10997

Wie es funktioniert

shortestist der Hauptalgorithmus. Es nimmt eine Zahl und findet den kürzesten Weg, um sie auf den Stapel zu legen, oder es nimmt zwei Zahlen und gibt Code zurück, um die zweite auf den Stapel zu legen, vorausgesetzt, die erste ist bereits aktiviert. $sist ein Hash, um die Ergebnisse dieser Operationen zur weiteren Verwendung zu speichern.

starryNimmt eine Zeichenfolge und teilt sie in ein Array von Zeichencodes (oder Zahlen für Digests) auf. Der Code beginnt mit einer Ziffer am unteren Rand des Stapels. Als nächstes wird der kürzeste Weg berechnet, auf dem jede aufeinanderfolgende Nummer generiert werden kann, wobei möglicherweise die letzte kopiert oder die am Anfang auf den Stapel gelegte Nummer verwendet wird.

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

Die relevante Funktion ist die treffend benannte metagolf.

Die Ergebnisse sind:

210
676
684
2007
1463
2071
2204
2530
Total: 11845

Die vollständige Ausgabe finden Sie hier .

Kurze Erklärung

Ich werde die Erklärung kurz halten, da noch viele Dinge verbessert werden müssen.

Der grundlegende Algorithmus betrachtet nur Zeichenpaare und findet den optimalen Weg, um über BFS von einem Zeichen zu einem anderen zu wechseln. Die Ziffern werden derzeit sofort verschoben und gedruckt, dies wird jedoch später geändert.

Es gibt auch eine kleine Folge, die am längsten dauert, um einige Elemente auf dem Stapel zu belassen und später wieder zu verwenden. Es ist nicht so gut wie eine Wiederholung, spart aber ordentlich Geld.

Sp3000
quelle
Hurra, jemand zum Kämpfen :-) Natürlich sehe ich jetzt, dass meiner einen langen Weg vor sich hat ...
ETHproductions
5

JavaScript, 25158 23778

Jetzt ES5-kompatibel!

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

Ergebnisse:

432
949
2465
3996
1805
3551
5205
5375
Total: 23778

Ein guter Start meiner Meinung nach, aber offenbar noch nicht beendet. Anstatt jedes Zeichen separat zu erstellen, wird der vorherige Zeichencode hinzugefügt oder daraus entfernt. Ich werde eine vollständige Erklärung hinzufügen, wenn ich mit dem Metagolf fertig bin.

ETHproductions
quelle
Ja, es funktioniert jetzt in Firefox, obwohl sich Chrome immer noch darüber beschwert charCodeAt.
Martin Ender