【问题标题】:Parsing a string of given format to build a binary decision tree解析给定格式的字符串以构建二叉决策树
【发布时间】:2020-03-20 21:44:22
【问题描述】:

我正在尝试解析带有 ((Question)(Left_Node)(right_node)) 形式括号的字符串。

例如,问题的形式为“如果段大小

【问题讨论】:

  • 不,我正在研究语音合成..所以需要这个作为它的一部分..

标签: python parsing binary-tree


【解决方案1】:

这种语法确实是pyparsing的目标。基本格式很简单,在pyparsing中是这样的:

decision = '(' + '('+question+')' + '('+action+')' + '('+action+')' + ')'

但是,一旦您开始添加算术表达式、布尔运算以及对“and”和“or”运算符的支持,事情就会变得复杂。此外,这是一种递归语法,因为一个动作本身可以是一个嵌套决策。

Pyparsing 具有简化算术和布尔表达式定义的内置支持,包括操作的优先级和括号中的分组,以及递归表达式。这是各个部分的 pyparsing 语法。首先是一些基本的解析元素:

LPAR,RPAR = map(Suppress,"()")
# old pyparsing way
number = Regex(r"[+-]?\d+(\.\d*)?").setParseAction(lambda tokens:float(tokens[0]))
# new pyparsing way - parses many numeric formats, and uses a parse action
# to convert to float
number = pyparsing_common.fnumber()
varname = Word(alphas+'_', alphanums+'_')

附加到数字表达式的解析操作会在解析时自动将解析后的数字转换为浮点值。 Word 类采用两个字符串:一个包含所有有效前导字符的字符串,以及一个包含所有有效正文字符的字符串。此 varname 定义支持类似于 Python 标识符的变量名称。

Pyparsing 具有infixNotation 方法,该方法接受基本操作数定义的表达式,以及定义每个级别运算符的元组列表:运算符的表达式,运算符是一元、二元还是三元的整数,以及是左关联还是右关联。 infixNotation 负责嵌套在括号中的算术表达式的递归定义。这个表达式定义了基本的 4 函数数学:

operand = number | varname
arithExpr = infixNotation(operand,
    [
    (oneOf("+ -"), 1, opAssoc.RIGHT),
    (oneOf("* /"), 2, opAssoc.LEFT),
    (oneOf("+ -"), 2, opAssoc.LEFT),
    ])

现在这里是布尔条件的定义(我们最终将使用它来定义决策问题):

TRUE = CaselessKeyword("TRUE") | Keyword("T")
FALSE = CaselessKeyword("FALSE") | Keyword("F")
comparisonOp = oneOf("< > <= >= = !=")
boolLiteral = TRUE | FALSE
arithComparison = arithExpr + comparisonOp + arithExpr
boolOperand = boolLiteral | arithComparison
booleanExpr = infixNotation(boolOperand,
    [
    ('not', 1, opAssoc.RIGHT),
    ('and', 2, opAssoc.LEFT),
    ('or', 2, opAssoc.LEFT),
    ])

你对动作的定义有点粗略,所以我编了一些可能的语句:赋值语句,print 语句,因为这是一个语音应用程序,所以say 语句与@987654331 非常相似@。

rhs = booleanExpr | arithExpr
assignment = varname("assign_var") + '=' + Group(rhs)("assign_value")
print_cmd = Keyword("print") + Group(delimitedList(rhs  | quotedString))
say_cmd = Keyword("say") + Group(delimitedList(rhs | quotedString))
cmd = print_cmd | say_cmd | assignment

除了表达式定义之外,您还会注意到一些表达式后面跟着一个带引号的字符串,就好像表达式是一个使用该字符串调用的函数一样。事实上,这个“调用”实际上返回了表达式的副本,并且匹配的标记用该名称标记。这些结果名称在解析后挑选单个匹配元素时非常有用(类似于正则表达式中的命名组)。

最后,将这些部分组合成您的决策表达式,下面是问题和行动表达式:

IF = CaselessKeyword("IF")
question = LPAR + IF + Group(booleanExpr)("condition") + RPAR
action = LPAR + cmd + RPAR | Group(decision)

请注意,动作定义可以包括决策,但动作用于定义决策。为了打破这种先有鸡还是先有蛋的依赖关系,我们在本节之前定义decision,但使用pyparsing Forward 类的占位符表达式:

decision = Forward()

然后在定义questionaction 之后,我们使用'decision 变量中:

decision <<= (LPAR
              + question
              + Group(action)("ifAction")
              + Optional(Group(action)("elseAction"))
              + RPAR)

