我正在尝试用“纯粹的”Prolog(没有is
,切割或类似的东西来写可逆关系。是的,这是家庭作业),我必须承认我不知道如何。我没有看到任何创建此类事物的过程。
我们被赋予“不可靠”但可逆的算术关系(add,mult,equal,less,...),我们必须使用它们来创建这些关系。
现在,我正在尝试通过创建关系tree(List,Tree)
来了解如何创建可逆函数,如果List
是二叉树Tree
的叶子列表,则该关系为真。
要实现这样的目的,我正在尝试创建tree_size(Tree,N)
关系,当Tree
有N
离开时,该关系为真。这是我天真的,不可逆转的关系:
tree_len(n(_,leaf,leaf),1).
tree_len(n(op,G,D),N) :-
tree_len(G,TG),
tree_len(D,TD),
add(TG,TD,N).
我可以执行查询tree_len(some tree, N)
,但不能说tree_len(X,3)
,所以它不可逆。到目前为止,我已经尝试了一些事情,但我必须承认我感到气馁,因为我不知道在哪里寻找什么。有没有办法做到这一点?
答案 0 :(得分:9)
首先,让我们试着理解为什么你的定义不可逆。我将使用failure-slice来更好地解释它。所以考虑一下tree_len(X,1).
乍一看,一切都很完美,你得到一个很好的答案!
?- tree_len(T,1).
T = n(_G267, leaf, leaf)
但是从不要求另一个答案,因为它会循环:
?- tree_len(T,1).
T = n(_G267, leaf, leaf) ;
** LOOPS **
所以我们得到的答案有点分散注意力。乍一看看起来一切都还好,只有在回溯时遇到了真正的问题。这是Prolog习惯的东西。显然,使用3的查询选择得更好。但那是运气。
一般来说,有一种简单的方法可以确保这一点。只需将额外目标false
添加到查询中即可。添加false
意味着我们不再对任何答案感兴趣,因为它不再能够成功。通过这种方式,所有分心都被消除了,我们直接面对问题:
?- tree_len(T,1), false.
** LOOPS **
那么,这个循环来自哪里?
在纯粹的,单调的Prolog程序(例如本程序)中,我们可以通过在程序中添加一些目标false
来本地化不终止的原因。如果生成的程序(称为failure-slice)未终止,则原始程序也不会终止。这是我们查询的最小失败切片:
?- tree_len(T,1), false.tree_len(n(_,leaf,leaf),1) :- false. tree_len(n(op,G,D),N) :- tree_len(G,TG), false,tree_len(D,TD),N is TG+TD.
我们的计划还剩下多少!正是这个微小的碎片负责非终止。如果我们想解决问题,我们必须在这一小部分做点什么。其他一切都是徒劳的。
所以我们需要做的是以某种方式更改程序,使这个片段不再循环。
实际上,我们有两种选择,我们可以使用successor arithmetics或constraints like clpfd。前者可用于任何Prolog系统,后者仅在某些类似SWI,YAP,SICStus,GNU,B中提供。
现在,3由s(s(s(0)))
表示。
tree_lensx(T, s(N)) :- tree_lendiff(T, N,0). tree_lendiff(n(_,leaf,leaf), N,N). tree_lendiff(n(op,G,D), s(N0),N) :- tree_lendiff(G, N0,N1), tree_lendiff(D, N1,N).
我在这里使用了几种常见的编码技术。
实际关系是tree_lendiff/3
,它表示不是一个参数的自然数,而是使用两个。实际数字是两者之间的差异。以这种方式,可以保持定义可逆。
另一种技术是避免左递归。 tree_lendiff/3
描述的长度实际上是长度减去的长度。还记得我们先得到的失败片吗?这里也会出现同样的故障切片!但是,通过将长度“移动”一个,递归规则的头部现在可以确保终止。
library(clpfd)
最初,开发了对有限域的约束来解决组合问题。但你也可以使用它们来获得可逆的算术。 SWI和YAP中的实现甚至可以编写成代码,这些代码通常与传统的不可逆(is)/2
相等,但仍然是可逆的。
:- use_module(library(clpfd)).
tree_fdlen(n(_,leaf,leaf),1).
tree_fdlen(n(op,G,D),N) :-
N #= TG+TD,
TG #>= 1,
TD #>= 1,
tree_fdlen(G,TG),
tree_fdlen(D,TD).
此程序更符合您原来的定义。然而,请注意两个目标TG #>= 1
和TD #>= 1
,它们添加了冗余信息以确保终止此程序。
我们现在可以枚举某个范围内的所有树,如下所示:
?- Size in 0..4, tree_fdlen(T, Size).
Size = 1, T = n(_A,leaf,leaf) ;
Size = 2, T = n(op,n(_A,leaf,leaf),n(_B,leaf,leaf)) ;
Size = 3, T = n(op,n(_A,leaf,leaf),n(op,n(_B,leaf,leaf),n(_C,leaf,leaf))) ;
Size = 4, ... ;
Size = 4, ... ;
Size = 3, ... ;
Size = 4, ... ;
Size = 4, ... ;
Size = 4, ... ;
false.
请注意答案替换的确切顺序!这不仅仅是1,2,3,4!相反,答案可以在某些顺序中找到。只要我们只对寻找所有解决方案感兴趣,哪一个都没关系!
答案 1 :(得分:1)
有趣的问题。
这是我要做的。基本上,你的关系是不可逆的,因为add / 3不是。我基本上做的是,用数字替换计数,用与叶子数量相对应的大小的列表计算 - 是可逆的(好吧,追加/ 3和长度/ 2是可逆的)。 / p>
这就像你需要的东西吗?发布的代码可在YAP下运行。
PS:这可能不是最简洁的解决方案,但它是我的头脑。如果您有任何其他问题,我会尽力提供帮助。
:- use_module(library(lists)).
do_tree_len(n(_,leaf,leaf), [X]).
do_tree_len(n(op,G,D), [X1,X2|T]) :-
append(TG, TD, [X1,X2|T]),
TG \= [X1,X2|T], % To prevent infinite loops, when TG or TD is []
TD \= [X1,X2|T],
do_tree_len(G, TG),
do_tree_len(D, TD).
tree_len(Tree, N):-
length(L, N),
do_tree_len(Tree, L).