【问题标题】:How to evaluate a custom math expression in Python如何在 Python 中评估自定义数学表达式
【发布时间】:2011-02-08 05:12:03
【问题描述】:

我正在用 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)”会失败。

【问题讨论】:

  • 你能告诉我们你尝试了什么以及它是如何失败的吗?
  • 最后只是创建了一个递归辅助函数。感谢大家的帮助!

标签: python math eval


【解决方案1】:

您可以将callback functionre.sub 一起使用。当您点击链接时,向下搜索以“如果 repl 是一个函数...”开头的段落

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

【讨论】:

    【解决方案2】:

    Python 不允许您编写全新的运算符,并且您不能使用常规语言编写括号。您必须编写一个递归下降解析器。不过,这对于您的掷骰子语言来说应该非常简单。

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

    【讨论】:

    • 我认为括号只是一个示范性的中间结果,而不是 DSL 本身的一部分。写1d6+2d6+2d6-72+4d100 = 84 并不能说明一切。不过,对于记录下降建议 +1。
    • 那我就糊涂了。为什么 OP 的“正则表达式 [fall] 在处理括号时分崩离析”?
    • 我比我想象的更含糊,抱歉。上面的例子工作正常。我的意思是,如果你在 d 的任一侧加上括号(如 (4+2)d6),解释器将无法处理评估。
    【解决方案3】:

    看看PyParsing 库。特别是,examples 页面的sample 与您想要的非常接近:

    dice2.py

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

    【讨论】:

      【解决方案4】:

      这使用了 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))
      

      一些快速说明:

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

      4 * [6] 生成列表[6,6,6,6]

      ((random.randint(1,x) for x in [6,6,6,6])) 是列表推导式的生成器;这个特定的将返回 1 到 6 之间的四个随机数。

      【讨论】:

        【解决方案5】:

        在我的Supybot dice plugin 中,我用

        解析表达式
        r'(?P<sign>[+-])((?P<dice>\d*)d(?P<sides>\d+)|(?P<mod>\d+))'
        

        然后得到每个骰子的总数和一个总修饰符,滚动它们并得到总结果(我想显示每个骰子的总数)。

        【讨论】:

          猜你喜欢
          • 2011-06-30
          • 2017-10-04
          • 1970-01-01
          • 2010-12-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多