Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to evaluate a custom math expression in Python

Tags:

python

math

eval

I'm writing a custom dice rolling parser (snicker if you must) in python. Basically, I want to use standard math evaluation but add the 'd' operator:

#xdy
sum = 0
for each in range(x):
    sum += randInt(1, y)
return sum

So that, for example, 1d6+2d6+2d6-72+4d100 = (5)+(1+1)+(6+2)-72+(5+39+38+59) = 84

I was using regex to replace all 'd's with the sum and then using eval, but my regex fell apart when dealing with parentheses on either side. Is there a faster way to go about this than implementing my own recursive parsing? Perhaps adding an operator to eval?

Edit: I seem to have given a bad example, as the above example works with my current version. What I'm looking for is some way to evaluate, say, (5+(6d6))d(7-2*(1d4)).
By "fell apart", I just meant that my current regex expression failed. I have been too vague about my failure, sorry for the confusion. Here's my current code:

def evalDice(roll_matchgroup):
    roll_split = roll_matchgroup.group('roll').split('d')
    print roll_split
    roll_list = []

    for die in range(int(roll_split[0])):
        roll = random.randint(1,int(roll_split[1]))
        roll_list.append(roll)

def EvalRoll(roll):
    if not roll: return 0
    rollPattern = re.compile('(?P<roll>\d*d\d+)')
    roll_string = rollPattern.sub(evalDice, roll.lower())

for this, "1d6+4d100" works just fine, but "(1d6+4)d100" or even "1d6+4d(100)" fails.

like image 318
taynaron Avatar asked Apr 15 '10 04:04

taynaron


3 Answers

You could use a callback function with re.sub. When you follow the link, search down to the paragraph beginning with "If repl is a function..."

import re
import random

def xdy(matchobj):
    x,y=map(int,matchobj.groups())
    s = 0
    for each in range(x):
        s += random.randint(1, y)
    return str(s)
s='1d6+2d6+2d6-72+4d100'
t=re.sub('(\d+)d(\d+)',xdy,s)
print(t)
# 5+10+8-72+197
print(eval(t))
# 148
like image 79
unutbu Avatar answered Oct 19 '22 03:10

unutbu


Python doesn't let you write brand new operators, and you can't do parentheses with a regular language. You'll have to write a recursive descent parser. This should be pretty simple for your dice-rolling language though.

Alternatively, you could coopt an existing Python operator and use Pythons parsing tools to convert the text into an AST.

like image 32
Marcelo Cantos Avatar answered Oct 19 '22 03:10

Marcelo Cantos


Take a look at the PyParsing library. In particular, the examples page has a sample fairly close to what you want:

dice2.py

A dice roll parser and evaluator for evaluating strings such as "4d20+5.5+4d6.takeHighest(3)".

like image 31
ars Avatar answered Oct 19 '22 03:10

ars