【问题标题】:Prolog Out of stack errorProlog 出栈错误
【发布时间】:2017-03-11 03:39:34
【问题描述】:

我正在处理来自99 Prolog Problems 的问题 26:

P26 (**) 生成从 K 个不同对象中选择的组合 列表的 N 个元素

例子:

?- combination(3,[a,b,c,d,e,f],L).

L = [a,b,c] ;

L = [a,b,d] ;

L = [a,b,e] ;

所以我的程序是:

:- use_module(library(clpfd)).

combination(0, _, []).
combination(Tot, List, [H|T]) :- 
    length(List, Length), Tot in 1..Length,
    append(Prefix, [H], Stem), 
    append(Stem, Suffix, List), 
    append(Prefix, Suffix, SubList),
    SubTot #= Tot-1,
    combination(SubTot, SubList, T).

我的查询结果开始正常,但随后返回全局堆栈错误:

?- combination(3,[a,b,c,d,e,f],L).
L = [a, b, c] ;
L = [a, b, d] ;
L = [a, b, e] ;
L = [a, b, f] ;
Out of global stack

我不明白为什么它一开始可以工作,但后来挂起,直到它给出全局堆栈错误。发生在终端的 SWISH 和 swi-prolog 上。

【问题讨论】:

  • 你试过trace吗?您可能会发现,一旦您的程序找到了解决方案,它就会不断地使用append 创建新的、不断增长的列表,它可以尝试以找到它永远无法满足的其他解决方案。你的第一个 append(Prefix, [H], Stem) 有两个变量,所以它们会无限增长。
  • @lurker,是的,我似乎有 traced,debugged,guitraced 一切,程序总是在生成 L = [a, b, f] ; 后挂起,直到出现堆栈错误(实际上使用debug 作为查询的第一个子句,那么错误就永远不会出现并且永远挂起)。如果我把trace放在append(Prefix, [H], Stem)之前,我会得到以下输出:Call:lists:append(_14580, [_14486], _14584)Call:lists:append(_14592, [_14498], _14596)Call:lists:append(_14604, [_14510], _14608)L = [a, b, c]L = [a, b, d]L = [a, b, d]L = [a, b, e]L = [a, b, f]Out of global stack

标签: prolog permutation combinatorics backtracking failure-slice


【解决方案1】:

如果您尝试在控制台提示符下输入这行代码,并要求回溯:

?- append(Prefix, [H], Stem).
Prefix = [],
Stem = [H] ;
Prefix = [_6442],
Stem = [_6442, H] ;
Prefix = [_6442, _6454],
Stem = [_6442, _6454, H] ;
...

也许您对(主要)问题有所了解。所有 3 个变量都是免费的,然后 Prolog 继续生成越来越长的回溯列表。正如鲍里斯已经建议的那样,你应该让你的程序更简单......例如

combination(0, _, []).
combination(Tot, List, [H|T]) :- 
    Tot #> 0,
    select(H, List, SubList),
    SubTot #= Tot-1,
    combination(SubTot, SubList, T).

产生

?- aggregate(count,L^combination(3,[a,b,c,d,e],L),N).
N = 60.

恕我直言,library(clpfd) 不会让您的生活变得更简单,而您正在迈出迈向 Prolog 的第一步。使用可用的基本结构对普通 Prolog 进行建模和调试已经很困难,而 CLP(FD) 是一项高级功能...

【讨论】:

  • 谢谢。首先,我为有助于说明问题的信息查询?- append(Prefix, [H], Stem) 选择了这个答案。其次,我不知道 select 谓词,这肯定有帮助。第三,为什么 CLP(FD) 是高级功能? SWI-Prolog 文档实际上说相反,CPL(FD) 是一个很好的默认值,低级谓词应该留给高级用户:swi-prolog.org/pldoc/man?section=clpfd
  • Prolog值得自己研究。 CLP(FD) 基于不同的概念,在许多 Prolog 实现中不可用。我认为对 Prolog 有很好的理解是使用 CLP(FD) 的先决条件,因为几个基本功能混合得很好。用 Prolog 解决问题不会教如何在 CLP(FD) 中解决,反之亦然。实际上,存在许多其他 CP(约束编程)实现,由(通常)过程语言托管。这是一件大事,真的......
  • @RobertL.SimioneII SWI-Prolog 实现中关于 CLP(FD) 的整章由 library(clpfd) 的作者编写。我不相信 CLP(FD) 是否是整数运算的良好默认值是否存在普遍共识。对于is/2 和朋友就足够的情况,它肯定会施加运行时惩罚。我个人有偏见的看法是,如果您了解 CLP(FD) 的局限性和权衡取舍,并且知道 何时 以及 为什么 使用它,那么 CLP(FD) 会很棒。
  • @RobertL.SimioneII 就像any 有用的抽象一样,CLP(FD) 是一个leaky 抽象。出于哲学和意识形态领域的原因,一些计算机科学家认为只教授泄漏抽象是可以的。
  • @Boris:感谢您向我介绍leaky abstraction 概念。它很好地总结了我对这个话题的感受(我总是羡慕你对英语的控制:)。
