如何将前N个元素移动到列表的末尾

时间:2017-05-22 20:56:25

标签: list prolog

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

[1,2,3,4]我希望移动前2个元素,因此结果将是[3,4,1,2]

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

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

4 个答案:

答案 0 :(得分:3)

既然我们说的是谓词 - 即参数之间的真实关系 - 而Prolog库内置函数在编写时考虑了效率和通用性,你应该知道 - 例如 - length / 2可以生成 a列表,以及'测量'它的长度和追加/ 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].

答案 1 :(得分:3)

您可以选择对该任务采用更一般的观点。如果你考虑一下,取一个列表的前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是一个结构约束,以确保两个列表具有相同的长度。这有助于在产生所有答案之后避免无限循环,只有第三个参数是基础的(要看到这个,删除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的使用,谓词在确定性上成功(在唯一的答案之后无​​需点击;,因为没有不必要的选择点被打开),如果其中一个列表和{的第二个参数{ {1}}是基础:

list_n_lrot/3

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

?- 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].

这个答案指出,对于具有一个元素的列表,通过任意数量的步骤进行任何旋转都会再次产生相同的列表。所以原则上,这个单一的一般性答案总结了无数的具体答案。此外,您还可以提出以下问题:通过2个步骤进行旋转的列表是什么?

?- 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] ;
...

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

答案 2 :(得分:1)

试试这个

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),!.

第一个谓词将一个元素移动到列表的末尾,然后我唯一要做的就是&#34;重复&#34; 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].

答案 3 :(得分:1)

我们可以使用分为两个阶段的谓词来完成此任务:

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

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

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