Convertir expresiones infijadas a notación postfix


23

Cuando vi el título de esta pregunta cerrada , pensé que parecía un interesante desafío de código de golf. Así que permítanme presentarlo como tal:

Reto:

Escriba un programa, expresión o subrutina que, dada una expresión aritmética en notación infija , como 1 + 2, genera la misma expresión en notación postfix , es decir 1 2 +.

(Nota: se publicó un desafío similar a principios de enero. Sin embargo, creo que las dos tareas son lo suficientemente diferentes en detalle para justificar este desafío por separado. Además, solo noté el otro hilo después de escribir todo lo siguiente, y prefiero no solo tirarlo todo)

Entrada:

La entrada consiste en una expresión aritmética infija válido que consta de números (números enteros no negativos representan como secuencias de uno o más dígitos decimales), equilibrado de paréntesis para indicar una subexpresión agrupados, y los cuatro infija binarios operadores + , -, *y /. Cualquiera de estos puede estar separado (y toda la expresión rodeada) por un número arbitrario de caracteres de espacio, que deben ignorarse. 1

Para aquellos a quienes les gustan las gramáticas formales, aquí hay una gramática simple similar a BNF que define entradas válidas. Por brevedad y claridad, la gramática no incluye los espacios opcionales, que pueden ocurrir entre dos tokens (que no sean dígitos dentro de un número):

expression     := number | subexpression | expression operator expression
subexpression  := "(" expression ")"
operator       := "+" | "-" | "*" | "/"
number         := digit | digit number
digit          := "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"

1 El único caso donde la presencia de espacios puede afectar el análisis es cuando separan dos números consecutivos; sin embargo, dado que dos números no separados por un operador no pueden aparecer en una expresión infija válida, este caso nunca puede ocurrir en una entrada válida.

Salida:

La salida debe ser una expresión postfix equivalente a la entrada. La expresión de salida debe consistir solamente de números y los operadores, con un solo carácter de espacio entre cada par de fichas adyacentes, como en el siguiente gramática (que no incluye los espacios) 2 :

expression  := number | expression sp expression sp operator
operator    := "+" | "-" | "*" | "/"
number      := digit | digit number
digit       := "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
sp          := " "

2 Nuevamente, por simplicidad, la numberproducción en esta gramática admite números con ceros a la izquierda, a pesar de que están prohibidos en la salida por las siguientes reglas.

Precedencia del operador:

En ausencia de paréntesis, se aplican las siguientes reglas de precedencia:

  • Los operadores *y /tienen mayor prioridad que +y -.
  • Los operadores *y /tienen la misma precedencia entre sí.
  • Los operadores +y -tienen la misma precedencia entre sí.
  • Todos los operadores son asociativos a la izquierda.

Por ejemplo, las siguientes dos expresiones son equivalentes:

1 + 2 / 3 * 4 - 5 + 6 * 7
((1 + ((2 / 3) * 4)) - 5) + (6 * 7)

y ambos deberían producir el siguiente resultado:

1 2 3 / 4 * + 5 - 6 7 * +

(Estas son las mismas reglas de precedencia que en el lenguaje C y en la mayoría de los lenguajes derivados de él. Probablemente se parecen a las reglas que le enseñaron en la escuela primaria, excepto posiblemente por la precedencia relativa de *y /.)

Reglas misceláneas:

  • Si la solución dada es una expresión o una subrutina, la entrada debe proporcionarse y la salida debe devolverse como una sola cadena. Si la solución es un programa completo, debería leer una línea que contenga la expresión infija de la entrada estándar e imprimir una línea que contenga la versión postfix en la salida estándar.

  • Los números en la entrada pueden incluir ceros a la izquierda. Los números en la salida no deben tener ceros a la izquierda (excepto el número 0, que se emitirá como 0).

  • No se espera que evalúe u optimice la expresión de ninguna manera. En particular, no debe suponer que los operadores necesariamente satisfacen cualquier identidad asociativa, conmutativa u otra identidad algebraica. Es decir, no debe suponer que, por ejemplo, 1 + 2es igual 2 + 1o que 1 + (2 + 3)es igual (1 + 2) + 3.

  • Puede suponer que los números en la entrada no exceden 2 31 - 1 = 2147483647.

El objetivo de estas reglas es garantizar que la entrada defina de forma exclusiva la salida correcta.

Ejemplos:

