Valutazione di espressione matematica

di il
3 risposte

Valutazione di espressione matematica

Molti anni fa, avevo scritto in GWbasic, un parser per valutare un'espressione matematica che rispetta le priorità di calcolo delle 4 operazioni matematiche di base + - * /
Per diletto l'ho convertito in C#, un lavoretto da pochi minuti.
Poi mi son detto, ora scrivo un nuovo parser simile ma usando le peculiarità di C# che conosco, altro lavoretto da pochi minuti.

Le due void accettano in ingresso una srtinga che contiene un'espressione matematica con i numeri spaziati dagli operatori, ad ogni calcolo eseguito mostro a schermo la nuova espressione generata.

Se si scrive l'espressione correttamente tutto funziona, ma volevo migliorare l'input permettendo all'utente di scrivere l'espressione come meglio crede, esempio:
ora se la stringa di ingresso contiene: "12 - 2 * -4 + 12 / 6" tutto funziona correttamente
come fare per normalizzare una stringa del tipo: "12-2*-4+12/6" ?
o peggio: "12 -2* -4+ 12/6" ?

all'inizio ingenuamente avevo immesso un correttivo:

// Aggiusto gli spazi dell'espressione immessa per compatibilizzarla con l'algoritmo di valutazione
   espressione = espressione.Replace(" ", "");
   espressione = espressione.Replace("+", " + ").Replace("-", " - ").Replace("*", " * ").Replace("/", " / ");
ma tale approcio funziona solo se non si immettono numeri negativi

Posto il codice completo realizzato e aspetto i vostri consigli anche su un diverso approcio per realizzare un parser di espressione matematica.

using System;
using System.Collections.Generic;
using System.Linq;

namespace Valutazione_espressione
{
    class Program
    {
        static void Main(string[] args)
        {
            string espressione = "";   

            riinput:
            Console.WriteLine("\nScrivi un'espressione matematica, operazioni matematiche ammesse + - * /");
            Console.WriteLine("Esempio: -2,5 * 8 * 25,6 / 3 - -8,6");
            Console.Write("\nEspressione immessa:  ");
            espressione = Console.ReadLine();

            // Aggiusto gli spazi dell'espressione immessa per compatibilizzarla con l'algoritmo di valutazione
            //espressione = espressione.Replace(" ", "");
            //espressione = espressione.Replace("+", " + ").Replace("-", " - ").Replace("*", " * ").Replace("/", " / ");

            Console.WriteLine("\n-----------------> GWBasic");
            CalcolaEspressioneBASIC(espressione);
            Console.WriteLine("\n-----------------> C#");
            CalcolaEspressioneNET(espressione);
            goto riinput;
        }

