Optimize Compiler para linguagem de programação de notação polonesa reversa simples


24

Descrição

A linguagem de programação imaginária (IPL) usa a notação reversa polonesa. Possui os seguintes comandos:

  • i - insira o número e empurre-o para a pilha
  • o - saída não destrutiva no topo da pilha (o número permanece na pilha)
  • d - descarte o topo da pilha
  • número inteiro - envie esse número para a pilha
  • + - * - retira dois números da pilha, executa a operação correspondente e empurra o resultado para trás. Não há divisão no IPL.

O IPL funciona apenas com números inteiros e é usado para cálculos simples. Um programa IPL é escrito em uma linha e separado por espaços. String vazia é um programa IPL válido.

Programa IPL:

i i + o 

Introduz dois números, soma-os e gera o resultado.

Os números de entrada e números inteiros que podem ser pressionados para empilhar estão no intervalo [-999, 999]; no entanto, a saída pode ser qualquer. Se o seu idioma não suportar grandes números, tudo bem.

Formato de entrada / saída

Você pode escolher qualquer formato de entrada / saída, desde que seja claro para entender e ler / gravar: string, lista, tokens etc.

Tarefa

Você recebe algum programa IPL, precisa otimizá-lo (reduzir o comprimento):

i 12 + 3 + o d 2 3 + d

Após a otimização se tornar

i 15 + o

Você não precisa preservar o estado da pilha, mas a quantidade de entradas e saídas e sua ordem devem corresponder ao programa original e otimizado.

Então, programa IPL:

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

Após a otimização se tornar

i -80 * o i 4 o i

ou

-80 i * o i 4 o i

(observe que você precisa salvar todas as entradas, mesmo que sejam irrelevantes).

Não deve haver codificação para casos de teste, o código deve funcionar em qualquer programa IPL arbitrário e produzir o programa IPL mais curto possível que atenda aos requisitos.

Pontuação

Pontuação por código-golfe padrão.

UPDATE: alteração da pontuação para pontuação de código puro, conforme sugestão do @Sanchises.

Casos de teste:

Entrada:

(empty string)

Saída possível:

(empty string)

Entrada:

i 4 * 2 + 3 * 6 - o

Saída possível:

i 12 * o

Entrada:

1 1 + o

Saída possível:

2 o

Entrada:

i 2 + 3 + o d 2 3 + d

Saída possível:

i 5 + o

Entrada:

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

Saída possível:

-80 i * o i 4 o i

Entrada:

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

Saída possível:

i i i i i d d d d o 

Entrada:

i i i 0 * * * o

Saída possível:

i i i 0 o

Entrada:

i i i 1 * * * o

Saída possível:

i i i * * o

Entrada:

i 222 + i 222 - + o

Saída possível:

i i + o

Entrada:

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

Saída possível:

i i i i i o 1 o

Entrada:

i 1 + 2 * 1 + o 

Saída possível:

i 2 * 3 + o

Entrada:

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

Saída possível:

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

11
Uma pergunta: você pode simplificar i i d opara i o i(a entrada está em ordem e a saída está em ordem) ou não deve simplificá-la? (o conjunto de entrada e saída devem estar em ordem)
Sanchises

11
@ Suporta não, entradas e saídas devem estar em ordem. Se o programa original digitar 2 números antes de produzir qualquer coisa otimizada, deverá fazer o mesmo.
Андрей Ломакин

11
Bem-vindo ao PPCG! Bom primeiro desafio!
Luis felipe De jesus Munoz

6
Na fila de revisão , não acho que esse desafio seja incerto. Se o fizer, comente sobre o porquê.
Mbomb007

2
@WW Acho que o OP significa que você não deve codificar apenas os casos de teste listados na pergunta. Você tem que suportar entrada arbitrária. Não deve haver nenhuma hardcode para casos de teste, código deve funcionar em qualquer programa IPL arbitrária
mbomb007

Respostas:


5

Wolfram Language (Mathematica) , 733 728 690 564 516 506 513 548 bytes

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

Experimente online!

Esta é uma excursão de força em quatro etapas que (1) substitui "-" por "-1 * +" para que não tenhamos que lidar com subtrações, (2) simplifica um pouco a lista de comandos ( 3) faz uma lista de todas as permutações dessa lista de comandos e seleciona aquelas que dão o mesmo resultado quando analisadas (executadas) e (4) simplifica um pouco essas listas de comandos e seleciona as mais curtas, depois de converter certas operações em subtrações.

Este código é terrivelmente ineficiente porque percorre a lista de todas as permutações do código de entrada. Para códigos de entrada longos, não recomendo a execução desse código; mas enquanto eu o li, não há restrições de tempo de execução ou memória neste desafio.

Esse código executa a etapa de otimização após converter todas as operações "-" em operações "+" com sinais invertidos e somente no final reintroduz o operador "-" ao converter o código novamente em seqüências de caracteres. Isso implica, por exemplo, que "i -1 i * + o" foi otimizado corretamente para "ii - o".

Como o requisito de formato de E / S é bastante flexível, esse código recebe e retorna códigos como listas, onde os símbolos "+", "-", "*" são representados por p, m, t, tokens, respectivamente. A conversão de e para strings é feita na função wrapper fornecida no TIO:

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

Versão sem golfe, incluindo o wrapper no formato de sequência e minimizando o comprimento final da sequência de códigos em vez do número de tokens e incluindo mais algumas características de transformação:

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

Agradecimentos à @redundancy por detectar um erro: O analisador precisa de uma Expandaplicação na saída para lidar com a equivalência distributiva. 506 → 513

atualizar

Agora também otimiza 1 o 1 + opara 1 o 2 o. Este foi um caso surpreendentemente difícil e tornou o código muito mais lento. 513 → 548


Parece que isso gera um erro no caso de teste i i 1 + i 1 + i 1 + i 1 + d d d d o.
Grimmy

@ Sujo como eu disse, esse código não corre para grandes problemas porque passa por uma exaustiva pesquisa combinatória do espaço do código. Seu erro é uma falha de falta de memória no TIO e não é devido ao meu código.
Roman

@Grimy para "ii 1 + d o" meu código fornece "iid o", que considero otimizado. Para "ii 1 + i 1 + dd o", fornece "iii + d o", que possui o mesmo número de tokens que a otimização "iiidd o" mais óbvia. Eu não tentei entradas mais longas.
Roman

Eu acredito que a entrada i 2 * i 2 * + odeve produzir uma saída otimizada i i + 2 * o, mas esse código retorna a entrada (não otimizada ).
redundância

Graças à redundância, ele foi corrigido e seu exemplo agora é um dos casos de teste incluídos.
Roman em
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.