如何在 Prolog 中求平均值 (2017)
Daniel's Blog Home Archives Search Feed About
如果我要计算一个数字列表的平均值,我可能会这样做:
averagelist(List, Avg) :-
length(List, N), sumlist(List, Sum),
Avg is Sum / N.
这很像实际的数学定义。 然后你可以创建一个数字列表并计算它的平均值。 @lurker 说得对,这很糟糕,但它能工作:
average(N, Avg) :-
findall(I, between(1, N, I), Is),
averagelist(Is, Avg).
这是一种构建抽象的方式。 但是,当然,这是为了一门课程,重要的是不要使用 Prolog 或学习声明式编程或解决实际问题,而是执行毫无意义的归纳体操来证明你理解递归。 因此,“更好”的(即更糟糕但更可能被一位不明智的教授接受)解决方案是采用过程式代码:
average(list) ::=
sum := 0
count := 0
repeat with i ∈ list
sum := sum + i
count := count + 1
return sum / count
并将其转换为等效的 Prolog 代码:
average(List, Result) :- average(List, 0, 0, Result).
average([], Sum, Count, Result) :- Result is Sum / Count.
average([X|Xs], Sum, Count, Result) :-
Sum1 is Sum + X,
succ(Count, Count1),
average(Xs, Sum1, Count1, Result).
我的 findall/3
的列表结果必须仅使用 18 世纪的工具精心手动组装,以免任何人觉得 Prolog 可以在少于 40 行代码中有效地使用:
iota(N, Result) :- iota(1, N, Result).
iota(X, Y, [X|Result]) :- X < Y, succ(X,X1), iota(X1, Y, Result).
iota(X, X, [X]).
然后你可以在没有库代码污染的情况下构建 averagelist/2
(当然,你必须编写 length/2
和 sumlist/2
,甚至可能编写 member/2
,即使它没有被使用,只是因为它很聪明,很有用,而且似乎应该在所有其他我们可能需要的东西旁边的源文件中),但它看起来大致像这样:
average(N, Avg) :-
iota(N, List),
averagelist(List, Avg).
现在,当然,将会指出,引入不是直接回答家庭作业的其他谓词是不合法的,并且会受到惩罚,因为这样做会导致可读性、可维护性、将问题分解为可管理的部分以及其他与分配目标(使 Prolog 显得乏味而又不透明)没有直接关系的事情,因此我们现在可以看看这个,并意识到如果我们想将这两个谓词展平在一起,我们应该能够通过简单地将它们的状态变量合并在一起并完成所有工作。 喜欢这个:
average(N, Avg) :- average(1, N, 0, 0, Avg).
average(X, Y, Sum, Count, Avg) :-
X < Y,
Sum1 is Sum + X,
succ(Count, Count1),
succ(X, X1),
average(X1, Y, Sum1, Count1, Avg).
average(X, X, Sum, Count, Avg) :-
Sum1 is Sum + X,
succ(Count, Count1),
Avg is Sum1 / Count1.
现在这开始看起来像编程语言教授的代码了! 我们基本上从四个小的可读行变成了 9 或 10 个重复的行和大量的簿记和状态! 我认为我们现在走在正确的轨道上,让我们回顾一下它是如何工作的:
average/2
只是对average/5
的调用,我们的状态已初始化(没有总和,没有计数,起始值 = 1)。average/5
有两种情况:一种是计数到值和当前计数值相等的基本情况,另一种是当前计数小于的归纳情况。- 把啦啦啦加起来,你明白我的意思了
这里的关键要点是:1) Prolog 有一个简洁、高级、可读和可理解的标准库,你在学校被禁止使用它,以及 2) 任何过程循环都可以通过创建一个递归辅助谓词并移动代码来使 Prolog 工作。
Tags
Date
June 9, 2017