transformer une chaine en opérateur mathématique

Nours312
Invité n'ayant pas de compte PHPfrance

31 mars 2009, 19:29

Bonjour,

Je suis en train de coder un truc assez complexe avec de nombreuses fonction mathématiques et je souhaiter stocker mes opérations dans une la variable ... mais avant ... est-ce envisageable ?

$cont = 0;

$var = array(

  array('+', 2),

  array('*', 3)

);

foreach ($var as $v){

  $cont = $cont .$v[0]. $v[1];

}

echo $cont;

 

Vous vous doutez bien que ceci ne peut fonctionner, la concaténation créant une chaine !
et que mon problème est bien plus complexe en quantité de données et d'opérations ...

Mais quelqu'un as t il déjà résolu un tels problème ?

Merci Bien d'avance cligne :wink:

Mammouth du PHP | 991 Messages

31 mars 2009, 21:26

Tu peux envisager de faire deux version , une version texte et une version mathématique ?
DevOps, Symfony4, Hoa

ViPHP
ViPHP | 5924 Messages

01 avr. 2009, 00:29

Alors tu n'y es pas du tout :)

Il faut vraiment être conscient de ce que tu proposes. Imaginons que tu concatènes les données pour les mettre dans une chaine. On ne peut pas dire que ce soit compliqué à faire, il suffit de concaténer les données dans l'ordre où elles arrivent. On obtient donc un résultat du type :

Code : Tout sélectionner

7 + 5 * 9 + 8 + 3 * 4
Bon, admettons on l'attaque à coup de explode(). Bon, maintenant on fait plus dur :

Code : Tout sélectionner

7 + 5 * abs(9 + 8) + 3 ^ 9 * 4 - sin(20)
Si tu réussis à décoder ça proprement sans un cours de compilation, t'es très fort.

Bon, donc les chaines de caractère ne sont pas du tout adaptées à ce genre de problématiques. Si tu réfléchis un peu, tu te rends compte que tes données sont organisées en tant qu'arbre. C'est plus flagrand quand tu rajoutes les parenthèses (et en notation préfixée):

Code : Tout sélectionner

+( 7, *( 5, abs( +( 9, 8 ) ) etc… )
En fait, on peut donc éviter de se compliquer la vie et directement adopter la structure ad-hoc. On peut penser le faire par tableau, c'est déjà pas mal, mais un peu faible. Le mieux est tout de même d'y aller en objet.
On a un schéma classique pour représenter de telles données en objet :
interface expression
{
   function __toString();
}

class valeur implements expression
{

   private $val; // Contient la valeur de la donnée

   function __construct($val)
   {
      $this->val = $val;
   }

   function __toString()
   {
      return $this->val;
   }

}

class plus implements expression
{

   private $exp1; // Contient la valeur de la donnée
   private $exp2; // Contient la valeur de la donnée

   function __construct(expression $exp1,expression $exp2)
   {
      $this->exp1 = $exp1;
      $this->exp2 = $exp2;
   }

   function __toString()
   {
      return "(" . $this->exp1 . " + " . $this->exp2 . ")";
   }

}
Là on retourne l'opération sous forme de chaine de caractère pour l'afficher, si tu veux calculer le résultat, c'est aussi simple, il suffit de définir une méthode calcul() dans toutes les classes.

Pour l'utilisation, une telle opération :

Code : Tout sélectionner

(3 * ((4 * 2) + 8))
Serait représentée ainsi :
$operation = new fois(valeur(3),plus(fois(valeur(4),valeur(2)),8));
Tu as alors ton opération représentée dans ta variable $operation. Ensuite, pour la traiter, il suffit d'utiliser la récursivité sur tes objets, comme je l'ai fait pour la méthode __toString().
Si ensuite, tu es obligé de stocker cette varaible dans une chaine de caractères, tu peux utiliser la serialisation avec les fonctions serialize() et unserialize().

Voilà, tu n'as plus qu'à adapter ceci à ton problème… :)

Nours312
Invité n'ayant pas de compte PHPfrance

01 avr. 2009, 11:40

Merci bien les gars, par contre je me suis peut-etre mal exprimé ou je n'ai pas compris les réponses, mais je ne veux pas concaténer les éléments ! ... je veux réaliser les opérations ....

Sachant que la variable initiale est extraite d'une Base de donnée donc ... les signes opérateurs sont des 'string' .. non ?

actuellement j'ai opter pour un truc du style :

Code : Tout sélectionner

