Precedenza della funzione nell'algoritmo Shunting-yard


9

Sto lavorando attraverso l' algoritmo Shunting-yard , come descritto da Wikipedia.

La descrizione dell'algoritmo quando si tratta di operatori è la seguente:

Se il token è un operatore, o1, allora:

mentre c'è un token operatore, o2, nella parte superiore della pila degli operatori, e uno dei due

o1 is left-associative and its precedence is less than or equal to
that of o2, or

o1 is right associative, and has precedence less than that of o2,

quindi estrarre o2 dallo stack operatore, nella coda di output;

spingere o1 sulla pila dell'operatore.

Tuttavia, danno il seguente esempio:

Ingresso: sin max 2 3 / 3 * 3.1415

Quando l'algoritmo colpisce il /token, la descrizione di ciò che dovrebbe accadere è la seguente:

Token |        Action       |   Output (in RPN) |   Operator Stack
...
/     | Pop token to output | 2 3 max           | / sin 
...

Stanno estraendo il token funzione maxda stacke inserendo in queue. Secondo il loro algoritmo, ciò significherebbe che il token di funzione è sia un operatore che ha una precedenza inferiore a quello /dell'operatore.

Non vi è alcuna spiegazione se questo sia o meno il caso. Quindi, per l' Shunting-yardalgoritmo, qual è la precedenza di una funzione? Le funzioni sono associative di destra o di sinistra? O Wikipedia è solo incompleta / imprecisa?

Risposte:


5

Credo che la risposta diretta sia semplicemente che le funzioni non sono operatori. Dalla pagina che hai collegato:

Se il token è un token funzione, quindi spingerlo nello stack.

Questo è tutto ciò che deve dire, poiché il caso di funzione (prefisso postfisso) è molto più semplice del caso operatore (da infisso a postfisso).

Per le domande di follow-up: Le nozioni di precedenza e associatività sono necessarie solo a causa dell'ambiguità ereditaria in qualsiasi espressione con più operatori infix. I token funzione stanno già utilizzando la notazione con prefisso, quindi semplicemente non hanno questo problema. Non è necessario sapere se sino maxha "più alto la precedenza" per capire che maxha bisogno di essere valutate per prime; è già chiaro dall'ordine dei token. Ecco perché i computer preferiscono iniziare la notazione pre / postfix e perché abbiamo questo algoritmo per convertire infix in pre / postfix.

È necessario disporre di una sorta di regola per cui gli argomenti di una funzione iniziano e finiscono quando non è presente alcuna parentesi, quindi si potrebbe dire che le funzioni "hanno la precedenza" sugli operatori o viceversa. Ma a differenza degli operatori infix, è sufficiente un'unica regola coerente per tutte le funzioni per rendere le loro composizioni completamente inequivocabili.


Il loro algoritmo è corretto, quindi; è il loro esempio che non è corretto. La notazione infissa dovrebbe includere la parentesi che avvolge le funzioni:sin( max( 2 3) / 3 * 3.1415)
MirroredFate

Non sono sicuro che lo definirei errato, ma questo è un argomento forte a favore delle lingue che richiedono parentesi e virgole attorno a tutte le chiamate di funzione.
Ixrec,

Penso che sia errato in quanto è impossibile analizzare l'infix usando l'algoritmo come lo descrivono.
MirroredFate

@Ixrec Non vedo la riga "Se il token è un token di funzione, quindi spingerlo nello stack." sulla pagina di Wikipedia. Ormai potrebbe essere modificato. Ma vuoi dire che posso trattare una funzione uguale a un numero nell'algoritmo?
Abhinav

3

Esistono due casi diversi da considerare, a seconda della sintassi della lingua. Se la tua lingua utilizza la parentesi per indicare l'applicazione della funzione (ad es. f(2+1)), La precedenza è irrilevante. La funzione deve essere inserita nello stack e espulsa dopo (per l'esempio sopra, il risultato è 2 1 + f). In alternativa, è possibile trattare la funzione come un valore e inviarla immediatamente e generare un'operazione di invocazione della funzione dopo la parentesi chiusa (che altrimenti dovrebbe essere trattata come qualsiasi altra parentesi), ad esempio f 2 1 + $dove si $trova l'operazione di invocazione della funzione.

