Domanda [RELEASE-PROJECT] solver di espressioni matematiche

Stato
Discussione chiusa ad ulteriori risposte.

Simur Birkoff

Utente Gold
17 Febbraio 2014
797
29
302
321
Ultima modifica:
Salve inforgiani! Oggi vi posto questo codice sorgente, scritto per la risoluzione di semplici espressioni matematiche del tipo (4+1)-(5*(4-1))+(5*10/2)+10
Il codice è meno di un alpha! Consideratelo una condivisione fra amici! @St3ve mi ha messo il picchio in testa in una delle ultime discussioni in sezione ed io ho tirato fuori una mia rustica versione prima che Lui tirasse fuori la sua (la quale utilizza un algoritmo molto differente, molto più semplice e molto più efficace).

2xt46pkvjq5k7qlsxy.png


Qui il codice sorgente, vi prego di correggermi ove necessario ricordando che questo è stato, e aveva l'obiettivo di essere, solamente un buon esercizio!

Saluti! :)

PHP:
#include<iostream>
#include<sstream>
#include<string>
#include<vector>

using namespace std;

float resolve_cast(string);
float conv_str(string);
string Rcast_clean(string);
bool check_cast(string);
template <class T>
string to_string(const T& t) 
{
  stringstream ss;
  ss << t;
  return ss.str();
}
string res_cicle(string);
void banner();


int main()
{
  string esp;
  
  banner();
  
  cout<<"inserisci espressione aritmetica\n(inserisci 'help' per le istruzioni): "; cin>>esp;
  
  if(esp=="help")
  {
    cout<<"\nDevi inserire l'espressione esplicitando le parentesi\nanche quando non necessarie.\n"
        <<"anzi che scrivere 3+1*4 devi scrivere (3+1)*4 ;\ncioè includendo nelle parentesi operazioni di pari\n"
    <<"priorità.\nNon inserire ((4*1-5)/3) ma inserisci ((4*1)-5)/3\n"
    <<"Le operazioni supportate sono +, -, *, /\r\n";
    return 0;    
  }
  
  cout<<res_cicle(esp)<<endl;
  
  while(1)
  {
    if(check_cast(esp)==false)
    {
      cout<<"risultato: "<<resolve_cast(esp)<<endl;
      return 0;
    }
    else
    {
      esp=res_cicle(esp);
      cout<<res_cicle(esp)<<endl;
    }
  }
  
  return 0;
}

