Звездный Метагольф

25

Starry - забавный эзотерический язык программирования, в котором код состоит только из того, +*.,`'где фактическая команда, представленная каждым из этих символов, определяется количеством пробелов перед ним. Это делает его сложным даже для задач с фиксированным выходом, потому что разные команды могут учитывать разное количество байтов. В частности, числовые литералы имеют унарное представление, что делает необходимым создание больших чисел, оперируя меньшими.

Поэтому задача состоит в том, чтобы написать программу, способную играть в такие звездные программы.

Как работает Starry?

(В esolangs некоторые детали не указаны, поэтому я собираюсь рассказать о поведении интерпретатора Ruby .)

Starry - это основанный на стеке язык с одним стеком целочисленных значений произвольной точности (который изначально пуст).

Единственные значимые символы:

+*.,`'

и пробелы. Все остальные символы игнорируются. Каждая последовательность пробелов, за которой следует один из этих непробельных символов, представляет собой одну инструкцию. Тип инструкции зависит от непробельного символа и количества пробелов.

Инструкции:

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. 

Обратите внимание, что интерпретатор сканирует исходный код для меток перед началом выполнения, поэтому можно переходить как вперед, так и назад.

Конечно, у Starry также есть команды ввода (использующие ,аналогично .), но они не имеют отношения к этой задаче.

Соревнование

Получив строку, сгенерируйте программу Starry, которая не принимает ввод и печатает эту строку точно в STDOUT.

Вы можете написать программу или функцию, принимая ввод через STDIN (или ближайшую альтернативу), аргумент командной строки или аргумент функции и выводя результат через STDOUT (или ближайшую альтернативу), возвращаемое значение функции или параметр функции (out).

Вы можете предположить, что строка не длиннее 128 символов и будет состоять только из печатных символов ASCII (кодовые точки от 0x20 до 0x7E).

Ваше решение должно обрабатывать любой такой ввод менее чем за 5 минут на подходящем настольном компьютере (есть некоторая свобода в этом; если на моем ноутбуке это займет несколько минут, я не против, но если это займет 15, я дисквалифицирую Это).

Ваше решение будет проверено на ряде различных строк, перечисленных ниже. Ваша оценка - это общее количество байт соответствующих программ Starry. В случае ничьей побеждает самый короткий метагольфер. То есть, не пытайтесь играть в свой собственный код, если нет ничьей (что, я думаю, произойдет только в том случае, если возможно оптимальное решение).

Вы не должны оптимизировать свой код под конкретные тестовые примеры, перечисленные ниже. В частности, вы не должны жестко кодировать для них решения, изготовленные вручную. Оптимизация под классы строк, структура которых аналогична структуре данных строк, это хорошо. Если я подозреваю кого-либо в решениях с жестким кодированием, я оставляю за собой право заменить некоторые или все тестовые случаи (на строки сопоставимых структур).

Тестовые случаи

Каждая строка представляет собой отдельный контрольный пример:

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

Кредиты для второго контрольного теста идут к Деннису . Кредиты для четвертого теста идут на Sp3000.

Эталонное решение

Вот действительно базовое справочное решение в CJam:

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

Вы можете запустить его для всего набора тестов здесь. Баллы:

1233
5240
4223
11110
7735
10497
11524
11392
Total: 62954

Это самый простой подход: выдвинуть кодовую точку каждого символа как литерал, а затем распечатать его. Здесь не используются небольшие различия между последовательными символами, целочисленной печатью, повторяющимися частями строки и т. Д. Я оставлю эти вещи вам.

Я считаю, что есть много возможностей для улучшения. Для справки, самое короткое ручной работы "Привет, мир!" всего 169 байт.

Мартин Эндер
источник

Ответы:

6

Рубин, 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}

Метод starryотвечает на данный вопрос.

Полученные результаты:

230
639
682
1974
1024
1897
2115
2436
Total: 10997

Как это работает

shortestэто основной алгоритм. Он принимает одно число и находит кратчайший способ поместить его в стек, или принимает два числа и возвращает код для помещения второго в стек, предполагая, что первый уже включен. $sХэш для хранения результатов этих операций для дальнейшего использования.

starryберет строку и разбивает ее на массив кодов символов (или чисел для дайджестов). Он начинает код с одного числа в нижней части стека. Затем он вычисляет кратчайший путь, которым он может сгенерировать каждое последующее число, возможно, копируя последнее или используя число, помещенное в стек в начале.

MegaTom
источник
9

Питон 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)

Соответствующая функция точно названа metagolf.

Результаты:

210
676
684
2007
1463
2071
2204
2530
Total: 11845

Вы можете найти полный вывод здесь .

Краткое объяснение

Я собираюсь держать объяснение кратким, так как многое еще предстоит улучшить.

Базовый алгоритм просто смотрит на пары символов и находит оптимальный способ перехода от одного символа к другому через BFS. Цифры в настоящее время нажимаются и печатаются немедленно, хотя это будет изменено позже.

Также существует небольшая самая длинная общая подпоследовательность, чтобы оставить несколько элементов в стеке для повторного использования позже. Это не так хорошо, как повторение, но обеспечивает достойную экономию.

Sp3000
источник
Ура, кто-то
сражается
5

JavaScript, 25158 23778

Теперь 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;
}

Полученные результаты:

432
949
2465
3996
1805
3551
5205
5375
Total: 23778

Хорошее начало, на мой взгляд, но явно не закончено. Вместо того, чтобы создавать каждый символ отдельно, он добавляет или вычитает предыдущий код символа. Я добавлю полное объяснение, когда я закончу мета-гольф.

ETHproductions
источник
Да, теперь он работает в Firefox, хотя Chrome все еще жалуется charCodeAt.
Мартин Эндер