        static void CalcolaEspressioneBASIC(string sEspressione)
        {   // accetta in ingresso un'espressione matematica con 1 spazio tra numeri e operatore
            // simboli matematici ammessi + - * /
            // Esempio espressione valida: 10 + 20 / 56 - 38.....
            // mio codice originale GWBasic, adattato in C#, cambiando il meno possibile
            string sInputElaborato = sEspressione;
            Console.WriteLine(sInputElaborato);

            int pos1 = 0;
            int pos2 = 0;
            int pos3 = 0;
            int ciclo = 0;
            double a1 = 0;
            double a2 = 0;
            double Risultato = 0;
            string sCarattere = null;
            string sOp = "";

            //-----------------------------------------------------
            // elaborazione moltiplicazioni e divisioni in sequenza
            //-----------------------------------------------------

            do
            {   // trovo la prima moltiplicazione o la divisione
                pos2 = 0;

                for (ciclo = 1; ciclo < sInputElaborato.Length; ciclo++)
                {
                    if (sInputElaborato.Substring(ciclo - 1, 2) == "* ")
                    {
                        pos2 = ciclo;
                        sOp = "*";
                        break;
                    }
                    if (sInputElaborato.Substring(ciclo - 1, 2) == "/ ")
                    {
                        pos2 = ciclo;
                        sOp = "/";
                        break;
                    }
                }

                //-----------------------------------------------------------------------
                if (pos2 == 0) { break; } // non ci sono piu' moltiplicazioni e divisioni
                //-----------------------------------------------------------------------

                // trovo lo spazio successivo a partire da pos2
                pos3 = sInputElaborato.Length + 1;
                for (ciclo = pos2 + 2; ciclo < sInputElaborato.Length; ciclo++)
                {
                    sCarattere = sInputElaborato.Substring(ciclo - 1, 1); // divido i caratteri digitati uno ad uno
                    if (sCarattere == " ")
                    {
                        pos3 = ciclo;
                        break;
                    }
                }

                // trovo lo spazio precedente a partire da pos2
                pos1 = 0;
                for (ciclo = pos2 - 2; ciclo >= 1; ciclo--)
                {
                    sCarattere = sInputElaborato.Substring(ciclo - 1, 1); // divido i caratteri digitati uno ad uno
                    if (sCarattere == " ")
                    {
                        pos1 = ciclo;
                        break;
                    }
                }

                // ora sono in grado di fare la moltiplicazione o la divisione
                a1 = Convert.ToDouble(sInputElaborato.Substring(pos1, pos2 - pos1 - 1));
                a2 = Convert.ToDouble(sInputElaborato.Substring(pos2, pos3 - pos2 - 1));
                if (sOp == "*")
                {
                    Risultato = a1 * a2;
                }
                else
                {
                    Risultato = a1 / a2;
                }
                // ricompongo la stringa sostituendo il risultato della moltiplicazione o divisione
                sInputElaborato = sInputElaborato.Substring(0, pos1) + Risultato.ToString() + sInputElaborato.Substring(pos3 - 1);
                Console.WriteLine(sInputElaborato);
            } while (true);

            //---------------------------------------------
            // elaborazione somme e sottrazioni in sequenza
            //---------------------------------------------

            do
            {
                // trovo la prima sottrazione o la prima somma
                pos2 = 0;
                for (ciclo = 1; ciclo < sInputElaborato.Length; ciclo++)
                {
                    if (sInputElaborato.Substring(ciclo - 1, 2) == "- ")
                    {
                        pos2 = ciclo;
                        sOp = "-";
                        break;
                    }
                    if (sInputElaborato.Substring(ciclo - 1, 2) == "+ ")
                    {
                        pos2 = ciclo;
                        sOp = "+";
                        break;
                    }
                }

                //--------------------------------------------------------------
                if (pos2 == 0) { break; }// non ci sono piu' somme o sottrazioni
                //--------------------------------------------------------------

                // trovo lo spazio successivo a partire da pos2
                pos3 = sInputElaborato.Length + 1;
                for (ciclo = pos2 + 2; ciclo < sInputElaborato.Length; ciclo++)
                {
                    sCarattere = sInputElaborato.Substring(ciclo - 1, 1); // divido i caratteri digitati uno ad uno
                    if (sCarattere == " ")
                    {
                        pos3 = ciclo;
                        break;
                    }
                }

                // trovo lo spazio precedente a partire da pos2
                pos1 = 0;
                for (ciclo = pos2 - 2; ciclo >= 1; ciclo--)
                {
                    sCarattere = sInputElaborato.Substring(ciclo - 1, 1); // divido i caratteri digitati uno ad uno
                    if (sCarattere == " ")
                    {
                        pos1 = ciclo;
                        break;
                    }
                }

                // ora sono in grado di fare la somma o la sottrazione
                a1 = Convert.ToDouble(sInputElaborato.Substring(pos1, pos2 - pos1 - 1));
                a2 = Convert.ToDouble(sInputElaborato.Substring(pos2, pos3 - pos2 - 1));
                if (sOp == "-")
                {
                    Risultato = a1 - a2;
                }
                else
                {
                    Risultato = a1 + a2;
                }
                // ricompongo la stringa sostituendo il risultato della sottrazione o somma
                sInputElaborato = sInputElaborato.Substring(0, pos1) + Risultato.ToString() + sInputElaborato.Substring(pos3 - 1);
                Console.WriteLine(sInputElaborato);
            } while (true);

        } //************************************************************************************


          //************************************************************************************
        static void CalcolaEspressioneNET(string sEspressione)
        {   // accetta in ingresso un'espressione matematica con 1 spazio tra numeri e operatore
            // simboli matematici ammessi + - * /
            // Esempio espressione valida: 10 + 20 / 56 - 38.....
            // mio codice odierno C#
            double risultato;
            List<string> DatiDivisi;
            DatiDivisi = sEspressione.Split(" ").ToList();
            Console.WriteLine(string.Join(" ", DatiDivisi));

            //-----------------------------------------------------
            // elaborazione moltiplicazioni e divisioni in sequenza
            //-----------------------------------------------------
            do
            {
                for (int ciclo = 0; ciclo < DatiDivisi.Count; ciclo++)
                {
                    if (DatiDivisi[ciclo] == "*")
                    {
                        risultato = Convert.ToDouble(DatiDivisi[ciclo - 1]) * Convert.ToDouble(DatiDivisi[ciclo + 1]);
                        SostituisceCalcolo(ref DatiDivisi, ciclo, risultato);
                        break;
                    }

                    if (DatiDivisi[ciclo] == "/")
                    {
                        risultato = Convert.ToDouble(DatiDivisi[ciclo - 1]) / Convert.ToDouble(DatiDivisi[ciclo + 1]);
                        SostituisceCalcolo(ref DatiDivisi, ciclo, risultato);
                        break;
                    }
                }
            } while (DatiDivisi.Contains("*") == true | DatiDivisi.Contains("/") == true);

            //---------------------------------------------
            // elaborazione somme e sottrazioni in sequenza
            //---------------------------------------------
            do
            {
                for (int ciclo = 0; ciclo < DatiDivisi.Count; ciclo++)
                {
                    if (DatiDivisi[ciclo] == "+")
                    {
                        risultato = Convert.ToDouble(DatiDivisi[ciclo - 1]) + Convert.ToDouble(DatiDivisi[ciclo + 1]);
                        SostituisceCalcolo(ref DatiDivisi, ciclo, risultato);
                        break;
                    }

                    if (DatiDivisi[ciclo] == "-")
                    {
                        risultato = Convert.ToDouble(DatiDivisi[ciclo - 1]) - Convert.ToDouble(DatiDivisi[ciclo + 1]);
                        SostituisceCalcolo(ref DatiDivisi, ciclo, risultato);
                        break;
                    }
                }
            } while (DatiDivisi.Contains("+") == true | DatiDivisi.Contains("-") == true);
        }

