在AST(或RPN)上应用分配律=>析取正常形式

时间:2014-07-14 21:11:20

标签: php abstract-syntax-tree rpn shunting-yard postfix-notation

我有以下表达式:

{1000} AND ({1001} OR {1002} OR {1003})

允许的运算符是OR和AND,表达式可以使用括号嵌套。 我已经设法使用在PHP 5.3中实现的Shunting Yard算法对此字符串进行标记并将其转换为抽象语法树(AST)。 上述表达式产生如下:

1000 1001 1002 | 1003 | &


    &
  /   \
1000   |
      / \
     |   1003
    / \
1001  1002

遍历此树时,我想输出用户可以选择的最终数字组合。在给定的表示中,这是不可能的。在应用分配法之后,我需要的实际上是形式:

(1000 & 1001) | (1000 & 1002) | (1000 & 1003)

1000 1001 & 1000 1002 & | 1000 1003 & |

               _______________|_____________
              /                             \
      _______|____                           &
     /            \                         / \
    &              &                    1000   1003
  /   \           / \
1000   1001    1000  1002

我总结说,唯一允许作为& -operator节点的节点是最后一个携带叶子的节点。所有其他人必须是| -operator节点。

如何将具有上述语法的任意AST转换为代表所有最终排列的AST?将分配法应用于中缀表示的标记是否更好?使用RPN表示而不是树更容易吗?

请注意,有更多困难的例子可能如下:

(1000 & 1008) & (1001 | 1002 | 1003)
1000 1008 & 1001 1002 | 1003 | &
       ______ & ___
      /            \
     &             |
    / \           / \
1000   1008      |   1003
                / \
            1001  1002

我想导致:

(1000 & 1008 & 1001) | (1000 & 1008 & 1002) | (1000 & 1008 & 1003)
1000 1008 & 1001 & 1000 1008 & 1002 & | 1000 1008 & 1003 & |

                        __________________|_________
                       /                            \
         _____________|_________                     &
        /                       \                   / \
       &                        &                  &   1003
      /  \                     / \                / \
     &    1001                &   1002        1000   1008
    / \                      / \
1000   1008              1000   1008

对于另一个(更复杂的)示例,只需切换左子树和右子树或添加另一个& -node代替1003 => 1003 1009&

我已经尝试过:搜索了很多,在订购前后遍历树,试图找到一个没有成功的算法。

我很感激任何有关正确方向的提示和指示。

2 个答案:

答案 0 :(得分:1)

您似乎想要做的是生产disjunctive normal form。这是 比起看起来更难做,因为有很多有趣的案例需要处理。

您要做的是实施以下重写规则, 详尽无遗地,你树上的任何地方(实际上,向上的叶子可能已经足够好了):

 rule distribute_and_over_or(a: term, b: term, c: term): term->term
    "  \a and (\b or \c) " ->  " \a and \b or \a and \c ";

从复杂的角度来说,您会获得多余的子条款,因此您可能需要这些规则:

 rule subsumption_identical_or_terms:(a: term): term->term
    "  \a or \a " ->  \a";

 rule subsumption_identical_and_terms:(a: term): term->term
    "  \a and \a " ->  \a";

你表达问题的方式,你没有使用"不是"但它可能会出现,所以你需要以下附加规则:

 rule cancel_nots:(term: x): term -> term
    " not (not \x)) " -->  "\x";

rule distribute_not_over_or(a: term, b: term): term->term
    " not( \a or \b ) " ->  " not \a  and not \b ";

 rule distribute_not_over_and(a: term, b: term): term->term
    " not( \a and \b ) " ->  " not \a  or not \b ";

您可能还会遇到自我取消的条款,因此您需要处理这些条款:

 rule self_cancel_and(a: term): term->term
     "  \a and not \a " -> "false";

 rule self_cancel_or(a: term): term->term
     "  \a or not \a " -> "true";

以及摆脱真假的方法:

 rule and_true(a: term): term->term
     " \a and true " -> " \a ";

 rule and_false(a: term): term->term
     " \a and false " -> " false ";

 rule or_true(a: term): term->term
     " \a or true " -> " true ";

 rule and_false(a: term): term->term
     " \a or false " -> " \a ";

 rule not_false(a: term): term->term
     " not false " -> " true ";

 rule not_true(a: term): term->term
     " not true " -> " false ";

(我假设表达式优先于"不是"绑定比&#34更严格;和"绑定比"或"更紧密。

所显示的规则假设各种子树最多只是#34;二进制",但它们可能有许多实际的子项,如您在示例中所示。实际上,你也必须担心联想法。如果您希望包含和取消法律确实有效,您还必须考虑到可交换法律。

你可能会发现一些隐含的"不是"传播,如果您的子表达式包含关系运算符,例如

    " not ( x > y ) " -->  " x <= y "

您可能还希望规范化您的关系比较:

    "  x < y " -->  " not (x >= y )"

由于您已经在PHP中实现了树,因此您必须通过在程序上爬上树来手动编写相应的树。这是可能的,但非常不方便。 (你可以在令牌作为RPN和AST上执行此操作,但我认为你会在AST上发现它更容易,因为你不必改变标记符号串。)

在操作符号公式时,应用引擎(通常为program transformation system)会更容易接受重写并将其应用于您。我在这里使用的符号取自我们的DMS软件重组工具包,它直接采用这些规则并自动处理关联性和可交换性。这可能不是PHP中可行的选择。

最后一个问题:如果你的条款有任何复杂性,最终的析取正常形式可以变得非常大,非常快。我们有一个客户想要这个,直到我们在一个很大的起始期间给他,这恰好产生了数百个叶子连接。 (到目前为止,我们还没有找到一种提供任意布尔术语的漂亮方法。)

答案 1 :(得分:0)

感谢您提及最能帮助我的关键字:析取正常形式。我不知道实际上是在寻找这种转变。

我在互联网上找不到详细的算法描述,所以我试着自己做。这是我在伪代码中完成它的方式。请告诉我,如果它不可理解。

- Traverse the AST recursively post order wise
- If an &-node is found, check if one of the children nodes is a |-node
- Set orChild and andChild accordingly
- Traverse the orChild-tree iterative pre order wise and for each OR-leaf push a new &-node with andChild and the OR-leaf value to the stack
- If you meet another &-node push a new &-node with andChild and the whole &-node you found to the stack
- After traversing is done, combine the nodes on the stack using an |-node
- The new sub tree, which has an |-node as root, replaces the &-node you started to traverse from
- As the outer traversal is post order, the newly created nodes are not traversed and have no effect on further changes
- Repeat the whole process until the resulting tree does not change anymore