Se la tua lingua, tuttavia, non utilizza la parentesi per indicare l'invocazione della funzione, ma colloca invece l'argomento direttamente dopo la funzione senza punteggiatura speciale (ad esempio f 2 + 1), come apparentemente è il caso dell'esempio di Wikipedia, allora le cose sono un po 'più complicate. Si noti che l'espressione che ho appena fornito è un esempio ambiguo: f viene applicato a 2 e 1 aggiunti al risultato oppure aggiungiamo 2 e 1 insieme e quindi chiamiamo f con il risultato?

Ancora una volta, ci sono due approcci. Puoi semplicemente spingere la funzione nello stack dell'operatore quando la incontri e assegnarla come preferisci. Questo è l'approccio più semplice ed è apparentemente ciò che ha fatto l'esempio citato. Vi sono tuttavia problemi pratici. Innanzitutto, come si identifica una funzione? Se hai un set finito è facile, ma se hai funzioni definite dall'utente, questo significa che il tuo parser ha bisogno di troppo feed back nel tuo ambiente, che può diventare disordinato rapidamente. E come gestite le funzioni con più argomenti?

La mia sensazione è che per questo stile di sintassi, usare le funzioni come valori che sono più maneggevoli da un operatore dell'applicazione di funzioni ha molto più senso. Quindi, puoi semplicemente iniettare l'operatore dell'applicazione ogni volta che leggi un valore e anche l'ultima cosa che leggi era un valore, quindi non hai bisogno di alcun modo speciale per dire quali identificatori sono funzioni. Puoi anche lavorare con espressioni che restituiscono funzioni (che è difficile o impossibile con lo stile funzione-come-operazione). Ciò significa che è possibile utilizzare il curry per gestire più funzioni di argomento, il che è una semplificazione enorme rispetto al tentativo di gestirle direttamente.

L'unica cosa che devi decidere allora è qual è la precedenza dell'applicazione di funzione. La scelta spetta a te, ma in tutte le lingue che ho usato che funziona in questo modo, è stato l'operatore più fortemente vincolante nella lingua ed è stato il giusto associativo. (L'unica variante interessante è Haskell, che oltre ad avere la versione fortemente vincolante descritta, ha anche un sinonimo per essa con il simbolo $che è l'operatore più debolmente vincolante nella lingua, consentendo a espressioni come f 2 + 1applicare f a 2 e f $ 2 + 1applicare a tutto il resto dell'espressione)


3

Ho implementato le "funzioni nel cantiere di smistamento" richieste dopo aver letto il pensiero originale di Dijkstra (pagine 7-11 nel documento del compilatore Algol 60, https://ir.cwi.nl/pub/9251 ) e ho bisogno di una soluzione solida, I ha fatto quanto segue:

analisi:

  • Premere il descrittore di funzione
  • Inserire una parentesi sinistra di inizio arg "" "proprio come l'inizio della parentesi sottoespressione.
  • Leggere una sequenza di elenchi di argomenti bilanciati "(" a ")" dall'input
  • Invia questo al flusso di token di output
  • Spingi una parentesi destra di fine arg "" "proprio come la sua" staffa di chiusura di compensazione "

Infix-to-postfix (cantiere di smistamento):

  • Aggiungi un altro stack, lo stack delle funzioni, proprio come lo stack dell'operatore
  • Quando si esegue la scansione di un nome di funzione, spingere le informazioni della funzione nello stack delle funzioni
  • Quando viene visualizzata una parentesi quadra di fine args, pop lo stack di funzioni per l'output

Funziona perfettamente con test efficaci e scenari complicati. Nella mia applicazione (un expander di argomenti contenente espressioni della riga di comando), supporto funzioni multi argomento e una virgola "," token per separarli, e questi scorrono attraverso l'intero processo.

Gli esempi sembrano "sqrt (3 ^ 2 + 4 ^ 2)" che diventa "3 2 ^ 4 2 ^ + sqrt" e alla fine "5" che è ciò che il programma pensa sia l'argomento. È bignum, quindi "" binomiale (64, 32) / gcd (binomiale (64, 32), binomiale (63, 31)) "==> cose grandi ==>" 2 "è utile." 123456 ^ 789 " è di 40.173 cifre e il tempo mostra "valutazione = 0.000390 sec" sul mio MacBookPro, così veloce.

Lo uso anche per espandere i dati nelle tabelle e lo trovo utile. Ad ogni modo, questo è il mio consiglio sulla mia strada per gestire attentamente le chiamate di funzione, più argomenti e l'annidamento profondo in un contesto di manovra di Dijkstra. L'ho appena fatto oggi dal pensiero indipendente. Non so se potrebbero esserci modi migliori.

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.