I have the following content in the value.txt
:
2A-25X-8A+34X-5B+11B
If I use MetaFont via terminal bash how below:
#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:
I can evaluate the expression without the '*' symbol between letters and numbers.
What I want is to do this using Python as cleanly and economically as possible but still without using '*'.
I haven't found anything about it yet.
I also hope it is a syntax that can be implemented with with open
, print =
and r
.
EDIT
A possible idea would be like this:
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)
Always interested in questions regarding parsing arithmetic. Here is a pyparsing-based solution (albeit a bit longer than you were hoping, and using more than just with, open, etc.).
The first 30 lines define a class for tallying up the variables, with support for adding, subtracting, and multiplying by an integer. (Integers are modeled as a Tally with a variable of ''.)
The next 30 lines define the actual parser, and the parse-time actions to convert the parsed tokens into cumulative Tally objects.
The final 25 lines are tests, including your sample expression.
The real "smarts" of the parser are in the infixNotation
method, which implements the parsing of the various operators, including handling of operator precedence and grouping
with ()'s. The use of "3A" to indicate "3 times A" is done by passing None
as the multiplication operator. This also supports constructs like "2(A+2B)" to give "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)
""")
Gives the output (runTests
echoes each test line, followed by the parsed result):
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)
[]
To show how to use expr to parse your expression string, see this code:
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]})
Prints:
[-6A+6B+9X]
-6A+6B+9X
<class '__main__.Tally'>
{'B': 6, 'A': -6, 'X': 9}