Newsgroup: it.comp.www.php
Titolo: Risolvere espressione stringa

Ciao.
Ho una stringa, ad esempio, così composta: "1+2*(3-1)".
Come faccio per convertire il contenuto della stringa in un numero e 
ottenere il risultato: 5?
Grazie per l'attenzione :)


Sostanzialmente devi interpretare l'espressione. Quella lì sopra viene
chiamata "notazione infissa". Il sistema formale più semplice è quello
di trasformarla in notazione postfissa.
 
Qui c'è un esempio
 
 
http://www.java-samples.com/showtutorial.php?tutorialid=1132
 
 
e qui un altro, con algoritmo
 
 
http://geekswithblogs.net/venknar/archive/2010/07/09/algorithm-for-infix-to-postfix.aspx
 
 
Una volta che hai l'espressione in notazione infissa la interpreti con
uno stack (che ti serve anche per intercettarne eventuali errori nella
sintassi).
 
Un altro sistema più brutale è quello di usare eval(), e lasciare fare
a lui. Se c'è il minimo rischio che qualcuno - alla domanda "Inserisci
la formula" - risponda
 
	1 + shell_exec("FORMAT C:")                 [1]
 
allora è il caso di --> NON! <-- usare eval().
 
In effetti, eval() sarebbe il caso di non usarla mai, punto :-D
 
Leonardo
 
[1] lo so che *questo* chiederebbe conferma. Ma si può aggirare, e poi
    c'è ben altro che questo.
-- 
 
Some say the world will end in fire; / some say in ice.
From what I’ve tasted of desire, / I hold with those who favor fire.
But if it had to perish twice, / I think I know enough of hate
To say that for destruction ice / Is also great, / And would suffice.


Grazie per il tuo intervento.
Se scrivo:
$variabile = "1+2*(3-1)";
echo eval($variabile) ;
 
Ottengo un errore.
Potresti essere più chiaro per favore? :)
Magari se puoi con qualche esempio terra terra...
 
 
 
 
 
 
 
 
 
 
 

 .
 
Probabilmente il problema č fatto che stai passando ad eval del codice 
PHP sbagliato.
 
-- 
Andrea
 

 PHP sbagliato.
 
Spiego allora più dettagliatamente :)
La pagina l'ho chiamata prova.php
il codice inserito nella pagina è questo:
 
<?php
$variabile = "1+2*(3-1)" ;
echo eval($variabile) ;
?>
 
L'errore che mi esce è:
Parse error: syntax error, unexpected end of file in 
D:\xampp\htdocs\prova.php(3) : eval()'d code on line 1
 
 
 
 
 


 
Andrea voleva darti un suggerimento, del tipo che basta leggere il manuale e
copiare l'esempio...
http://php.net/manual/en/function.eval.php
 
 
Ciao!

 echo eval($variabile) ;
 
Mi piace come Leonardo dica "non usare eval" dandoti soluzioni 
alternative, e tu sia andato dritto dritto su eval.
 
Te lo ripeto: non usare eval!
 
Tra l'altro, eval non funziona su HHVM, e alcuni spingono per toglierla 
anche da PHP.
 
Non usare eval. Per nessuno motivo.
 
Bye.


Sono d'accordo di non usare eval in generale, ma mi suona come il "non usare
mai goto" in C/C++. Sì, vabbè, perché? Se mi serve e so quel che faccio li
uso (anzi, così come in C usare il goto a volte rende il codice più leggibile,
allo stesso modo il problema dell'OP è risolvibile in una riga, letteralmente,
invece che in trecento).
Il fatto che non funzioni su HHVM sembra più una pecca di HHVM che altro :)
 
Più che "non usare" direi "non abusare", ovvero usalo una/due volte nella
tua vita: questo è uno dei casi in cui io, personalmente, lo userei senza
sentire d'abusarne (una volta controllato l'input, anche se è ovvio, lo
specifico di nuovo :) )
 