function p($a, $b) { return $a + $b; } function m($a, $b) { return $a - $b; } function f($a, $b) { return $a * $b; } function d($a, $b) { return $a / $b; } $var = array( array('p', 5), array('m', 1), array('f', 3), array('d', 2) ); foreach ($var as $v){ $cont = fonc($cont, $v); }
sachant que ma variables est Beaucoup plus complexe qu'ici, et qu'elle tourne dans des boucles de près de 20 000 tours répartis en plusieurs endroits du script

je trouve que c'est un peu long, et me fait perdre beaucoup de performance serveur ....

-> en plaçant les fonction dans l'objet parent, gagnerais-je en rapidité
-> Y a -t-il mieux ?

Merci Beaucoup ;)

Nours312
Invité n'ayant pas de compte PHPfrance

01 avr. 2009, 11:42

Oups :

ce n'est pas fonc () mais {$v[0]}()

:oops: copié/collé raté ;)

Avatar du membre
Modérateur PHPfrance
Modérateur PHPfrance | 10684 Messages

01 avr. 2009, 12:01

eval() :?:

A faire à chaque opération pour gérer les priorités des opérateurs (* et / sur + et -)
Ce n'est pas en améliorant la bougie que l'on a inventé l'ampoule...

ViPHP
ViPHP | 5924 Messages

01 avr. 2009, 13:07

Merci bien les gars, par contre je me suis peut-etre mal exprimé ou je n'ai pas compris les réponses, mais je ne veux pas concaténer les éléments ! ... je veux réaliser les opérations ....
Alors, relis ma réponse. Comme je te disais, là j'ai mis une méthode ( __toString() ) pour afficher l'opération, mais en plaçant des méthodes calcul(), tu peux réaliser les opérations. Exemple pour la classe plus :
function calcul()
{
   return $this->exp1->calcul() + $this->exp2->calcul();
}
Exemple pour la classe valeur :
function calcul()
{
   return $this->valeur;
}
Et pour stocker l'opération complète dans une chaine, tu serialises ton objet, de telle manière que tu puisses le mettre en base de données. Tu trouveras difficilement plus propre.

Nours312
Invité n'ayant pas de compte PHPfrance

01 avr. 2009, 14:23

heuu .. ouais .. donc c'est ce que je craignais ... je n'ai pas compris ... et je ne comprends pas ...

Pourrais tu développer Sékiltoyai s'il te plait ...

j'assaie, mais je ne pige pas le fonctionnement de ces classes ... j'ai jamais travaillé avec la notion d'interface .... :oops:

mais si tu peux Juste un exempleplus ou moins complet, aprés je le développerais pour toutes les opérations possible, mais histoir que je comprenne bien le fonctionnement ...

Merci Beaucoup ;)

@ Ryle >> eval() fonctionne avec des variable mais pas avec des opérateur, en tout cas, d'après les test que j'ai fais ...

Merci ;)

Avatar du membre
Modérateur PHPfrance
Modérateur PHPfrance | 10684 Messages

01 avr. 2009, 15:56

Bah euh.... eval() il évalue une chaine comme si c'était une instruction php... il se fout de savoir si c'est des variables ou des opérations, tant que c'est du php :)

