本页上的练习09 http://www.ic.unicamp.br/~meidanis/courses/mc336/2009s2/prolog/problemas/要求创建一个谓词,该谓词将重复的元素打包到子列表中。

一个简单的解决方案很简单

pack([], []).
pack([H|T], [I|U]) :-
    split(H, T, I, P),
    pack(P, U).

其中split(Head, Tail, HeadGroup, Rest)是split的定义为
split(A, [], [A], []).
split(A, [B|T], [A], [B|T]) :- A \= B.
split(A, [A|T], [A|U], B) :- split(A, T, U, B).

效果很好,并且与上述网页上提供的示例解决方案非常吻合。

该解决方案失败的地方是诸如pack(X, [[a], [b, b]]).之类的查询。两个解决方案集之间的对应关系是双射的(对于A中的每个pack(A, B),只有一个B且只有一个),因此必须有一个更好的解决方案。

解决它的一种方法是更改​​求值顺序,帮助prolog根据参数类型选择非无限分支,如下所示
pack([], []).
pack(A, B) :-
  ( var(A) ->
    A = [H|T],
    B = [I|U],
    pack(P, U),
    split(H, T, I, P)
  ; A = [H|T],
    B = [I|U],
    split(H, T, I, P),
    pack(P, U)
  ).

在这方面有两个问题。

首先,这令人难以置信的丑陋,因此是否有更好的方法根据参数类型来选择规则的顺序?

其次,可能更复杂的问题是,有没有一种方法可以不用var(A)重写解决方案,如果不是,为什么呢?

最佳答案

从声明的角度来看,像var/1(\=)/2这样的非单调构造非常有问题。

为什么?一探究竟:

?- var(A), A=a.
A = a.

?- A=a, var(A).
false.

因此,这破坏了合取的可交换性,这是我们在实际推理逻辑程序时所依赖的核心属性之一。

那么(\=)/2呢,您认为这表示两个术语是不同的?一探究竟:
?- X \= Y.
false.

No two terms that are different exist, yes? Seems a bit strange to me, to say the least, so apparently the predicate really means something else.

Luckily, in your case, the solution is very easy. Simply use the pure constraint dif/2 to denote that two terms are different. See prolog-dif for more information. You only have to change a single line of code to make your solution much more general. Instead of:

split(A, [B|T], [A], [B|T]) :- A \= B.

只需使用dif/2:
split(A, [B|T], [A], [B|T]) :- dif(A, B).

With this change, your example works completely as expected:

?- pack(X, [[a], [b, b]]).
X = [a, b, b] ;
false.

请注意,现有的Prolog文献是已过时的方式,大多数此类解决方案集来自于大多数Prolog系统甚至没有免费提供dif/2的时代。

09-11 17:54