1

I've got a python formula that randomly places operands in between numbers. The list could, for example, look like this:

['9-8+7', '7-8-6']

What I want to do is get the value of each string, so that looping through the strings, an array would see 9-8+7 and would append 8 and 7-8-6 would append -7. I can't convert a string with operands to int, so is this possible at all? Or should I change the algorithm so that instead of creating a string with each random output it calculates the value of it immediately?

Thank you in advance.

jsalonen
  • 29,593
  • 15
  • 91
  • 109
eltb
  • 107
  • 3
  • 10

3 Answers3

4

You can do eval on the list items, but that's a potential security hole and should only be used if you fully trust the source.

>>> map(eval, ['9-8+7', '7-8-6'])
[8, -7]

If you control the code producing the string, computing the values directly sounds like a better approach (safer and probably faster).

Fredrik
  • 940
  • 4
  • 10
2

As Fredrik pointed out, you can do eval in Python. I thought I'd add a more general approach that would work in any language, and might shed some light on simple parsers for those that haven't seen them in action.

You're describing a language whose formal definition looks something like this:

expr := sum
sum := prod [("+" | "-") prod]...
prod := digit [("*" | "/") digit]...
digit := '0'..'9'

This grammar (which I'm not bothering to make correct EBNF) accepts these strings: "3", "4*5/2", and "8*3+9", and so on.

This gives us a clue how to parse it, and evaluation is no more work than accumulating results as we go. The following is working Python 2 code. Notice how closely the code follows the grammar.

class ParseFail(Exception):
    pass

def eval_expr(str):
    value, pos = eval_sum(str, 0)
    return value

def eval_sum(str, pos):
    value, pos = eval_product(str, pos)
    accum = value
    while pos != len(str):
        op = str[pos]
        if not str[pos] in ['+', '-']:
            raise ParseFail("Unexpected symbol at position "
                            "{pos} of {str}".format(str=str, pos=pos))
        value, pos = eval_product(str, pos + 1)
        if op == '+':
            accum += value
        else:
            accum -= value
    return accum, pos

def eval_product(str, pos):
    value, pos = eval_digit(str, pos)
    accum = value
    while pos != len(str):
        op = str[pos]
        if not str[pos] in ['*', '/']:
            return accum, pos
        value, pos = eval_digit(str, pos + 1)
        if op == '*':
            accum *= value
        else:
            accum /= value
    return accum, pos

def eval_digit(str, pos):
    if not str[pos].isdigit():
        raise ParseFail("Unexpected symbol at position "
                        "{pos} of {str}".format(str=str, pos=pos))
    return int(str[pos]), pos + 1

try:
    print "3 ->", eval_expr("3")
    print "3*4 ->", eval_expr("3*4")
    print "2+3*4-5 ->", eval_expr("2+3*4-5")

    # Should raise ParseFail
    print "2+3*4^2-5 ->", eval_expr("2+3*4^2-5")
except ParseFail as err:
    print
    print err.args[0]

Here's a sample run:

$ python simple_expr.py
3 -> 3
3*4 -> 12
2+3*4-5 -> 9
2+3*4^2-5 ->
Unexpected symbol at position 5 of 2+3*4^2-5

It would be pretty easy to extend this to a full string calculator with more operators, such as the exponent operator '^' and multi-digit integers. Parentheses, floats and functions might be a bit of work, but not that hard either. Every programmer should try it once in their lives, in my opinion.

Codie CodeMonkey
  • 7,669
  • 2
  • 29
  • 45
1

This of course depends on how well-behaved and restricted your expressions are.

Since subtraction is addition with a negative number, you can write the subtractions as additions with a negative number. Spit on + to find the terms. Then parse the terms of the sum to integers, and sum them. Do so for each expression.

[sum(map(int,l.replace('-', '+-').split('+'))) for l in ['9-8+7','7-8-6']]
flup
  • 26,937
  • 7
  • 52
  • 74