› Excel e gli applicativi Microsoft Office › Sfida di Pasqua: calcolatore con notazione polacca
-
AutoreArticoli
-
Questa sfida e' di media complessita'. Diciamo che chi ottiene un risultato apprezzabile, sa il fatto suo
Si tratta di costruire un semplice calcolatore (magari limitiamolo alle quattro operazioni, pero' in astratto virtualmente sarebbero possibili tutte le operazioni di una calcolatrice professionale) che agisca mediante la "notazione polacca".
Per i dettagli sulla notazione polacca vi rimando a Wikipedia: https://it.wikipedia.org/wiki/Notazione_polacca
In soldoni, la notazione polacca, o notazione prefissa, è una particolare sintassi matematica in cui gli operatori si trovano tutti a sinistra degli argomenti. Questo significa che possiamo scrivere formule senza l’uso di parentesi o altri elementi di separazione.
Ad esempio l'espressione "+ 5 * 3 2" deve dare come risultato 11: in notazione infissa (quella che utilizziamo in aritmetica) sarebbe "5 + 3 * 2" dove ovviamente la moltiplicazione ha la precedenza sull'operatore di addizione. E come vedete non servono le parentesi perche' l'ordine di calcolo e' dato dalla posizione degli operatori.La sfida e' interessante perche' prevede (per ottimizzare il codice) l'uso della ricorsione.
La procedura dovrebbe accettare una stringa in ingresso, valutarla e produrre il risultato. Un miglior risultato prevede anche la gestione degli errori. Potete fornire Sub o Function come volete, pero' sappiate che faro' dei test di funzionamento
Vi lascio alcune stringhe di espressione di prova:
"- + 2 * / 6 2 4 1" ~~~> 13 cioe' (((6/2) * 4) + 2) - 1
"* / 15 3 2" ~~~> 10 cioe' (15/3) * 2
"/ 15 * 3 2" ~~~> 2,5 cioe' 15 / (3*2)
"/ 3 0" ~~~> errore (divisione per zero) cioe' 3/0Scossa e' invitato a produrre una soluzione commentata cosi' anche noi poveri mortali ci capiamo qualcosa
Buon lavoro e grazie in anticipo a chi vorra' provarci!
Ad esempio l'espressione "+ 5 * 3 2" deve dare come risultato 11: in notazione infissa (quella che utilizziamo in aritmetica) sarebbe "5 + 3 * 2"
Da utilizzatore di calcolatrici HP fin da metà anni '70, ho sempre amato la RPN (Notazione Polacca Inversa): "2 3 * 5 +" cioè "2 Enter 3 * 5 +" che nello stack diventa:
input display 2 2 Enter 2,00 3 3 * 6,00 5 5 + 11,00Sfida interessante, tempo permettendo proverò a cimentarmi.
Da utilizzatore di calcolatrici HP fin da metà anni '70, ho sempre amato la RPN
Lo sapevo, ci avrei scommesso qualcosa
in effetti sarebbe piu' usuale. Ma anche piu' facile chiaramente. Per questo penso che una cosa meno intuitiva sarebbe piu' utile anche agli altri (la mia prevede la ricorsione, forse ci sono altri modi).Grazie scossa
Ciao
se ho capito bene ogni operatore lavora tra i due successivi numeri che trova alla sua destra. Se alla sua destra trova un altro operatore (op2) quest'ultimo prende i due numeri alla sua destra e il risultato viene usato per l'operatore originario
"- + 2 * / 6 2 4 1" ~~~> 13 cioe' (((6/2) * 4) + 2) - 1
Domanda 1
"- + 3 * / 6 2 5 1" ~~~> 17 cioe' (((6/2) * 5) + 3) - 1 ??
Domanda 2
"- + 2 * + 6 2 4 1" vale (((6+2) * 4) + 2) - 1 oppure 6+2 * 4 + 2 - 1 ovvero 6+(2 * 4) + 2 - 1
Domanda 2
"* + 2 * + 6 2 4 1" vale (((6+2) * 4) + 2) - 1 oppure 6+2 * 4 + 2 - 1 ovvero 6+(2 * 4) + 2 - 1
"* + 2 * + 6 2 4 3" vale (((6+2) * 4) + 2) *3 oppure 6+2 * 4 + 2 *3 ovvero 6+(2 * 4) + (2 * 3)
Sinceramente ho qualche difficoltà a capire l'ordine delle operazioni se segue la notazione (ovvero da sinistra verso destra) oppure se segue l'ordine delle operazione (ovvero prima * e / e poi +e-)
La logica di fondo e': un operatore e due operandi (perche' per semplicita' consideriamo solo le quattro operazioni fondamentali... eventuali ampliamenti possono essere discussi piu' avanti).
Quando incontri un operatore, poi per forza devi cucire insieme i due numeri successivi (ricordo che c'e' sempre solo uno spazio a separarli). Quindi:
Domanda 1
"- + 3 * / 6 2 5 1" ~~~> 17 cioe' (((6/2) * 5) + 3) - 1 ??
verrebbe computato come segue: 6/2 poi il risultato moltiplicato per 5 poi il risultato viene aggiunto a 3 poi il risultato viene sottratto a 1. Risultato 17.
Domanda 2
"- + 2 * + 6 2 4 1" vale (((6+2) * 4) + 2) - 1 oppure 6+2 * 4 + 2 - 1 ovvero 6+(2 * 4) + 2 - 1
Deve dare 33. Infatti calcoli 6+2 che moltiplichi per 4 cui aggiungi 2 e sottrai 1.
"* + 2 * + 6 2 4 1" vale (((6+2) * 4) + 2) - 1 oppure 6+2 * 4 + 2 - 1 ovvero 6+(2 * 4) + 2 - 1
Questo deve dare 34 cioe' ((6+2) * 4 + 2) * 1.
"* + 2 * + 6 2 4 3" vale (((6+2) * 4) + 2) *3 oppure 6+2 * 4 + 2 *3 ovvero 6+(2 * 4) + (2 * 3)
Questo restituisce 102 cioe' (((6+2) * 4) + 2)* 3.
Sinceramente ho qualche difficoltà a capire l'ordine delle operazioni se segue la notazione (ovvero da sinistra verso destra) oppure se segue l'ordine delle operazione (ovvero prima * e / e poi +e-)
Segue l'ordine in cui sono esposti gli operatori ed e' per questo che non servono le parentesi (guarda i miei primi esempi, il secondo e il terzo che sono simili ma non uguali),
Per questo penso che una cosa meno intuitiva sarebbe piu' utile anche agli altri
Mah, io sarei partito con la più intuitiva per favorire la partecipazione; comunque io ho pronta una UDF, dimmi tu quando posso pubblicarla.
Domande a scossa
1) Esiste pure Inversa? Si capisce se -+*/ iniziano da destra?
2) Sul mio allegato, creo una stringa EX (3/0), come potrei far uscire il risultato in un msgbox (senza scrivere la formula in una cella?Ps. Non sono molto bravo, mi sembra d'aver trovato un metodo per le stringhe che avete allegato sul forum
Allegati:
You must be logged in to view attached files.Sinceramente mi viene il dubbio di non aver compreso il quiz
Lo scopo è quello di riprodurre la "logica" della "polish notation" e quindi elaborare con tale logica l'espressione (cit.: "Si tratta di costruire un semplice calcolatore"), oppure semplicemente di tradurre la stringa PN in una espressione infissa (vedi codice di Raffaele)?
Resto in attesa di delucidazioni.
P.S.: @raffaele: Notazione polacca inversa
io sarei partito con la più intuitiva per favorire la partecipazione
Finora le mie proposte sono sempre state molto facili. Proviamo ad alzare un po' il livello, cosi' magari le facciamo diventare un pochino piu' interessanti.
dimmi tu quando posso pubblicarla.
Anch'io ho pronta la mia, volevo attendere un paio di giorni per non influenzare nessuno, ma sei libero di scegliere il momento che ritieni adatto, come tutti.
Sinceramente mi viene il dubbio di non aver compreso il quiz
Sono sicuro invece che hai capito bene
Lo scopo è quello di riprodurre la "logica" della "polish notation" e quindi elaborare con tale logica l'espressione (cit.: "Si tratta di costruire un semplice calcolatore"), oppure semplicemente di tradurre la stringa PN in una espressione infissa (vedi codice di Raffaele)?
In queste sfide e' gradita la fantasia... io non avrei pensato a una soluzione come quella di Raffaele (che trovo abbastanza semplice e funzionale perche' raggiunge l'obiettivo). Volevo costruire un parser che traducesse una stringa in un risultato numerico seguendo determinate regole. Quindi il calcolatore parte da un'espressione stringa, la rielabora nei suoi componenti costituivi, traduce i simboli che incontra e calcola un risultato (numerico o di errore). A te anzi posso anche chiedere di spingerti a costruire un calcolatore esteso, per esempio aggiungendo funzioni che accettano un solo operando e ignorano il secondo come Abs e Sqrt. Del resto quando hai costruito un algoritmo universale, ampliarlo non sara' complicato.
Come sapete non ci sono limiti all'ingegno, quindi bravo Raffaele che ha mostrato una soluzione funzionante
2) Sul mio allegato, creo una stringa EX (3/0), come potrei far uscire il risultato in un msgbox (senza scrivere la formula in una cella?
Non sono scossa ma ti rispondo ugualmente
Potresti utilizzare Evaluate invece che inserire T1, la stringa che contiene l'espressione, direttamente come formula di Excel. Se il risultato e' un valore di errore, lo intercetti.
Fine: e = Evaluate(T1) If IsError(e) Then MsgBox "L'espressione produce un errore." Target.Offset(0, 1).Formula = "=" & T1Nota: benedetto sia Option Explicit e chi l'ha inventato
Ciao,
visto che nessuno rompe il ghiaccio lo faccio io con una UDF versione base: prodotto, divisione, addizione, sottrazione e potenza (di conseguenza anche radice, visto che elevare un numero p.e 16 allo 0,5 corrisponde alla radice quadrata).
La funzione non è ricorsiva e simula il principio della catasta (stack) tipico della notazione Polacca mediante una Collection:
'--------------------------------------------------------------------------------------------------------- ' Procedure : fPN ' Author : scossa ' Date : 28/03/2024 ' Purpose : valuta un'espressione stringa in "Notazione Polacca" (NP) ' Argoments ' sArg : espressione stringa da valutare ' ' https://www.excelvba.it/forumexcel/forums/discussione/sfida-di-pasqua-calcolatore-con-notazione-polacca/ '--------------------------------------------------------------------------------------------------------- Function fPN(ByVal sArg As String) Const sOps As String = " * / + - ^ " Dim vArg As Variant, vRet As Variant Dim j As Long, nArg As Long, k As Long Dim vOp As Variant, vStack As Variant Dim cStack As Collection Set cStack = New Collection If sArg <> "" Then vArg = Split(Trim(sArg), " ") vStack = vArg nArg = UBound(vArg) For k = 0 To nArg vArg(nArg) = vStack(k) vStack(k) = 0 nArg = nArg - 1 Next k For Each vOp In vArg If InStr(sOps, " " & vOp & " ") = 0 Then cStack.Add Replace(vOp, ",", ".") Else j = cStack.Count vRet = Evaluate(CStr(cStack(j)) & vOp & CStr(cStack(j - 1))) cStack.Remove (j) cStack.Remove (j - 1) cStack.Add vRet End If Next vOp Else vRet = CVErr(xlErrNA) End If fPN = vRet Set cStack = Nothing End Functionquesta la sub per testarla in VBA:
Sub TestPN() Dim sExpr As String sExpr = "- + 3 * / 6 2 5 1" '17 Debug.Print sExpr, fPN(sExpr) sExpr = "- + 2 * + 6 2 4 1" '33 Debug.Print sExpr, fPN(sExpr) sExpr = "* + 2 * + 6 2 4 1" '34 Debug.Print sExpr, fPN(sExpr) sExpr = "* + 2 * + 6 2 4 3" '102 Debug.Print sExpr, fPN(sExpr) sExpr = "/ 15 * 3 2" '2,5 Debug.Print sExpr, fPN(sExpr) sExpr = "/ 3 0" 'error Debug.Print sExpr, fPN(sExpr) sExpr = "* / 15 3 2" '10 Debug.Print sExpr, fPN(sExpr) sExpr = "^ 2 3" '8 Debug.Print sExpr, fPN(sExpr) sExpr = "^ 2 0,5" '1,41421356 Debug.Print sExpr, fPN(sExpr) sExpr = "^ 9 1/3" '3 Debug.Print sExpr, fPN(sExpr) sExpr = "+ 2 0,5" '2,5 Debug.Print sExpr, fPN(sExpr) End SubLato celle, invece, previa formattazione come testo delle celle che contengono l'espressione, p.e. in A2 - + 3 * / 6 2 5 1 nella cella a fianco:: =fPN(A2):
Espressione risultato - + 3 * / 6 2 5 1 17 - + 2 * + 6 2 4 1 33 * + 2 * + 6 2 4 1 34 * + 2 * + 6 2 4 3 102 / 15 * 3 2 2,5 / 3 0 #DIV/0! * / 15 3 2 10 ^ 2 3 8 ^ 2 0,5 1,414213562 ^ 4 1/2 2 ^ 9 1/3 3La funzione non è ricorsiva
Bello. Ero curioso di vedere una soluzione con uno stack infatti. Avevo pensato anche io alla Collection per questo scopo ma poi ho lasciato perdere
di conseguenza anche radice
Non ci avevo pensato ma naturalmente e' una conseguenza logica. Nel tuo codice ogni pezzo viene prevalutato e quindi considerato come argomento per l'operazione successiva.
Comunque mi ricorda il funzionamento dello stack del VIC20. Perlomeno, ero partito da quell'idea li' anche se poi non l'ho combinata granche'.
Questa e' la mia versione
Ottimo Aldo grazie. Anche tu hai adottato una logica simile alla mia attraverso l'enumerazione delle quattro operazioni (io ho scelto un costrutto Select Case per leggibilita'). Buono anche il controllo degli errori, l'unico pugno in un occhio e' quel
GoTo Err_CalcolatriceNotazionePolaccaMa va bene ugualmente
l'unico pugno in un occhio e' quel
GoTo Err_CalcolatriceNotazionePolaccaHai ragione Francesco, ma mi sono incartato e non sapevo come uscirne
, ci studio meglio e vedo come risolvere.Ogni suggerimento e' bene accetto
Allego la mia proposta:
Option Explicit 'NOTAZIONE POLACCA 'https://it.wikipedia.org/wiki/Notazione_polacca Function notazione_polacca(s As String) As Variant Dim tokens() As String Dim index As Integer s = Trim(s) tokens = Split(s) index = 0 On Error GoTo ErrorHandler notazione_polacca = evaluate_token(tokens, index) If index < UBound(tokens) Then ' Controlla la coerenza dei token Err.Raise vbObjectError + 1, "notazione_polacca", "Struttura dell'espressione non valida: troppi token." End If Exit Function ErrorHandler: notazione_polacca = CVErr(xlErrValue) MsgBox "Errore nella valutazione dell'espressione: " & Err.Description, vbCritical, "Errore di Valutazione" End Function Private Function evaluate_token(tokens() As String, ByRef index As Integer) As Double Dim token As String Dim oLeft As Double Dim oRight As Double If index > UBound(tokens) Then Err.Raise vbObjectError + 2, "evaluate_token", "Struttura dell'espressione non valida: operandi insufficienti." End If token = tokens(index) If IsNumeric(token) Then evaluate_token = Val(token) Else Select Case token Case "+", "-", "*", "/", "mod", "abs", "sqr" index = index + 1 oLeft = evaluate_token(tokens, index) If token <> "abs" And token <> "sqr" Then index = index + 1 oRight = evaluate_token(tokens, index) End If Select Case token Case "+" evaluate_token = oLeft + oRight Case "-" evaluate_token = oLeft - oRight Case "*" evaluate_token = oLeft * oRight Case "/" If oRight = 0 Then Err.Raise vbObjectError + 3, "evaluate_token", "Divisione per zero." evaluate_token = oLeft / oRight Case "mod" evaluate_token = oLeft Mod oRight Case "abs" evaluate_token = Abs(oLeft) Case "sqr" evaluate_token = Sqr(oLeft) End Select Case Else Err.Raise vbObjectError + 4, "evaluate_token", "Token non valido:" & vbNewLine & "[" & Chr(34) & token & Chr(34) & "]" End Select End If End FunctionE la sub di test:
Sub test_notazione_polacca() Dim s As String Dim result As Variant ' s = "/ 15 * 3 2" ' 2,5 ' s = "* / 15 3 2" ' 10 ' s = "- + 2 * / 6 2 4 1" ' 13 ' s = "/ 3 0" ' error ' s = "+ 5 * 3 2" ' 11 ' s = "* + 2 * + 6 2 4 3" ' 102 ' s = " sqr + abs - 5 12 2 " ' 3 result = notazione_polacca(s) If Not isError(result) Then MsgBox "Il risultato di " & vbNewLine & s & vbNewLine & "e' : " & result End If End SubRisolto
Allegati:
You must be logged in to view attached files.Grazie Aldo, ma vorrei farti notare un problema di fronte ad espressioni come "1 2" oppure "+ 1 2 3". E' volutamente una condizione di errore. Il programma si inchioda in un loop che si sblocca con Ctrl-break: quindi la routine non manda Excel in crash, ma entra in un circolo vizioso... problema presente anche nella prima versione.
Ciao a tutti e buona Pasqua,
io in questo periodo non ce la faccio,....se mi aspettate provero a ragionarci dopo l'8 di aprile
Err.Raise vbObjectError + 4, "evaluate_token", "Token non valido:"
sai che uso abitualmente Err.Raise, ma sai anche che, secondo me, le udf devono comportarsi come le funzioni native, cioè limitarsi a restiutire un codice di errore, niente testi esplicativi o, peggio, msgbox. Nella mia udf quindi sta all'operatore scrivere nella corretta sintassi della NP la stringa da elaborare.
Risolto anche questo
Allegati:
You must be logged in to view attached files.sai che uso abitualmente Err.Raise
Volevo provare a fare come te. Tentativo fallito
-
AutoreArticoli