【解决方案2】:

我不明白为什么它一开始可以工作,但后来一直挂起,直到它给出 Out of global stack 错误。

Prolog 为特定查询生成的答案以增量方式显示。也就是说,实际答案是根据需要延迟生成的。首先,有一些你期望的答案,然后遇到了一个循环。为确保查询完全终止,您必须遍历所有查询,点击 SPACE/或 ;每时每刻。但是有一个更简单的方法:

只需在查询末尾添加 false。现在,所有的答案都被压制了:

?- 组合(3,[a,b,c,d,e,f],L),。 错误:超出全局堆栈

通过在您的程序中添加更多 false 目标,您可以定位真正的罪魁祸首。请参阅下面我的所有尝试:我从第一次尝试开始,然后进一步添加 false,直到找到终止片段 ()。

组合(0,_,[]):- false。 % 第一 组合(Tot,列表,[H|T]):- length(List, Length), Tot in 1..Length, % 4th 终止 append(Prefix, [H], Stem), false, % 3rd 循环 append(Stem, Suffix, List), false, % 2nd 循环 追加(前缀,后缀,子列表)SubTot #= Tot-1, false, % 1st 循环 组合(SubTot,SubList,T)

要消除不终止的问题,您必须修改剩余可见部分中的某些内容。显然,PrefixStem 都是第一次出现在这里。

【讨论】:

    【解决方案3】:

    在这种情况下使用 library(clpfd) 非常可疑。在length(List, Length) 之后,Length 肯定会绑定一个非负整数,那么为什么要约束呢?而且您的Tot in 1..Length 也很奇怪,因为您在递归的每一步中都不断创建一个新的约束变量,并且您尝试将其与 0 统一。我不确定我是否理解您的逻辑总体:-(

    如果我了解练习的要求,我会建议以下更简单的方法。首先,确保您的 K 不大于元素的总数。然后,一次只选择一个元素,直到你有足够的。它可能是这样的:

    k_comb(K, L, C) :-
        length(L, N),
        length(C, K),
        K =< N,
        k_comb_1(C, L).
    
    k_comb_1([], _).
    k_comb_1([X|Xs], L) :-
        select(X, L, L0),
        k_comb_1(Xs, L0).
    

    这里的重要信息是,定义递归的是列表本身,你真的不需要计数器,更不用说有约束的了。

    select/3 是一个教科书谓词,我想你也应该在标准库中找到它;无论如何,请参阅 here 了解实现。

    执行以下操作:

    ?- k_comb(2, [a,b,c], C).
    C = [a, b] ;
    C = [a, c] ;
    C = [b, a] ;
    C = [b, c] ;
    C = [c, a] ;
    C = [c, b] ;
    false.
    

    还有你的例子:

    ?- k_comb(3, [a,b,c,d,e,f], C).
    C = [a, b, c] ;
    C = [a, b, d] ;
    C = [a, b, e] ;
    C = [a, b, f] ;
    C = [a, c, b] ;
    C = [a, c, d] ;
    C = [a, c, e] ;
    C = [a, c, f] ;
    C = [a, d, b] ;
    C = [a, d, c] ;
    C = [a, d, e] ;
    C = [a, d, f] ;
    C = [a, e, b] ;
    C = [a, e, c] . % and so on
    

    请注意,这不会检查第二个参数中列表的元素是否确实是唯一的;它只是从不同位置获取元素。

    此解决方案仍然存在终止问题,但我不知道这是否与您相关。

    【讨论】:

    • 谢谢鲍里斯。 Tot in 1..Length 是一项检查,即您从列表中要求的项目数量不超过列表中的项目数量,正如您所指出的“首先,确保您的 K 不大于元素总数”。另外,我不知道选择谓词,这肯定有帮助。我认为你的观点是“这里的重要信息是列表本身定义了递归,你真的不需要计数器,更不用说有约束的计数器了。”对我来说,这既是练习的重要部分又是棘手的部分。
    • @RobertL.SimioneII 如果你知道Length 是一个整数,那么就有between(1, Length, Tot)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多