float resolve_cast(string esp)
{
  string tmp=esp;
  vector<char> op;
  vector<float> vals;
  float ris;
  int plus=0;
  
  for(int i=0; i<esp.size(); i++)
    {
      if(esp[i]=='+' || esp[i]=='-' || esp[i]=='*' || esp[i]=='/')
      op.push_back(esp[i]);
    }
  
  if(op[0]=='+' || op[0]=='-')
  {
    
    op.resize(0);
    
    for(int i=0; i<tmp.size(); i++)
    {
      if(tmp[i]=='+' || tmp[i]=='-') 
      {
    vals.push_back(conv_str(tmp.substr(0, i)));
    tmp=tmp.substr(i+1, (tmp.size()-1));
    i=0; 
      }
    }

    vals.push_back(conv_str(tmp)); 

    for(int i=0; i<esp.size(); i++)
    {
      if(esp[i]=='+' || esp[i]=='-')
    op.push_back(esp[i]);
    }

    if(op[0]=='+')
      ris=vals[0]+vals[1];
    else if(op[0]=='-')
      ris=vals[0]-vals[1];

    if(vals.size()==2)
      return ris;


    for(int i=1; i<op.size(); i++)
    {
      if(op[i]=='+')
    ris=ris+vals[i+1];
      else if(op[i]=='-')
    ris=ris-vals[i+1];
    }

    return ris; 
  }
  
  ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  else if(op[0]=='*' || op[0]=='/')
  {
    
    op.resize(0);
    
    for(int i=0; i<tmp.size(); i++)
    {
      if(tmp[i]=='*' || tmp[i]=='/') 
      {
    vals.push_back(conv_str(tmp.substr(0, i)));
    tmp=tmp.substr(i+1, (tmp.size()-1));
    i=0; 
      }
    }
    
    vals.push_back(conv_str(tmp)); 

    for(int i=0; i<esp.size(); i++)
    {
      if(esp[i]=='*' || esp[i]=='/')
    op.push_back(esp[i]);
    }

    if(op[0]=='*')
      ris=vals[0]*vals[1];
    else if(op[0]=='/')
      ris=vals[0]/vals[1];
    
    if(vals.size()==2)
      return ris;
    
    for(int i=1; i<op.size(); i++)
    {
      if(op[i]=='*')
    ris=ris*vals[i+1];
      else if(op[i]=='/')
    ris=ris/vals[i+1];
    }
    
    return ris;
  }
} 
float conv_str(string s)
{
  float num = 0;
  
  for(size_t it = 0; it < s.length(); it++) 
  {
    num *= 10;   
    num += s[it] - '0'; 
  } 
    
  return num;
}
string Rcast_clean(string s)
{
  string cls;
  for(int i=0; i<s.length(); i++)
  {
    if(s[i]==')' || s[i]=='(')
    {
      cls=s.substr(0, i);
      return cls;
    }
  }
  cls=s;
  return cls;
}
bool check_cast(string str)
{
  bool casts=false;
  
  for(int i=0; i<str.length(); i++)
  {
    if(str[i]==')' || str[i]=='(')
      return true;
  }
  
  return false;
}
string res_cicle(string esp)
{
  float res, sol;
  string tmp;
  vector<char> cast;
  vector<int> Pcast;
  
  if(check_cast(esp)==false)   //controllo parentesi
    {
      res=resolve_cast(esp);
      esp=to_string(res);
      return esp;
    }
  else
  {
    for(int i=0; i<esp.length(); i++)
    {
      if(esp[i]==')' || esp[i]=='(') 
      {
    cast.push_back(esp[i]);
    Pcast.push_back(i);
      }
    }
  }
  
  for(int i=0; i<cast.size(); i++)
  {
    if(cast[i]=='(' && cast[i+1]==')')
    {
      tmp=esp.substr(Pcast[i]+1, Pcast[i+1]); //estraggo il contenuto della parentesi più esterna
      tmp=Rcast_clean(tmp);  //eventuali correzioni di parentesi finali residue;
      res=resolve_cast(tmp);  //risolvo l'interno della parentesi
      int len=tmp.length();   //salvo la lunghezza dell'espressione appena risolta
      tmp=to_string(res);  //converto il float in str
      esp=esp.replace(Pcast[i], len+2, tmp);  //sostituisco alla parentesi una parentesi contenente il risultato
      return esp;
    }
  }
}
void banner()
{
  cout<<"###################################\n"
      <<"#           Simur Birkoff         #\n"
      <<"#                                 #\n"
      <<"#                                 #\n"
      <<"#                www.inforge.net  #\n"
      <<"###################################\r\n\n";
}
 
Re: [PROJECT-RELASE] solver di espressioni aritmetiche di medio/basso livello

Dopo lo provo :\
Te lo mando in crash subito, con una bella espressioncina :etuno:
 
[MENTION=156155]St3ve[/MENTION]
[MENTION=129009]EvOlUtIoN_OGM[/MENTION]

So che funziona con qualche stringa ed é pieno di bug, ma mantenendomi sulla falsa riga di questo algoritmo potrei migliorarlo in che modo?
 
  • Mi piace
Reazioni: St3ve
Ultima modifica:
Ho dato solo un occhiata veloce...come mai controlli prima la presenza di + e - sapendo che il * e la / hanno la precedenza :omfg: (forse mi sto sbagliando)

Ho dato una seconda occhiata e "credo" che non dia proprio importanza al segno operazionale il primo che arriva esegue :\

ovviamente non ho testato nulla ho solo visionato 5s la funzione "resolve_cast"
 
  • Mi piace
Reazioni: L0LLo
Ho dato solo un occhiata veloce...come mai controlli prima la presenza di + e - sapendo che il * e la / hanno la precedenza :omfg: (forse mi sto sbagliando)

Ho dato una seconda occhiata e "credo" che non dia proprio importanza al segno operazionale il primo che arriva esegue :\

ovviamente non ho testato nulla ho solo visionato 5s la funzione "resolve_cast"
No infatti! Il grosso limite di questo codice è che SONO NECESSARIE le parentesi! Se non le metti va in crash! Risolve le singole parentesi contenenti per forza operazioni di pari priorità!
dunque esegue:
(4*3/1)-(4+(3*4))
ma non esegue
(4*3/1)-(4+3*4)
 