Alors naturellement tout dépend du niveau de compléxité de ton problème (et j'avoue pas avoir eu le courage de lire tout vos échanges), mais pour les opérations basiques indiquées au début, je trouve ça bien suffisant :
$cont = 0;

$var = array(
  array('+', 2),
  array('*', 3)

);

foreach ($var as $v){
	// affiche l'opération en cours
	echo $cont .' '. $v[0] .' '. $v[1] . ' = ';

	// evaluation de l'opération
	eval('$cont = $cont ' . $v[0] . $v[1] . ';');

	// affiche le résultat de l'opération
	echo $cont . '<br />';
}

echo 'Total : ' . $cont . '<br />';
Ce n'est pas en améliorant la bougie que l'on a inventé l'ampoule...

Nours312
Invité n'ayant pas de compte PHPfrance

01 avr. 2009, 16:12

:oops: :oops: :oops:

En effet, je sais plus ce que j'avais fait, mais ça fonctionne bien :oops: Merci Ryle !!!

par contre, c'est lent ... Bien plus que d'appeler es fonction de manière externalisée ...

Test réalisé :

function p($a, $b) {
	return $a + $b;
}
function m($a, $b) {
	return $a - $b;
}
function f($a, $b) {
	return $a * $b;
}
function d($a, $b) {
	return $a / $b;
}
function fonc($cont, $v){
		switch($v[0]) {
			case 'p' :
			$cont = $cont + $v[1];
			break;
			case 'm' :
			$cont = $cont - $v[1];
			break;
			case 'f' :
			$cont = $cont * $v[1];
			break;
			case 'd' :
			$cont = $cont / $v[1];
		}
	return $cont;
}
$var = array(
	array('p', 5),
	array('m', 1),
	array('f', 3),
	array('d', 2)
);

$nb_occur = 100000;

// 1er test
$time_start = microtime(true);
for($i = 0; $i < $nb_occur; $i++) {
$cont = 0;
	foreach ($var as $v){
		$cont = fonc($cont, $v);
	}
	reset($var);
}
$time_end = microtime(true);
$time = $time_end - $time_start;

echo "<p>Durée 1er test : ".$time.' secondes</p>';

// 2ème test
$time_start = microtime(true);
for($i = 0; $i < $nb_occur; $i++) {
$cont = 0;
	foreach ($var as $v){
		$cont = $v[0] ($cont,  $v[1]);
	}
	reset($var);
}
$time_end = microtime(true);
$time = $time_end - $time_start;
echo "<p>Durée 2ème test : ".$time.' secondes</p>';

$var = array(
	array('+', 5),
	array('-', 1),
	array('*', 3),
	array('/', 2)
);
// 3ème test
$time_start = microtime(true);
for($i = 0; $i < $nb_occur; $i++) {
$cont = 0;
	foreach ($var as $v){
		eval('$cont = $cont ' . $v[0] . $v[1] . ';');
	}
	reset($var);
}
$time_end = microtime(true);
$time = $time_end - $time_start;
echo "<p>Durée 3ème test : ".$time.' secondes</p>';

résultat :
Durée 1er test : 0.31564712524414 secondes

Durée 2ème test : 0.23449206352234 secondes

Durée 3ème test : 2.026309967041 secondes

et là, ça pèche drôlement !!

Quelqu'un pourrait-il m'aider à comprendre la solution déployée par Sékiltoyai, afin que je puisse réaliser un teste à ce niveau, et par la même occasion, comprendre cette méthodologie ... ?

Merci Beaucoup @ tous !! :d ;)

ViPHP
ViPHP | 5924 Messages

01 avr. 2009, 19:07

C'est bien que tu sois intéressé par cette solution, je développerais dans quelques heures. :)

ViPHP
ViPHP | 5924 Messages

01 avr. 2009, 23:00

Je vais reprendre pas à pas et expliquer ce que j'ai fait.
Déjà on va expliciter avec un diagramme :
Image

Le bloc Expression est une interface et non une classe. Cela signifie que toute classe qui implémente Expression devra posséder une méthode calcul(). On fixe en fait un contrat de telle manière que l'on soit sûr que cette méthode existera dans toute classe qui se déclare être une Expression :
interface Expression 
{ 
   function calcul(); 
}

Les classes Valeur, Fois, et Plus (on peut en imaginer d'autres) sont des Expression.
La classe Valeur est une Expression assez simple, puisque sa seule donnée est un entier. Quand on va vouloir calculer la valeur de cette Expression, il suffira de retourner cet argument. C'est pourquoi la méthode calcul() est très simple :
class Valeur implements Expression 
{ 

   private $val;

   function __construct(int $val) 
   { 
      $this->val = $val; 
   } 

   function calcul() 
   { 
      return $this->val; 
   } 

}
Les classes Plus et Fois sont quasiment identiques (je ne décris que Plus). En fait, c'est là qu'arrive l'intérêt de la récursivité. Comment traiter les cas de 5 - (3 + 2 + 5) ou 8 * 9 * (3 + 1) ? Il y a des parenthèses à gérer. C'est là qu'on simplifie en revenant à la définition stricte d'une opération :
Une opération Plus (par exemple) prend deux Expression et les additionne. Cette opération étant elle-même une Expression. On applique simplement ce principe, notre opération Plus aura un attribut exp1 et un attribut exp2, tous deux des Expressions, et est elle-même une Expression (donc notre opération Plus va aussi implémenter la classe Expression).
Donc dans le cas où on a 5 + 6 + 8, notre première opération Plus aura pour arguments l'Expression 5 (représenté dans une classe Valeur), et l'Expression 6+8 (représenté dans une classe Plus).

Pour le calcul la récursivité est là aussi géniale. En fait, une fois que tu as ton Expression Plus composée de 5 et de 6+8, le résultat du calcul sera simplement le résultat du calcul de 5 ajouté au résultat du calcul de 6+8. L'objet Plus qui représentera 6+8 se chargera à son tour d'appeler le calcul de 6 et le calcul de 8 pour les additionner, et retourner le résultat à la méthode qui l'a appelé.

Ce qui donne :
class Plus implements Expression 
{ 

   private $exp1;
   private $exp2;

   function __construct(Expression $exp1, Expression $exp2) 
   { 
      $this->exp1 = $exp1; 
      $this->exp2 = $exp2; 
   } 

   function calcul() 
   { 
      return $this->exp1->calcul() + $this->exp2->calcul();
   } 

}
Pour les autres fonctions de calcul c'est exactement pareil. Après je ne dis pas que tu auras un truc forcément plus rapide, mais il faut que tu comprennes que cette approche est la meilleure. La Programmation Orientée Objet te permet de structurer tes programmes et de simplifier des opérations complexes.
Après c'est à toi de voir si tu l'appliques mais il faut absolument que tu comprennes cette solution pour élargir ta vision des choses. Si tu peux comprendre et réutiliser le principe de récursivité, tu feras un très grand pas dans l'informatique.

Voilà, n'hésite pas à poser d'autres questions. :)