        static void SostituisceCalcolo(ref List<string> lista, int indice, double sostituto)
        {
            lista[indice] = sostituto.ToString();
            lista.RemoveAt(indice + 1);
            lista.RemoveAt(indice - 1);
            Console.WriteLine(string.Join(" ", lista));
        }
    }
}

3 Risposte

  • Re: Valutazione di espressione matematica

    Trovato sistema per spaziare correttamente la formula matematica.
    Si può inserire la formaula con tutti gli spazi che si vuole anche nessuno.
    Nuovo correttivo che funziona anche con i numeri negativi:
                // Aggiusto gli spazi dell'espressione immessa per compatibilizzarla con l'algoritmo di valutazione
                espressione = espressione.Replace(" ", ""); // tolgo tutti gli spazi
                espressione = espressione.Replace("+-", "-").Replace("--", "+"); // sostituisco +- con - e -- con +
                espressione = espressione.Replace("+", " + ").Replace("-", " - ").Replace("*", " * ").Replace("/", " / "); // spazio tutti gli operatori
                espressione = espressione.Replace("  - ", " -"); // i numeri negativi hanno generato un doppio spazio
                if (espressione.Substring (1,1) == "-") { espressione = "-" + espressione.Substring(3); } // se c'è un meno all'inizio è un numero negativo, tolgo lo spazio
                
  • Re: Valutazione di espressione matematica

    Orribile implementazione

    Non perche' non sia funzionante!

    Ma perche' con pochissimo studio aggiuntivo e MENO DELLA META' delle righe che hai scritto, puoi supportare:

    1) somma, sottrazione, moltiplicazione, divisione, OVVIAMENTE
    2) parantesi a qualunque livello di profondita' e in qualunque ordine
    3) interi con segno


    Ad esempio: (6 + 1)/(3*7 - (4*(5+5) + 4)) + -5

    Inoltre

    4) con un epsilon di sforzo puoi supportare i numeri razionali e i numeri reali, e non solo gli interi
    5) con un'altro epsilon di sforzo in piu': variabili ed assegnamento
    6) con un'ulteriore epsilon di sforzo in piu': funzioni (PREDEFINITE) con qualunque numero di parametri

    Ad esempio:
    x = 44
    x*x+2*x+1


    Si chiama: "parser ricorsivo discendente" ed e' il piu' semplice parser implementabile, letteralmente con quattro righe di codice.

    https://en.wikipedia.org/wiki/Recursive_descent_parse


    Poi potresti aggiungere il supporto alla ""definizione di funzioni"" e magari anche predicati ed operatori di confronto.
    Ma per questo bisogna essere un po' piu' smaliziati

    Il passo successivo e' il calcolo simbolico: x+x -> 2*x
    Ma questo e' un bel po' piu' complicato
  • Re: Valutazione di espressione matematica

    migliorabile ha scritto:


    Orribile implementazione

    Non perche' non sia funzionante!
    Sono daccordo, infatti in premessa specifico che è frutto di una conversione fatta da un mio codice GWbasic, scritto quando non c'era internet per andare a pescare info non reperibili nei libri in possesso.
    Ma perche' con pochissimo studio aggiuntivo e MENO DELLA META' delle righe che hai scritto, puoi supportare:
    Se guardi la parte di codice che pur sfruttando la stessa idea GWBasic, ma reinterpretata in C#
    "static void CalcolaEspressioneNET(string sEspressione)",
    sono 30 righe, comprese le dichiarazione delle variabili e i Console.WriteLine(), che servirebbe solo l'ultimo...
    using System;
    using System.Collections.Generic;
    using System.Linq;
    
    namespace Valutazione_espressione
    {
        class Program
        {
            static void Main(string[] args)
            {
                string espressione = "";   
    
                riinput:
                Console.WriteLine("\nScrivi un'espressione matematica, operazioni matematiche ammesse + - * /");
                Console.Write("\nEspressione immessa:  ");
                espressione = Console.ReadLine();
    
                // Permette di inserire l'espressione senza vincoli di spazi
                espressione = espressione.Replace(" ", ""); // tolgo tutti gli spazi
                espressione = espressione.Replace("+-", "-").Replace("--", "+"); // sostituisco +- con - e -- con +
                espressione = espressione.Replace("+", " + ").Replace("-", " - ").Replace("*", " * ").Replace("/", " / "); // spazio tutti gli operatori
                espressione = espressione.Replace("  - ", " -"); // i numeri negativi hanno generato un doppio spazio
                if (espressione.Substring (1,1) == "-") { espressione = "-" + espressione.Substring(3); } // se c'è un meno all'inizio è un numero negativo, tolgo lo spazio
                
                Console.WriteLine("\n-----------------> C#");
                CalcolaEspressioneNET(espressione);
                goto riinput;
            }
    
            static void CalcolaEspressioneNET(string sEspressione)
            {   // accetta in ingresso un'espressione matematica spaziata
                // simboli matematici ammessi + - * /
               
                double risultato;
                List<string> DatiDivisi;
                DatiDivisi = sEspressione.Split(" ").ToList();
                Console.WriteLine(string.Join(" ", DatiDivisi));
    
                //-----------------------------------------------------
                // elaborazione moltiplicazioni e divisioni in sequenza
                //-----------------------------------------------------
                do
                {
                    for (int ciclo = 0; ciclo < DatiDivisi.Count; ciclo++)
                    {
                        if (DatiDivisi[ciclo] == "*")
                        {
                            risultato = Convert.ToDouble(DatiDivisi[ciclo - 1]) * Convert.ToDouble(DatiDivisi[ciclo + 1]);
                            SostituisceCalcolo(ref DatiDivisi, ciclo, risultato);
                            break;
                        }
    
                        if (DatiDivisi[ciclo] == "/")
                        {
                            risultato = Convert.ToDouble(DatiDivisi[ciclo - 1]) / Convert.ToDouble(DatiDivisi[ciclo + 1]);
                            SostituisceCalcolo(ref DatiDivisi, ciclo, risultato);
                            break;
                        }
                    }
                } while (DatiDivisi.Contains("*") == true | DatiDivisi.Contains("/") == true);
    
                //---------------------------------------------
                // elaborazione somme e sottrazioni in sequenza
                //---------------------------------------------
                do
                {
                    for (int ciclo = 0; ciclo < DatiDivisi.Count; ciclo++)
                    {
                        if (DatiDivisi[ciclo] == "+")
                        {
                            risultato = Convert.ToDouble(DatiDivisi[ciclo - 1]) + Convert.ToDouble(DatiDivisi[ciclo + 1]);
                            SostituisceCalcolo(ref DatiDivisi, ciclo, risultato);
                            break;
                        }
    
                        if (DatiDivisi[ciclo] == "-")
                        {
                            risultato = Convert.ToDouble(DatiDivisi[ciclo - 1]) - Convert.ToDouble(DatiDivisi[ciclo + 1]);
                            SostituisceCalcolo(ref DatiDivisi, ciclo, risultato);
                            break;
                        }
                    }
                } while (DatiDivisi.Contains("+") == true | DatiDivisi.Contains("-") == true);
            }
    
            static void SostituisceCalcolo(ref List<string> lista, int indice, double sostituto)
            {
                lista[indice] = sostituto.ToString();
                lista.RemoveAt(indice + 1);
                lista.RemoveAt(indice - 1);
                Console.WriteLine(string.Join(" ", lista));
            }
        }
    }
    Si chiama: "parser ricorsivo discendente" ed e' il piu' semplice parser implementabile, letteralmente con quattro righe di codice.

    https://en.wikipedia.org/wiki/Recursive_descent_parse
    Grazie dell'info, l'ho guardata con veloce attenzione, per essere usata l'espressione prima deve essere tokenizzata, Il codice non è neanche menzionato, poi bisogna mettere in sym i token uno per volta tramite un'altra funzione ricorsiva nextsym (non mostrata).

    Bella e completa, ma non sarebbero quattro righe di codice, nenche se si implementassero solo le quattro operazioni di base, bisogna anche passargli un'espressione correttamente formattata, altrimenti esce con errore.
    Solo piccole puntualizzazioni, che nulla toglie alla gradita ed esaustiva risposta.
Devi accedere o registrarti per scrivere nel forum
3 risposte