Ciao!

 uso (anzi, così come in C usare il goto a volte rende il codice più leggibile,
 
Ma anche no, il "goto" è stato il male assoluto per i programmatori...
 
http://xkcd.com/292/
 
 
 
 
 


 
ehehe, la conoscevo, naturalmente :)
Forse non l'hai capita, quella vignetta di Randall dice, in maniera
sarcastica, esattamente quello che dico io - o forse eri sarcastico anche te,
non l'ho capito, senza smileys si applica la legge di Poe ;)
 
Ciao!

> Te lo ripeto: non usare eval!
 
Quale alternativa ho allora per risolvere l'espressione matematica?
Ciao :)


Prima trasformi da notazione infissa a postfissa (RPN) e poi risolvi con uno
stack. Sono entrambi algoritmi semplici che dovresti sapere..
 
Ciao!

 Quale alternativa ho allora per risolvere l'espressione matematica?
 
Beh, avresti potuto leggere il link che ti ho dato così ora forniresti 
le informazioni utili a far capire a chi ti legge di che cosa si parli 
in questa discussione.
 
Un conto è se la tua esigenza è davvero 'Voglio far risolvere 
all'interprete PHP l'espressione "1+2*(3-1)", e questa espressione non 
cambierà mai perché è una stringa costante nel sorgente.' mentre ben 
diverso è "Dato che sono un furbo di tre cotte mi faccio mandare una 
espressione matematica da una form e poi uso l'interprete per valutarla 
invece che scrivere un opportuno interprete a mano*".
 
 
* ci sono senz'altro librerie che lo fanno già in PHP
 
-- 
Andrea
 


 
Non è che tocca essere un genio dell'informatica per scrivere uno shunting
yard, eh... son due stack e una funzione ricorsiva - giusto la precedenza a
destra per l'esponente ma, voglio dì, chi non riesce a fare manco questo
meglio che prende direttamente 'na zappa e va a lavorare nei campi.
 
Io c'ho messo 'na mezz'oretta ( magari ricontrollatela prima d'usarla ;) )
 