Ultima modifica:
No infatti! Il grosso limite di questo codice è che SONO NECESSARIE le parentesi! Se non le metti va in crash! Risolve le singole parentesi contenenti per forza operazioni di pari priorità!
dunque esegue:
(4*3/1)-(4+(3*4))
ma non esegue
(4*3/1)-(4+3*4)
A me li esegue entrambi...il problema del risultato si verifica quando fai un operazione con piu segni operazionali...di questo genere "10+2*2" oppure "10*2+2" escono risultati astronomici...secondo me hai sbagliato qualche algoritmo e usa gli operatori stessi nelle operazioni:omfg:
infatti...:
PHP:
vals.push_back(conv_str(tmp.substr(0, i)));
    tmp=tmp.substr(i+1, (tmp.size()-1));
    cout<<"tmp:"<<tmp<<endl;
2wcir6f.png


tu moltiplichi 10 a 1+2..... il programma vede il + come un numero che ora non so a quanto corrisponda e di conseguenza il risultato risulta sballato(teoricamente dovrebbe essere questo il problema)

un altro problema che si verifica sempre in quei casi è che se il programma trova un + o - e di seguito ci sono altre operazioni le considera come tali non gli viene nemmeno in mente che possano essere * o / e viceversa
 
e pensare che ieri ho fatto un programma in PHP che faccia operazioni tra stringhe :\ però è ovvio che lo devo sistemare :asd:
 
Ultima modifica:
In effetti dovresti dare un ordine agli operatori, partendo da quelli di priorità più alta. Non ho controllato il codice perchè sono in un periodo nero con esami non informatici e rischierei di capire ben altro XD (sono a digiuno di programmazione da un pò :'( ). Ma il modo che penso sia quello giusto (forse non il migliore) di procedere sia questo:
Hai una serie di operatori, facciamo che siano solo: (), ^, /, *, +, -, in ordine di priorità (decrescente, quindi vengono prima le parentesi)
1) Controlli se ci sono occorrenze del primo operatore
2) Le risolvi
3) Torni al punto uno controllando sull'operatore successivo

Attenzione a ^ se scrivo una cosa tipo 5^2*3 il *3 dovrebbe essere moltiplicato all'intero numero, non all'esponente, nel caso si usano le parentesi. Nel caso delle parentesi diventa una specie di chiamata a funzione, ti isoli quella parentesi (in effetti è una sotto-espressione) e te la risolvi, una volta finita passi alla successiva, finchè non vanno via tutte.
Potresti anche pensare ad una classe espressione con l'operatore (e quindi una classe operatore) e 1-2 (dipende dall'operatore se è unario o binario) addendi, che possono essere numeri, o a loro volta altre espressioni.

Non so se nel codice che hai scritto hai fatto in questo modo, ma di sicuro facendo così non dovresti sbagliare, poi vabbè le ottimizzazioni le puoi pensare anche dopo :)

Forse per semplificarti (anche se non è detto) il lavoro potresti utilizzare le espressioni regolari che sono state introdotte nel C++ con lo standard 2011 (libreria <regex>). Le usai una volta in php per farmi una piccola libreria che faceva il parsing di alcuni tag del bbcode (convertendoli in html+css)
 
In effetti dovresti dare un ordine agli operatori, partendo da quelli di priorità più alta. Non ho controllato il codice perchè sono in un periodo nero con esami non informatici e rischierei di capire ben altro XD (sono a digiuno di programmazione da un pò :'( ). Ma il modo che penso sia quello giusto (forse non il migliore) di procedere sia questo:
Hai una serie di operatori, facciamo che siano solo: (), ^, /, *, +, -, in ordine di priorità (decrescente, quindi vengono prima le parentesi)
1) Controlli se ci sono occorrenze del primo operatore
2) Le risolvi
3) Torni al punto uno controllando sull'operatore successivo

Attenzione a ^ se scrivo una cosa tipo 5^2*3 il *3 dovrebbe essere moltiplicato all'intero numero, non all'esponente, nel caso si usano le parentesi. Nel caso delle parentesi diventa una specie di chiamata a funzione, ti isoli quella parentesi (in effetti è una sotto-espressione) e te la risolvi, una volta finita passi alla successiva, finchè non vanno via tutte.
Potresti anche pensare ad una classe espressione con l'operatore (e quindi una classe operatore) e 1-2 (dipende dall'operatore se è unario o binario) addendi, che possono essere numeri, o a loro volta altre espressioni.

Non so se nel codice che hai scritto hai fatto in questo modo, ma di sicuro facendo così non dovresti sbagliare, poi vabbè le ottimizzazioni le puoi pensare anche dopo :)

