【问题标题】:Evaluate string by matching parentheses [duplicate]通过匹配括号来评估字符串[重复]
【发布时间】:2020-10-10 12:55:41
【问题描述】:

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

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

执行如下:

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

我觉得必须有一个简单的方法来实现这一点,但我无法理解它。

【问题讨论】:

  • 这只是解决了部分问题,因为它没有考虑到 '&' 和 '|'在字符串中。
  • StackOverflow 不是代码编写服务。请编辑您的代码以尝试将此问题解决到您的问题中。
  • 如果我知道如何解决这个问题,我一开始就不会问这个问题。链接的线程没有改变这一点,因为它只占问题的一小部分。跨度>
  • 我说的是“尝试”——你应该诚实地尝试解决问题。阅读stackoverflow.com/help/dont-ask
  • 对于需要在非常具体的技术方面拥有大量知识和经验。这正是stackoverflow的用途。

标签: python parentheses pyparsing


【解决方案1】:

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

首先,鉴于此示例,您需要一个基本的中缀符号解析器。在 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, 2, pp.opAssoc.LEFT,),
                         ])

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,
                         [
                             (AND, 2, pp.opAssoc.LEFT,),
                             (OR, 2, pp.opAssoc.LEFT,),
                         ])

# 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])

【讨论】:

    【解决方案2】:

    好吧,如果您的字符串不比您显示的更复杂(例如,仅由这些符号加上字母/数字组成),您可以根据需要使用一些简单的正则表达式替换匹配项来解析它。之后,您可以使用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
    

    【讨论】:

    • 这确实解决了问题。但是,如果输入字符串格式错误,这种解决方案似乎很容易出错,并且使用 eval() 通常被认为是不好的做法(stackoverflow.com/questions/1832940/…),所以我认为应该有更好的解决方案来解决这个问题。
    • @JulianFreyberg 使用 eval() 是不好的做法,只有当您不知道自己在做什么并且在用户输入中使用它时。该功能的存在是有原因的:被使用。当然,正确的做法是写一个递归解析器,但这会比这个简单快速的解决方案复杂得多,这就是为什么我在代码前加上“免责声明”的原因。
    • 我完全同意你的观点,并且会在我的情况下使用你的解决方案,因此我接受了你的回答。我只是想指出,可能存在更多故障安全解决方案,这些解决方案在其他情况下可能更合适。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-04-10
    • 2020-04-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-07-06
    相关资源
    最近更新 更多