Zoptymalizuj kompilator pod kątem prostego języka programowania odwrotnej polskiej notacji


24

Opis

Wyimaginowany język programowania (IPL) używa polskiej odwrotnej notacji. Ma następujące polecenia:

  • i - wprowadź liczbę i pchnij ją na stos
  • o - nieniszcząca moc wyjściowa na szczycie stosu (liczba pozostaje na stosie)
  • d - odrzuć wierzch stosu
  • liczba całkowita - pchnij ten numer na stos
  • + - * - wyłóż dwie liczby ze stosu, wykonaj odpowiednią operację i odepchnij wynik. W IPL nie ma podziału.

IPL działa tylko z liczbami całkowitymi i służy do prostych obliczeń. Program IPL jest zapisany w jednym wierszu i oddzielony spacjami. Pusty ciąg jest prawidłowym programem IPL.

Program IPL:

i i + o 

Wpisuje dwie liczby, dodaje je razem i wyświetla wynik.

Liczby wejściowe i liczby całkowite, które można wypychać na stos, znajdują się w zakresie [-999, 999], jednak dane wyjściowe mogą być dowolne. Jeśli twój język nie obsługuje dużych liczb, jest to w porządku.

Format wejścia / wyjścia

Możesz wybrać dowolny format wejściowy / wyjściowy, o ile zrozumiałe i odczytywane / zapisywane są: ciąg, lista, tokeny itp.

Zadanie

Otrzymujesz program IPL, musisz go zoptymalizować (zmniejszyć długość):

i 12 + 3 + o d 2 3 + d

Po optymalizacji stanie się

i 15 + o

Nie musisz zachowywać stanu stosu, ale ilość wejść i wyjść oraz ich kolejność powinny odpowiadać oryginalnemu i zoptymalizowanemu programowi.

Więc program IPL:

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

Po optymalizacji stanie się

i -80 * o i 4 o i

lub

-80 i * o i 4 o i

(pamiętaj, że musisz zapisać wszystkie dane wejściowe, nawet jeśli są one nieistotne).

Nie powinno być żadnego twardego kodowania dla przypadków testowych, kod powinien działać na dowolnym dowolnym programie IPL i generować możliwie najkrótszy program IPL, który spełnia wymagania.

Punktacja

Domyślna punktacja golfa.

AKTUALIZACJA: zmieniono punktację na czystą ocenę golfa, zgodnie z sugestią @Sanchises.

Przypadki testowe:

Wkład:

(empty string)

Możliwe wyjście:

(empty string)

Wkład:

i 4 * 2 + 3 * 6 - o

Możliwe wyjście:

i 12 * o

Wkład:

1 1 + o

Możliwe wyjście:

2 o

Wkład:

i 2 + 3 + o d 2 3 + d

Możliwe wyjście:

i 5 + o

Wkład:

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

Możliwe wyjście:

-80 i * o i 4 o i

Wkład:

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

Możliwe wyjście:

i i i i i d d d d o 

Wkład:

i i i 0 * * * o

Możliwe wyjście:

i i i 0 o

Wkład:

i i i 1 * * * o

Możliwe wyjście:

i i i * * o

Wkład:

i 222 + i 222 - + o

Możliwe wyjście:

i i + o

Wkład:

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

Możliwe wyjście:

i i i i i o 1 o

Wkład:

i 1 + 2 * 1 + o 

Możliwe wyjście:

i 2 * 3 + o

Wkład:

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

Możliwe wyjście:

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
Pytanie: można uprościć i i d odo i o i(wejście jest w porządku, a wyjście jest w porządku) albo nie należy go uprościć? (zestaw wejść i wyjść powinien być w porządku)
Sanchises

1
@ Sanchises no, wejścia i wyjścia powinny być w porządku. Jeśli oryginalny program wprowadza 2 liczby przed wypisaniem czegoś zoptymalizowanego, powinien zrobić to samo.
Андрей Ломакин

1
Witamy w PPCG! Ładne pierwsze wyzwanie!
Luis Felipe De Jesus Munoz