Eléphant du PHP | 209 Messages

01 avr. 2009, 23:17

Hyper intéressant, as-tu des liens vers des cours expliquant ces principes ?

Nours312
Invité n'ayant pas de compte PHPfrance

02 avr. 2009, 00:54

ssssssssSSSSSSPLENDIDE !!!!!!!!!!!!!!

mais ceci est une petite merveille ! .....

je n'ai pas encore compris comment l'utiliser concrètement (l'expression à monter pour visualiser le résultat), mais j'ai marqué cette page, et j'ai bien l'intention d'étudier tous ceci de plus près !!

... Mon imaginaire s'emballe !! ... il faut avant tout que je termine mon prog ... Dès que j'ai un peu de temps ou que je doivent intervenir sur des stats, je reprendrais contact sur le sujet !!

Merci beaucoup !!

le problème actuel est que je dois faire une très grande quantité d'opérations avec peu de paramètres ... donc ça n'est pas adapté, mais à l'inverse, se doit avoir un rendement merveilleux !!


Tant que j'écris, ce doit etre très utile pour générer des menus à sous menus variés dont identifiant parent est inclus dans l'élément ...

array(array(id=>1, parent=>null, menu1), array(id=>1, parent=>1, menu1), array(id=>3, parent=>2, menu1), array(id=>4, parent=>2, menu1), ...)

actuellement, je faisait une boucle pour recomposer un tableau avec divers parametres ce qui me donne une suite indigeste de foreach à la conception, et tout autant à la régénération ...

mais là, ça doit m'apporter une grande aide et un dynamisme monstre, ainsi qu'une capacité infinie de sous menus ....


Merci bien !

ViPHP
ViPHP | 5924 Messages

02 avr. 2009, 01:18

Hyper intéressant, as-tu des liens vers des cours expliquant ces principes ?
Euh, je dois t'avouer que non :-/
Peut être ces cours de developpez.com mais à part ça… Concernant cet exemple, c'est une version simplifiée de ce que l'on a pu voir dans nos cours de POO.
ssssssssSSSSSSPLENDIDE !!!!!!!!!!!!!!

mais ceci est une petite merveille ! .....
leaule, merci :D
je n'ai pas encore compris comment l'utiliser concrètement (l'expression à monter pour visualiser le résultat), mais j'ai marqué cette page, et j'ai bien l'intention d'étudier tous ceci de plus près !!
En fait, là aussi c'est assez simple, pour représenter l'expression 5 + 6, tu écris :
$exp = new Plus(Valeur(5),Valeur(6));
Et pour la calculer :
echo $exp->calcul();
J'ai pas testé mais si j'ai pas dit que des conneries, ça devrait donner 11. :D
le problème actuel est que je dois faire une très grande quantité d'opérations avec peu de paramètres ... donc ça n'est pas adapté, mais à l'inverse, se doit avoir un rendement merveilleux !!
Bah ça dépend ce que t'appelle rendement. Si tu veux une vitesse d'éxecution ultra-rapide, je ne peux pas te dire. Mais de toute façon, ce n'est pas forcément ce qui fait la qualité d'un programme, la propreté et la structure du code sont très importantes.
Tant que j'écris, ce doit etre très utile pour générer des menus à sous menus variés dont identifiant parent est inclus dans l'élément ...

array(array(id=>1, parent=>null, menu1), array(id=>1, parent=>1, menu1), array(id=>3, parent=>2, menu1), array(id=>4, parent=>2, menu1), ...)

actuellement, je faisait une boucle pour recomposer un tableau avec divers parametres ce qui me donne une suite indigeste de foreach à la conception, et tout autant à la régénération ...

mais là, ça doit m'apporter une grande aide et un dynamisme monstre, ainsi qu'une capacité infinie de sous menus ....
Tu commences à comprendre la POO. :)

Mais renseigne toi sur la Programmation Orientée Objet, cela te permettra de structurer ton code…
A savoir que les principaux langages utilisés en entreprise sont des langages orientés objet, c'est très important de connaître ces notions.