Forse per semplificarti (anche se non è detto) il lavoro potresti utilizzare le espressioni regolari che sono state introdotte nel C++ con lo standard 2011 (libreria <regex>). Le usai una volta in php per farmi una piccola libreria che faceva il parsing di alcuni tag del bbcode (convertendoli in html+css)

Magari se avresti letto il codice sapresti che quel programma ancora non considere le funzioni esponenziali
 
Magari se avresti letto il codice sapresti che quel programma ancora non considere le funzioni esponenziali

Non è quello il punto del mio discorso, se si trovano complicazioni con certi operatori si possono aggiungere dopo senza nessun problema. Ho cercato di dare una soluzione generale per risolvere le espressioni, indipendente dal numero e dagli operatori che hai. Volendo successivamente si possono aggiungere anche funzioni matematiche (logaritmo, valore assoluto, ecc.) semplicemente inizializzando un oggetto operatore e definendone il modo per risolverlo.
 
  • Mi piace
Reazioni: Simur Birkoff
aggiungere metodi è un attimo! Il difficile è gestirli! Ho espressamente premesso che questo codice un'espressione tipo 10*3-1 la risputa, mentre (10*3)-1 la accetta e la risolve!
Se avete consigli per ovviare al problema postate CODICE mantenendovi sulla falsa riga di questo algoritmo! Sono qui per imparare!
 
aggiungere metodi è un attimo! Il difficile è gestirli! Ho espressamente premesso che questo codice un'espressione tipo 10*3-1 la risputa, mentre (10*3)-1 la accetta e la risolve!
Se avete consigli per ovviare al problema postate CODICE mantenendovi sulla falsa riga di questo algoritmo! Sono qui per imparare!

Basta che mentre legge il numero con cui eseguire l'operazione appena trova un nuovo segno si deve fermare a leggere...
 
Ho letto il codice e ho dovuto leggerlo diverse volte per capirci qualcosa (può darsi sia un problema mio, come dicevo prima sono a digiuno di programmazione). Visto che stai scrivendo in C++ direi che è meglio ripensarlo in OOP, perchè così com'è, anche se non sono molte righe, diventa un problema aggiungere nuovi simboli e nuove operazioni, oltre al fatto che trovare i bug non è facile. Parti dal caso generale (insieme di operatori ed addendi) e man mano implementando le classi arrivi ai singoli casi.
 
  • Mi piace
Reazioni: Simur Birkoff
Ultima modifica:
Concettualmente sembra semplice, ma alla fine si rivela una sfida trovare un modo veramente pulito per farlo.

Ho letto il codice e ho dovuto leggerlo diverse volte per capirci qualcosa (può darsi sia un problema mio, come dicevo prima sono a digiuno di programmazione). Visto che stai scrivendo in C++ direi che è meglio ripensarlo in OOP, perchè così com'è, anche se non sono molte righe, diventa un problema aggiungere nuovi simboli e nuove operazioni, oltre al fatto che trovare i bug non è facile. Parti dal caso generale (insieme di operatori ed addendi) e man mano implementando le classi arrivi ai singoli casi.

Il codice non l'hai capito al colpo perché non è scritto in modo pulito, si vede che ci ha ragionato sopra mentre lo scriveva e aveva in mente fin da subito un algoritmo da usare.