再次,我冒昧地使用了您定义的格式,认为可选的 else 子句可能有用。如果您不希望这是可选的,只需删除 Group(action)("elseAction") 周围的 Optional 包装器即可。

定义了语法,现在这里有一些测试用例。对parseString 返回的结果使用dump() 是打印标记以及附加到它们的任何名称的好方法。

tests = """\
    ((if TRUE)(a=99))

    ((if TRUE)(a = (a=99)))

    ((if a<100)(a = a + 1))

    ((if a<100)(a = a + 1)(a = a-1))

    (
     (if a<100)
     (print a, "is too small") 
     ( (if a>100) (print a,"is too big") (print a, "is just right") )
    )

    (
     (if a<100)
     (say a, "is too small!") 
     ( (if a>100) (say a,"is too big!") (say a, "is just right!") )
    )
    """

for d in decision.searchString(tests):
    print d.dump()
    print

这是输出:

['IF', ['TRUE'], ['a', '=', [99.0]]]
- condition: ['TRUE']
- ifAction: ['a', '=', [99.0]]
  - assign_value: [99.0]
  - assign_var: a

['IF', ['TRUE'], ['a', '=', ['a', '=', 99.0]]]
- condition: ['TRUE']
- ifAction: ['a', '=', ['a', '=', 99.0]]
  - assign_value: ['a', '=', 99.0]
  - assign_var: a

['IF', ['a', '<', 100.0], ['a', '=', [['a', '+', 1.0]]]]
- condition: ['a', '<', 100.0]
- ifAction: ['a', '=', [['a', '+', 1.0]]]
  - assign_value: [['a', '+', 1.0]]
  - assign_var: a

['IF', ['a', '<', 100.0], ['a', '=', [['a', '+', 1.0]]], 
    ['a', '=', [['a', '-', 1.0]]]]
- condition: ['a', '<', 100.0]
- elseAction: ['a', '=', [['a', '-', 1.0]]]
  - assign_value: [['a', '-', 1.0]]
  - assign_var: a
- ifAction: ['a', '=', [['a', '+', 1.0]]]
  - assign_value: [['a', '+', 1.0]]
  - assign_var: a

['IF', ['a', '<', 100.0], ['print', ['a', '"is too small"']], 
    [['IF', ['a', '>', 100.0], ['print', ['a', '"is too big"']], 
    ['print', ['a', '"is just right"']]]]]
- condition: ['a', '<', 100.0]
- elseAction: [['IF', ['a', '>', 100.0], ['print', ['a', '"is too big"']], 
                ['print', ['a', '"is just right"']]]]
- ifAction: ['print', ['a', '"is too small"']]

['IF', ['a', '<', 100.0], ['say', ['a', '"is too small!"']], 
    [['IF', ['a', '>', 100.0], ['say', ['a', '"is too big!"']], 
        ['say', ['a', '"is just right!"']]]]]
- condition: ['a', '<', 100.0]
- elseAction: [['IF', ['a', '>', 100.0], ['say', ['a', '"is too big!"']], 
                ['say', ['a', '"is just right!"']]]]
- ifAction: ['say', ['a', '"is too small!"']]

这里是完整解析程序的链接 - http://pastebin.com/DnaNrx7j

这只是第一阶段,解析输入。下一步实际上是通过处理返回的标记来评估表达式。 pyparsing 示例 SimpleBool.py (https://github.com/pyparsing/pyparsing/blob/master/examples/simpleBool.py) 包含一个附加解析操作的示例,以将解析的标记转换为可调用的类实例,从而简化评估结果。

希望这会有所帮助。

【讨论】:

    【解决方案2】:

    如果您可以决定输入格式的细节,请使用原始 python 源代码。例如,您可以将树存储在 python 节点字典中:

    tree = {
        "root": ("a", "b")
        "a": ("c", "d"),
        "b": ("e", "f"),
        "c": (None, None), #No children, a leaf.
        "d": (None, None),
        "e": (None, None),
        "f": (None, None),
    }
    

    现在您可以使用 python 解析器简单地解析这棵树。

    from tree_data import tree #We're done parsing!
    
    root_node = tree["root"]
    left_child = tree[root_node[0]]
    

    好吧,如果已经指定了输入格式,那么如果您不分享该格式的详细信息,我们将无法为您提供帮助。

    【讨论】:

    • 谢谢,实际上我并没有完全清楚我想要什么..是的,我现在正在编辑问题..请帮助我实施...
    猜你喜欢
    • 2019-10-18
    • 2013-01-07
    • 2018-06-16
    • 2021-11-26
    • 1970-01-01
    • 2021-11-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多