可逆树长度关系

时间:2012-12-30 23:30:03

标签: prolog clpfd failure-slice successor-arithmetics

我正在尝试用“纯粹的”Prolog(没有is,切割或类似的东西来写可逆关系。是的,这是家庭作业),我必须承认我不知道如何。我没有看到任何创建此类事物的过程。

我们被赋予“不可靠”但可逆的算术关系(add,mult,equal,less,...),我们必须使用它们来创建这些关系。

现在,我正在尝试通过创建关系tree(List,Tree)来了解如何创建可逆函数,如果List是二叉树Tree的叶子列表,则该关系为真。

要实现这样的目的,我正在尝试创建tree_size(Tree,N)关系,当TreeN离开时,该关系为真。这是我天真的,不可逆转的关系:

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),所以它不可逆。到目前为止,我已经尝试了一些事情,但我必须承认我感到气馁,因为我不知道在哪里寻找什么。有没有办法做到这一点?

2 个答案:

答案 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 arithmeticsconstraints like clpfd。前者可用于任何Prolog系统,后者仅在某些类似SWI,YAP,SICStus,GNU,B中提供。

使用successor-arithmetics

现在,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 #>= 1TD #>= 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).