Qui ho commentato quello che succede per far vedere come ci sia qualcosa che non quadra nel ragionamento generale, alla fine per funzionare funziona (con le dovute restrizioni), ma si capisce che il codice è scritto è scritto molto "sul momento"
Codice:
float resolve_cast(string esp)
{
    string tmp=esp;
    vector<char> op;
    vector<float> vals;
    float ris;
    int plus=0;                                                      // inutile
  
    for(int i=0; i<esp.size(); i++)
    {
        if(esp[i]=='+' || esp[i]=='-' || esp[i]=='*' || esp[i]=='/')
            op.push_back(esp[i]);                                    // metto in op tutti gli operatori
    }
  
    if(op[0]=='+' || op[0]=='-')                                     // se il primo operatore è + o -
    {
        op.resize(0);                                                // cancella tutti gli operatori --- ma allora a cosa mi è servito tutto quello che ho fatto prima?
                                                                     //   potevo fare la stessa cosa in modo molto più semplice.
        for(int i=0; i<tmp.size(); i++)
        {
            if(tmp[i]=='+' || tmp[i]=='-')                           // qui dovrebbero esserci anche tutti gli altri simboli (*, / e parentesi), altrimenti me li mangia conv_str --- (*)
            {
                vals.push_back(conv_str(tmp.substr(0, i)));
                tmp=tmp.substr(i+1, (tmp.size()-1));
                i=0; 
            }
        }

        vals.push_back(conv_str(tmp));                               // e questo non dovrebbe esistere, basta mettere anche '\0' a (*) e far fare tutto al for

        for(int i=0; i<esp.size(); i++)                              // avevo letto tutti gli operatori e li avevo messi in op
        {                                                            //   poi ho visto che il primo era un for così sono entrato nell'if e ho azzerato op. 
            if(esp[i]=='+' || esp[i]=='-')                           //   ora mi ripasso tutti gli operatori perché mi accorgo che ho ancora bisogno di informazioni sugli operatori
                op.push_back(esp[i]);                                //   ma io sapevo già tutto sugli operatori, avevo eliminato dei dati che mi servivano ancora.
        }

        if(op[0]=='+')                                               // (***)
            ris=vals[0]+vals[1];
        else if(op[0]=='-')
            ris=vals[0]-vals[1];

        if(vals.size()==2)                                           // non è che serva più di tanto con il codice che c'è sotto --- (**)
            return ris;


        for(int i=1; i<op.size(); i++)                               // (**) se ho un solo operatore (visto che gli operatori sono solo + e -) ritorno giustamente ris
        {                                                          //   ma ancora meglio si può unire (***) a questo for 
            if(op[i]=='+')
                ris=ris+vals[i+1];
            else if(op[i]=='-')
                ris=ris-vals[i+1];
        }

        return ris; 
    }

    // CONTINUA (il resto è praticamente identico al pezzo sopra, quindi ho evitato di ripetermi)

A mio parere con un algoritmo strutturato in questo modo l'OOP non porta grossi vantaggi, anzi...
Se si vuole sfruttare l'OOP bisogna riscriverlo e ripensarlo da zero, con un metodo di risoluzione completamente differente da questo.


So che funziona con qualche stringa ed é pieno di bug, ma mantenendomi sulla falsa riga di questo algoritmo potrei migliorarlo in che modo?

Secondo me conviene prima sistemare il codice per renderlo più pulito, poi si riparte da quello ripulito per provare a sistemare il problema delle precedenze. Già risistemando quel pezzetto che ho commentato sopra (che era giusto il primo che ho esaminato, probabilmente ci sono "problemi" di quel tipo un po' ovunque) avresti dei valori meno campati per aria (ma comunque sbagliati) in caso di operazioni del tipo 1+2*3.
 
A mio parere con un algoritmo strutturato in questo modo l'OOP non porta grossi vantaggi, anzi...
Se si vuole sfruttare l'OOP bisogna riscriverlo e ripensarlo da zero, con un metodo di risoluzione completamente differente da questo.

Quoto, infatti nel mio primo post più sopra (questo) ho cercato di dare un metodo generale per poter risolvere le espressioni, ma l'ho pensato al volo e non so se sia il migliore. Purtroppo sono impegnato con un esame per tutto il weekend, altrimenti avrei scritto direttamente il codice.
 
[MENTION=156155]St3ve[/MENTION]
L'ho postato appena finito e senza riguardarlo apposta per farmi correggere! :asd: mi sono fatto certe risate! In effetti alcuni passaggi sono assurdi! :asd:
Ora mi studio le correzioni, questo merita screen! Me lo conservo! Nessuno posta mai codici-getto, grazie mille!
 
Ma fare cosi
int main(){
STD::string exp;
Int expp;
cout << "inserisci espressione: "
Cin >> exp;

Expp = s_atoi(exp)// trasforma da alfanumerico a intero
cout << expp;
}


Second me puo funzionare

Inviato dal mio Xperia U utilizzando Tapatalk
 
Ma fare cosi
int main(){
STD::string exp;
Int expp;
cout << "inserisci espressione: "
Cin >> exp;

Expp = s_atoi(exp)// trasforma da alfanumerico a intero
cout << expp;
}


Second me puo funzionare

Inviato dal mio Xperia U utilizzando Tapatalk

:oddio: Non metto in dubbio che i caratteri numerici vanno convertiti in numero (intero, float, o qualsiasi altro tipo ti serve), ma quella funzione ignora ogni altro tipo di carattere, cioè lo salta proprio. Cioè alla fine i segni vanno a farsi benedire.
In una espressione del genere: 3^2+5*3-1 il numero che viene fuori con s_atoi è 32531.
 
Stato
Discussione chiusa ad ulteriori risposte.