在SWI-Prolog中打破基于/3的"loop",同时保持其后面的选择点



我需要在从1到Length的一系列数字上迭代(例如使用between/3(,其中Length是给定列表的长度。迭代后的数字,比如N,然后应用于谓词,比如do_something,直到它停止失败。也就是说,do_something搜索正确的N,之后必须丢弃between/3的所有选择点。然而,必须执行应用了适当Ndo_something的所有选择点。

第一次尝试是这样的:

% main(+InputList, -N, -OutputList)
main(InputList, N, OutputList) :-
length(InputList, Length),
between(1, Length, N),
% cut all between/3 choice points as soon as Num is instantiated
freeze(Num, !),
do_something(InputList, N, OutputList),
Num is N.

这并没有起到作用,因为freeze/2没有削减between/3的选择点。带有when/2的版本也不起作用。我想,这是因为freeze/2when/2都是在一个单独的线程中执行的,不会影响between/3所在的主线程

过了一段时间,我得到了以下代码,它可以完成所需的操作,但效率低下:

main(InputList, N, OutputList) :-
length (InputList, Length),
between(1, Length, N),
do_something(InputList, N, _),
% cut all prior choice points as soon as proper N is found
!,
% start do_something over!
do_something(InputList, N, OutputList).

无效性是CCD_ 17的具有适当CCD_ 18的选择点被执行两次。先是在切割前,然后在切割后再次。

尽管此解决方案适用于我的情况,但它不适用于所有do_something选择点必须只执行一次的一般情况。

注意,CCD_ 20被要求是CCD_ 21范围之外的最小值,并且不是预先已知的。因此用CCD_ 22进行搜索。

有更好的解决方案吗?有没有一种方法可以实现类似于between/3的谓词,当以某种方式发出信号时可以停止?有没有一个专门的内置谓词可以满足需要?任何富有成果的想法都将受到高度赞赏。

使用*->/2还有另一种可能性,它是->/2的变体,不会杀死条件的选择点。现在我们不在了,我们想杀死一个旧的选择点。我不知道是否有Prolog系统可以做到这一点。大多数系统都有杀死所有选择点的规定,因为有些标记,但我不知道有哪个系统会杀死特定的选择点。因此,我们必须插入一些代码来有条件地停止进一步的处理。这导致:

main(InputList, N, OutputList) :-
length(InputList, Length),
State = state(cont),
between(1, Length, N),
(   State = state(cont)
->  true
;   !,
fail
),
(   do_something(InputList, N, OutputList)
*-> nb_setarg(1, State, stop)
;   fail
).

这是完全不可移植的,尽管许多系统都有*->(有时命名为if/3(,并且许多系统都具有某种形式的不可回溯的赋值,而如果你非常绝望,你可以使用断言/收回来实现这一点。

在线查看SWISH

保罗的答案当然更容易理解。不过,这应该更快,并且在返回第一个之前不会评估do_something的所有解决方案,也不会评估do_something两次。

希望我理解您的问题描述:

main(InputList, N, OutputList) :-
length(InputList, Length),
between(1, Length, N),
findall(
OutputList0,
do_something(InputList,N,OutputList0),
OutputLists
),
% cut all prior choice points as soon as proper N is found
OutputLists = [_|_],
!,
member(OutputList, OutputLists).

findall/3调用将返回Solutions = [],直到do_something/3谓词成功。当这种情况发生时,findall/3调用确保对于N的值,访问do_something(InputList, N, OutputList)的所有选择点。接下来的剪切将固定N的值,您可以从此处开始。

p.S.更新了您在评论中描述的更改,使其适用于您的案例。如果你不想收集所有的解决方案,有一些不可移植的技巧只能找到给定数量的解决方案。

事实证明between/3是一种干扰。我们不需要它,因此一个简单、高效和便携的解决方案是可能的:

main(InputList, N, OutputList) :-
length(InputList, Length),
Length >= 1,
main(1, Length, InputList, OutputList).
main(N, Length, InputList, OutputList) :-
(   do_something(InputList, N, OutputList) *->
true
;   N < Length,
M is N + 1,
main(M, Length, InputList, OutputList)
).

与Jan的解决方案一样,它不会在返回第一个之前评估do_something/3的所有解决方案,也不会两次评估谓词。但它也不需要令人讨厌和不可移植的nb_setarg/2谓词技巧。

注意,正如Jan所说,软切割控制结构*->/2或其if/3元谓词变体可以在几个Prolog系统中找到(包括CxProlog、Ciao Prolog、ECLiPSe、GNU Prolog、JIProlog、SICStus Prolog、SWI Prolog和YAP(。

附言:我保留了我的第一个答案,因为它更便携,并举例说明了一种可能对其他问题有用的模式。

最新更新