Implémentation d'un évaluateur d'expressions arithmétiques en Python

Objectifs du projet

L'ambtiion est de développer un analyseur syntaxique capable d'évaluer des expressions mathématiques complexes incluant les opérations fondamentales (addition, soustraction, multiplication, division) tout en respectant la priorité des parenthèses. L'implémentation doit traiter des formules telles que :

1 - 2 * ( (6-3 +(-5/5)*(9-2*3/3 + 7/3*7/4*12 +10 * 5/5 )) - (-4*3)/ (12-3*2) )

Le système doit produire un résultat identique à celui d'une calculatrice scientifique standard.

Approche algorithmique

La stratégie retenue consiste à traiter récursivement les groupes de parenthèses en commençant par les plus imbriqués. Pour chaque groupe identifié, on procède à une évaluation en deux phases : résolution des opérations de multiplication et division, puis traitement des additions et soustractions.

Implémentation

Le code suivant propose une solution encapsulée dans une classe dédiée, offrant une meilleure modularité.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import re

class AnalyseurCalcul:
    """Classe principale pour l'évaluation d'expressions arithmétiques."""
    
    def __init__(self, expression):
        """Initialisation avec nettoyage des espaces."""
        self.expr = expression.replace(' ', '')
    
    def _resoudre_multiplication_division(self, terme):
        """Traite les opérations * et / au sein d'un terme."""
        elements = re.split('([*/])', terme)
        if not elements or len(elements) == 0:
            return 0.0
        
        resultat = float(elements[0])
        for i in range(1, len(elements), 2):
            operateur = elements[i]
            nombre = float(elements[i+1])
            if operateur == '*':
                resultat *= nombre
            else:
                resultat /= nombre
        return resultat
    
    def _resoudre_addition_soustraction(self, sous_expr):
        """Gère les opérations + et - avec gestion des signes initiaux."""
        # Gestion du signe préfixe
        signe_initial = '+'
        if sous_expr.startswith(('+', '-')):
            signe_initial = sous_expr[0]
            sous_expr = sous_expr[1:]
        
        # Décomposition en opérandes et opérateurs
        parties = re.split('([+-])', sous_expr)
        if signe_initial == '-':
            parties[0] = '-' + parties[0]
        
        # Filtrage des éléments vides
        parties = [p for p in parties if p]
        
        # Calcul itératif
        total = float(parties[0])
        for i in range(1, len(parties), 2):
            op = parties[i]
            val = float(parties[i+1])
            total += val if op == '+' else -val
        
        return total
    
    def _evaluer_groupe(self, groupe):
        """Évalue le contenu d'un groupe de parenthèses."""
        contenu = groupe[1:-1]  # Suppression des parenthèses
        
        # Si présence de multiplication/division
        if '*' in contenu or '/' in contenu:
            # Décomposition en termes séparés par + ou -
            termes = re.split('([+-])', contenu)
            resultats_termes = []
            
            i = 0
            while i < len(termes):
                terme = termes[i]
                if terme in '+-':
                    resultats_termes.append(terme)
                    i += 1
                    continue
                
                # Résolution des opérations de priorité
                if '*' in terme or '/' in terme:
                    val = self._resoudre_multiplication_division(terme)
                    resultats_termes.append(str(val))
                else:
                    resultats_termes.append(terme)
                i += 1
            
            # Évaluation finale des additions/soustractions
            expression_simplifiee = ''.join(resultats_termes)
            return self._resoudre_addition_soustraction(expression_simplifiee)
        else:
            # Uniquement additions/soustractions
            return self._resoudre_addition_soustraction(contenu)
    
    def calculer(self):
        """Boucle principale d'évaluation."""
        iteration = 0
        max_iterations = 20
        
        while '(' in self.expr and iteration < max_iterations:
            # Localisation du groupe de parenthèses le plus profond
            match = re.search(r'\([^()]+\)', self.expr)
            if not match:
                break
            
            groupe = match.group()
            resultat = self._evaluer_groupe(groupe)
            
            # Substitution dans l'expression originale
            self.expr = self.expr.replace(groupe, str(resultat), 1)
            
            # Normalisation des signes
            self.expr = self.expr.replace('+-', '-').replace('--', '+')
            
            iteration += 1
            print(f"Étape {iteration}: {self.expr}")
        
        # Traitement final sans parenthèses
        if '*' in self.expr or '/' in self.expr:
            termes = re.split('([+-])', self.expr)
            resultats = []
            
            i = 0
            while i < len(termes):
                element = termes[i]
                if element in '+-':
                    resultats.append(element)
                    i += 1
                    continue
                
                if '*' in element or '/' in element:
                    val = self._resoudre_multiplication_division(element)
                    resultats.append(str(val))
                else:
                    resultats.append(element)
                i += 1
            
            self.expr = ''.join(resultats)
        
        # Évaluation terminale
        return self._resoudre_addition_soustraction(self.expr)

# Programme principal
if __name__ == '__main__':
    formule_complexe = '1 - 2 * ( (6-3 +(-5/5)*(9-2*3/3 + 7/3*7/4*12 +10 * 5/5 )) - (-4*3)/ (12-3*2) )'
    analyseur = AnalyseurCalcul(formule_complexe)
    resultat_final = analyseur.calculer()
    print(f"\nRésultat final: {resultat_final}")

Trace d'exécution

L'évlauation progressive produit les transformations suivantes :

Étape 1: 1-2*((6-3-1.0*(9-2*3/3+7/3*7/4*12+10*1.0))-(-4*3)/(12-3*2))
Étape 2: 1-2*((6-3-1.0*(9-2.0+49.00000000000001+10.0))-(-4*3)/(12-3*2))
Étape 3: 1-2*((6-3-1.0*66.0)-(-4*3)/(12-3*2))
Étape 4: 1-2*((6-3-66.0)-(-4*3)/(12-3*2))
Étape 5: 1-2*(-63.0-(-4*3)/(12-3*2))
Étape 6: 1-2*(-63.0-(-12.0)/(12-3*2))
Étape 7: 1-2*(-63.0+12.0/(12-3*2))
Étape 8: 1-2*(-63.0+12.0/(12-6.0))
Étape 9: 1-2*(-63.0+12.0/6.0)
Étape 10: 1-2*(-63.0+2.0)
Étape 11: 1-2*-61.0
Étape 12: 1+122.0
Résultat final: 123.0

Étiquettes: Python regex expression-parser calculator arithmetic-evaluation

Publié le 10 septembre à 06h34