是否可以声明升序列表?

时间:2015-09-16 14:17:39

标签: list prolog clpfd

我可以像这样制作升序整数列表:

?- findall(L,between(1,5,L),List).

我知道我也可以使用以下方法生成值:

?- length(_,X).

但我不认为我可以在findall中使用它,如下面的循环:

?- findall(X,(length(_,X),X<6),Xs).

我还可以使用生成一个列表。

:- use_module(library(clpfd)).

list_to_n(N,List) :-
   length(List,N),
   List ins 1..N,
   all_different(List),
   once(label(List)).

list_to_n2(N,List) :-
   length(List,N),
   List ins 1..N,
   chain(List,#<),
   label(List).

最后一种方法对我来说最好,因为它是最具声明性的,不使用once/1between/3findall/3等。

还有其他方法吗?在纯粹的&#39;中是否有一种声明性的方式来做到这一点。 Prolog的?有没有最好的&#39;方式是什么?

3 个答案:

答案 0 :(得分:3)

“最佳”方式取决于您的具体用例!这是使用执行此操作的另一种方法:

:- use_module(library(clpfd)).

我们在a previous answer of a related question的评论中根据@mat的建议定义谓词equidistant_stride/2

equidistant_stride([],_).
equidistant_stride([Z|Zs],D) :- 
   foldl(equidistant_stride_(D),Zs,Z,_).

equidistant_stride_(D,Z1,Z0,Z1) :-
   Z1 #= Z0+D.

基于equidistant_stride/2,我们定义:

consecutive_ascending_integers(Zs) :-
   equidistant_stride(Zs,1).

consecutive_ascending_integers_from(Zs,Z0) :-
   Zs = [Z0|_],
   consecutive_ascending_integers(Zs).

consecutive_ascending_integers_from_1(Zs) :-
   consecutive_ascending_integers_from(Zs,1).

让我们运行一些查询!首先,您的原始用例:

?- length(Zs,N), consecutive_ascending_integers_from_1(Zs).
  N = 1, Zs = [1]
; N = 2, Zs = [1,2]
; N = 3, Zs = [1,2,3]
; N = 4, Zs = [1,2,3,4]
; N = 5, Zs = [1,2,3,4,5]
...

使用,我们可以提出相当一般的查询 - 并获得逻辑上合理的答案!

?- consecutive_ascending_integers([A,B,0,D,E]).
A = -2, B = -1, D = 1, E = 2.

?- consecutive_ascending_integers([A,B,C,D,E]).
A+1#=B, B+1#=C, C+1#=D, D+1#=E.

equidistant_stride/2的替代实现:

我希望新代码能更好地利用约束传播。

感谢@WillNess建议推动此重写的测试用例!

equidistant_from_nth_stride([],_,_,_).
equidistant_from_nth_stride([Z|Zs],Z0,N,D) :-
   Z  #= Z0 + N*D,
   N1 #= N+1,
   equidistant_from_nth_stride(Zs,Z0,N1,D).

equidistant_stride([],_).
equidistant_stride([Z0|Zs],D) :-
   equidistant_from_nth_stride(Zs,Z0,1,D).

使用@ mat的clpfd:

比较旧版本与新版本

首先,旧版本:

?- equidistant_stride([1,_,_,_,14],D).
_G1133+D#=14,
_G1145+D#=_G1133,
_G1157+D#=_G1145,
1+D#=_G1157.                               % succeeds with Scheinlösung

?- equidistant_stride([1,_,_,_,14|_],D).
  _G1136+D#=14, _G1148+D#=_G1136, _G1160+D#=_G1148, 1+D#=_G1160
; 14+D#=_G1340, _G1354+D#=14, _G1366+D#=_G1354, _G1378+D#=_G1366, 1+D#=_G1378
...                                        % does not terminate universally

现在让我们切换到新版本并询问相同的查询!

?- equidistant_stride([1,_,_,_,14],D).      
false.                                     % fails, as it should

?- equidistant_stride([1,_,_,_,14|_],D).
false.                                     % fails, as it should

更多,现在,再次!我们可以暂时通过暂时使用冗余约束来失败吗?

以前,我们建议使用约束Z1 #= Z0+D*1, Z2 #= Z0+D*2, Z3 #= Z0+D*3代替Z1 #= Z0+D, Z2 #= Z1+D, Z3 #= Z2+D (这个答案的第一个版本的代码确实如此)。

再次感谢@WillNess推动这个小实验 注意到目标equidistant_stride([_,4,_,_,14],D)没有失败,而是以未决目标成功:

?- Zs = [_,4,_,_,14], equidistant_stride(Zs,D).
Zs = [_G2650, 4, _G2656, _G2659, 14],
14#=_G2650+4*D,
_G2659#=_G2650+3*D,
_G2656#=_G2650+2*D,
_G2650+D#=4.

让我们用equidistantRED_stride/2添加一些冗余约束:

equidistantRED_stride([],_).
equidistantRED_stride([Z|Zs],D) :-
   equidistant_from_nth_stride(Zs,Z,1,D),
   equidistantRED_stride(Zs,D).

示例查询:

?- Zs = [_,4,_,_,14], equidistant_stride(Zs,D), equidistantRED_stride(Zs,D).
false.

完成?还没!通常,我们不希望二次数量的冗余约束。原因如下:

?- Zs = [_,_,_,_,14], equidistant_stride(Zs,D).
Zs = [_G2683, _G2686, _G2689, _G2692, 14],
14#=_G2683+4*D,
_G2692#=_G2683+3*D,
_G2689#=_G2683+2*D,
_G2686#=_G2683+D.

?- Zs = [_,_,_,_,14], equidistant_stride(Zs,D), equidistantRED_stride(Zs,D).
Zs = [_G831, _G834, _G837, _G840, 14],
14#=_G831+4*D,
_G840#=_G831+3*D,
_G837#=_G831+2*D,
_G834#=_G831+D,
14#=_G831+4*D,
_G840#=_G831+3*D,
_G837#=_G831+2*D,
_G834#=_G831+D,
D+_G840#=14,
14#=2*D+_G837,
_G840#=D+_G837,
14#=_G834+3*D,
_G840#=_G834+2*D,
_G837#=_G834+D.

但是如果我们使用双重否定技巧,残留物仍然存在于成功的情况下......

?- Zs = [_,_,_,_,14], equidistant_stride(Zs,D), \+ \+ equidistantRED_stride(Zs,D).
Zs = [_G454, _G457, _G460, _G463, 14],
14#=_G454+4*D,
_G463#=_G454+3*D,
_G460#=_G454+2*D,
_G457#=_G454+D.

......和......

?- Zs = [_,4,_,_,14], equidistant_stride(Zs,D), \+ \+ equidistantRED_stride(Zs,D).
false.

......我们在比以往更多的情况下检测到失败!

让我们深入挖掘一下!我们能否在更广泛的用途中及早发现失败?

到目前为止,使用代码,这两个逻辑错误的查询不会终止:

?- Zs = [_,4,_,_,14|_], \+ \+ equidistantRED_stride(Zs,D), equidistant_stride(Zs,D).
...                                        % Execution Aborted

?- Zs = [_,4,_,_,14|_], equidistant_stride(Zs,D), \+ \+ equidistantRED_stride(Zs,D).
...                                        % Execution Aborted

得到修复? 得到了黑客!

?- use_module(library(lambda)).
true.

?- Zs = [_,4,_,_,14|_], 
   \+ ( term_variables(Zs,Vs), 
        maplist(\X^when(nonvar(X),integer(X)),Vs),
        \+ equidistantRED_stride(Zs,D)),
   equidistant_stride(Zs,D).
false.

黑客不保证终止冗余约束“部分”,但IMO对于快速第一次射击来说并不算太糟糕。在integer/1中实例化任何变量时的测试Zs意味着允许求解器将变量域约束为单例,而使用cons对进行实例化(直接导致非基于列表的谓词的终止被抑制。

意识到黑客可以通过多种方式很容易地破解(例如,使用循环术语)。欢迎任何建议和意见!

答案 1 :(得分:3)

在下文中,我们将讨论this previous answer中提供的代码。

目标consecutive_ascending_integers_from_1([2,3,5,8|non_list])失败,但为什么?

让我们一步一步地进行:

  1. 以下是我们开始的代码:

    :- use_module(library(clpfd)).
    
    equidistant_from_nth_stride([],_,_,_).
    equidistant_from_nth_stride([Z|Zs],Z0,I0,D) :-
       Z  #= Z0 + I0*D,
       I1 #= I0 + 1,
       equidistant_from_nth_stride(Zs,Z0,I1,D).
    
    equidistant_stride([],_).
    equidistant_stride([Z0|Zs],D) :-
       equidistant_from_nth_stride(Zs,Z0,1,D).
    
    consecutive_ascending_integers(Zs) :-
       equidistant_stride(Zs,1).
    
    consecutive_ascending_integers_from(Zs,Z0) :-
       Zs = [Z0|_],
       consecutive_ascending_integers(Zs).
    
    consecutive_ascending_integers_from_1(Zs) :-
       consecutive_ascending_integers_from(Zs,1).
    
  2. 首先,我们使(某些)统一更明确:

    equidistant_from_nth_stride([],_,_,_).
    equidistant_from_nth_stride([Z|Zs],Z0,I0,D) :-
       Z  #= Z0 + I0*D,
       I1 #= I0 + 1,
       equidistant_from_nth_stride(Zs,Z0,I1,D).
    
    equidistant_stride([],_).
    equidistant_stride([Z0|Zs],D) :-
       I = 1,
       equidistant_from_nth_stride(Zs,Z0,I,D).
    
    consecutive_ascending_integers(Zs) :-
       D = 1,
       equidistant_stride(Zs,D).
    
    consecutive_ascending_integers_from(Zs,Z0) :-
       Zs = [Z0|_],
       consecutive_ascending_integers(Zs).
    
    consecutive_ascending_integers_from_1(Zs) :-
       Z0 = 1,
       consecutive_ascending_integers_from(Zs,Z0).
    
  3. 我们遵循方法和惯例introduced in this fine answer

      

    通过删除目标,我们可以推广一个程序。这是我最喜欢的方式。通过像这样添加谓词(*)/1

    :- op(920,fy, *).
    
    *_.
    
  4. @WillNess正确地指出:

      

    consecutive_ascending_integers_from_1([2|_])失败,因此其专业化consecutive_ascending_integers_from_1([2,3,5,8|non_list])也必定失败。

    如果最大限度地概括代码以使consecutive_ascending_integers_from_1([2|_])失败,我们“确切地知道:程序的可见剩余部分中的某些内容必须修复。”

  5.     consecutive_ascending_integers_from(Zs,Z0) :-
           Zs = [Z0|_],
           * consecutive_ascending_integers(Zs).
    
        consecutive_ascending_integers_from_1(Zs) :-
           Start = 1,
           consecutive_ascending_integers_from(Zs,Start).
    
    1. 让我们另一个解释!

      对于版本#2(见上文),我们观察到以下广义目标也失败了:

      ?- consecutive_ascending_integers_from_1([_,_,_,_|non_list]).
      false.
      

      为什么失败?让我们最大限度地概括代码,使目标失败:

    2.     equidistant_from_nth_stride([],_,_,_).
          equidistant_from_nth_stride([Z|Zs],Z0,I0,D) :-
             * Z  #= Z0 + I0*D,
             * I1 #= I0 + 1,
             equidistant_from_nth_stride(Zs,Z0,I1,D).
      
          equidistant_stride([],_).
          equidistant_stride([Z0|Zs],D) :-
             * I = 1,
             equidistant_from_nth_stride(Zs,Z0,I,D).
      
          consecutive_ascending_integers(Zs) :-
             * D = 1,
             equidistant_stride(Zs,D).
      
          consecutive_ascending_integers_from(Zs,Z0) :-
             * Zs = [Z0|_],
             consecutive_ascending_integers(Zs).
      
          consecutive_ascending_integers_from_1(Zs) :-
             * Start = 1,
             consecutive_ascending_integers_from(Zs,Start).
      

      为什么目标consecutive_ascending_integers_from_1([2,3,5,8|non_list])会失败?

      到目前为止,我们已经看到了两种解释,但可能还有更多......

      事实就是这样:加入狩猎!

答案 2 :(得分:1)

我们将升序列表定义为包含至少两个增加整数的元素(非递减列表可以为空或单例,但“升序”是更明确的属性)。这有点武断。

在SWI Prolog:

ascending( [A,B|R] ):-
   freeze(A, 
      freeze(B, (A < B, freeze(R, (R=[] -> true ; ascending([B|R])))) )).

为了轻松填写,我们可以使用

mselect([A|B],S,S2):- select(A,S,S1), mselect(B,S1,S2).
mselect([], S2, S2).

测试:

  

15? - 上升(LS),mselect(LS,[10,2,8,5],[])。
  LS = [2,5,8,10] ;
  错误。

   
  16? - mselect(LS,[10,2,8,5],[]),升序(LS)。
  LS = [2,5,8,10] ;
  假的。

关于赏金问题,根据https://stackoverflow.com/tags/logical-purity/info

  

只有单调(也称为“单调”)谓词是纯粹的:如果谓词对任何参数都成功,那么它对于这些参数的任何泛化都不会失败,并且如果它对任何参数组合都失败,那么它对这些论点的任何专业化都没有成功。

consecutive_ascending_integers_from_1([2|B])失败,因此其专业化consecutive_ascending_integers_from_1([2,3,5,8|non_list])也必定失败。

对于延期奖金" consecutive_ascending_integers_from_1([2,3,5,8|non_list]) fails, but why?",其他失败的目标是:( 1

consecutive_ascending_integers_from_1([_,3|_])

代码

equidistant_from_nth_stride([],_,_,_).     
equidistant_from_nth_stride([Z|Zs],Z0,I0,D) :-
   Z  #= Z0 + I0*D,                        % C1
   *( I1 #= I0 + 1 ),
   equidistant_from_nth_stride(Zs,Z0,I1,D).

,其余未更改,因为C1变为3 #= 1 + 1*1。此外,( 2 3

consecutive_ascending_integers_from_1([A,B,5|_])
consecutive_ascending_integers_from_1([A,B,C,8|_])

都会因未更改的代码而失败,因为第1个定义了

A = 1, B #= 1 + 1*1, 5 #= 1 + 2*1

和第二个定义

A = 1, B #= 1 + 1*1, C #= 1 + 2*1, 8 #= 1 + 3*1

另一种可能性( 4 )是

consecutive_ascending_integers_from_1([_,3,5|_])

与广义

consecutive_ascending_integers_from_1(Zs) :-
   *( Z0 = 1 ),
   consecutive_ascending_integers_from(Zs,Z0).

consecutive_ascending_integers_from(Zs,Z0) :-
   *( Zs = [Z0|_] ),
   consecutive_ascending_integers(Zs).

因为

26 ?- 3 #= Z + 1*1, 5 #= Z + 2*1.
false.

同样,使用类似修改后的代码,目标( 5

consecutive_ascending_integers_from_1([_,3,_,8|_])

,因为

27 ?- 3 #= Z + 1*1, 8 #= Z + 3*1.
false.

以及( 6 ... 9

consecutive_ascending_integers_from_1([2,3,_,8|_])
consecutive_ascending_integers_from_1([2,_,_,8|_])
consecutive_ascending_integers_from_1([2,_,5,8|_])
consecutive_ascending_integers_from_1([2,_,5|_])

出于同样的原因。另一种可能的代码概括是保持D未初始化(原始代码的其余部分保持不变):

consecutive_ascending_integers(Zs) :-
   *( D = 1 ),
   equidistant_stride(Zs,D).

因为目标( 5 ...[_,3,_,8|_]...再次失败,因为

49 ?- 3 #= 1 + 1*D, 8 #= 1 + 3*D.
false.

但是,

50 ?- 3 #= 1 + 1*D, 5 #= 1 + 2*D.
D = 2.

所以...[_,3,5|_]...会成功(确实如此)。 ( 10

consecutive_ascending_integers_from_1([_,_,5,8|_])
由于同样的原因,

也失败了。

可能会有更多的组合,但它的一般要点变得更加清晰:这一切都取决于该谓词创建的约束如何运作。

相关问题