Last active
April 25, 2018 14:28
-
-
Save vadella/b20dcd82c1bf175da9e6420366e82d52 to your computer and use it in GitHub Desktop.
parser for mathematical expressions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import re | |
from operator import add, sub, mul, floordiv, truediv | |
from functools import reduce | |
import itertools | |
OPERATIONS = ( | |
(re.compile('\+'), add), | |
(re.compile('(?<=[\d\)])-'), sub), # not match the - in `(-1)` | |
(re.compile('\*'), mul), | |
(re.compile('//'), floordiv), | |
(re.compile('/'), floordiv), # or '/': truediv, | |
) | |
PATTERN_PARENTHESES = re.compile('^(.*?)\((.*)\)(.*?)$') | |
PATTERN_PAREN_SIMPLE= re.compile('\((-?\d+)\)') | |
PAREN_OPEN = '|' | |
PAREN_CLOSE = '#' | |
def _encode(expression): | |
return PATTERN_PAREN_SIMPLE.sub(rf'{PAREN_OPEN}\1{PAREN_CLOSE}', expression) | |
def _decode(expression): | |
return expression.replace(PAREN_OPEN, '(').replace(PAREN_CLOSE, ')') | |
def contains_parens(expression): | |
return '(' in _encode(expression) | |
def calculate(expression): | |
expression = expression.replace(' ', '') | |
while contains_parens(expression): | |
expression = _extract_parens(expression) | |
if PATTERN_PAREN_SIMPLE.fullmatch(expression): | |
expression = expression[1:-1] | |
try: | |
return int(expression) | |
except ValueError: | |
pass | |
operation, parts = split_expression(expression) | |
parts = map(calculate, parts) # recursion | |
return reduce(operation, parts) | |
def split_expression(expression): | |
for pattern, operation in OPERATIONS: | |
parts = pattern.split(expression) | |
if len(parts) > 1: | |
return operation, parts | |
def _extract_parens(expression, func=calculate): | |
# print('paren: ', expression) | |
expression = _encode(expression) | |
begin, expression = expression.split('(', 1) | |
characters = iter(expression) | |
middle = _search_closing_paren(characters) | |
middle = _decode(''.join(middle)) | |
middle = func(middle) | |
end = ''.join(characters) | |
result = f'{begin}({middle}){end}' if( begin or end) else str(middle) | |
return _decode(result) | |
def _search_closing_paren(characters, close_char=')', open_char='('): | |
count = 1 | |
for char in characters: | |
if char == open_char: | |
count += 1 | |
if char == close_char: | |
count -= 1 | |
if not count: | |
return | |
else: | |
yield char |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment