Оптимизировать компилятор для простого языка программирования с обратной польской нотацией


24

Описание

Воображаемый язык программирования (IPL) использует польскую обратную нотацию. У него есть следующие команды:

  • я - введите число и поместите его в стек
  • o - неразрушающий вывод вершины стека (число остается в стеке)
  • d - сбросить вершину стека
  • целое число - поместите этот номер в стек
  • + - * - вытолкнуть два числа из стека, выполнить соответствующую операцию и вернуть результат обратно. В IPL нет разделения.

IPL работает только с целыми числами и используется для простых вычислений. Программа IPL написана в одну строку и разделена пробелами. Пустая строка является допустимой программой IPL.

Программа IPL:

i i + o 

Вводит два числа, складывает их вместе и выводит результат.

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

Формат ввода / вывода

Вы можете выбрать любой формат ввода / вывода, если он понятен для чтения и чтения / записи: строка, список, токены и т. Д.

задача

Вам дана некоторая программа IPL, вам нужно ее оптимизировать (уменьшить длину):

i 12 + 3 + o d 2 3 + d

После оптимизации станет

i 15 + o

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

Итак, программа IPL:

-40 i * 2 * o i + 3 1 + o i 2 *

После оптимизации станет

i -80 * o i 4 o i

или

-80 i * o i 4 o i

(обратите внимание, что вы должны сохранить все входные данные, даже если они не имеют значения).

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

счет

Код по умолчанию - оценка игры в гольф.

ОБНОВЛЕНИЕ: изменено начисление баллов на чистое, в соответствии с предложением @Sanchises.

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

Входные данные:

(empty string)

Возможный вывод:

(empty string)

Входные данные:

i 4 * 2 + 3 * 6 - o

Возможный вывод:

i 12 * o

Входные данные:

1 1 + o

Возможный вывод:

2 o

Входные данные:

i 2 + 3 + o d 2 3 + d

Возможный вывод:

i 5 + o

Входные данные:

-40 i * 2 * o i + 3 1 + o i 2 *

Возможный вывод:

-80 i * o i 4 o i

Входные данные:

i i 1 + i 1 + i 1 + i 1 + d d d d o 

Возможный вывод:

i i i i i d d d d o 

Входные данные:

i i i 0 * * * o

Возможный вывод:

i i i 0 o

Входные данные:

i i i 1 * * * o

Возможный вывод:

i i i * * o

Входные данные:

i 222 + i 222 - + o

Возможный вывод:

i i + o

Входные данные:

i 2 + 3 * 2 + 3 * 2 + 3 * i * d i 2 + 3 * i + d i o 2 + 2 - 0 * 1 o

Возможный вывод:

i i i i i o 1 o

Входные данные:

i 1 + 2 * 1 + o 

Возможный вывод:

i 2 * 3 + o

Входные данные:

1 1 + o i 2 + 3 + o d 2 3 + d 4 i * 2 * o i + 3 1 + o i 2 * i i 1 + i 1 + i 1 + i 1 + d d d d o i i i 0 * * * o i i i 1 * * * o i 2 + i 2 - + o i 2 + 3 * 2 + 3 * 2 + 3 * i * d i 2 + 3 * i + d i o 2 + 2 - 0 * 1 o

Возможный вывод:

2 o i 5 + o 8 i * o i 4 o i i i i i i d d d d o i i i 0 o i i i * * * o i i + o i i i i i o 1 o

1
Вопрос: можете ли вы упростить i i d oдо i o i(вход в порядке, а выход в порядке) или вы не должны упростить его? (набор входов и выходов должен быть в порядке)
Sanchises

1
@Sanchises нет, входы и выходы должны быть в порядке. Если оригинальная программа вводит 2 числа, прежде чем выводить что-либо оптимизированное, следует сделать то же самое.
Андрей Ломакин

1
Добро пожаловать в PPCG! Хороший первый вызов!
Луис Фелипе Де Иисус Муньос

