通过匹配括号评估字符串

问题描述

以编程方式翻译像这样的字符串的最佳方法是什么

"((abc&(def|ghi))|jkl)&mno"

执行为:

if ((func('abc') and (func('def') or func('ghi'))) or func('jkl')) and func('mno'):
    return True

我觉得必须有一种简单的方法来实现这一目标,但是我无法解决这个问题。

解决方法

好吧,如果您的字符串不比显示的字符串复杂(例如,仅由那些符号加上字母/数字组成),则可以使用一些简单的正则表达式来解析它,并根据需要替换匹配项。之后,您只需使用eval()即可将其作为python代码运行。

例如:

import re

def func(x):
    # just an example...
    return True

s = "((abc&(def|ghi))|jkl)&mno"

s = re.sub(r'(\w+)',r"func('\1')",s)
s = s.replace('&',' and ')
s = s.replace('|',' or ')

print(s)
print(eval(s))

输出:

((func('abc') and (func('def') or func('ghi'))) or func('jkl')) and func('mno')
True
,

这是一个有趣的小问题,有很多层解决方案。

首先,给出此样本,您需要一个基本的中止符号解析器。在pyparsing中,有一个内置的辅助方法infixNotation。几个pyparsing示例显示了如何使用infixNotation解析布尔表达式。这是一个解析器,它将解析您的示例表达式:

import pyparsing as pp

term = pp.Word(pp.alphas)
AND = pp.Literal("&")
OR = pp.Literal("|")
expr =  pp.infixNotation(term,[
                             (AND,2,pp.opAssoc.LEFT,),(OR,])

print(expr.parseString(sample).asList())

对于您的样品,将打印:

[[[['abc','&',['def','|','ghi']],'jkl'],'mno']]

您可以看到我们不仅捕获了表达式,而且还捕获了括号分组。

我们可以通过添加解析动作来开始转换为所需的输出。这些是pyparsing将调用的解析时回调,用于将解析的令牌替换为其他值(该令牌不必是字符串,可以是用于评估的AST节点-但在这种情况下,我们将返回修改后的字符串)。>

AND.addParseAction(lambda: " and ")
OR.addParseAction(lambda: " or ")
term.addParseAction(lambda t: "func('{}')".format(t[0]))
expr.addParseAction(lambda t: "({})".format(''.join(t[0])))

解析动作可以是具有各种签名的方法:

function()
function(tokens)
function(location,tokens)
function(input_string,location,tokens)

对于AND和OR,我们只需要将解析的运算符替换为其相应的“ and”和“ or”关键字。对于已解析的变量项,我们想将“ xxx”更改为“ func(xxx)”,因此我们编写了一个解析动作,该动作采用已解析的标记,并返回修改后的字符串。

expr的解析操作很有趣,因为它所做的只是获取已解析的内容,使用''.join()加入它们,然后将其包装在() s中。由于expr实际上是一个递归表达式,我们将看到它在已解析的嵌套列表中的每个级别上都正确地包装了()。

添加了这些解析操作后,我们可以尝试再次调用parseString(),现在给出:

["(((func('abc') and (func('def') or func('ghi'))) or func('jkl')) and func('mno'))"]

靠近!

要格式化为所需的if语句,我们可以使用另一个解析动作。但是我们无法将此解析操作直接附加到expr,因为我们看到expr(及其关联的解析操作)将在所有嵌套级别上进行解析。因此,我们可以创建expr的“外部”版本,它只是expr的容器表达式:

outer_expr = pp.Group(expr)

解析操作类似于我们在expr中看到的操作,在该操作中,我们使用输入令牌返回了新的字符串:

def format_expression(tokens):
    return "if {}:\n    return True".format(''.join(tokens[0]))

outer_expr.addParseAction(format_expression)

现在,我们使用outer_expr来解析输入字符串:

print(outer_expr.parseString(sample)[0])

获取:

if (((func('abc') and (func('def') or func('ghi'))) or func('jkl')) and func('mno')):
     return True

(此值上可能会有()的另外一组,可以根据需要在outer_expr的解析操作中将其删除。)

解析器的完成版本(取消注释中间打印语句以查看解析器功能的进展):

sample = "((abc&(def|ghi))|jkl)&mno"

import pyparsing as pp

term = pp.Word(pp.alphas)
AND = pp.Literal("&")
OR = pp.Literal("|")
expr =  pp.infixNotation(term,])

# print(expr.parseString(sample).asList())

AND.addParseAction(lambda: " and ")
OR.addParseAction(lambda: " or ")
term.addParseAction(lambda t: "func('{}')".format(t[0]))
expr.addParseAction(lambda t: "({})".format(''.join(t[0])))

# print(expr.parseString(sample).asList())

def format_expression(tokens):
    return "if {}:\n    return True".format(''.join(tokens[0]))

outer_expr = pp.Group(expr).addParseAction(format_expression)
print(outer_expr.parseString(sample)[0])