Aquí hay algunas expresiones de entrada válidas y las salidas correspondientes, presentadas en la forma "input" -> "output":

"1"                  ->  "1"
"1 + 2"              ->  "1 2 +"
" 001  +  02 "       ->  "1 2 +"
"(((((1))) + (2)))"  ->  "1 2 +"
"1+2"                ->  "1 2 +"
"1 + 2 + 3"          ->  "1 2 + 3 +"
"1 + (2 + 3)"        ->  "1 2 3 + +"
"1 + 2 * 3"          ->  "1 2 3 * +"
"1 / 2 * 3"          ->  "1 2 / 3 *"
"0102 + 0000"        ->  "102 0 +"
"0-1+(2-3)*4-5*(6-(7+8)/9+10)" -> "0 1 - 2 3 - 4 * + 5 6 7 8 + 9 / - 10 + * -"

(Al menos, espero que todo esto sea correcto; hice la conversión a mano, por lo que los errores podrían haber aparecido).

Para ser claros, las siguientes entradas no son válidas; sí no importa lo que su solución no si les da (aunque, por supuesto, por ejemplo, devuelve un mensaje de error es más agradable que, por ejemplo, el consumo de una cantidad infinita de memoria):

""
"x"
"1 2"
"1 + + 2"
"-1"
"3.141592653589793"
"10,000,000,001"
"(1 + 2"
"(1 + 2)) * (3 / (4)"

¿Es aceptable la notación de Lisp? Por ejemplo, 1 2 3 4 +significa `1 + 2 + 3 + 4`.
Hauleth

3
@Hauleth: No en este desafío, no. Además, sin paréntesis, ¿cómo analizarías 1 2 3 4 + *?
Ilmari Karonen

Entonces, ¿no se permite ningún espacio en blanco al final (incluida una nueva línea) en el otuput?
breadbox

@breadbox: las nuevas líneas finales están bien. De hecho, permítanme aclarar explícitamente que se permite cualquier espacio en blanco al final.
Ilmari Karonen

Tengo una solución que muestra "0 1 - 2 3 - 4 * 5 6 7 8 + 9 / - 10 + * - +" para el último ejemplo válido, que me parece correcto. ¿Puedes revisar? (Observe el último operador)
coredump

Respuestas:


8

Utilidades de Shell: 60 caracteres

bc -c|sed -re's/[@iK:Wr]+/ /g;s/[^0-9]/ &/g;s/ +/ /g;s/^ //'

Se corrigieron los diversos problemas, pero se hizo mucho más largo :(


1
Esto es bastante inteligente, excepto que no parece manejar números mayores que 9 correctamente.
breadbox

@breadbox, lo sed -re's/[:@iKWr]+/ /g'arregla a un costo de 1 personaje.
Ugoren

Vaya, aunque la sugerencia @ugoren no funciona ya que los operadores consecutivos ya no tienen espacio entre ellos; También tengo que encontrar una solución para eso
Geoff Reedy

4

C, 250 245 236 193 185 caracteres

char*p,b[99];f(char*s){int t=0;for(;*p-32?
*p>47?printf("%d ",strtol(p,&p,10)):*p==40?f(p++),++p:
t&&s[t]%5==2|*p%5-2?printf("%c ",s[t--]):*p>41?s[++t]=*p++:0:++p;);}
main(){f(p=gets(b));}

Aquí hay una versión legible de la fuente no protegida, que aún refleja la lógica básica. En realidad es un programa bastante sencillo. El único trabajo real que tiene que hacer es empujar un operador de baja asociatividad a una pila cuando se encuentra un operador de alta asociatividad, y luego volverlo a abrir al "final" de esa subexpresión.

#include <stdio.h>
#include <stdlib.h>

static char buf[256], stack[256];
static char *p = buf;

static char *fix(char *ops)
{
    int sp = 0;

    for ( ; *p && *p != '\n' && *p != ')' ; ++p) {
        if (*p == ' ') {
            continue;
        } else if (*p >= '0') {
            printf("%ld ", strtol(p, &p, 10));
            --p;
        } else if (*p == '(') {
            ++p;
            fix(ops + sp);
        } else {
            while (sp) {
                if ((ops[sp] == '+' || ops[sp] == '-') &&
                        (*p == '*' || *p == '/')) {
                    break;
                } else {
                    printf("%c ", ops[sp--]);
                }
            }
            ops[++sp] = *p;
        }
    }
    while (sp)
        printf("%c ", ops[sp--]);
    return p;
}

int main(void)
{
    fgets(buf, sizeof buf, stdin);
    fix(stack);
    return 0;
}

Guardar caracteres mediante la eliminación if. Ej. if(!*p||*p==41)return p;s[++t]=*p;}->return*p&&*p-41?s[++t]=*p:p;
ugoren

Declaración de estilo K&R:*f(p,s)char*p,s;{
ugoren

1. Es un error regresar si la ifprueba falla. 2. Lo sé, pero la función K&R declina es donde dibujo la línea. Simplemente no puedo volver a ellos.
breadbox

Pensé que el retorno está en el final de la función de todos modos. Perdí el }}y for. Pero aquí hay una mejora:printf(" %ld"+!a,...
Ugoren

1
También creo que deberías hacer pglobal (la llamada recursiva solo asigna la llamada pa la persona que llama). Entonces hazlo f(p=gets(b)).
ugoren

2

Bash con Haskell con Preprocesador C sed, 180 195 198 275

echo 'CNumO+O-O*fromInteger=show
CFractionalO/
main=putStr$'$*|sed 's/C\([^O]*\)/instance \1 String where /g
s/O\(.\?\)/a\1b=unwords\[a,b,\"\1\"];/g'|runghc -XFlexibleInstances 2>w

Por fin, ya no es más que la solución C. La parte crucial de Haskell es casi tan floja como la solución bc ...

Toma datos como parámetros de línea de comandos. wSi no le gusta este cambio, se creará un archivo con algunos mensajes de advertencia de ghc runghc 2>/dev/null.


1
Basked? ( Bas h + H aske ll + s ed )
CalculatorFeline

2

Python 2, 290 272 268 250 243 238 bytes

¡Ahora finalmente más corto que la respuesta de JS!

Este es un programa completo que utiliza una implementación básica del algoritmo de patio de maniobras . La entrada se proporciona como una cadena entre comillas y el resultado se imprime en STDOUT.

import re
O=[];B=[]
for t in re.findall('\d+|\S',input()):exec("O=[t]+O","i=O.index('(');B+=O[:i];O=O[i+1:]","while O and'('<O[0]and(t in'*/')<=(O[0]in'*/'):B+=O.pop(0)\nO=[t]+O","B+=`int(t)`,")[(t>'/')+(t>')')+(t>'(')]
print' '.join(B+O)

Pruébalo en línea!


Explicación:

Lo primero que debemos hacer es convertir la entrada en tokens. Hacemos esto mediante la búsqueda de todas las coincidencias de la expresión regular \d+|\S, traducidas aproximadamente a "cualquier grupo de dígitos y cualquier carácter no espacial". Esto elimina el espacio en blanco, analiza los dígitos adyacentes como tokens individuales y analiza los operadores por separado.

Para el algoritmo de patio de maniobras, hay 4 tipos de tokens distintos que debemos manejar:

  • ( - Paréntesis izquierdo
  • ) - Paréntesis derecho
  • +-*/ - Operadores
  • 9876543210 - Literales numéricos

Afortunadamente, los códigos ASCII de estos están agrupados en el orden mostrado, por lo que podemos usar la expresión (t>'/')+(t>')')+(t>'(')para calcular el tipo de token. Esto da como resultado 3 para dígitos, 2 para operadores, 1 para paréntesis derecho y 0 para paréntesis izquierdo.

Usando estos valores, indexamos en la tupla grande luego execde obtener el fragmento correspondiente para ejecutar, según el tipo de token. Esto es diferente para cada ficha y es la columna vertebral del algoritmo de patio de maniobras. Se utilizan dos listas (como pilas): O(pila de operaciones) y B(búfer de salida). Una vez que se han ejecutado todos los tokens, los operadores restantes en la Opila se concatenan con el búfer de salida y se imprime el resultado.


2

Prólogo (SWI-Prolog) , 113 bytes

c(Z,Q):-Z=..[A,B,C],c(B,S),c(C,T),concat_atom([S,T,A],' ',Q);term_to_atom(Z,Q).
p(X,Q):-term_to_atom(Z,X),c(Z,Q).

Pruébalo en línea!

SWI Prolog tiene un conjunto mucho mejor de componentes integrados para esto que GNU Prolog, pero aún se ve frenado por la verbosidad de la sintaxis de Prolog.

Explicación

term_to_atomSi se ejecuta hacia atrás, analizará una expresión de notación infija (almacenada como un átomo) en un árbol de análisis (obedeciendo las reglas de precedencia habituales y eliminando los ceros a la izquierda y los espacios en blanco). Luego usamos el predicado auxiliar cpara hacer una recursión estructural sobre el árbol de análisis, convirtiéndolo en notación postfix de una manera profunda primero.


1

Javascript (ES6), 244 bytes

f=(s,o={'+':1,'-':1,'*':2,'/':2},a=[],p='',g=c=>o[l=a.pop()]>=o[c]?g(c,p+=l+' '):a.push(l||'',c))=>(s.match(/[)(+*/-]|\d+/g).map(c=>o[c]?g(c):(c==')'?eval(`for(;(i=a.pop())&&i!='(';)p+=i+' '`):c=='('?a.push(c):p+=+c+' ')),p+a.reverse().join` `)

Ejemplo:
Llamada: f('0-1+(2-3)*4-5*(6-(7+8)/9+10)')
Salida:0 1 - 2 3 - 4 * + 5 6 7 8 + 9 / - 10 + * - (con un espacio final)

Explicación:

f=(s,                                                     //Input string
    o={'+':1,'-':1,'*':2,'/':2},                          //Object used to compare precedence between operators
    a=[],                                                 //Array used to stack operators
    p='',                                                 //String used to store the result
    g=c=>                                                 //Function to manage operator stack
        o[l=a.pop()]>=o[c]?                               //  If the last stacked operator has the same or higher precedence
            g(c,p+=l+' '):                                //  Then adds it to the result and call g(c) again
            a.push(l||'',c)                               //  Else restack the last operator and adds the current one, ends the recursion.
)=>                                                       
    (s.match(/[)(+*/-]|\d+/g)                             //Getting all operands and operators
    .map(c=>                                              //for each operands or operators
        o[c]?                                             //If it's an operator defined in the object o
            g(c)                                          //Then manage the stack
            :(c==')'?                                     //Else if it's a closing parenthese
                eval(`                                    //Then
                    for(;(i=a.pop())&&i!='(';)            //  Until it's an opening parenthese
                        p+=i+' '                          //  Adds the last operator to the result
                `)                                        
                :c=='('?                                  //Else if it's an opening parenthese
                    a.push(c)                             //Then push it on the stack
                    :p+=+c+' '                            //Else it's an operand: adds it to the result (+c removes the leading 0s)
        )                                                 
    )                                                     
    ,p+a.reverse().join` `)                               //Adds the last operators on the stack to get the final result

1

R, 142 bytes

R es capaz de analizarse a sí mismo, por lo que, en lugar de reinventar la rueda, simplemente ponemos a trabajar el analizador, que genera la notación de prefijo, y usamos una función recursiva para cambiarlo a notación de postfix.

f=function(x,p=1){
if(p)x=match.call()[[2]]
if((l=length(x))>1){
f(x[[2]],0)
if(l>2)f(x[[3]],0)
if((z=x[[1]])!="(")cat(z,"")
}else cat(x,"")
}

El pargumento es controlar el uso de la evaluación no estándar (la pesadumbre de los programadores de R en todas partes), y hay algunos ifs adicionales para controlar la salida de paréntesis (que queremos evitar).

Entrada: (0-1+(2-3)*4-5*(6-(7+8)/9+10))

Salida: 0 1 - 2 3 - 4 * + 5 6 7 8 + 9 / - 10 + * -

Entrada: (((((1))) + (2)))

Salida: 1 2 +

Como beneficio adicional, funciona con símbolos arbitrarios y cualquier función predefinida con hasta dos argumentos:

Identidad de Euler

Entrada: e^(i*pi)-1

Salida: e i pi * ^ 1 -

Dividendos de 13 entre 1 y 100

Entrada: which(1:100 %% 13 == 0)

Salida: 1 100 : 13 %% 0 == which

Regresión lineal del peso del pollo bebé en función del tiempo.

Entrada: summary(lm(weight~Time, data=ChickWeight))

Salida: weight Time ~ ChickWeight lm summary

El último ejemplo está quizás un poco fuera del alcance del OP, pero usa notación postfix, así que ...

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.