为什么SWI-Prolog只提供一种解决方案?

时间:2013-09-14 21:49:11

标签: prolog prolog-dif logical-purity

我说实话,我是Prolog的新手,所以请原谅我的无知。

我有一个简单的谓词来计算列表中原子的出现次数,如下所示:

count(L, B, C) :-
    L = [], C = 0, !;
    L = [H|T], H \= B, count(T, B, C), !;
    L = [H|T], H = B, count(T, B, C1), C is C1 + 1.

以下查询会返回正确的结果:

?- count([a, c, g, t], a, C).
C = 1.

?- count([a, c, g, t], c, C).
C = 1.

?- count([a, c, g, t], g, C).
C = 1.

?- count([a, c, g, t], t, C).
C = 1.

但是,如果我尝试找到所有可能的解决方案,它只会提供一个。

?- count([a, c, g, t], X, C).
X = a,
C = 1.

如何让它提供所有解决方案?我虽然它可能与切割操作符有关,但删除它似乎也不起作用。

4 个答案:

答案 0 :(得分:4)

剪切确实是一个问题:尝试例如最常见的查询

?- count(Ls, L, C).

并且看到它只产生一个解决方案,尽管显然应该有无限多,因为第一个参数可以是任意长度的列表。所以首先删除所有削减。另一个问题是(\=)/2,这不是一个真正的关系:只有它的论据是基础的才是合理的。而不是(\=)/2,使用更一般的谓词dif/2,它可以在SWI-Prolog以及其他系统中使用,并将其参数约束为不同的术语。然后你的谓语将向各个方向发挥作用。

编辑:我展开了“可用于所有方向”的观点。考虑以下版本的list_term_count/3,它将列表与该列表中术语的出现次数相关联,除了dif/2之外还使用约束:

list_term_count([], _, 0).
list_term_count([L|Ls], L, N) :-
        list_term_count(Ls, L, N0),
        N #= N0 + 1.
list_term_count([L|Ls], E, N) :-
        dif(L, E),
        list_term_count(Ls, E, N).

我们可以用最常见的方式使用它,这样可以保留所有参数,并获得正确的答案:

?- list_term_count(Ls, E, N).
Ls = [],
N = 0 ;
Ls = [E],
N = 1 .

为了公平地列举所有解决方案,我们可以使用length/2

?- length(Ls, _), list_term_count(Ls, E, N).
Ls = [],
N = 0 ;
Ls = [E],
N = 1 ;
Ls = [_G167],
N = 0,
dif(_G167, E) .

请注意dif/2约束作为剩余目标发生,并且当EN时,该约束将列表的元素与0区分开来。这就是我们如何表达一组无限制的术语,除了与E不同之外,这些术语不受任何其他限制。

任何其他实例化模式也是可以接受的。例如:

?- list_term_count([a], E, N).
E = a,
N = 1 ;
N = 0,
dif(E, a).

或者例如:

?- list_term_count([X], a, N).
X = a,
N = 1 ;
N = 0,
dif(X, a).

这种通用性是在程序中使用纯单调谓词的好处之一。使用纯粹的目标也可以让我们非常自由地重新排序它们。

答案 1 :(得分:2)

我认为你正在探索Prolog的“黑暗面”:聚合。

实际上,你的count / 3谓词可能是 - 图书馆(aggregate) -

count(L, B, C) :-
    aggregate(count, member(B, L), C).

作为一种基于relational数据模型的语言,我们可以期待SQL中常用的好东西,从较简单的那些开始:

select count( distinct B ) from L

如果您检查库实现(例如通过?- edit(aggregate/3).),您将会理解将“一元一元”导向的Prolog 执行模型概括为所需的复杂性一个'面向'的人。

答案 2 :(得分:1)

意外的行为来自Prolog的统一,它总是试图成功。

H \= B视为not(H = B)。如果B未绑定,H = B将始终成功,因为可以进行统一,因此not(...)将始终失败。因此,在B绑定到H之后,递归仅发生在第三个分支中。

让我们假设H \= B对于未绑定的B会成功,并且会发生递归。现在,如果再次出现相同的元素H,则此时它可能会绑定到B,这将为每个值提供多个结果。例如,count([a,a],X,C)将返回X = a, C = 1. X = a, C = 2.。当然不是你想要的。

  

但是,如果我尝试找到所有可能的解决方案,它只会提供一个。

?- count([a, c, g, t], X, C). X = a, C = 1.

你会说“所有可能的解决方案”? XC的{​​{1}}为无限值(Protip:try ?- X.)。或者你的意思是列表中的所有值?

您的问题有一个非常简单的解决方案:只需确保在达到B时绑定\=。实现这一目标的最简单方法是简单地改变谓词的顺序,即将不等式移到最后。

% count(?List, ?Element, ?Count)
count([], _, 0).
count([E|R], E, C) :-
     count(R, E, C0),
     C is C0+1.
count([F|R], E, C) :-
     count(R, E, C),
     F \= E.

优化是留给读者的练习。

最后一点:在你真正了解它们之前不要使用剪切(!),大多数时候你都不需要它们。

答案 3 :(得分:0)

这似乎有效。

base(a).
base(c).
base(g).
base(t).

count(L, B, C) :-
    base(B),
    (
        L = [], C = 0;
        L = [H|T], H = B, count(T, B, C1), C is C1 + 1;
        L = [H|T], H \= B, count(T, B, C)
    ).

这是有道理的,因为Prolog没有任何其他方式可以知道我认为B的域是什​​么。我很惊讶它没有给我那个'论证没有充分实例化'错误的原始方式。此外,如果我包含剪切操作符,它不起作用,我现在也无法解释。