gpt4 book ai didi

prolog - 使用手动列表迭代与失败递归的优缺点是什么

转载 作者:行者123 更新时间:2023-12-04 05:08:22 24 4
gpt4 key购买 nike

我一直遇到这种情况,我永远不知道用哪种方法来攻击它。以下是处理某些季节事实的两种方法。

我正在努力解决的是使用方法1还是2,以及每种方法的优缺点,尤其是大量的事实。
methodone既然事实是可用的,这似乎很浪费,为什么还要建立一个列表(尤其是一个大列表)。如果列表足够大,这也一定会影响内存?而且它没有利用 Prolog 的自然回溯功能。
methodtwo利用回溯为我进行递归,我想内存效率会更高,但是通常这样做是一种好的编程习惯吗?可以说它更丑陋,可能还有其他副作用吗?

我能看到的一个问题是每次fail被调用,我们失去了将任何东西传回调用谓词的能力,例如。如果是 methodtwo(SeasonResults) ,因为我们不断地故意使谓词失败。所以methodtwo需要断言事实来存储状态。

大概(?)方法2会更快,因为它没有(大)列表处理要做?

我可以想象,如果我有一个列表,那么 methodone将是要走的路..还是总是这样?在任何情况下使用 methodone 将列表声明为事实是否有意义?然后使用方法二处理它们?彻底的疯狂?

但是话又说回来,我读到断言事实是一项非常“昂贵”的业务,因此即使对于大型列表,列表处理也可能是要走的路?

有什么想法吗?或者有时根据(什么)情况使用一个而不是另一个更好?例如。对于内存优化,使用方法 2,包括断言事实,对于速度使用方法 1?

season(spring).
season(summer).
season(autumn).
season(winter).

% Season handling
showseason(Season) :-
atom_length(Season, LenSeason),
write('Season Length is '), write(LenSeason), nl.

% -------------------------------------------------------------
% Method 1 - Findall facts/iterate through the list and process each
%--------------------------------------------------------------
% Iterate manually through a season list
lenseason([]).
lenseason([Season|MoreSeasons]) :-
showseason(Season),
lenseason(MoreSeasons).


% Findall to build a list then iterate until all done
methodone :-
findall(Season, season(Season), AllSeasons),
lenseason(AllSeasons),
write('Done').

% -------------------------------------------------------------
% Method 2 - Use fail to force recursion
%--------------------------------------------------------------
methodtwo :-
% Get one season and show it
season(Season),
showseason(Season),

% Force prolog to backtrack to find another season
fail.

% No more seasons, we have finished
methodtwo :-
write('Done').

最佳答案

让我们看看你的例子。它非常简单,所以我们可以想象它更复杂。但是,您似乎理所当然地认为副作用是必不可少的。让我质疑一下:

在您的示例中,您有一个非常有趣的发现:所有季节的名称长度相同。多么惊天动地的洞察力!但是等等,这是真的吗?
验证这一点的最直接的方法是:

?- 季节(S),atom_length(S,L)。
S = Spring ,
L = 6 ;
S = 夏天,
L = 6 ;
S = 秋天,
L = 6 ;
S = 冬天,
L = 6。

不需要findall/3 , 不需要 write/1 .

对于大量答案,目视检查是不切实际的。想象一下 400 个季节。但我们可以通过以下方式验证这一点:

?- 季节(S),atom_length(S,L),dif(L,6)。
错误的。

所以我们现在肯定知道没有不同长度的季节。

这是我对你的问题的第一个回答:

As long as you can, use the toplevel shell and not your own side effecting procedures! Stretch things a little bit further to avoid side-effects altogether. This is the best way to avoid failure driven loops right from the beginning.



坚持使用顶层 shell 是个好主意的原因还有很多:
  • 如果您的程序可以在顶层轻松查询,那么为它们添加测试用例将是微不足道的。
  • 许多其他用户都在使用顶层 shell,因此经过了很好的测试。你自己的写作往往是有缺陷的和未经检验的。想想约束。想想写花车。你会用write/1也适用于花车?编写浮点数以使其可以准确读回的正确方法是什么? 中有一种方法可以做到这一点.答案是:

  • 在 ISO 中, writeq/1,2 , write_canonical/1,2 , write_term/2,3带选项 quoted(true)保证可以准确地读回浮点数。也就是说,它们是相同的w.r.t。 (==)/2
  • 顶层 shell 向您显示有效的 Prolog 文本。事实上,答案本身就是一个查询!它可以粘贴回顶层 - 只是为了得到相同的答案。通过这种方式,您将了解 Prolog 更奇特但不可避免的细节,例如引用、转义和括号。否则几乎不可能学习语法,因为 Prolog 解析器通常非常宽容。
  • 你的程序很可能更容易被声明性推理所接受。

  • 很有可能,你的两个程序 methodonemethodtwo不正确:您在写入 Done 后忘记了换行符.所以 methodone, methodone包含一条乱码。如何轻松测试?

    但是,让我们更深入地了解您的程序。失败驱动循环的典型之处在于,它们一开始是无辜的,只是做“仅”副作用的事情,但迟早它们也会吸引更多的语义部分。在您的情况下, atom_length/2隐藏在故障驱动循环中,完全无法进行测试或推理。

    效率考虑

    Prolog 系统通常通过释放堆栈来实现故障。因此,故障驱动循环不需要垃圾收集器。这就是为什么人们相信失败驱动的循环是有效的。但是,情况不一定如此。对于像 findall(A, season(A), As) 这样的目标 A 的每个答案被复制到某个空间。对于原子之类的东西来说,这是一个微不足道的操作,但可以想象一个更大的术语。说:

    责备([])。
    blam([L|L]):- blam(L)。

    bigterm(L) :- 长度(L,64), blam(L)。

    在许多系统中, findall/3assertz/1因为这个大术语将卡住系统。

    此外,像 SWI、YAP、SICStus 这样的系统确实有相当复杂的垃圾收集器。使用更少的故障驱动循环将有助于进一步改进这些系统,因为这会产生对 more sophisticated techniques 的需求。 .

    关于prolog - 使用手动列表迭代与失败递归的优缺点是什么,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9744641/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com