6
Из очереди просмотра я не думаю, что этот вызов неясен. Если вы это сделаете, пожалуйста, прокомментируйте почему.
mbomb007

2
@WW Я думаю, что OP означает, что вы не должны жестко кодировать только контрольные примеры, перечисленные в вопросе. Вы должны поддерживать произвольный ввод. Для тестовых случаев не должно быть жесткого кодирования, код должен работать на любой произвольной программе IPL
mbomb007

Ответы:


5

Wolfram Language (Mathematica) , 733 728 690 564 516 506 513 548 байт

j=Integer;f=Flatten;s=SequenceReplace;A=FixedPoint[f@s[#,{{x_j,p,y_j,t}->{y,t,x*y,p},{x_j,y_j,p}->x+y,{x_j,y_j,t}->x*y,{x_j,p,y_j,p}->{x+y,p},{x_j,t,y_j,t}->{x*y,t},{0,p}|{1,t}->{},{0,t}->{d,0}}]//.{a___,Except[i|o]}->{a}&,#]&;B=Expand@Check[f@FoldPairList[f/@Switch[#2,i,{{i},{#,i@c++}},o,{{Last@#},#},d,{{},Most@#},p,{{},{#[[;;-3]],Tr@#[[-2;;]]}},t,{{},{#[[;;-3]],#[[-2]]*Last@#}},_,{{},{##}}]&,c=0;{},#],x]&;F=MinimalBy[w=A@f[#/.m->{-1,t,p}];z=B@w;s[#,{-1,t,p}->m]&/@A/@Select[Permutations@Join[w,Cases[z /.i@_->i,_j,∞]],B@#==z&],Length][[1]]&

Попробуйте онлайн!

Это четырехэтапная демонстрация силы, которая (1) заменяет «-» на «-1 * +», чтобы нам не приходилось иметь дело с вычитаниями, (2) немного упрощает список команд, ( 3) составляет список всех перестановок в этом списке команд и выбирает те, которые дают одинаковый результат при разборе (выполнении), и (4) немного упрощает эти списки команд и выбирает самые короткие после преобразования определенных операций обратно в вычитания.

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

Этот код выполняет шаг оптимизации после преобразования всех операций «-» в операции «+» с перевернутыми знаками, и только в конце вновь вводит оператор «-» при преобразовании кода обратно в строки. Это подразумевает, например, что «i -1 i * + o» правильно оптимизирован до «ii - o».

Так как требования к формату ввода / вывода довольно просты, этот код принимает и возвращает код в виде списков, где символы «+», «-», «*» представлены символами p, m, t, соответственно. Преобразование из и в строки выполняется в функции-обёртке, заданной для TIO:

G[S_] := StringReplace[{"p" -> "+", "m" -> "-", "t" -> "*"}]@StringRiffle@
         Quiet@F@
         ToExpression[StringSplit[S] /. {"+" -> p, "-" -> m, "*" -> t}]

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

(* convert code string to list of operators *)
inputfilter[s_] := ToExpression[Flatten[StringSplit[s] /.
  {"i" -> i, "o" -> o, "d" -> d, "+" -> p, "-" -> {-1, t, p}, "*" -> t}]]

(* convert list of operators to code string *)
outputfilter[s_] := StringReplace[StringRiffle@Flatten@SequenceReplace[s,
  {{-1, t, p} -> m,                         (* convert "-1 t p" back to "-"             *)
   {x_ /; x < 0, p} -> {-x, m},             (* convert "y x +" to "y -x -" when x<0     *)
   {x_ /; x < 0, t, p} -> {-x, t, m}}],     (* convert "y x * +" to "y -x * -" when x<0 *)
  {"m" -> "-", "p" -> "+", "t" -> "*"}]     (* backsubstitution of symbols              *)

(* simplify a list of operators somewhat *)
simplifier[s_] := FixedPoint[Flatten@SequenceReplace[#,
  {{x_Integer, p, y_Integer, t} -> {y, t, x*y, p},  (*  "x + y *" -> "y * (xy) +"       *)
   {x_Integer, y_Integer, p} -> x + y,              (*  "x y +" -> "(x+y)"              *)
   {x_Integer, y_Integer, t} -> x*y,                (*  "x y *" -> "(xy)"               *)
   {x_Integer, p, y_Integer, p} -> {x + y, p},      (*  "x + y +" -> "(x+y) +"          *)
   {x_Integer, t, y_Integer, t} -> {x*y, t},        (*  "x * y *" -> "(xy) *            *)
   {0, p} | {1, t} -> {},                           (*  "0 +" and "1 *" are deleted     *)
   {x_Integer, i, p} -> {i, x, p},                  (*  "x i +" -> "i x +"              *)
   {x_Integer, i, t} -> {i, x, t},                  (*  "x i *" -> "i x *"              *)
   {0, t} -> {d, 0}}] //.                           (*  "0 *" -> "d 0"                  *)
  {a___, Except[i | o]} -> {a} &, s]                (* delete trailing useless code     *)

(* execute a list of operators and return the list of generated outputs *)
parse[s_] := Expand@Quiet@Check[Flatten@FoldPairList[  (* stack faults are caught here     *)
  Function[{stack, command},                        (* function called for every command*)
    Flatten /@ Switch[command,                      (* code interpretation:             *)
    i, {{i}, {stack, i[inputcounter++]}},           (* output "i" and add input to stack*)
    o, {{stack[[-1]]}, stack},                      (* output top of stack              *)
    d, {{}, Most[stack]},                           (* delete top of stack              *)
    p, {{}, {stack[[;; -3]], stack[[-2]] + stack[[-1]]}},  (* add two stack elements    *)
    t, {{}, {stack[[;; -3]], stack[[-2]]*stack[[-1]]}},    (* multiply two stack elements*)
    _, {{}, {stack, command}}]],                    (* put number onto stack            *)
    inputcounter = 0; {},                           (* start with zero input counter and empty stack*)
    s],                                             (* loop over code list              *)
  x]                                                (* return "x" if an error occurred  *)

(* the main function that takes a code string and returns an optimized code string *)
F[s_] := Module[{w, q},
  w = simplifier@inputfilter@s;      (* convert input to useful form *)
  q = parse[w];                      (* execute input code *)
  MinimalBy[
    outputfilter@*simplifier /@      (* simplify and stringify selected codes          *)
      Select[Permutations[w],        (* all permutations of code list                  *)
             parse[#] == q &],       (* select only those that give the correct output *)
    StringLength] // Union]          (* pick shortest solution by length               *)

Спасибо @redundancy за обнаружение ошибки: парсер нуждается в Expand применить к выводу для обработки дистрибутивной эквивалентности. 506 → 513

Обновить

Теперь также оптимизирует 1 o 1 + oк 1 o 2 o. Это был удивительно сложный случай, и код стал намного медленнее. 513 → 548


Похоже, это дает ошибку в тестовом примере i i 1 + i 1 + i 1 + i 1 + d d d d o.
Grimmy

@ Грими, как я уже сказал, этот код не работает для больших проблем, потому что он проходит через исчерпывающий комбинаторный поиск пространства кода. Ваша ошибка - ошибка нехватки памяти на TIO, а не из-за моего кода.
Роман

@Grimy для «ii 1 + d o» мой код дает «iid o», что я считаю оптимизированным. Для «ii 1 + i 1 + dd o» он дает «iii + d o», который имеет то же количество токенов, что и более очевидная оптимизация «iiidd o». Я не пробовал больше входов.
Роман

Я считаю, что ввод i 2 * i 2 * + oдолжен производить оптимизированный вывод i i + 2 * o, но этот код возвращает (неоптимизированный) ввод.
резервирование

Спасибо @redundancy, это исправлено, и ваш пример теперь является одним из включенных тестовых случаев.
Роман
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.