我有以下表达式:
{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&
我已经尝试过:搜索了很多,在订购前后遍历树,试图找到一个没有成功的算法。
我很感激任何有关正确方向的提示和指示。
答案 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