在SWI-Prolog中基于/ 3的“循环”之间切换,同时保持其遵循的选择点

时间:2018-11-20 07:52:55

标签: loops prolog prolog-cut

我需要遍历从1到between/3的数字范围(例如,使用Length),其中Length是给定列表的长度。然后,将循环数N应用于谓词do_something,直到停止失败为止。也就是说,do_something搜索正确的N,之后必须丢弃between/3的所有选择点。但是,必须执行do_something中所有应用了正确的N的选择点。

拳头尝试是这样的:

% 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).

效率低下是do_something具有正确的N的选择点被执行了两次。首先在切割之前,然后在切割之后再次。

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

请注意,N的最小值必须超出1..Length范围,并且事先未知。因此,用do_something搜索它。

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

3 个答案:

答案 0 :(得分:3)

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

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的值,您可以从那里开始。

PS 。已更新,其中包含您在评论中描述的更改,以使其适用于您的情况。如果您不想收集全部解决方案,则有一些非便携式黑客只能找到给定数量的解决方案。

答案 1 :(得分:3)

使用*->/2还有另一种可能性,它是-> / 2的变体,不会杀死条件的选择点。现在我们不在那里想要杀死一个 older 选择点。我不知道是否有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上在线查看

Paulo的答案当然更容易移植。不过,这应该更快一些,并且不会在返回第一个之前先评估do_something的所有解,也不会对do_something进行两次评估。

答案 2 :(得分:2)

事实证明,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的解决方案,也不会两次评估谓词。但这也不需要讨厌且不可移植的*->/2谓词技巧。

请注意,正如Jan所言,软剪切控制结构if/3或其jj <- data.frame(TrainNum = 2100, SeqNum = 1:30) jj %>% mutate(SeqNum = str_pad(SeqNum, width = 2, pad = "0")) unite(tnSn, TrainNum, SeqNum) %>% arrange(tnSn) 元谓词变体可以在多个Prolog系统中找到(包括一种或多种形式的CxProlog,Ciao Prolog,ECLiPSe,GNU Prolog,JIProlog,SICStus Prolog,SWI-Prolog和YAP)。

P.S。我保留了第一个答案,因为它更易于移植,并举例说明了一种可能对其他问题有用的模式。