Davidhall
Davidhall

Reputation: 35

Decompose mathematical expression into steps

I would like to know if there is a way to print each step considering python follows PEMDAS 1. Can I make it print each step it does in solving this problem without building my own library?

print_steps('(3+7-2)*4/(3+32)')

1 Parentheses, Exponents, Multiplication and Division, and Addition and Subtraction

Upvotes: 0

Views: 284

Answers (3)

Cristian Ciupitu
Cristian Ciupitu

Reputation: 20930

You could use ast.parse and ast.dump to get a grasp of it.

import ast
ast.dump(ast.parse('(3+7-2)*4/(3+32)', mode='eval'))

If formatted nicely, the output can look like this:

Expression(
  body=BinOp(
    left=BinOp(
      left=BinOp(
        left=BinOp(
          left=Num(n=3),
          op=Add(),
          right=Num(n=7)
        ),
        op=Sub(),
        right=Num(n=2)
      ),
      op=Mult(),
      right=Num(n=4)
    ),
    op=Div(),
    right=BinOp(
      left=Num(n=3),
      op=Add(),
      right=Num(n=32)
    )
  )
)

Inspired by jez's answer I came up with the following solution to transform the dump into some linear steps:

import operator
Expression = lambda body: body
Num = lambda n: n
USub = lambda : ('-', operator.neg)
Add = lambda : ('+', operator.add)
Sub = lambda : ('-', operator.sub)
Mult = lambda : ('*', operator.mul)
Div = lambda : ('/', operator.truediv)
FloorDiv = lambda : ('//', operator.floordiv)

def UnaryOp(op, operand):
    print(op[0], operand)
    result = op[1](operand)
    print(' =', result)
    return result

def BinOp(left, op, right):
    result = op[1](left, right)
    print(left, op[0], right, '=', result)
    return result

eval(ast.dump(ast.parse('(3+7-2)*4/(3+32)', mode='eval')))

It prints:

3 + 7 = 10
10 - 2 = 8
8 * 4 = 32
3 + 32 = 35
32 / 35 = 0.9142857142857143

astviewer can give you a graphical represention of the tree. For example astviewer.main.view(source_code='(3+7-2)*4/(3+32)', mode='eval') gives you: astviewer for 3+7-2)*4/(3+32)

Upvotes: 2

jez
jez

Reputation: 15369

The "library" can be pretty lightweight:

class verbose_number:
    def __init__(self, value): self.value = value
    def operate(self, operator, other):
        other = getattr(other, 'value', other)
        result = eval('self.value %s other' % operator)
        print('%r %s %r = %r' % (self.value, operator, other, result))
        return self.__class__(result)
    def __add__(self, other): return self.operate('+', other)
    def __sub__(self, other): return self.operate('-', other)
    def __mul__(self, other): return self.operate('*', other)
    def __div__(self, other): return self.operate('/', other)
    def __floordiv__(self, other): return self.operate('//', other)
    def __truediv__(self, other): return self.operate('/', other)
    def __pow__(self, other): return self.operate('**', other)
    def __mod__(self, other): return self.operate('%', other)
    def __neg__(self): return self.__class__(-self.value)
    def __pos__(self): return self.__class__(+self.value)
    def __repr__(self): return repr(self.value)
    def __int__(self): return int(self.value)
    def __float__(self): return float(self.value)

Now any arithmetic computation that involves a verbose_number instance gets printed and returns another verbose_number, so the calculation is printed step by step:

>>> from __future__ import division
>>> (verbose_number(3)+7-2)*4/(3+32)
3 + 7 = 10
10 - 2 = 8
8 * 4 = 32
3 + 32 = 35
32 / 35 = 0.9142857142857143

Note that this relied on the fact that I made the first number in the expression a verbose_number. A refinement on the approach is to write a handy routine for parsing string expressions:

import re
def print_steps(expression):
    return eval(re.sub(r'([0-9\.]+([eE]-?[0-9]+)?)', r'verbose_number(\1)', expression))

This works by turning every numeric literal in the string into a verbose_number constructor call, thereby ensuring that all steps will be verbose no matter where Python's parser starts in the expression. Here's how it can be used to evaluate your example:

>>> print_steps('(3+7-2)*4/(3+32)')
3 + 7 = 10
10 - 2 = 8
8 * 4 = 32
3 + 32 = 35
32 / 35 = 0.9142857142857143

Upvotes: 2

Kevin
Kevin

Reputation: 76254

Nope. Not even dis.dis will show you individual steps since it all gets simplified out to a single constant before being compiled to bytecode.

>>> def f():
...     return (3+7-2)*4/(3+32)
...
>>> import dis
>>> dis.dis(f)
  2           0 LOAD_CONST              10 (0.9142857142857143)
              3 RETURN_VALUE

Upvotes: 1

Related Questions