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


24

Опис

У уявній мові програмування (IPL) використовується польська зворотна нотація. Він має такі команди:

  • i - вхідний номер і натисніть його на стек
  • 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(вхід в порядку , і вихід в порядку) , або якщо ви не спрощувати? (набір входів і виходів повинен бути в порядку)
Санчіз

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

1
Ласкаво просимо до PPCG! Приємний перший виклик!
Luis felipe De jesus Munoz

6
З черги на огляд я не думаю, що цей виклик є незрозумілим. Якщо це так, прокоментуйте, будь ласка, чому.
mbomb007

2
@WW Я думаю, що ОП означає, що ви не повинні жорстко кодувати лише тестові випадки, перелічені у питанні. Ви повинні підтримувати довільний ввід. У тестових випадках не повинно бути жорсткого кодування, код повинен працювати на будь-якій довільній програмі IPL
mbomb007,

Відповіді:


5

Мова Вольфрама (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.
Гриммі

@Grimy, як я вже сказав, цей код не працює для великих проблем, оскільки він проходить через вичерпний комбінаторний пошук кодового простору. Ваша помилка - помилка поза пам'яттю в 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.