1

If a have the string calculation = '1+1x8'. How can I convert this into calculation = 1+1*8? I tried doing something like

for char in calculation:
    if char == 'x':
        calculation = calculation.replace('x', *)

    # and

    if char == '1':
        calculation = calculation.replace('1', 1)

This clearly doesn't work, since you can't replace just one character with an integer. The entire string needs to be an integer, and if I do that it doesn't work either since I can't convert 'x' and '+' to integers

3 Answers3

5

Let's use a more complicated string as an example: 1+12x8. What follows is a rough outline; you need to supply the implementation for each step.

First, you tokenize it, turning 1+12x8 into ['1', '+', '12', 'x', '8']. For this step you need to write a tokenizer or a lexical analyzer. This is the step where you define your operators and literals.

Next, you convert the token stream into a parse tree. Perhaps you represent the tree as an S-expression ['+', '1', ['x', '12', '8']] or [operator.add, 1, [operator.mul, 12, 8]]. This step requires writing a parser, which requires you to define things like the precedence of your operators.

Finally, you write an evaluator that can reduce your parse tree to a single value. Doing this in two steps might yield

  1. [operator.add, 1, [operator.mul, 12, 8]] to [operator.add, 1, 96]
  2. [operator.add, 1, 96] to 97
chepner
  • 497,756
  • 71
  • 530
  • 681
0

You could write something like:

def parse_exp(s):
    return eval(s.replace('x','*'))

and expand for whatever other exotic symbols you want to use.

To limit the risks of eval you can also eliminate bad characters:

import string

good = string.digits + '()/*+-x'

def parse_exp(s):
    s2 = ''.join([i for i in s if i in good])
    return eval(s2.replace('x','*'))

Edit: additional bonus is that the in-built eval function will take care of things like parenthesis and general calculation rules :)

Edit 2: As another user pointed out, evalcan be dangerous. As such, only use it if your code will ever only run locally

S. L.
  • 630
  • 8
  • 19
  • Additional bonus that eval will execute *any* arbitrary code and so is potentially a large security risk – Chris_Rands Oct 21 '19 at 14:30
  • Valid but the OP asked for a way of evaluating expressions without specifying further. Why have him write a complicated parser when he can use something simple? – S. L. Oct 21 '19 at 14:38
  • because it's a big security flaw as i said. anyway, it is not hard to write a simple parser if they just want to support a few operators like multiply, add etc., and they would learn from doing this – Chris_Rands Oct 21 '19 at 14:43
  • If it's so easy I suggest you write one and post it :) – S. L. Oct 21 '19 at 14:44
  • here's a pretty complete one https://stackoverflow.com/a/9558001/6260170 – Chris_Rands Oct 21 '19 at 14:44
0

Adding code to what chepner suggested:

Tokenize '1+12x8' -> ['1', '+', '12', 'x', '8']. Use order of operation '/*+-' -> reduce calculation 1 + (12*8) Return the answer

import re
import operator




operators = {
        '/': operator.truediv,
        'x':operator.mul,
        '+':operator.add,
        '-':operator.sub,

    }

def op(operators, data):
    # apply operating to all occurrences 

    for p in operators:
        while p in data:
            x = data.index(p)
            replacer = operators.get(p)(int(data[x-1]) , int(data[x+1]))
            data[x-1] = replacer
            del data[x:x+2]


    return data[0]


def func(data):
    # Tokenize

    d = [i for i in re.split('(\d+)', data) if i ]

    # Use order of operations 
    d = op(operators, d)

    return d


s1 = "1+1x8"
s2 = '2-4/2+5'


s = func(s1) # 9
print(s)

t = func(s2) #-5
print(t)
Prayson W. Daniel
  • 14,191
  • 4
  • 51
  • 57