Converti le espressioni infix in notazione postfix


23

Quando ho visto il titolo di questa domanda chiusa , ho pensato che sembrava un'interessante sfida per il golf del codice. Vorrei quindi presentarlo come tale:

Sfida:

Scrivi un programma, un'espressione o una subroutine che, data un'espressione aritmetica nella notazione infix , come 1 + 2, genera la stessa espressione nella notazione postfix , cioè 1 2 +.

(Nota: una sfida simile è stata pubblicata all'inizio di gennaio. Tuttavia, ritengo che i due compiti siano sufficientemente diversi nei dettagli per giustificare questa sfida separata. Inoltre, ho notato l'altro thread solo dopo aver digitato tutto di seguito, e preferirei non buttare via tutto.)

Ingresso:

L'ingresso è costituito da un infisso espressione aritmetica valida rappresentati da numeri (numeri interi non negativi rappresentati come sequenze di una o più cifre decimali), bilanciati parentesi per indicare una sottoespressione raggruppato, e le quattro infisso binari operatori + , -, *e /. Ognuno di questi può essere separato (e l'intera espressione circondata) da un numero arbitrario di caratteri spaziali, che dovrebbe essere ignorato. 1

Per coloro a cui piacciono le grammatiche formali, ecco una semplice grammatica simile a BNF che definisce input validi. Per brevità e chiarezza, la grammatica non include gli spazi opzionali, che possono verificarsi tra due token (diversi dalle cifre all'interno di un numero):

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 L'unico caso in cui la presenza di spazi può influenzare l'analisi è quando separano due numeri consecutivi; tuttavia, poiché due numeri non separati da un operatore non possono verificarsi in un'espressione infix valida, questo caso non può mai verificarsi in un input valido.

Produzione:

L'output dovrebbe essere un'espressione postfix equivalente all'input. L'espressione output dovrebbe consistere solo di numeri e operatori, con un singolo carattere spazio tra ogni coppia di token adiacenti, come nel seguente grammatica (che non includono gli spazi) 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 Ancora per semplicità, la numberproduzione in questa grammatica ammette numeri con zeri iniziali, anche se sono vietati nell'output dalle regole seguenti.

Precedenza dell'operatore:

In assenza di parentesi, si applicano le seguenti regole di precedenza:

  • Gli operatori *e /hanno una precedenza maggiore rispetto a +e -.
  • Gli operatori *e /hanno uguale precedenza l'uno con l'altro.
  • Gli operatori +e -hanno uguale precedenza l'uno con l'altro.
  • Tutti gli operatori sono associativi di sinistra.

Ad esempio, le seguenti due espressioni sono equivalenti:

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

e dovrebbero entrambi produrre il seguente output:

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

(Queste sono le stesse regole di precedenza come nel linguaggio C e nella maggior parte delle lingue che ne derivano. Probabilmente assomigliano alle regole che ti sono state insegnate nella scuola elementare, tranne forse per la relativa precedenza di *e /.)

Regole varie:

  • Se la soluzione fornita è un'espressione o una subroutine, è necessario fornire l'input e restituire l'output come singola stringa. Se la soluzione è un programma completo, dovrebbe leggere una riga contenente l'espressione infix dall'input standard e stampare una riga contenente la versione postfix sull'output standard.

  • I numeri nell'input possono includere zeri iniziali. I numeri nell'output non devono avere zeri iniziali (ad eccezione del numero 0, che deve essere emesso come 0).

  • Non devi valutare o ottimizzare l'espressione in alcun modo. In particolare, non si deve presumere che gli operatori soddisfino necessariamente qualsiasi identità associativa, commutativa o algebrica. Cioè, non dovresti assumere che eg 1 + 2eguaglia 2 + 1o che sia 1 + (2 + 3)uguale (1 + 2) + 3.

  • Si può presumere che i numeri nell'input non superino 2 31 - 1 = 2147483647.

Queste regole hanno lo scopo di garantire che l'output corretto sia definito in modo univoco dall'input.

Esempi:

Ecco alcune espressioni di input valide e gli output corrispondenti, presentati nel modulo "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 + * -"

(Almeno, spero che tutti questi siano corretti; ho fatto la conversione a mano, quindi gli errori potrebbero essere insorti.)

Per essere chiari, i seguenti input sono tutti non validi; esso non importa che cosa il vostro soluzione non se li dato (anche se, naturalmente, ad esempio, restituisce un messaggio di errore è più bello rispetto, ad esempio, consumando una quantità infinita di memoria):

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

Lisp è accettabile come notazione? Ad esempio, 1 2 3 4 +significa "1 + 2 + 3 + 4".
Hauleth,

3
@Hauleth: non in questa sfida, no. Inoltre, senza parentesi, come analizzeresti 1 2 3 4 + *?
Ilmari Karonen,

Quindi, nessuno spazio bianco finale (inclusa una nuova riga) è consentito in otuput?
breadbox

@breadbox: le nuove righe finali sono OK. In effetti, vorrei chiarire esplicitamente che è consentito qualsiasi spazio bianco finale.
Ilmari Karonen,

Ho una soluzione che riporta "0 1 - 2 3 - 4 * 5 6 7 8 + 9 / - 10 + * - +" per l'ultimo esempio valido, che mi sembra corretto. Puoi controllare? (Nota l'ultimo operatore +)
coredump

Risposte:


8

Utilità Shell: 60 caratteri

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