<?php
class AST {
    protected $opDef = array(
            '^' => array('prec' => 4, 'assoc' => 'r'),
            'u' => array('prec' => 4, 'assoc' => 'l'),  /* unary minus */
            '*' => array('prec' => 3, 'assoc' => 'l'),
            '/' => array('prec' => 3, 'assoc' => 'l'),
            '+' => array('prec' => 2, 'assoc' => 'l'),
            '-' => array('prec' => 2, 'assoc' => 'l'),
        );
    protected function readToken($str, &$pos, &$expectNum)
    {
        while ($str[$pos]==' ') ++$pos;
        $ret = $str[$pos++];
        if ($expectNum) {
            if ($ret>='0' && $ret<='9') {
                while ($pos<strlen($str)&&$str[$pos]>='0' && $str[$pos]<='9')
                    $ret .= $str[$pos++];
                $expectNum = false;
                return $ret;
            } else if ($ret=='-') {
                $ret = 'u';
                return $ret;
            } else if ($ret=='(') {
                $expectNum = false;
            } else {
                throw new \Exception('malformed expression');
            }
        } else {
            $expectNum = true;
            if ($ret=='(')
                return '(';
            if ($ret==')') {
                $expectNum = false;
                return ')';
            }
            if (!isset($this->opDef[$ret]))
                throw new \Exception("unknown operator $ret");
            return $ret;
        }
    }
    protected function newNodeNum(&$stack, $num)
    {
        $node = array('val' => $num);
        array_push($stack, $node);
    }
    protected function newNodeOp(&$stack, $op)
    {
        if ($op=='u') {
            if (count($stack)<1)
                throw new \Exception("malformed expression");
            $node = array('val'=>$op,
                          'children'=>array(array_pop($stack)));
        } else {
            if (count($stack)<2)
                throw new \Exception("malformed expression");
            $node = array('val'=>$op,
                          'children'=>array(array_pop($stack),
                                            array_pop($stack)));
        }
        array_push($stack, $node);
    }
    protected function shuntingYard($str)
    {
        $pos = 0;
        $opStack = array();
        $astStack = array();
        $lastWasOp = true;
        while ($pos<strlen($str)) {
            $tok = $this->readToken($str, $pos, $lastWasOp);
            if (is_numeric($tok)) {
                $this->newNodeNum($astStack, $tok);
            } else if ($tok=='(') {
                array_push($opStack, '(');
            } else if ($tok==')') {
                do {
                    if (count($opStack)<=0)
                        throw new \Exception("mismatched parenthesis");
                    $pt = array_pop($opStack);
                    if ($pt!='(')
                        $this->newNodeOp($astStack, $pt);
                } while ($pt!='(');
            } else {
                $op1 = $this->opDef[$tok];
                while (count($opStack)>0) {
                    $tok2 = array_pop($opStack);
                    if ($tok2=='(' || $tok2==')') {
                        array_push($opStack, $tok2);
                        break;
                    }
                    $op2 = $this->opDef[$tok2];
                    if (($op1['assoc']=='l' && $op1['prec']<=$op2['prec']) ||
                        ($op1['assoc']=='r' && $op1['prec']<$op2['prec']) ) {
                        $this->newNodeOp($astStack, $tok2);
                    } else {
                        array_push($opStack, $tok2);
                        break;
                    }
                }
                array_push($opStack, $tok);
            }
        }
        while (count($opStack)>0) {
            $tok = array_pop($opStack);
            if ($tok=='(' || $tok==')')
                throw new \Exception("mismatched parenthesis");
            $this->newNodeOp($astStack, $tok);
        }
        return $astStack[0];
    }
    protected function solveAST($node) {
        if (!isset($node['children'])) {
            return $node['val'];
        } else {
            $child1 = $node['children'][0];
            if ($node['val']!='u')
                $child2 = $node['children'][1];
            switch($node['val']) {
            case 'u': return -$this->solveAST($child1);
            case '+': return $this->solveAST($child2)+$this->solveAST($child1);
            case '-': return $this->solveAST($child2)-$this->solveAST($child1);
            case '/': return $this->solveAST($child2)/$this->solveAST($child1);
            case '*': return $this->solveAST($child2)*$this->solveAST($child1);
            case '^':
                return pow($this->solveAST($child2),$this->solveAST($child1));
            }
        }
    }
    public function solve($str) {
        return $this->solveAST($this->shuntingYard($str));
    }
}
 
$str = "1+2*(3-1)";
$ast = new AST();
echo $ast->solve($str)."\n";

 
 
Per completezza, prima avevo suggerito di risolvere in due step: da infissa a
RPN e poi risolvere quella, che è il modo più facile, visto che sono due
algoritmi da manuale. Lo Shunting Yard risolve direttamente la infissa passando
da un AST, ma il concetto è praticamente lo stesso (sempre ad averci un po'
di dimestichezza con gli alberi).
 
Ciao!

 * ci sono senz'altro librerie che lo fanno già in PHP
 
Cortesemente mi potresti indirizzare verso qualcosa?
Ciao
 
 
 
 

 Io c'ho messo 'na mezz'oretta ( magari ricontrollatela prima d'usarla ;) )
 
 
 
Grazie infinite per il tuo impegno!
Il codice è comunque molto complicato per le mie conoscenze...
Non so onestamente dove mettere le mani per correggere quel che non va.
Infatti mi restituisce il seguente errore:
 
Fatal error: Uncaught exception 'Exception' with message 'unknown 
operator 3' in D:\xampp\htdocs\prova2.php:38 Stack trace: #0 
D:\xampp\htdocs\prova2.php(70): AST->readToken('1+2*(3-1)', 6, true) #1 
D:\xampp\htdocs\prova2.php(130): AST->shuntingYard('1+2*(3-1)') #2 
D:\xampp\htdocs\prova2.php(136): AST->solve('1+2*(3-1)') #3 {main} 
thrown in D:\xampp\htdocs\prova2.php on line 38


Mi sa che t'ho incollato la versione sbagliata XD
ma è solo una riga - copia-incolla maledetto!
riga 30, invece di "} else {" ci dovrebbe essere un "} if (!$expectNum) {"
 
Ciao!

 Ciao!
 
 
Ha funzionato!!!
Grazie mille.
Complimenti, sei un bravo programmatore, io non sarei mai riuscito a 
fare una cosa simile.
Ho provato a mettere altre espressioni, e me le risolve :)
Visto che l'hai scritto tu, mi potresti dire se c'è qualche limite?
Ossia qualcosa che non possa risolvere?
Ciao

 Ossia qualcosa che non possa risolvere?
 
Bah, vediamo...
 
A meno che non abbia infilato strani bug che non vedo (spero non ce ne siano,
ma non si sa mai - magari scrivi uno unit-test ;) )...
 
Questo usa solo le quattro operazioni, l'esponente e il "meno unario". Se vuoi
aggiungere anche funzioni (come sin(), cos(), etc.) o costanti/variabili (tipo
x, pi, etc.) bisogna rivedere il tokenizer (la funzione readToken()).
 
Essendo la solveAST() una funzione ricorsiva è naturalmente soggetta a
possibili stack overflow, ma ad occhio e croce direi che perché succeda devi
dargli un'espressione irragionevolmente lunga (ma tutto è possibile). In caso
bisognerebbe linearizzarla, visto che c'è un solo parametro sullo stack è
anche abbastanza immediato (e non elimina comunque il problema di un out of
memory, che in PHP è tanto brutto quanto uno stack overflow, per cui boh).
 
Oltre a questo, come puoi ben notare, il codice non è proprio un esempio di
chiarezza o ottimizzazione, ed entrambi i punti si potrebbero migliorare con
un pochino di fantasia :)
 
Ciao!

 x, pi, etc.) bisogna rivedere il tokenizer (la funzione readToken()).
 
Ho usato in un'operazione dei numeri decimali.
Ed è venuto fuori un errore.
Per cui ne deduco che il codice da te propostomi funziona solo con 
numeri interi.
Ti verrebbe difficile cambiare (se possibile, intendiamoci, non vorrei 
metterti in difficoltà :)) il codice per fargli manipolare i numeri 
decimali?
Ciao e grazie sempre :)

 Ciao e grazie sempre :)
 
Figurati!
Ciao!
 
 
(*) Continuo a rimanere comunque dell'idea che usare eval() con un po'
d'attenzione in questo caso era meglio, eh! :D

 d'attenzione in questo caso era meglio, eh! :D
 
 
Potresti farmi un esempio pratico con eval()?
Non ho capito bene come usarla.
Ciao
 
 
 
 
 


 
function solve($str)
{
    $str = preg_replace('/[^0-9\+\-\*\/\^\.]/','', $str);
    $ev = eval("\$a=$str;");
    return $a;
}
 
$str = "1+2*(3.2-1)";
echo solve($str)."\n";
 
Dove la preg_replace() controlla che nell'espressione ci siano solo numeri, le
quattro operazioni e il punto. Non mi pare che sia possibile infilare un
input maligno alla funzione.. tutto può essere, ma così su due piedi non mi
viene in mente nulla.
 
Ciao!

viene in mente nulla.
 
1/0 causa un errore, il che può andare bene in alcuni casi - e magari meno in
altri.
 
Ed e' possibile ottenere in uscita un valore stringa, anziché numerico. Anche
questo può essere un problema o no, dipende.
 
Leonardo


 
 
Anche questo poco male, basta un cast.
 
Il problema principale è che non è possibile sapere se l'espressione in eval
è corretta o meno (ovvero da syntax-error). Una volta che si sa questo, gli
altri problemi (almeno, quelli che mi vengono in mente) sono di poco conto!
 
Ciao!

è corretta o meno (ovvero da syntax-error).
 
__credo__ che in quel caso $a valga FALSE (o forse NULL).


__credo__ che in quel caso $a valga FALSE (o forse NULL).
 
...'a prima. 'a prima c'ho detto.
 
