【发布时间】:2020-11-11 18:20:26
【问题描述】:
我有一个包含对象列表的字典
objects = {'A1': obj_1,
'A2': obj_2,
}
然后我有一个字符串
cmd = '(1.3A1 + 2(A2 + 0.7A3)) or 2(A4 to A6)'
我想把它翻译成一个命令
max( 1.3*objects['A1'] + 2*(objects['A2'] + 0.73*objects['A3']), 2*max(objects['A4'], objects['A5'], objects['A6']))
我的尝试
由于没有找到更好的选择,我开始从头开始编写解析器。
个人注意:我不认为将 150 行代码附加到 SO 问题是一种好的做法,因为这意味着读者应该阅读并理解它,这是一项艰巨的任务。尽管如此,我之前的问题被否决了,因为我没有提出我的解决方案。所以你来了……
import re
from more_itertools import stagger
def comb_to_py(string, objects):
# Split the line
toks = split_comb_string(string)
# Escape for empty string
if toks[0] == 'none':
return []
# initialize iterator
# I could use a deque here. Let's see what works the best
iterator = stagger(toks, offsets=range(2), longest=True)
return comb_it_to_py(iterator, objects)
def split_comb_string(string):
# Add whitespaces between tokes when they could be implicit to allow string
# splitting i.e. before/after plus (+), minus and closed bracket
string = re.sub(r' ?([\+\-)]) ?', r' \1 ', string)
# remove double spaces
string = re.sub(' +', ' ', string)
# Avoid situations as 'A1 + - 2A2' and replace them with 'A1 - 2A2'
string = re.sub(r'\+ *\-', r'-', string)
# Avoid situations as 'A1 - - 2A2' and replace them with 'A1 + 2A2'
string = re.sub(r'\- *\-', r'+', string)
# Add whitespace after "(" (we do not want to add it in front of it)
string = re.sub(r'\( ?', r'( ', string)
return string.strip().split(' ')
def comb_it_to_py(iterator, objects):
for items in iterator:
# item[0] is a case token (e.g. 1.2A3)
# This should occur only with the first element
if re.fullmatch(r'([\d.]*)([a-zA-Z(]+\d*)', items[0]) is not None:
res = parse_case(items[0], objects, iterator)
elif items[0] == ')' or items[0] is None:
return res
# plus (+)
elif items[0] == '+':
# skip one position
skip_next(iterator)
# add following item
res += parse_case(items[1], objects, iterator)
# minus (-)
elif items[0] == '-':
# skip one position
skip_next(iterator)
# add following item
res -= parse_case(items[1], objects, iterator)
else:
raise(ValueError(f'Invalid or misplaced token {items[0]}'))
return res
def parse_case(tok, objects, iterator):
# Translate a case string into an object.
# It handles also brackets as "cases" calling comb_it_to_py recursively
res = re.match(r'([\d.]*)(\S*)', tok)
if res[1] == '':
mult = 1
else:
mult = float(res[1])
if res[2] == '(':
return mult * comb_it_to_py(iterator, objects)
else:
return mult * objects[res[2]]
def skip_next(iterator):
try:
next(iterator)
except StopIteration:
pass
if __name__ == '__main__':
from numpy import isclose
def test(string, expected_result):
try:
res = comb_to_py(string, objects)
except Exception as e:
print(f"Error during test on '{string}'")
raise e
assert isclose(res.value, expected_result), f"Failed test on '{string}'"
objects = {'A1': 1, 'A2':2, 'A10':3}
test('A2', 2)
test('1.3A2', 2.6)
test('1.3A2 + 3A1', 5.6)
test('1.3A2+ 3A1', 5.6)
test('1.3A2 +3A1', 5.6)
test('1.3A2+3A1', 5.6)
test('1.3A2 - 3A1', -0.4)
test('1.3A2 -3A1', -0.4)
test('1.3A2- 3A1', -0.4)
test('1.3A2-3A1', -0.4)
test('1.3A2 + -3A1', -0.4)
test('1.3A2 +-3A1', -0.4)
test('1.3A2 - -3A1', 5.6)
test('A1 + 2(A2+A10)', 25)
test('A1 - 2(A2+A10)', -23)
test('2(A2+A10) + A1', 25)
test('2(A2+A10) - A1', 23)
test('2(A2+A10) - -A1', 25)
test('2(A2+A10) - -2A1', 26)
这段代码不仅冗长,而且很容易破解。整个代码基于字符串的正确拆分,而正则表达式部分只是为了确保正确拆分字符串,这完全取决于字符串中空格的位置,即使 - 在这个特定的语法中 - 根本不应解析大多数空格。
此外,此代码仍然无法处理 or 关键字(其中 A or B 应转换为 max(A,B) 和 to 关键字(其中 A1 to A9 应转换为 max([Ai for Ai in range(A1, A9)]))。
问题
这是最好的方法还是对于此类任务有更强大的方法?
注意
我看了pyparsing。它看起来是一种可能性,但是,如果我理解得很好,它应该被用作更强大的“线分割”,而令牌仍然必须手动一个一个地转换为操作。这是正确的吗?
【问题讨论】:
-
不要提出新问题,请编辑现有问题重新打开它。
-
你现在喜欢我删除这个还是那个?
-
这次真的无所谓了。 总的来说我认为删除现有问题并发布新问题是不受欢迎的(因为您可能会被视为试图规避网站的编辑过程)。
-
我删除了旧版本。感谢您的来信
-
我不认为将 150 行代码附加到 SO 问题是一种好习惯这就是为什么你应该创建一个 minimal reproducible example。
标签: python text-parsing