Оценить (почти алгебраическое c) выражение без символа '*' в python - PullRequest
1 голос
/ 06 января 2020

У меня есть следующее содержимое в value.txt:

2A-25X-8A+34X-5B+11B

Если я использую MetaFont через терминал bash как ниже:

#mf
This is METAFONT, Version 2.7182818 (TeX Live 2019/Arch Linux) (preloaded base=mf)
**expr
(/usr/share/texmf-dist/fonts/source/public/knuth-lib/expr.mf
gimme an expr: 2A-25X-8A+34X-5B+11B
>> 6B+9X-6A
gimme an expr:

Я могу оценить выражение без символа «*» между буквами и цифрами.

Я хочу сделать это, используя Python как можно более чисто и экономно, но все же без использования '*'. Я еще ничего не нашел об этом. Я также надеюсь, что это синтаксис, который может быть реализован с with open, print = и r.

EDIT

Возможная идея была бы такой :

with open ("value.txt", "r") as value:
    data = value.read()

#some python method for evaluate value.txt expression and save in variable value2

print = (value2)

1 Ответ

2 голосов
/ 06 января 2020

Всегда интересуют вопросы, касающиеся арифметики разбора c. Вот решение на основе pyparsing (хотя и немного длиннее, чем вы ожидали, и использующее больше, чем просто с open, et c.).

Первые 30 строк определяют класс для подсчета переменные, с поддержкой сложения, вычитания и умножения на целое число. (Целые числа смоделированы как Tally с переменной ''.)

Следующие 30 строк определяют фактический синтаксический анализатор и действия времени анализа для преобразования проанализированных токенов в накопленные объекты Tally.

Последние 25 строк - это тесты, включая ваше примерное выражение.

Реальные "умные" способности синтаксического анализатора находятся в методе infixNotation, который реализует синтаксический анализ различных операторов, включая обработку приоритета операторов. и группировка с (). Использование «3A» для обозначения «3 раза A» выполняется путем передачи None в качестве оператора умножения. Это также поддерживает конструкции типа «2 (A + 2B)», чтобы дать «2A + 4B».

import pyparsing as pp

# special form of dict to support addition, subtraction, and multiplication, plus a nice repr
class Tally(dict):
    def __add__(self, other):
        ret = Tally(**self)
        for k, v in other.items():
            ret[k] = ret.get(k, 0) + v
            if k and ret[k] == 0:
                ret.pop(k)
        return ret

    def __mul__(self, other):
        if self[''] == 0:
            return Tally()
        ret = Tally(**other)
        for k in ret:
            ret[k] *= self['']
        return ret

    def __sub__(self, other):
        return self + MINUS_1 * other

    def __repr__(self):
        ret = ''.join("{}{}{}".format("+" if coeff > 0 else "-", str(abs(coeff)) if abs(coeff) != 1 else "", var)
                          for var, coeff in sorted(self.items()) if coeff)

        # leading '+' signs are unnecessary
        ret = ret.lstrip("+")
        return ret

MINUS_1 = Tally(**{'': -1})

var = pp.oneOf(list("ABCDEFGHIJKLMNOPQRSTUVWXYZ"))

# convert var to a Tally of 1
var.addParseAction(lambda t: Tally(**{t[0]: 1}))
integer = pp.pyparsing_common.integer().addParseAction(lambda tokens: Tally(**{'': tokens[0]}))

def add_terms(tokens):
    parsed = tokens[0]
    ret = parsed[0]
    for op, term in zip(parsed[1::2], parsed[2::2]):
        if op == '-':
            ret -= term
        else:
            ret += term
    return ret

def mult_terms(tokens):
    coeff, var = tokens[0]
    return coeff * var

# only the leading minus needs to be handled this way, all others are handled
# as binary subtraction operators
def leading_minus(tokens):
    parsed = tokens[0]
    return MINUS_1 * parsed[1]
leading_minus_sign = pp.StringStart() + "-"

operand = var | integer
expr = pp.infixNotation(operand,
                        [
                            (leading_minus_sign, 1, pp.opAssoc.RIGHT, leading_minus),
                            (None, 2, pp.opAssoc.LEFT, mult_terms),
                            (pp.oneOf("+ -"), 2, pp.opAssoc.LEFT, add_terms),
                        ])


expr.runTests("""\
    B
    B+C
    B+C+3B
    2A
    -2A
    -3Z+42B
    2A+4A-6A
    2A-25X-8A+34X-5B+11B
    3(2A+B)
    -(2A+B)
    -3(2A+B)
    2A+12
    12
    -12
    2A-12
    (5-3)(A+B)
    (3-3)(A+B)
    """)

Дает вывод (runTests повторяет каждую строку теста, после чего анализируется результат):

B
[B]

B+C
[B+C]

B+C+3B
[4B+C]

2A
[2A]

-2A
[-2A]

-3Z+42B
[42B-3Z]

2A+4A-6A
[]

2A-25X-8A+34X-5B+11B
[-6A+6B+9X]

3(2A+B)
[6A+3B]

-(2A+B)
[-2A-B]

-3(2A+B)
[-6A-3B]

2A+12
[12+2A]

12
[12]

-12
[-12]

2A-12
[-12+2A]

(5-3)(A+B)
[2A+2B]

(3-3)(A+B)
[]

Чтобы показать, как использовать expr для анализа строки выражения, см. Следующий код:

result = expr.parseString("2A-25X-8A+34X-5B+11B")
print(result)
print(result[0])
print(type(result[0]))

# convert back to dict
print({**result[0]})

Отпечатки:

[-6A+6B+9X]
-6A+6B+9X
<class '__main__.Tally'>
{'B': 6, 'A': -6, 'X': 9}
...