Risolti i vari problemi, ma è diventato molto più lungo :(


1
Questo è piuttosto intelligente, tranne per il fatto che non sembra gestire correttamente numeri maggiori di 9.
breadbox

@breadbox, sed -re's/[:@iKWr]+/ /g'risolto al costo di 1 carattere.
ugoren,

oops, anche se il suggerimento di @ugoren non funziona poiché gli operatori consecutivi non hanno più uno spazio tra loro; Devo trovare una soluzione anche per quello
Geoff Reedy,

4

C, 250 245 236 193 185 caratteri

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));}

Ecco una versione leggibile della fonte non golfata, che riflette ancora la logica di base. In realtà è un programma piuttosto semplice. L'unico vero lavoro che deve fare è spingere un operatore a bassa associatività su uno stack quando viene incontrato un operatore ad alta associatività, quindi rimuoverlo alla "fine" di quella sottoespressione.

#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;
}

Salva i caratteri rimuovendoli if. Ad esempio if(!*p||*p==41)return p;s[++t]=*p;}->return*p&&*p-41?s[++t]=*p:p;
ugoren,

Dichiarazione di stile K&R:*f(p,s)char*p,s;{
ugoren,

1. È un errore restituire se il iftest fallisce. 2. Lo so, ma la funzione K&R decls è dove traccio la linea. Non posso tornare da loro.
breadbox,

Pensavo che il ritorno fosse comunque alla fine della funzione. Perso il }}e for. Ma ecco un miglioramento:printf(" %ld"+!a,...
ugoren,

1
Inoltre penso che dovresti rendere pglobale (la chiamata ricorsiva assegna solo pil chiamante al chiamante). Allora fallo f(p=gets(b)).
ugoren,

2

Bash con Haskell con Preprocessore 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

Alla fine, non è più lungo della soluzione C. La parte cruciale di Haskell è quasi pigra quanto la soluzione bc ...

Accetta input come parametri della riga di comando. wVerrà creato un file con alcuni messaggi di avviso ghc, se questa modifica non ti piace runghc 2>/dev/null.


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

2

Python 2, 290 272 268 250 243 238 byte

Ora finalmente più breve della risposta JS!

Questo è un programma completo che utilizza un'implementazione di base dell'algoritmo di shunting yard . L'input viene dato come una stringa tra virgolette e il risultato viene stampato su 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)

Provalo online!


Spiegazione:

La prima cosa che dobbiamo fare è convertire l'input in token. Lo facciamo usando trovando tutte le partite della regex\d+|\S , tradotto approssimativamente in "qualsiasi gruppo di cifre e qualsiasi carattere non spaziale". Ciò rimuove gli spazi bianchi, analizza le cifre adiacenti come token singoli e analizza gli operatori separatamente.

Per l'algoritmo di shunting yard, ci sono 4 tipi di token distinti che dobbiamo gestire:

  • ( - Parentesi sinistra
  • ) - Giusta parentesi
  • +-*/ - Operatori
  • 9876543210 - Letterali numerici

Per fortuna, i codici ASCII di questi sono tutti raggruppati nell'ordine mostrato, quindi possiamo usare l'espressione (t>'/')+(t>')')+(t>'(')per calcolare il tipo di token. Ciò risulta in 3 per le cifre, 2 per gli operatori, 1 per una parentesi destra e 0 per una parentesi sinistra.

Utilizzando questi valori, eseguiamo l'indicizzazione nella tupla grande dopo execper ottenere l'esecuzione dello snippet corrispondente, in base al tipo di token. Questo è diverso per ogni token ed è la spina dorsale dell'algoritmo di shunting yard. Vengono utilizzati due elenchi (come stack): O(stack operativo) e B(buffer di output). Dopo aver eseguito tutti i token, gli operatori rimanenti nello Ostack vengono concatenati con il buffer di output e il risultato viene stampato.


2

Prolog (SWI-Prolog) , 113 byte

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).

Provalo online!

SWI Prolog ha un set di builtin molto migliore di GNU Prolog, ma è ancora un po 'frenato dalla verbosità della sintassi di Prolog.

Spiegazione

term_to_atomse eseguito all'indietro, analizzerà un'espressione di notazione infissa (memorizzata come un atomo) in un albero di analisi (obbedendo alle normali regole di precedenza ed eliminando zeri iniziali e spazi bianchi). Usiamo quindi il predicato helper cper eseguire una ricorsione strutturale sull'albero di analisi, convertendoli in notazione postfix in modo approfondito.


1

Javascript (ES6), 244 byte

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` `)

Esempio:
Call: f('0-1+(2-3)*4-5*(6-(7+8)/9+10)')
Output: 0 1 - 2 3 - 4 * + 5 6 7 8 + 9 / - 10 + * -(con uno spazio finale)

Spiegazione:

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 byte

R è in grado di analizzare se stesso, quindi piuttosto che reinventare la ruota, mettiamo semplicemente al lavoro il parser, che genera notazione prefissata e usiamo una funzione ricorsiva per passare alla notazione postfissa.

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,"")
}

L' pargomento è controllare l'uso della valutazione non standard (la rovina dei programmatori R ovunque), e ci sono alcuni ifsecondi in più per controllare l'output delle parentesi (che vogliamo evitare).

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

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

Ingresso: (((((1))) + (2)))

Produzione: 1 2 +

Come bonus, funziona con simboli arbitrari e qualsiasi funzione predefinita con un massimo di due argomenti:

L'identità di Eulero

Ingresso: e^(i*pi)-1

Produzione: e i pi * ^ 1 -

Dividendi di 13 tra 1 e 100

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

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

Regressione lineare del peso del pollo in funzione del tempo

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

Produzione: weight Time ~ ChickWeight lm summary

L'ultimo esempio è forse un po 'al di fuori dell'ambito dell'OP, ma utilizza la notazione postfix, quindi ...

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.