Как оценить пользовательское математическое выражение в Python - PullRequest
6 голосов
/ 15 апреля 2010

Я пишу собственный анализатор бросков костей (сникер, если нужно) на 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?

Edit: я, кажется, дал плохой пример, так как приведенный выше пример работает с моей текущей версией. Что я ищу, так это какой-то способ оценки, скажем, (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)" завершается неудачей.

Ответы [ 5 ]

6 голосов
/ 15 апреля 2010

Вы можете использовать функцию обратного вызова с re.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
5 голосов
/ 15 апреля 2010

Python не позволяет вам писать совершенно новые операторы, и вы не можете использовать круглые скобки с обычным языком. Вам придется написать парсер рекурсивного спуска. Это должно быть довольно просто для вашего языка игры в кости.

Кроме того, вы можете использовать существующий оператор Python и использовать инструменты синтаксического анализа Pythons для преобразования текста в AST.

2 голосов
/ 15 апреля 2010

Взгляните на библиотеку PyParsing . В частности, на странице examples есть sample , достаточно близкий к тому, что вы хотите:

dice2.py

Анализатор и оценщик броска кубиков для оценки строк, таких как "4d20 + 5.5 + 4d6.takeHighest (3)".

0 голосов
/ 15 апреля 2010

В моем плагине Supybot dice Я анализирую выражение с помощью

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

затем получите общее количество каждой кости и общий модификатор, бросьте их и получите общий результат (я хотел показать общее количество каждой кости).

0 голосов
/ 15 апреля 2010

Это использует 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.

...