Prolog - 计算数字的出现次数

时间:2017-06-05 10:05:23

标签: prolog

我想写一个谓词,它可以计算所有遇到的数字:

count(1, [1,0,0,1,0], X).
X = 2.

我试着把它写成:

count(_, [], 0).
count(Num, [H|T], X) :- count(Num, T, X1), Num = H, X is X1 + 1.

为什么不起作用?

5 个答案:

答案 0 :(得分:7)

  

为什么不起作用?

Prolog是一种编程语言,通常可以直接回答这个问题。看看我如何从失败的查询开始尝试你的定义:

?- count(1, [1,0,0,1,0], X).
false.

?- count(1, Xs, X).
Xs = [],
X = 0 ;
Xs = [1],
X = 1 ;
Xs = [1, 1],
X = 2 ;
Xs = [1, 1, 1],
X = 3 ...

?- Xs = [_,_,_], count(1, Xs, X).
Xs = [1, 1, 1],
X = 3 ;
false.

首先我意识到查询根本不起作用,然后我概括查询。我用变量Xs替换了大列表并说:Prolog,为我填空!而Prolog做到了这一点,并准确地向我们揭示了它将成功的案例。

实际上,它仅仅以1的列表成功。这很奇怪。您的定义太受限制 - 它正确计算列表中的1,其中只有1,但所有其他列表都被拒绝。 @coder向您展示了如何扩展您的定义。

这是另一个使用library(reif)的人 SICStus | SWI。或者,请参阅tfilter/3

count(X, Xs, N) :-
   tfilter(=(X), Xs, Ys),
   length(Ys, N).

更符合其他定义风格的定义:

count(_, [], 0).
count(E, [X|Xs], N0) :-
   if_(E = X, C = 1, C = 0),
   count(E, Xs, N1),
   N0 is N1+C.

现在有一些更普遍的用途:

  

四元素列表如何看起来有3倍1?

| ?- length(L, 4), count(1, L, 3).
     L = [1,1,1,_A],
     dif(1,_A)
  ;  L = [1,1,_A,1],
     dif(1,_A)
  ;  L = [1,_A,1,1],
     dif(1,_A)
  ;  L = [_A,1,1,1],
     dif(1,_A)
  ;  false.

因此剩下的元素必须与1不同。

这是Prolog为我们提供的优良通用性。

答案 1 :(得分:4)

问题是如@lurker所述,如果条件(或更好的统一)失败,则谓词将失败。你可以为此目的制作另一个子句,使用纯粹的iso / 2并在iso中定义:

count(_, [], 0).
count(Num, [H|T], X) :- dif(Num,H), count(Num, T, X).
count(Num, [H|T], X) :- Num = H, count(Num, T, X1), X is X1 + 1.

以上不是最有效的解决方案,因为它留下了许多选择点,但它是一种快速而正确的解决方案。

答案 2 :(得分:2)

您只需让谓词在统一Num = X失败。基本上,就像你不接受与你所计算的唯一条款不同的条款。

我向你推荐这个简单的解决方案,它使用尾递归并在线性时间内扫描列表。尽管篇幅很长,但它非常高效和优雅,它利用了声明性编程技术和Prolog引擎的回溯。

count(C, L, R) :-
    count(C, L, 0, R).

count(_, [], Acc, Acc).
count(C, [C|Xr], Acc, R) :-
    IncAcc is Acc + 1,
    count(C, Xr, IncAcc, R).
count(C, [X|Xr], Acc, R) :-
    dif(X, C),
    count(C, Xr, Acc, R).

count/3是启动器谓词。它需要术语计数,列表并给你结果值。 第一个count/4是递归的基本情况。 当列表的头部与您要查找的术语统一时,将执行第二个count/4。 在回溯时达到第三个count/4:如果该术语不匹配,则统一失败,您不需要递增累加器。

Acc允许您扫描传播递归处理的部分结果的整个列表。最后你只需返回它。

答案 3 :(得分:0)

我自己解决了这个问题:

count(_, [], 0).
count(Num, [H|T], X) :- Num \= H, count(Num, T, X).
count(Num, [H|T], X) :- Num = H, count(Num, T, X1), X is X1 + 1.

答案 4 :(得分:0)

我决定将我的解决方案添加到此处的列表中。

这里的其他解决方案使用显式统一/失败统一,或使用库/其他功能,但是我的解决方案使用cuts和隐式统一。请注意,我的解决方案与Ilario的解决方案相似,但使用削减简化了此过程。

count(_, [], 0) :- !.

count(Value, [Value|Tail],Occurrences) :- !, 
    count(Value,Tail,TailOcc), 
    Occurrences is TailOcc+1.

count(Value, [_|Tail], Occurrences) :- count(Value,Tail,Occurrences).

这是如何工作的?以及如何编码?

将这样的问题等同于通过归纳,基础案例和证明步骤来证明如何减少问题的方法来解决这样的问题。

第1行-基本情况

第1行(count(_, [], 0) :- !.)处理“基本情况” 。 在处理列表时,必须查看每个元素,最简单的情况是零个元素([])。因此,我们希望元素为零的列表不包含要查找的Value的实例。

请注意,我在最终代码中将Value替换为_-这是因为如果列表中没有值,我们不在乎要寻找的值!因此,为避免单例变量,我们在此取反。

此后,我还添加了!(剪切)-因为对于我们不希望Prolog回溯和失败的出现次数,只有一个正确的值-因此我们告诉Prolog我们找到了正确的值通过添加此剪切。

第2/3行-归纳步幅

第2行和第3行处理“归纳步骤” 。如果我们在列表中有一个或多个元素,则应该处理。在Prolog中,我们只能直接查看列表的开头,因此让我们一次查看一个元素。因此,我们有两种情况-列表开头的值是我们正在寻找的Value,或者不是。

第2行

第2行(count(Value, [Value|Tail],Occurrences) :- !, count(Value,Tail,TailOcc), Occurrences is TailOcc+1.)处理列表的开头和我们要查找的值是否匹配。因此,我们仅使用相同的变量名,以便Prolog统一它们。

在我们的解决方案中,切口被用作第一步(通过告诉Prolog不要尝试任何其他规则,这使每种情况互斥,并使我们的解决方案在最后调用时得到优化)。

然后,我们找出列表中其余部分(称为TailOcc)中术语的实例。我们目前尚不知道列表中有多少个词,但我们知道它比列表中其余词多一个(因为我们有一个匹配项)。

一旦我们知道列表的其余部分中有多少个实例(称为Tail),我们就可以采用该值并将其添加1,然后将其作为最后一个值返回我们的计数函数(称为Occurences)。

第3行

第3行(count(Value, [_|Tail], Occurrences) :- count(Value,Tail,Occurrences).)处理列表的开头和我们要查找的值是否不匹配。

由于我们在第2行中使用了剪切,因此只有在第2行失败(即没有匹配项)的情况下,才可以尝试此行。

我们只需要获取列表其余部分(末尾)中的实例数,并返回相同的值即可,而无需对其进行编辑。