6
Z kolejki recenzji nie sądzę, aby to wyzwanie było niejasne. Jeśli tak, skomentuj dlaczego.
mbomb007,

2
@WW Myślę, że OP oznacza, że ​​nie powinieneś kodować na stałe tylko przypadków testowych wymienionych w pytaniu. Musisz wspierać dowolne dane wejściowe. Nie powinno być żadnego twardego kodowania dla przypadków testowych, kod powinien działać na dowolnym dowolnym programie IPL
mbomb007

Odpowiedzi:


5

Wolfram Language (Mathematica) , 733 728 690 564 516 506 513 548 bajtów

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]]&

Wypróbuj online!

Jest to czteroetapowy tour-de-force, który (1) zamienia „-” na „-1 * +”, abyśmy nie musieli radzić sobie z odejmowaniem, (2) nieco upraszcza listę poleceń ( 3) tworzy listę wszystkich permutacji z tej listy poleceń i wybiera te, które dają ten sam wynik podczas analizowania (wykonywania), oraz (4) upraszcza nieco te listy poleceń i wybiera najkrótsze, po konwersji niektórych operacji z powrotem na odejmowanie.

Ten kod jest strasznie nieefektywny, ponieważ przechodzi przez listę wszystkich permutacji kodu wejściowego. W przypadku długich kodów wejściowych nie polecam uruchamiania tego kodu; ale kiedy go czytam, w tym wyzwaniu nie ma żadnych ograniczeń czasu wykonywania ani pamięci.

Ten kod wykonuje krok optymalizacji po przekonwertowaniu wszystkich operacji „-” na operacje „+” z odwróconymi znakami i dopiero na końcu ponownie wprowadza operator „-” podczas konwersji kodu z powrotem na ciągi znaków. Oznacza to na przykład, że „i -1 i * + o” jest poprawnie zoptymalizowane do „ii - o”.

Ponieważ wymagania dotyczące formatu we / wy są dość luźne, kod ten przyjmuje i zwraca kod jako listy, gdzie symbole „+”, „-”, „*” są reprezentowane odpowiednio przez p, m, t, tokeny. Konwersja zi na łańcuchy odbywa się w funkcji otoki podanej w TIO:

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

Wersja bez gry w golfa, w tym opakowanie formatu łańcucha i minimalizowanie końcowej długości łańcucha kodu zamiast liczby tokenów, a także kilka dodatkowych drobiazgów transformacji:

(* 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               *)

Dzięki @redundancy za wyłapanie błędu: Analizator wymaga Expandzastosowania wyjścia, aby obsłużyć równoważność dystrybucyjną. 506 → 513

aktualizacja

Teraz również optymalizuje się 1 o 1 + odo 1 o 2 o. Był to zaskakująco trudny przypadek, który spowodował spowolnienie kodu. 513 → 548


Wygląda na to, że daje to błąd w przypadku testowym i i 1 + i 1 + i 1 + i 1 + d d d d o.
Grimmy

@Grimy, jak powiedziałem, ten kod nie działa z dużymi problemami, ponieważ przechodzi wyczerpujące, kombinatoryczne wyszukiwanie przestrzeni kodu. Twój błąd jest błędem braku pamięci w TIO i nie wynika z mojego kodu.
Rzym

@Grimy dla „ii 1 + d o” mój kod podaje „iid o”, co uważam za zoptymalizowane. Dla „ii 1 + i 1 + dd o” daje „iii + d o”, który ma taką samą liczbę tokenów, co bardziej oczywista optymalizacja „iiidd o”. Nie próbowałem już wejść.
Roman

Uważam, że dane wejściowe i 2 * i 2 * + opowinny generować zoptymalizowane dane wyjściowe i i + 2 * o, ale ten kod zwraca (niezoptymalizowane) dane wejściowe.
redundancja

Dzięki @redundancy, zostało to naprawione, a twój przykład jest teraz jednym z dołączonych przypadków testowych.
Rzym,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.