如何在Python中评估自定义数学表达式。

6

我正在用Python编写一个自定义的骰子掷出解析器(如果你想嘲笑我也可以)。基本上,我想使用标准的数学计算方法,但是加入‘d’运算符:

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

因此,例如,1d6 + 2d6 + 2d6-72 + 4d100 =(5)+(1 + 1)+(6 + 2)-72 +(5 + 39 + 38 + 59)= 84。
我曾经使用正则表达式将所有的“d”替换为求和,然后使用eval函数,但是当处理两侧括号时,我的正则表达式失败了。有没有比实现递归解析更快的方法?或者可以向eval中添加一个运算符吗?
编辑:我似乎给出了一个错误的例子,因为上面的例子可以在我的当前版本中正常工作。我正在寻找一种评估方式,例如(5 +(6d6))d(7-2 *(1d4))。 所谓“失败”,我只是指我的当前正则表达式表达式失效了。我对我的失败描述过于笼统,对于造成的困惑,我很抱歉。以下是我的当前代码:
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())

对于这个问题,"1d6+4d100" 可以正常工作,但是 "(1d6+4)d100" 或者 "1d6+4d(100)" 就会失败。

你能展示一下你尝试过什么,以及它们是如何失败的吗? - Daniel Stutzbach
最终只是编写了一个递归辅助函数。感谢所有的帮助! - taynaron
5个回答

6
你可以使用 `re.sub` 和一个回调函数来进行操作。请查看 这个链接,然后跳到 "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

5

Python不允许你编写全新的运算符,也不能使用常规语言来括号表达式。因此,你需要编写一个递归下降解析器。对于掷骰子语言来说,这应该相当简单。

或者,你可以利用现有的Python运算符,并使用Python的解析工具将文本转换为AST。


我认为括号只是用来作为演示中间结果的,不是DSL本身的一部分。直接写成 1d6+2d6+2d6-72+4d100 = 84 也不会很解释清楚。不过对于递归下降的建议还是加1。 - Marc Bollinger
那我有点困惑。为什么原帖中的正则表达式“在处理括号时崩溃了”? - Marcelo Cantos
1
我比想象中更含糊不清,抱歉。上面的例子很好用。我的意思是,如果你在d的两侧加上括号(如(4+2)d6),解释器无法处理评估它。 - taynaron

2
请看一下PyParsing库。特别是examples页面,其中有一个sample与您所需的非常接近:

dice2.py

一个用于解析和评估字符串的骰子掷出解析器,例如“4d20 + 5.5 + 4d6.takeHighest(3)”。


0

这里使用了 eval,实际上这是相当糟糕的做法,但是为了演示而已。

>>> x = '1d6+2d6+2d6-72+4d100'
>>> eval(re.sub(r'(\d+)d(\d+)',r'sum((random.randint(1,x) for x in \1 * [\2]))', x))

一些快速笔记:

这将会用 sum((random.randint(1,x) for x in 4 * [6])) 替换 4d6

4 * [6] 返回一个列表 [6,6,6,6]

((random.randint(1,x) for x in [6,6,6,6])) 是生成器表达式的等价形式;这个特定的表达式将返回四个介于1和6之间的随机数。


0
在我的 Supybot 掷骰子插件 中,我使用以下表达式进行解析。
r'(?P<sign>[+-])((?P<dice>\d*)d(?P<sides>\d+)|(?P<mod>\d+))'

然后获取每个骰子的总数和总修正值,进行投掷并获得总结果(我想显示每个骰子的总数)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接