IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Contribuez Discussion :

[C#] Shunting-Yard & Calcul d'un RPN [Algo. Postfix]


Sujet :

Contribuez

  1. #1
    Nouveau membre du Club
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2010
    Messages
    26
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Oise (Picardie)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Finance

    Informations forums :
    Inscription : Février 2010
    Messages : 26
    Points : 36
    Points
    36
    Par défaut [C#] Shunting-Yard & Calcul d'un RPN [Algo. Postfix]
    Bonjour,

    dans le cadre d'un développement qu'il ma été demandé de réaliser, j'ai eu a développer un outil pour parser une chaine (string) en entrée afin de la calculer.
    Cette chaîne pouvant contenir des variables pré-définis.

    Je me suis donc tourné vers les algorithmes "Shunting-Yard" et celui du calcul d'un nombre au format "RPN".

    A la base cette demande était toute simple, et fonctionne actuellement.
    Quelques jours après je me suis dit : "tient, pourquoi ne pas implémenter l'algo. complet" (prise en compte de nombre decimaux/fonctions multi-paramètres), afin de mettre à disposition sur développez.com cette mini-contribution.

    Edit : je tient également compte des nombres négatifs. (ex: 5*(-6))

    L'algo. est présenté sur Wikipédia, mais ne présente aucune forme de reconnaissance des fonctions ou des nombres décimaux, et d’ailleurs je n'es trouvé aucune implémentation prenant en compte les nombres décimaux. (Il s'agit de l'algo. "brut")

    Donc, ma question est : Comment vous faire profiter de mon implémentation (en C#), si celle-ci en vaux la peine, et comment la présenter en amont afin que quelques volontaire passent un coup d’œil afin de rendre cet implémentation la plus "propre" possible ?

    Merci d'avance

  2. #2
    Nouveau membre du Club
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2010
    Messages
    26
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Oise (Picardie)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Finance

    Informations forums :
    Inscription : Février 2010
    Messages : 26
    Points : 36
    Points
    36
    Par défaut
    Dans ce cas je vais présenter mon implémentation ci-dessous,
    Ma question allais plus loin, j'aurais aimé faire passer cette implémentation comme "source" (dans le site).
    (fournir un SLN complet, pour exemple & présentation expliqué du code source des implémentations en "article"),
    et je ne connais pas les démarches pour cela.

    Si des volontaire se présentent n'hésitez pas à me remonter toutes erreurs/incohérence, ou manques sévère d'optimisation.

    Je prends aussi les questions

    ----------------------------------------------------------------------

    Les 2 Algorithmes implémentés sont issus de leur descriptions wikipédia :
    - Shunting-Yard
    - Calcul RPN

    --------------------------------------------------------------------------------------------------------------------
    Note : j'ai banni tout regex des algorithmes présentés, estimant qu'il "plombaient" beaucoup trop les performances
    --------------------------------------------------------------------------------------------------------------------

    --------------------------------------------------------------------------------------------------------------------
    Intérêt de l'implémentation des 2 algo. présentés :
    --------------------------------------------------------------------------------------------------------------------
    - 0 Regex (vous l'aurez compris^^)
    - Gestion des nombres/variables/fonctions(ex: -ARRONDI(2.23;1)) négatifs [chose que je n'est pas trouvé dans mes recherches google]
    - Gestion des nombres décimaux à l'itération [idem, non trouvé]
    - Gestion de variables intégrées à la chaine de calcul [idem, non trouvé]
    - Gestion de fonctions avec un nombre de paramètres tendant vers +infini [idem, non trouvé]
    - Utilisation d'un "delegate" afin d'améliorer le couplage du calcul des fonctions suivant les besoins
    (possibilté d'appliquer un pattern Strategy aisément, pour le calcul RPN) [idem, non trouvé]

    Vous me direz p-e que je ne sais pas chercher , mais cette implémentation embarque l'intégralité des points ci-dessus.
    --------------------------------------------------------------------------------------------------------------------

    --------------------------------------------------------------------------------------------------------------------
    - Je vous présenterai en premier lieu les 2 implémentations,
    - puis les fonctions de reconaissances annexes et les constantes de classes utilisées,
    - les 2 fonctions principales utilisées étant au sein de la même classe statique.
    - Je laisserais à votre soin le plaisir d'imaginer les classes "UserException" et "TechnicalException"
    - Pour les nombres négatifs la gestion s'opère comme suit :
    9*(-6) => 9*(0 - 6) // -6 => (0 - 6) // -(-(-6)) => 0 - ( 0 - ( 0 - 6 ) )
    Un zéro est simplement ajouté. (Le fonctionnement est le même pour les fonctions et les variables pré-définies)
    ------------------------------------------------------------------------
    --------------------------- [Shunting-Yard] ----------------------------
    ------------------------------------------------------------------------

    Sinature de la méthode :
    Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
    internal static Queue<string> ShuntingYard(string chaineInput, string prefix_parametre)
    La chaine de calcul en entrée est nécessaire,
    ainsi que la valeur indiquant comment seront préfixé les compteurs de paramètres lors de la "tokenization" des fonctions

    Variables utilisées :
    Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
     
    //Deux listes indispensables : Queue & Stack, sont utilisés pour l'algorithme "Shunting-yard"
    	//permettant le stockage des nombres, variables et opérateurs
    	Queue<string> listOutput = new Queue<string>(); //Utilisé afin de stocker la chaine RPN
    	Stack<string> listStack = new Stack<string>(); //Utilisé afin de stocker les opérateurs le temps de l'algorithme
     
    	//Utilisé afin de stocker les valeur de parametrage en cas de fonction imbriqués, permet une gestion de la profondeur
    	Stack<int> listStack_parametrage = new Stack<int>();
     
    	//Variable permettant de déterminer si l'on doit continuer de parcourir l'élément stack ou non lorsque l'on rencontre un opérateur de calcul
    	bool parcourStack = false;
     
    	//Utiliser pour detecter la précédence d'un nombre (Gestion des nombres négatifs)
    	bool precNum = false;
     
    	//Utilisé pour la constitution des token "variable prédéfini"
    	string chaineLectureVariable = string.Empty;
     
    	//Utilisé pour la constitution des token "nombre double"
    	string chaineLecture = string.Empty;

    Boucle de lecture :
    - "Grandes Etapes"
    Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
     
    //Retrait de tout espace (Il est inutile de traiter les espaces)
    //Il faut remplacer le point par une virgule, sinon cela pose des problème de Parse 
    //et de détection des nombres (on peut aussi en interdire la saisie au préalable)
    chaineInput = chaineInput.Replace(" ", "").Replace(".", ",").ToUpper();
     
    //Boucle de parcours de la chaine passée en entrée, chaques éléments de la chaine ne sera lu, qu'une, et qu'une seule fois,
    //il n'y a donc qu' N itérations
    foreach (char caractere in chaineInput)
    {
    	/********************************************************************************
    	 ********** ETAPE 1 : Creation des tokens de nombres / variables / fonctions ****
    	 ********************************************************************************/
    	//Permet de déterminer s'il s'agit d'un nombre 
    	if (string.IsNullOrEmpty(chaineLectureVariable) && EstNumeric(caractere))
    	{
    		//On enrichi la chaine tant qu'on trouve un morceau de nombre (permet la constitution de nombre double par incrémentation de lecture)
    		chaineLecture += caractere;
    		//On passe au caractère suivant, puisque le caractère courant a été traité
    		continue;
    	}
     
    	//S'il ne s'agit pas d'un nombre,  ni d'un operateur, ni d'une parenthèse, ni d'un séparateur de fonction, 
    	//il s'agit donc d'une variable prédéfinie OU d'une fonction
    	//De la même maniere que la construction des nombres doubles, on construit la variable sur plusieurs itérations
    	if (string.IsNullOrEmpty(chaineLecture) && !EstOperateur(caractere) && !caractere.Equals('(')
    		&& !caractere.Equals(')') && !caractere.Equals(';'))
    	{
    		//Construction de la variable
    		chaineLectureVariable += caractere;
    		//On passe au caractère suivant, puisque le caractère courant a été traité
    		continue;
    	}
     
    	//Si chaineLectureVariable on est deja dans la constitution d'une variable
    	//Si on détecte une parenthèse ouvrante après le nom de variable c'est qu'il s'agit d'une fonction
    	if (!string.IsNullOrEmpty(chaineLectureVariable) && caractere.Equals('('))
    	{
    		//Detection d'une fonction
    		//On ajoute la fonction au stack comme un opérateur
    		listStack.Push(chaineLectureVariable);
    		chaineLectureVariable = string.Empty;
     
    		//Ajout de la parenthèse et passage à la lecture suivante
    		listStack.Push(caractere.ToString());
    		listStack_parametrage.Push(1);
    		continue;
    	}
     
    	//Si un token de nombre à précédement été constitué on l'ajoute à la queue
    	if (!string.IsNullOrEmpty(chaineLecture) && EstNumeric(chaineLecture) && !chaineLecture[chaineLecture.Length - 1].Equals(','))
    	{
    		listOutput.Enqueue(chaineLecture);
    		//Si un nombre a été détecté, il y a précédence par un nombre, donc opération de calcul sur le symbole '-'
    		precNum = true;
    		chaineLecture = string.Empty;
    	}
    	else
    	{
    		//Erreur, un nombre a été mal renseigné (ex nombre decimal incomplet => 3. ou 3.h (avec un caractère))
    		if (!string.IsNullOrEmpty(chaineLecture))
    		{
    			throw new UserException("L'un des nombres renseignés est incorrect.");
    		}
    	}
     
    	//Si un token de variable prédéfinie à précédement été constitué on l'ajoute à la queue
    	if (!string.IsNullOrEmpty(chaineLectureVariable))
    	{
    		listOutput.Enqueue(chaineLectureVariable);
    		//Si une variable prédéfini a été détecté, il y a précédence par un nombre, donc opération de calcul sur le symbole '-'
    		precNum = true;
    		chaineLectureVariable = string.Empty;
    	}
     
    	//Les point-virgules agissent comme séparateur de nombres (pour les fonctions),
    	//permettent  de compter les parametres présents dans la fonction et
    	//agissent comme des parenthèses droite (sans retrait de parenthèse gauche)
    	if (caractere.Equals(';'))
    	{
    		//L'ensemble des opérateurs doivent être dé-stacker avant le passage au parametres suivant
    		while (listStack.Count > 0 && !listStack.Peek().Equals("("))
    		{
    			listOutput.Enqueue(listStack.Pop().ToString());
    		}
     
    		//Il reste forcement la parenthèse gauche pour identifier le début de fonction, sinon => erreur de parenthèsage
    		if (listStack.Count.Equals(0))
    			throw new UserException("Problème de parenthèsage");
     
    		//Incrémentation du nombre de parametres à chaques identification du symbole ';'
    		listStack_parametrage.Push(listStack_parametrage.Pop() + 1);
     
    		//Lorsque l'on dépile on retombe sur une parenthèse, il n'y a donc pas    précédence par un nombre
    		precNum = false;
     
    		continue;
    	}
     
    	/***********************************************************************************************************
    	 ****** ETAPE 2 : Creation des tokens operateurs_arithmetiques et de la gestion des nombres negatifs *******
    	 ***********************************************************************************************************/
     
    	//s'il s'agit d'un opérateur on ajoute au stack
    	if (EstOperateur(caractere))
    	{
    		if (caractere.Equals('-') && (listOutput.Count.Equals(0) || (listStack.Count > 0 && listStack.Peek().Equals("(") && !precNum)))
    		{
    			//Gestion des nombres/variables negatifs :
    			//Si le nombre est écrit au debut de la chaine (ex : -5 + 6) ou suite à une parenthèse (ex : -5*(-6))
    			//Dans ce cas on ajoute un zero avant d'ajouter l'operateur (ex: 0 - 5 + 6 // 0-5*(0-6))
    			listOutput.Enqueue("0");
    			listStack.Push(caractere.ToString());
    			continue;
    		}
    		else
    		{
    			parcourStack = true;
    			//Verification de la précédence afin de déterminer si on ajoute l'operateur à la queue ou au stack
    			while (parcourStack)
    			{
    				if (listStack.Count > 0 && Precedence(caractere, listStack.Peek()[0]))
    				{
    					//si la priorite : caractere <= listack.peek()
    					listOutput.Enqueue(listStack.Pop().ToString());
    				}
    				else
    				{
    					//s'il n'y a pas de précédence caractere > listack.peek OU caractere est autre chose qu'un opérateur
    					listStack.Push(caractere.ToString()); //On ajoute l'opérateur à la liste STack
    					parcourStack = false; //Arrêt de la boucle de lecture
    				}
    			}
    		}
    		//Si un opérateur a été détecté, il n'y a pas précédence par un nombre
    		precNum = false;
     
    		//On passe au record suivant
    		continue;
    	}
     
    	/**************************************************************************************
    	 ****** ETAPE 3 : Creation des tokens de parenthèse ET de gestion des fonctions *******
    	 **************************************************************************************/
     
    	//Dès que l'on  rencontre une parenthèse gauche on la stack, elle servira au dépilement (plus tard dans l'algo)
    	if (caractere.Equals('('))
    	{
    		listStack.Push(caractere.ToString());
     
    		//Si une parenthèse a été détecté, il n'y a pas précédence par un nombre
    		precNum = false;
     
    		//On passe au record suivant
    		continue;
    	}
     
    	//Lorsque l'on trouve une parenthèse fermante on traite l'ensemble du contenu des parenthèse
    	if (caractere.Equals(')'))
    	{
    		//Tant que l'on ne trouve pas de parenthèse gauche on depile les operateurs_arithmetiques dans la file
    		while (listStack.Count > 0 && !listStack.Peek().Equals("("))
    		{
    			listOutput.Enqueue(listStack.Pop().ToString());
    		}
     
    		//En sortie de boucle on controle le premier element de la pile
    		if (listStack.Count > 0 && listStack.Peek().Equals("("))
    		{
    			listStack.Pop(); //On retire la parenthèse ouvrante de la pile, car elle a été traitée
    			//On recompte le nombre d'éléments du stack pour s'assurer qu'il ne s'agisse pas d'une parenthèse non attaché à une fonction
    			if (listStack.Count > 0 && EstFonction(listStack.Peek()[0]))
    			{
    				//Ajout nombre parametrage
    				listOutput.Enqueue(prefix_parametre + listStack_parametrage.Pop().ToString());
     
    				//Une fois les parametres ajoutés, on peut ajouter la fonction qui utilisera le parametrage
    				listOutput.Enqueue(listStack.Pop());
     
    				precNum = true;
    			}
    			continue;
    		}
    		else
    		{
    			throw new UserException("Problème de parenthèsage");
    		}
    	}
    	//La boucle est pre-interrompue après tout traitement, cette ligne ne doit jamais s'executer sinon => erreur
    	//ex : probleme de nombre decimaux incomplets
    	throw new TechnicalException("Une erreur est survenue, problème technique, algorithme mal implémenté.");
    }


    Données restantes à traiter après la boucle de lecture :
    Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
     
    //TRAITEMENT DE LA DERNIERE CHAINE LUE
    //En sortie de boucle il ne peut rester qu'un nombre(ou variable prédéfinie)
    //Si un token de nombre à précédement été constitué on l'ajoute à la queue
    if (!string.IsNullOrEmpty(chaineLecture) && EstNumeric(chaineLecture) && !chaineLecture[chaineLecture.Length - 1].Equals(','))
    {
    	listOutput.Enqueue(chaineLecture); //Ajout à la queue du nombre constitué
    }
    else
    {
    	//Erreur, un nombre a été mal renseigné (ex nombre decimal incomplet => 3. ou 3.h (avec un caractère))
    	if (!string.IsNullOrEmpty(chaineLecture))
    	{
    		throw new UserException("L'un des nombres renseignés est incorrect.");
    	}
    }
     
    //Si un token de variable à précédement été constitué on l'ajoute à la queue
    if (!string.IsNullOrEmpty(chaineLectureVariable))
    {
    	listOutput.Enqueue(chaineLectureVariable); //Ajout à la queue de la variable identifié
    }
     
    /**************************************************************************************
     ***** ETAPE 4 : vidage de la pile on ajoute l'ensemble des opérateurs à la file ******
     **************************************************************************************/
    int taille = listStack.Count;
     
    //Ajout de la pile a la file
    for (int index = 0; index < taille; index++)
    {
    	if (listStack.Peek().Equals("("))
    	{
    		throw new UserException("Problème de parenthèsage");
    	}
     
    	listOutput.Enqueue(listStack.Pop().ToString());
    }
    Le dernier petit bout de code pour le Shunting-Yard :

    Ci-dessous une externalisation de code, servant à déterminer les précédences :
    -Info importante : Une fonction est une sorte d'opérateur 'hybride' et aura donc toujours une précédence maximale sur un opérateur arithmétique
    Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
     
    private static bool Precedence(char operateur1, char operateur2)
    {
    	//La fonction "Precedence" ne doit être appellé que pour comparer le stack avec un opérateur
    	//Cette fonction sert également, uniquement à externalier du code, pour la lisibilité
    	int valPrec1, valPrec2;
     
    	//Une fonction à toujours la priorité max, (puisque parenthèsée)
    	if (EstFonction(operateur2)) 
    		return true;
     
    	if (EstFonction(operateur1)) 
    		return false;
     
    	//S'il ne s'agit pas d'un opérateur référencé (ex : parenthèse) il n'y a pas de précédence
    	if (operateurs_arithmetiques.TryGetValue(operateur1, out valPrec1) && operateurs_arithmetiques.TryGetValue(operateur2, out  valPrec2))
    	{
    		return valPrec1 <= valPrec2;
    	}
    	else
    	{
    		//Il n'y a précédence que si l'operateur1 possède une priorité plus faible ou égal que l'opérateur2
    		return false;
    	}
    }

    ------------------------------------------------------------------------
    ----------------------------- [Calcul RPN] -----------------------------
    ------------------------------------------------------------------------

    Sinature de la méthode :
    Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    internal static string Calcul_RPN(Queue<string> expression, Dictionary<string, int> dico_fonctions,
     dlg_calcul_fonction fonction_de_calcul, string prefix_parametre)
    - L'élément "Queue<string> expression" reprend l'expression tokenizé, soit provenant du Shunting-Yard,
    soit d'autre chose, du moment qu'il s'agit d'un format RPN
    - L'élément "Dictionary<string, int> dico_fonctions" permet de transmettre le dictionnaire de fonction pour la fonction
    "fonction_de_calcul" reçu, et permet également de dicerner les fonctions présentes dans l'expression RPN.
    - L'élément "prefix_parametre" sera transmis à la fonction fonction_de_calcul et sert d'identificateur de paramètre
    au même titre que pour l'algorithme Shunting-Yard (le même prefix doit être transmis au deux fonctions, sinon
    cela provoquera une erreur lors de la lecture des variables de prametre)

    Variables utilisées :
    Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    	string tokenLecture; //Token en lecture
    	Stack<string> stackCalcul = new Stack<string>(); //Stack utilisé pour le calcul
    	double valeur1, valeur2;//Utilisés pour les calcul_arithmétique (uniquement)

    Boucle de calcul :
    Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
     
     //tant qu'il reste des éléments dans la file on les traites
    while (expression.Count > 0)
    {
    	tokenLecture = expression.Dequeue();
     
    	//S'il s'agit d'un nombre on l'ajoute au stack
    	if (EstNumeric(tokenLecture))
    	{
    		stackCalcul.Push(tokenLecture);
    		continue;
    	}
     
    	if (EstOperateur(tokenLecture))
    	{
    		//Chaques opérateurs prend en compte 2 valeurs
    		if (stackCalcul.Count > 1)
    		{
    			//Les  pop() sont exécutés dans l'ordre d'apparition
    			//On recupere la derniere valeur comme valeur2(ordre d'apparition dans la file et non dans la pile(stack))
    			if (double.TryParse(stackCalcul.Pop(), out valeur2) && double.TryParse(stackCalcul.Pop(), out valeur1))
    			{
    				stackCalcul.Push(Calcul_Arithmetiques(valeur1, valeur2, tokenLecture));
    				continue;
    			}
    			else
    			{
    				throw new UserException("Opérateurs trop nombreux ou nombres insuffisants, vérifiez la formule.");
    			}
    		}
    		else
    		{
    			throw new UserException("Opérateurs trop nombreux ou nombres insuffisants, vérifiez la formule. \n N'oulier pas les parenthèse pour les nombres negatifs (ex: -5*(12^(-2))");
    		}
    	}
     
    	//S'il s'agit d'une variable d'identificaiton de parametrage
    	if (tokenLecture.StartsWith(prefix_parametre) && EstEntier(tokenLecture.Substring(prefix_parametre.Length)))
    	{
    		stackCalcul.Push(tokenLecture);
    		continue;
    	}
     
    	//S'il ne s'agit ni d'un nombre, ni d'un opérateur, il s'agit d'une fonction (ou d'une erreur)
    	//Une fonction peux prendre entre 1 et X parametres
    	if (dico_fonctions.ContainsKey(tokenLecture))
    	{
    		stackCalcul.Push(fonction_de_calcul(tokenLecture, ref stackCalcul, dico_fonctions, prefix_parametre));
    	}
    	else
    	{
    		throw new TechnicalException("Au moins l'une des fonctions utilisées est inconnu");
    	}
    }
     
    //S'il ne reste qu'une valeur dans la pile c'est qu'il s'agit du resultat, sinon il y a eu une erreur
    //Trop de valeurs dans l'expression
    if (stackCalcul.Count.Equals(1))
    {
    	return stackCalcul.Pop();
    }
    else
    {
    	throw new UserException("L'expression mathématique est mal renseignée");
    }

    ------------------------------------------------------------------------
    ----- Fonctions de Reconnaissances/Calcul_ Arithmétique & Fonction -----
    ------------------------------------------------------------------------
    Fonctions d'identifications :
    Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
     
    #region Fonctions d'identifications "Primaires"
    /// <summary>
    /// Indique si un nombre est numerique
    /// </summary>
    /// <param name="s">Chaine en entrée</param>
    /// <returns>Vrai si la chaine est un numérique avec un séparateur ',' </returns>
    internal static bool EstNumeric(string s)
    {
    	bool trouveSeparateur = false;
    	foreach (char c in s)
    	{
    		if (!char.IsDigit(c) && !c.Equals(','))
    		{
    			return false;
    		}
    		else
    		{
    			if (c.Equals(','))
    			{
    				if (s.Length < 2)
    					return false;
     
    				if (trouveSeparateur)
    					return false;
    				else
    					trouveSeparateur = true;
    			}
    		}
    	}
    	return true;
    }
    /// <summary>
    /// Indique si un caractere fait parti des caractère acceptés pour construire un numerique
    /// </summary>
    /// <param name="s">Caractère en entrée</param>
    /// <returns>Vrai si le caractère est un numérique ou un séparateur ',' </returns>
    internal static bool EstNumeric(char c)
    {
    	if (!char.IsDigit(c) && !c.Equals(','))
    	{
    		return false;
    	}
    	return true;
    }
    /// <summary>
    /// Indique si un nombre est un entier
    /// </summary>
    /// <param name="s">Chaine en entrée</param>
    /// <returns>Vrai si le caractère est un entier</returns>
    internal static bool EstEntier(string s)
    {
    	foreach (char c in s)
    	{
    		if (!char.IsDigit(c))
    		{
    			return false;
    		}
    	}
    	return true;
    }
    /// <summary>
    /// Permet de déterminer si le caractère correspond à un opérateur
    /// </summary>
    /// <param name="chaine">Caractère à tester</param>
    /// <returns>Vrai s'il s'agit d'un opérateur référencé, Faux sinon</returns>
    private static bool EstOperateur(char c)
    {
    	return operateurs_arithmetiques.ContainsKey(c);
    }
    /// <summary>
    /// Permet de déterminer si la chaine correspond à un opérateur
    /// </summary>
    /// <param name="chaine">Chaine à tester</param>
    /// <returns>Vrai s'il s'agit d'un opérateur référencé, Faux sinon</returns>
    private static bool EstOperateur(string chaine)
    {
    	return chaine.Length > 1 ? false : operateurs_arithmetiques.ContainsKey(chaine[0]);
    }
    /// <summary>
    /// Permet de déterminer si la chaine correspond à une fonction UNIQUEMENT SI LA CHAINE VIENT DU STACK
    /// Cette FCT Factorise simplement du code, rien de plus
    /// </summary>
    /// <param name="chaine">Chaine en entrée à identifier</param>
    /// <returns>Vrai s'il s'agit d'une fonction, Faux sinon</returns>
    /// <remarks>Cette fonction permet uniquement de factoriser du code</remarks>
    private static bool EstFonction(char caractere)
    {
    	//return fonctions.ContainsKey(chaine);
    	//Seul le premeir caractère compte (si + d'1 caractère, c'est obligatoirement une fonction)
    	//Une fonction peut très bien être nommé par un seul caractère (ex : F(5;6) )
    	return (!EstOperateur(caractere) && !caractere.Equals('(') && !caractere.Equals(')') && !caractere.Equals(';'));
    }
    #endregion

    Fonction "Calcul_Arithmétique" :
    Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
     
    private static string Calcul_Arithmetiques(double valeurG, double valeurD, string operateur)
    {
    	switch (operateur)
    	{
    		case "+":
    			return (valeurG + valeurD).ToString();
    		case "-":
    			return (valeurG - valeurD).ToString();
    		case "*":
    			return (valeurG * valeurD).ToString();
    		case "/":
    			if (valeurD.Equals(0))
    				throw new UserException(string.Format("Division par 0 impossible"));
    			return (valeurG / valeurD).ToString();
    		case "^":
    			return Math.Pow(valeurG, valeurD).ToString();
    		default:
    			throw new TechnicalException("Une erreur est survenue, problème technique, algorithme mal implémenté.");
    	}
    }

    Fonction "Calcul_Fonction" :
    Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
     
    /// <summary>
    /// Methode permettant de retourner le résultat d'une fonction et d'appliquer les changements au stacks
    /// induits par le calcul des-dits fonctions
    /// </summary>
    /// <param name="nomFonction">Nom de la fonction à calculer</param>
    /// <param name="lstStack">Stack utilisé afin de lire/extraire les nombres à calculer</param>
    /// <returns>chaine string représentant le résultat du calcul de la fonction</returns>
    private static string Calcul_Fonctions(string nomFonction, ref Stack<string> lstStack, Dictionary<string, int> dico_fonction, string prefix_parametre)
    {
    	//On ne controle pas que la valeur existe dans le dictionnaire....
    	//vous etes sensé le renseigner et tester votre application ...
     
    	//Variable de controle pour le nombre de parametres présents pour les fonctions
    	int nb_parametres_attendus = dico_fonction[nomFonction];
     
    	//Le parametre est créé par programmation, celui-ci est forcement un entier
    	//Le parse fonctionne dans 100% des cas, sauf erreur de programmation...
    	int nb_parametres = int.Parse(lstStack.Pop().Replace(prefix_parametre, ""));
     
    	//Un parametre attendu égal à -1 signifie un nombre de parametres indeterminé (+infini)
    	if (!nb_parametres_attendus.Equals(-1) && !nb_parametres_attendus.Equals(nb_parametres))
    	{
    		if (nb_parametres_attendus > nb_parametres)
    			throw new UserException(string.Format(
    				"Le nombre de paramètres passés à la fonction {0} est trop petit.\n {1} parametres attendu(s).", nomFonction, nb_parametres_attendus));
    		else
    			throw new UserException(string.Format(
    				"Le nombre de paramètres passés à la fonction {0} est trop grand.\n {1} parametres attendu(s).", nomFonction, nb_parametres_attendus));
    	}
     
    	try
    	{
    		switch (nomFonction)
    		{
    			//Exemple de fonction à 2 parametres
    			case "ARRONDI":
    				int nbApresVirgule;
    				if (!int.TryParse(lstStack.Pop(), out nbApresVirgule))
    				{
    					throw new UserException("Le paramètre indiquant nombre de décimal après la virgule à retourner \n de la fonction \"ARRONDI\" doit être un nombre entier. \n ex: ARRONDI(2.58965;2)");
    				}
    				return Math.Round(double.Parse(lstStack.Pop()), nbApresVirgule).ToString();
    			case "RACINE_Y":
    				int nb_racine;
    				if (!int.TryParse(lstStack.Pop(), out nb_racine))
    				{
    					throw new UserException("Le paramètre indiquant nombre racine à appliquer \n pour la fonction \"RACINE_Y\" doit être un nombre entier. \n ex: RACINE_Y(2.58965;4)");
    				}
    				return Math.Pow(double.Parse(lstStack.Pop()), 1.0 / nb_racine).ToString();
    			//Exemple d'une fonction à 1 parametre
    			case "RACINE_CARRE":
    				return Math.Sqrt(double.Parse(lstStack.Pop())).ToString();
    			//Exemple d'une fonction multi-parametre
    			case "ECART_TYPE":
    				//La fonction ecart type par du principe que les valeurs représentent la population total à calculer
    				#region Calcul de l'écart-type
    				if (nb_parametres.Equals(0))
    					throw new UserException("Au moins un parametre est nécessaire pour utiliser la fonction ECART_TYPE");
    				//Calcul de la moyenne
    				double somme = 0;
    				double[] valeurs = new double[nb_parametres];
     
    				for (int index = 0; index < nb_parametres; index++)
    				{
    					valeurs[index] = double.Parse(lstStack.Pop());
    					somme += valeurs[index];
    				}
    				double moyenne = somme / nb_parametres;
     
    				//Calcul de la variance
    				somme = 0; //Re-utilisation de la somme pour calculer la variance
    				double delta;
    				for (int index = 0; index < nb_parametres; index++)
    				{
    					delta = (valeurs[index] - moyenne);
    					somme += (delta * delta);
    				}
     
    				//Calcul de l'écart-type
    				return Math.Sqrt(somme / nb_parametres).ToString();
    				#endregion
    			default:
    				throw new TechnicalException("Une erreur est survenue, problème technique, algorithme mal implémenté.");
    		}
    	}
    	catch (FormatException)
    	{
    		throw new UserException("L'un des nombres entré ne possède pas le bon format");
    	}
    	catch (OverflowException)
    	{
    		throw new UserException("L'un des nombres calculé est trop grand");
    	}
    }

    ------------------------------------------------------------------------
    ------------------ Classe statique englobante --------------------------
    ------------------------------------------------------------------------
    L'ensemble des fonctions utilisé et variables utilisés (hormis concernant les calcul de fonctions)
    Sont présents au sein d'une classe que j'ai nommé EvaluateurMathematique.cs
    Comprenant : un delegate, un dictionnaire des opérateurs arithmétiques, une fonction "main" permettant
    d'éxecuter à la suite shunting-yard // renseignement des variables prédéfinies // calcul_rpn

    Delegate pour les calcul_fonctions :
    Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    	//Toute fonction passé doit être en mesure de traiter un stack, ainsi qu'un nom de fonction, un dictionnaire de fonctions et un prefix pour les parametres
    	public delegate string dlg_calcul_fonction(string nomFonction, ref Stack<string> lstStack, Dictionary<string, int> dico_fonctions, string prefix_parametre);

    Dictionnaire des opérateurs arithmétiques :
    Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    	//Le dictionnaire référence des opérateurs arithmétique
    	private static readonly Dictionary<char, int> operateurs_arithmetiques = new Dictionary<char, int> //Liste des opérateurs de calcul
    	{ { '+', 2 }, { '-', 2 }, { '*', 3 }, { '/', 3 }, { '^', 4 } };

    Methode main_calcul :
    Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
     
    public static string main_calcul(string chaine_a_calculer, Dictionary<string, double> dico_variables, 
    	Dictionary<string, int> dico_fonctions, dlg_calcul_fonction fonction_de_calcul, string prefix_parametre)
    	{
    		Dictionary<string, double> dico_variables_safe = new Dictionary<string, double>(dico_variables);
    		Dictionary<string, int> dico_fonctions_safe = new Dictionary<string, int>(dico_fonctions);
     
    		//"Tokenization" de la chaine
    		Queue<string> formuleTokenize = EvaluateurMathematique.ShuntingYard(chaine_a_calculer, prefix_parametre);
     
    		int tailleToken = formuleTokenize.Count;
    		string temp;
    		double val;
     
    		//Remplacement des variables prédéfinies, par leur valeur
    		for (int j = 0; j < tailleToken; j++)
    		{
    			temp = formuleTokenize.Dequeue();
     
    			//S'il s'agit d'une variable on la rempalce par sa valeur
    			if (dico_variables_safe.ContainsKey(temp))
    			{
    				dico_variables_safe.TryGetValue(temp, out val);
    				formuleTokenize.Enqueue(val.ToString());
    			}
    			else
    			{
    				//Sinon on re-met la valeur lue dans la file
    				formuleTokenize.Enqueue(temp);
    			}
    		}
     
    		//Toute variable possèdant le même nom qu'une fonction engendrera une erreur de calcul.
    		//Appel au calculateur RPN avec notre fonction de calcul choisie et le dictionnaire des fonctions associés
    		return EvaluateurMathematique.Calcul_RPN(formuleTokenize, dico_fonctions_safe, fonction_de_calcul, prefix_parametre);
    	}

    Utilisation de main_calcul :
    Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    	lbl_affiche_resultat.Text =
    	EvaluateurMathematique.main_calcul(tbx_entree.Text, dico_variables, fonctions, Calcul_Fonctions, "nombre_param_");

    Dictionnaires de données fournis :
    Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    Dictionary<string, double> dico_variables = new Dictionary<string, double> //Liste des opérateurs de calcul
    	{ { "MT1", 2.5896 }, { "CONST_TOTO", 2 }, { "CONST_2", 8.25 }, { "m5test", 10 } };
     
    	//Le nombre de parametre indiqué présente le parametrage en plus de la valeur cible (ex : ARRONDI(1.568,2), contient un parametre => le chiffre 2)
    	Dictionary<string, int> fonctions = new Dictionary<string, int> //Liste des fonctions de calcul avec le parametrage attendu
    	{ { "ARRONDI", 2 }, { "RACINE_Y", 2 }, { "RACINE_CARRE", 1 }, { "ECART_TYPE", -1 } };
    ------------------------------------------------------------------------
    ------------------------ Pour aller plus loin -----------------------------
    ------------------------------------------------------------------------
    1 - On peut aisément imaginer créé une classe Info_Fonction_Calcul servant de descripteur
    Afin de transmettre le dictionnaire de fonction, la méthode (Calcul_Fonction), et
    le préfixe utilisé dans une seule variable de description.

    2 - On pourrait renseigner les variables pré-définies lors de leur insertion dans la Queue<string> du Shunting-Yard

    3 - On pourrait rendre la fonction Calcul_Arithmétique aussi "indépendante" que Calcul_Fonction
    permettant ainsi des restrictions sur les opérateurs disponibles et utilisables

    4 - Votre imagination n'a pas de limites

    Il s'agit là de mon premier "vrai" post sur le forum, s'il vous semble manquer des informations n'hésitez pas à me le signaler.

    Merci d'avance.

    Edit: une petite coquille c'était glissée, elle a été retirée.

Discussions similaires

  1. Calcul matriciel dans un algo
    Par thtghgh dans le forum MATLAB
    Réponses: 8
    Dernier message: 21/12/2009, 15h09
  2. [TP7] Calculer sin, cos, tan, sqrt via le FPU
    Par zdra dans le forum Assembleur
    Réponses: 8
    Dernier message: 25/11/2002, 04h09
  3. Calcul des numéros de semaine d'un calendrier
    Par Invité dans le forum Algorithmes et structures de données
    Réponses: 4
    Dernier message: 06/11/2002, 21h29
  4. Récupérer 10 nb différents avec un calcul aléatoire
    Par BXDSPORT dans le forum Langage
    Réponses: 3
    Dernier message: 04/08/2002, 02h35
  5. Algo de calcul de FFT
    Par djlex03 dans le forum Traitement du signal
    Réponses: 15
    Dernier message: 02/08/2002, 17h45

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo