【问题标题】:How to move first N elements to the end of the list如何将前 N 个元素移动到列表的末尾
【发布时间】:2017-05-29 02:32:54
【问题描述】:

我想知道如何从列表中移动前 N 个元素并将它们放在最后。 例如:

[1,2,3,4] 我想移动前 2 个元素,所以结果将是 [3,4,1,2]

rule(List1,N,List2) :- length(List1,Y), ...

我不知道如何开始,有什么建议吗?

【问题讨论】:

  • 为什么需要第一个列表的长度?
  • [1] 应该关联什么?因此,您的示例中包含少于 2 个元素的列表。

标签: list prolog


【解决方案1】:

既然我们说的是谓词 - 即参数之间的真实关系 - 并且 Prolog 库内置函数在编写时考虑到了效率和通用性,您应该知道 - 例如 - length/2 可以生成列表,以及“测量”它的长度,并且 append/3 也可以将列表拆分一分为二。那么,你的任务可能是

'move first N elements to the end of the list'(L,N,R) :-
 length(-,-),
 append(-,-,-),
 append(-,-,-).

用适当的变量替换每个破折号,你会得到

?- 'move first N elements to the end of the list'([1,2,3,4],2,R).
R = [3, 4, 1, 2].

【讨论】:

    【解决方案2】:

    您可以选择对任务采用更一般的观点。如果你想一想,取出列表的前 N ​​个元素并将它们附加到末尾可以看作是向左旋转 N 步(想象一下列表元素排列成一个圆圈)。 @Willem Van Onsem 的回答中的谓词名称rotate/3 也表明了这一观点。您实际上可以将这样的谓词定义为真正的关系,即使其在各个方向上都起作用。此外,最好避免对参数施加不必要的限制,同时保留良好的终止属性。为了反映谓词的关系性质,让我们选择一个描述性名称。由于第三个参数是第一个参数列表的N 步骤的左旋转,所以我们可以将其称为list_n_lrot/3 并像这样定义它:

    :- use_module(library(lists)).
    :- use_module(library(clpfd)).
    
    list_n_lrot([],0,[]).                 % <- special case
    list_n_lrot(L,N,LR) :-
       list_list_samelength(L,LR,Len),    % <- structural constraint
       NMod #= N mod Len,
       list_n_heads_tail(L,NMod,H,T),
       append(T,H,LR).
    
    list_list_samelength([],[],0).
    list_list_samelength([_X|Xs],[_Y|Ys],N1) :-
       N1 #> 0,
       N0 #= N1-1,
       list_list_samelength(Xs,Ys,N0).
    
    list_n_heads_tail(L,N,H,T) :-
       if_(N=0,(L=T,H=[]),
               (N0#=N-1,L=[X|Xs],H=[X|Ys],list_n_heads_tail(Xs,N0,Ys,T))).
    

    现在让我们逐步了解定义并通过示例观察它的一些效果。 list_n_lrot/3 的第一条规则仅用于处理空列表的特殊情况:

    ?- list_n_lrot([],N,R).
    N = 0,
    R = [] ;
    false.
    
    ?- list_n_lrot(L,N,[]).
    L = [],
    N = 0 ;
    false.
    
    ?- list_n_lrot(L,N,R).
    L = R, R = [],
    N = 0 ;
    ...
    

    如果您不想在解决方案中包含这些案例,请忽略该规则。在整个谓词中,CLP(FD) 用于算术约束,因此list_n_lrot/3 的第二个参数可以是可变的,而不会导致实例化错误。目标list_list_samelength/2 是确保两个列表长度相同的结构约束。这有助于避免在只有第三个参数为ground 的情况下生成所有答案后出现无限循环(要看到这一点,请删除list_n_lrot/3 的前两个目标并将第三个目标替换为list_n_heads_tail(L,N,H,T),然后尝试查询?- list_n_lrot(L,N,[1,2,3]). )。这也是为什么最通用的查询是以公平的顺序列出解决方案的原因,即为每个列表长度产生所有可能性,而不是仅列出 0 步的轮换:

    ?- list_n_lrot(L,N,R).
    ...                                   % <- first solutions
    L = R, R = [_G2142, _G2145, _G2148],  % <- length 3, rotation by 0 steps
    N mod 3#=0 ;
    L = [_G2502, _G2505, _G2508],         % <- length 3, rotation by 1 step
    R = [_G2505, _G2508, _G2502],
    N mod 3#=1 ;
    L = [_G2865, _G2868, _G2871],         % <- length 3, rotation by 2 steps
    R = [_G2871, _G2865, _G2868],
    N mod 3#=2 ;
    ...                                   % <- further solutions
    

    最后,它还描述了两个列表的实际长度,用于在下一个目标中确定N对列表长度取模后的余数。考虑以下情况:如果将长度为 N 的列表旋转 N 步,您将再次到达初始列表。因此,旋转 N+1 步会产生与旋转 1 步相同的列表。从代数上讲,这个目标是利用同余模 N 将无限的整数集划分为有限数量的残差类的事实。因此,对于长度为 N 的列表,产生与 N 个残差类别相对应的 N 个旋转足以覆盖 所有 可能的旋转(请参阅上面的查询,了解 N=3)。另一方面,给定的 N > 列表长度 可以通过取其残差类的最小非负成员来轻松计算。例如,给定一个包含三个元素的列表,分别旋转 2 步或 5 步会产生相同的结果:

    ?- list_n_lrot([1,2,3],2,R).
    R = [3, 1, 2].
    
    ?- list_n_lrot([1,2,3],5,R).
    R = [3, 1, 2].
    

    当然,您也可以将列表左旋转负数步,即向另一个方向旋转:

    ?- list_n_lrot([1,2,3],-1,R).
    R = [3, 1, 2].
    

    附带说明:由于这构成向右旋转,您可以通过简单地编写来轻松定义右旋转:

    list_n_rrot(L,N,R) :-
       list_n_lrot(L,-N,R).
    
    ?- list_n_rrot([1,2,3],1,R).
    R = [3, 1, 2].
    

    谓词list_n_heads_tail/4 与Willem 帖子中的splitAt/4 非常相似。然而,由于if_/3 的使用,如果list_n_lrot/3 的列表之一和第二个参数是地面,则谓词确定性地成功(在唯一的答案之后无​​需点击;,因为没有打开不必要的选择点) :

    ?- list_n_lrot2([a,b,c,d,e],2,R).
    R = [c, d, e, a, b].
    
    ?- list_n_lrot2(L,2,[c,d,e,a,b]).
    L = [a, b, c, d, e].
    

    您可以观察到将 CLP(FD) 与最一般查询的第二种解决方案一起使用的另一个很好的效果:

    ?- list_n_lrot(L,N,R).
    L = R, R = [],
    N = 0 ;
    L = R, R = [_G125],       % <- here
    N in inf..sup ;           % <- here
    ...
    

    此答案指出,对于具有一个元素的列表,任意旋转任意步数都会再次产生相同的列表。所以原则上,这个单一的一般答案总结了无数具体的答案。此外,您还可以提出以下问题:关于两步旋转的列表有哪些?

    ?- list_n_lrot2(L,2,R).
    L = R, R = [_G19] ;
    L = R, R = [_G19, _G54] ;
    L = [_G19, _G54, _G22],
    R = [_G22, _G19, _G54] ;
    ...
    

    最后回到你问题中的例子:

    ?- list_n_lrot([1,2,3,4],2,R).
    R = [3, 4, 1, 2].
    

    请注意,这种在列表上定义任意旋转的更通用方法如何包含您将前 N 个元素重新定位到列表末尾的用例。

    【讨论】:

      【解决方案3】:

      试试这个

      despI([C|B],R):-append(B,[C|[]],R).
      desp(A,0,A).
      desp([C|B],N,R):-N1 is N - 1, despI([C|B],R1), desp(R1,N1,R),!.
      

      第一个谓词将一个元素移动到列表的末尾,然后我唯一要做的就是“重复”N次。

      ?-de([1,2,3,4],2,R).
      R = [3, 4, 1, 2].
      
      ?- de([1,2,3,4,5,6,7],4,R).
      R = [5, 6, 7, 1, 2, 3, 4].
      

      【讨论】:

      • 你为什么用[C|[]]而不是[C]
      【解决方案4】:

      我们可以使用分两个阶段工作的谓词来做到这一点:

      • 一个收集阶段:我们收集列表的第一个N项;和
      • 发射阶段:我们构造一个列表,在其中添加这些元素到尾部。

      让我们用单独的谓词构造两个阶段。对于收集阶段,我们可以使用以下谓词:

      % splitAt(L,N,L1,L2).
      
      splitAt(L,0,[],L).
      splitAt([H|T],N,[H|T1],L2) :-
          N > 0,
          N1 is N-1,
          splitAt(T,N1,T1,L2).
      

      现在对于发射阶段,我们可以使用append/3。那么完整的谓词是:

      rotate(L,N,R) :-
          splitAt(L,N,L1,L2),
          append(L2,L1,R).
      

      这给出了:

      ?- rotate([1,2,3,4],0,R).
      R = [1, 2, 3, 4] .
      
      ?- rotate([1,2,3,4],1,R).
      R = [2, 3, 4, 1] .
      
      ?- rotate([1,2,3,4],2,R).
      R = [3, 4, 1, 2] .
      
      ?- rotate([1,2,3,4],3,R).
      R = [4, 1, 2, 3] .
      
      ?- rotate([1,2,3,4],4,R).
      R = [1, 2, 3, 4].
      

      算法的工作时间O(n)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-03-17
        • 2011-06-22
        • 1970-01-01
        • 2011-12-19
        • 1970-01-01
        • 1970-01-01
        • 2017-07-10
        • 1970-01-01
        相关资源
        最近更新 更多