Leonardo che mi ci sarebbe voluto poco a controllare subito, ma so' pigro.



 
:)
 
In questo caso, mettendo insieme i pezzi, si dovrebbe fare qualcosa del tipo:
 
set_error_handler("myerrorhandler");
/* blahblah su $str */
try {
    if (@eval("\$a=$str;")===false)
        echo "error\n"; /* o meglio throw \Exception("parse error"); */
    return $a;
} catch(\Exception $ex) {
    return $ex->getMessage();
}
 
che in effetti, se non l'avessi scritto io, a guardarlo mi verrebbe voglia di
sputare nell'occhio dell'autore :D
 
Ciao!

> ...'a prima. 'a prima c'ho detto.
 
No, mi sbagliavo. Contrordine, compagni.
 
In quel caso, il risultato è INDETERMINATO.
 
Per esempio 1 + (1/0) ritorna 1, perché il sistema crolla calcolando 1/0, però
ha già calcolato 1.
 
Quindi ci vuole qualcosa tipo
 
function solve($str) {
    $handler    = set_error_handler(
        function() use (&$e) { $e = true; return true; }
    );
    $str = preg_replace('/[^0-9\+\-\*\/\^\.]/','', $str);
    $e  = false;
    $ev = eval("\$result={$str};");
    set_error_handler($handler);
    if ($e) {
        return false;
    }
    return $result;
}
 
Leonardo S.E.&O.


 Ciao!
 
Funziona :)
Grazie!
Come Leonardo ha fatto notare produce errore quando si ha divisione per 
zero.
 
 
 
 
 

 Leonardo S.E.&O.
 
Testata or ora e funzionante anche questa :)
A differenza dell'altra di fmassei non produce errore in una divisione 
per zero.
A questo punto vi domando: quale delle tre soluzioni sarebbe più 
conveniente utilizzare?
Le due di fmassei, quella con il molto codice, e l'altra con solve?
O la versione di Leonardo?
Ciao e grazie ad entrambi per avermi aiutato :)
 
 
 
 
 
 
 
 
 
 

 
 
Devi comunque silenziare l'output di eval con "@eval", se non vuoi avere una
scatervata di notices, warnings e errors su input errati.
 
Ciao!

 O la versione di Leonardo?
 
Dipende che ci devi fare.
 
A livello generale, la cosa con eval() conveniva per non scrivere tutta la
roba che ho scritto io, che comunque non sarebbe stato necessario scrivere
se avessimo preso una libreria che già fa quel lavoro. Ma in particolare,
per il progetto che hai sottomano, devi decidere te in base ai tuoi requisiti
qual'è la soluzione migliore.
 
Ciao!

 codice più leggibile,
 
eval è esattamente come goto: la puoi usare solo quando hai il brevetto 
da pilota e 10.000 ore di volo. :P
 
Bye.

scatervata di notices, warnings e errors su input errati.
 
...non basta l'error_handler temporaneamente disattivato? Dovrebbe
intercettare anche i warning (ma non ho provato...).
 
Leonardo


 
 
Io sì. In teoria non so, in pratica l'ho scritto perché così ha fatto a me :)
Ciao!


Beh, se intendiamo letteralmente è un casino - il brevetto da pilota
purtorppo non lo posso prendere, anche se in aereo faccio volentieri il
passeggero :) (*)
Se è per ore lavoro.. vediamo! :D
10.000 ore / (8ore al giorno * 20 giorni lavorativi al mese * 12 mesi l'anno)
fanno 5.2 anni lavorativi - direi che sia in PHP che in C (dove sono purtroppo
più o meno il doppio) posso stare tranquillo (**) - il tempo che ho lavorato
in altri linguaggi non lo conto neppure, se questa cosa voleva farmi sentire
vecchio ci sei riuscito in pieno! :)
 
Ciao!
 
 
(*)(**) a livello personale un'altra metrica sarebbe il tempo speso mentre
lavoravo su progetti aeronautici, ma non arrivo a 5 anni, mi son fermato a
3 e qualcosa.