解析给定格式的字符串以构建二元决策树

时间:2010-05-23 17:34:27

标签: python parsing binary-tree

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

例如,问题的形式是“如果分段大小<1.5,则选择左侧节点,否则右侧”。问题可以是带有键和值的字典。左侧和右侧节点表示完整的左半部分树或右半部分树,将以递归方式遍历,直到到达叶节点。通过这种方式,我们将构建一个二元决策树。

2 个答案:

答案 0 :(得分:0)

如果您可以决定输入格式的详细信息,请使用原始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]]

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

答案 1 :(得分:0)

这种语法实际上是pyparsing的目标。基本格式很简单,在pyparsing中,它看起来像:

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

但是一旦你开始添加算术表达式,布尔运算以及对'and'和'或'运算符的支持,事情变得复杂了。此外,这是一个递归语法,因为一个动作本身可以是一个嵌套的决定。

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

LPAR,RPAR = map(Suppress,"()")
number = Regex(r"[+-]?\d+(\.\d*)?").setParseAction(lambda tokens:float(tokens[0]))
varname = Word(alphas+'_', alphanums+'_')

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

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

operand = number | varname
arithExpr = operatorPrecedence(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 = operatorPrecedence(boolOperand,
    [
    ('not', 1, opAssoc.RIGHT),
    ('and', 2, opAssoc.LEFT),
    ('or', 2, opAssoc.LEFT),
    ])

你对行动的定义有点粗略,所以我编写了一些可能的陈述:一个赋值语句,一个print语句,因为这是一个语音应用程序,一个say语句非常simliar到print

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之后,我们使用'&lt;&lt;' operator将实际表达式定义“插入”现有的decision变量:

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

同样,我对你定义的格式采取了自由,认为可选的else子句可能有用。如果您不希望这是可选的,只需删除Optional周围的Group(action)("elseAction")包装。

定义了语法,现在这里有一些测试用例。在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 wiki示例SimpleBool.py(http://pyparsing.wikispaces.com/file/view/simpleBool.py)包含一个附加解析操作的示例,用于将解析的标记转换为可调用的类实例,从而简化评估结果。

希望这有帮助。