J'ai créé un programme permettant de convertir infix en postfix en python. Le problème est quand je présente les arguments. Si j'introduis quelque chose comme ceci: (ce sera une chaîne)
( ( 73 + ( ( 34 - 72 ) / ( 33 - 3 ) ) ) + ( 56 + ( 95 - 28 ) ) )
il sera divisé avec .split () et le programme fonctionnera correctement. Mais je veux que l'utilisateur puisse introduire quelque chose comme ceci:
((73 + ( (34- 72 ) / ( 33 -3) )) + (56 +(95 - 28) ) )
Comme vous pouvez le constater, je souhaite que les espaces vides puissent être triviaux, mais le programme continue de scinder la chaîne en parenthèses, en nombres entiers (et non en chiffres) et en opérandes.
J'essaie de le résoudre avec un for
mais je ne sais pas saisir le nombre entier (73, 34, 72), mais un chiffre à la fois (7, 3, 3, 4, 7, 2)
En résumé, ce que je veux, c'est scinder une chaîne telle que ((81 * 6) /42+ (3-1))
en:
[(, (, 81, *, 6, ), /, 42, +, (, 3, -, 1, ), )]
ast
Vous pouvez utiliser ast
pour obtenir un arbre de l'expression:
import ast
source = '((81 * 6) /42+ (3-1))'
node = ast.parse(source)
def show_children(node, level=0):
if isinstance(node, ast.Num):
print(' ' * level + str(node.n))
else:
print(' ' * level + str(node))
for child in ast.iter_child_nodes(node):
show_children(child, level+1)
show_children(node)
Il produit:
<_ast.Module object at 0x7f56abbc5490>
<_ast.Expr object at 0x7f56abbc5350>
<_ast.BinOp object at 0x7f56abbc5450>
<_ast.BinOp object at 0x7f56abbc5390>
<_ast.BinOp object at 0x7f56abb57cd0>
81
<_ast.Mult object at 0x7f56abbd0dd0>
6
<_ast.Div object at 0x7f56abbd0e50>
42
<_ast.Add object at 0x7f56abbd0cd0>
<_ast.BinOp object at 0x7f56abb57dd0>
3
<_ast.Sub object at 0x7f56abbd0d50>
1
Comme @ user2357112 l'a écrit dans les commentaires: ast.parse
interprète la syntaxe Python et non les expressions mathématiques. (1+2)(3+4)
serait analysé comme un appel de fonction et les interprétations de liste seraient acceptées même si elles ne devraient probablement pas être considérées comme une expression mathématique valide.
Si vous voulez une structure plate, une regex pourrait fonctionner:
import re
number_or_symbol = re.compile('(\d+|[^ 0-9])')
print(re.findall(number_or_symbol, source))
# ['(', '(', '81', '*', '6', ')', '/', '42', '+', '(', '3', '-', '1', ')', ')']
Il cherche soit:
Une fois que vous avez une liste d'éléments, vous pouvez vérifier si la syntaxe est correcte, par exemple avec un stack
pour vérifier si les parenthèses correspondent ou si chaque élément est connu.
Vous devez implémenter un tokenizer très simple pour votre entrée. Vous avez les types de jetons suivants:
Vous pouvez les trouver dans votre chaîne d'entrée séparée par toutes sortes d'espaces.
Une première étape consiste donc à traiter la chaîne du début à la fin, à extraire ces jetons, puis à analyser les jetons, plutôt que la chaîne elle-même.
Une façon astucieuse de procéder consiste à utiliser l'expression régulière suivante: '\s*([()+*/-]|\d+)'
. Vous pouvez alors:
import re
the_input='(3+(2*5))'
tokens = []
tokenizer = re.compile(r'\s*([()+*/-]|\d+)')
current_pos = 0
while current_pos < len(the_input):
match = tokenizer.match(the_input, current_pos)
if match is None:
raise Error('Syntax error')
tokens.append(match.group(1))
current_pos = match.end()
print(tokens)
Ceci imprimera ['(', '3', '+', '(', '2', '*', '5', ')', ')']
Vous pouvez également utiliser re.findall
ou re.finditer
, mais vous éviterez alors les non-correspondances, qui sont des erreurs de syntaxe dans ce cas.
Il serait plutôt trivial de lancer à la main un tokenizer d’expression simple. Et je pense que vous en apprendrez davantage de cette façon aussi.
Donc, dans un souci d'éducation et d'apprentissage, voici une implémentation triviale de tokenizer d'expression qui peut être étendue. Cela fonctionne sur la règle "maximum-much" . Cela signifie qu'il agit "gourmand" en essayant de consommer autant de caractères que possible pour construire chaque jeton.
Sans plus tarder, voici le tokenizer:
class ExpressionTokenizer:
def __init__(self, expression, operators):
self.buffer = expression
self.pos = 0
self.operators = operators
def _next_token(self):
atom = self._get_atom()
while atom and atom.isspace():
self._skip_whitespace()
atom = self._get_atom()
if atom is None:
return None
Elif atom.isdigit():
return self._tokenize_number()
Elif atom in self.operators:
return self._tokenize_operator()
else:
raise SyntaxError()
def _skip_whitespace(self):
while self._get_atom():
if self._get_atom().isspace():
self.pos += 1
else:
break
def _tokenize_number(self):
endpos = self.pos + 1
while self._get_atom(endpos) and self._get_atom(endpos).isdigit():
endpos += 1
number = self.buffer[self.pos:endpos]
self.pos = endpos
return number
def _tokenize_operator(self):
operator = self.buffer[self.pos]
self.pos += 1
return operator
def _get_atom(self, pos=None):
pos = pos or self.pos
try:
return self.buffer[pos]
except IndexError:
return None
def tokenize(self):
while True:
token = self._next_token()
if token is None:
break
else:
yield token
Voici une démonstration de l'utilisation:
tokenizer = ExpressionTokenizer('((81 * 6) /42+ (3-1))', {'+', '-', '*', '/', '(', ')'})
for token in tokenizer.tokenize():
print(token)
Qui produit la sortie:
(
(
81
*
6
)
/
42
+
(
3
-
1
)
)
Réponse regex rapide: re.findall(r"\d+|[()+\-*\/]", str_in)
Manifestation:
>>> import re
>>> str_in = "((81 * 6) /42+ (3-1))"
>>> re.findall(r"\d+|[()+\-*\/]", str_in)
['(', '(', '81', '*', '6', ')', '/', '42', '+', '(', '3', '-', '1',
')', ')']
Pour la partie des parenthèses imbriquées, vous pouvez utiliser une pile pour garder une trace du niveau.
Cela ne donne pas tout à fait le résultat souhaité mais pourrait intéresser les autres utilisateurs de cette question. Il utilise la bibliothèque pyparsing .
# Stolen from http://pyparsing.wikispaces.com/file/view/simpleArith.py/30268305/simpleArith.py
# Copyright 2006, by Paul McGuire
# ... and slightly altered
from pyparsing import *
integer = Word(nums).setParseAction(lambda t:int(t[0]))
variable = Word(alphas,exact=1)
operand = integer | variable
expop = Literal('^')
signop = oneOf('+ -')
multop = oneOf('* /')
plusop = oneOf('+ -')
factop = Literal('!')
expr = operatorPrecedence( operand,
[("!", 1, opAssoc.LEFT),
("^", 2, opAssoc.RIGHT),
(signop, 1, opAssoc.RIGHT),
(multop, 2, opAssoc.LEFT),
(plusop, 2, opAssoc.LEFT),]
)
print (expr.parseString('((81 * 6) /42+ (3-1))'))
Sortie:
[[[[81, '*', 6], '/', 42], '+', [3, '-', 1]]]
Si vous ne voulez pas utiliser le module re
, vous pouvez essayer ceci:
s="((81 * 6) /42+ (3-1))"
r=[""]
for i in s.replace(" ",""):
if i.isdigit() and r[-1].isdigit():
r[-1]=r[-1]+i
else:
r.append(i)
print(r[1:])
Sortie:
['(', '(', '81', '*', '6', ')', '/', '42', '+', '(', '3', '-', '1', ')', ')']
En utilisant grako:
start = expr $;
expr = calc | value;
calc = value operator value;
value = integer | "(" @:expr ")" ;
operator = "+" | "-" | "*" | "/";
integer = /\d+/;
grako transpile en python.
Pour cet exemple, la valeur de retour ressemble à ceci:
['73', '+', ['34', '-', '72', '/', ['33', '-', '3']], '+', ['56', '+', ['95', '-', '28']]]
Normalement, vous utiliseriez la classe de sémantique générée comme modèle pour un traitement ultérieur.
Pour fournir une approche regex plus verbeuse que vous pouvez facilement étendre:
import re
solution = []
pattern = re.compile('([\d\.]+)')
s = '((73 + ( (34- 72 ) / ( 33 -3) )) + (56 +(95 - 28) ) )'
for token in re.split(pattern, s):
token = token.strip()
if re.match(pattern, token):
solution.append(float(token))
continue
for character in re.sub(' ', '', token):
solution.append(character)
Ce qui vous donnera le résultat:
solution = ['(', '(', 73, '+', '(', '(', 34, '-', 72, ')', '/', '(', 33, '-', 3, ')', ')', ')', '+', '(', 56, '+', '(', 95, '-', 28, ')', ')', ')']