简体   繁体   English

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

[英]How to move first N elements to the end of the list

I am wondering how can I move first N elements from a list and put them at the end. 我想知道如何从列表中移动前N个元素并将它们放在最后。 For example: 例如:

[1,2,3,4] and I want to move first 2 elements , so the result will be [3,4,1,2] . [1,2,3,4]我想移动前2个元素,结果将是[3,4,1,2]

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

I don't know how to start, any advice ? 我不知道怎么开始,有什么建议吗?

Since we are speaking of predicates - ie true relations among arguments - and Prolog library builtins are written with efficiency and generality in mind, you should know that - for instance - length/2 can generate a list, as well as 'measuring' its length, and append/3 can also split a list in two. 既然我们说的是谓词 - 即参数之间的真实关系 - 而Prolog库内置函数在编写时考虑了效率和通用性,你应该知道 - 例如 - length / 2可以生成一个列表,以及'测量'它的长度和append / 3也可以列表分成两部分。 Then, your task could be 然后,你的任务可能是

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

Replace each dash with an appropriate variable, and you'll get 用适当的变量替换每个破折号,你就会得到

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

You could opt to adopt a more general perspective on the task. 您可以选择对该任务采用更一般的视角。 If you think about it, taking the first N elements of a list and appending them at the end can be seen as a rotation to the left by N steps (just imagine the list elements arranged in a circle). 如果你考虑一下,取一个列表的前N个元素并在末尾附加它们可以看作是向左旋转N个步骤(想象一下列表元素排列成一个圆圈)。 The predicate name rotate/3 in @Willem Van Onsem's answer also indicates this perspective. @Willem Van Onsem的答案中的谓词名称rotate/3也表明了这一观点。 You can actually define such a predicate as a true relation, that is making it work in all directions. 实际上,您可以将这样的谓词定义为真正的关系,即使其在所有方向上都能正常工作。 Additionally it would be desirable to abstain from imposing unnecessary restrictions on the arguments while retaining nice termination properties. 另外,希望避免对参数施加不必要的限制,同时保留良好的终止属性。 To reflect the relational nature of the predicate, let's choose a descriptive name. 为了反映谓词的关系性质,让我们选择一个描述性的名称。 As the third argument is the left rotation by N steps of the list that is the first argument, let's maybe call it list_n_lrot/3 and define it like so: 因为第三个参数是作为第一个参数的列表的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))).

Now let's step through the definition and observe some of its effects by example. 现在让我们逐步完成定义并通过示例观察它的一些效果。 The first rule of list_n_lrot/3 is only included to deal with the special case of empty lists: 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 ;
...

If you don't want to include these cases in your solution just omit that rule. 如果您不想在解决方案中包含这些案例,请忽略该规则。 Throughout the predicates CLP(FD) is used for arithmetic constraints, so the second argument of list_n_lrot/3 can be variable without leading to instantiation errors. 在整个谓词中,CLP(FD)用于算术约束,因此list_n_lrot/3的第二个参数可以是变量而不会导致实例化错误。 The goal list_list_samelength/2 is a structural constraint to ensure the two lists are of same length. 目标list_list_samelength/2是结构约束,以确保两个列表具有相同的长度。 This helps avoiding an infinite loop after producing all answers in the case that only the third argument is ground (to see this, remove the first two goals of list_n_lrot/3 and replace the third with list_n_heads_tail(L,N,H,T) and then try the query ?- list_n_lrot(L,N,[1,2,3]). ). 这有助于避免在仅产生第三个参数的情况下产生所有答案之后的无限循环(要看到这一点,删除list_n_lrot/3的前两个目标并用list_n_heads_tail(L,N,H,T)替换第三个目标,然后尝试查询?- list_n_lrot(L,N,[1,2,3]). )。 It's also the reason why the most general query is listing the solutions in a fair order, that is producing all possibilities for every list length instead of only listing the rotation by 0 steps: 这也是为什么最常见的查询以公平顺序列出解决方案的原因,即为每个列表长度生成所有可能性,而不是仅按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

Finally, it also describes the actual length of the two lists, which is used in the next goal to determine the remainder of N modulo the length of the list. 最后,它还描述了两个列表的实际长度,这两个列表在下一个目标中用于确定N的剩余部分以模块的长度为模。 Consider the following: If you rotate a list of length N by N steps you arrive at the initial list again. 请考虑以下事项:如果您将长度为N的列表旋转N步,则会再次到达初始列表。 So a rotation by N+1 steps yields the same list as a rotation by 1 step. 因此,按N + 1步进行旋转会产生与旋转1步相同的列表。 Algebraically speaking, this goal is exploiting the fact that congruence modulo N is partitioning the infinite set of integers into a finite number of residue classes. 从代数的角度来说,这个目标正在利用同余模N的事实将无限整数集划分为有限数量的残差类。 So for a list of length N it is sufficient to produce the N rotations that correspond to the N residue classes in order to cover all possible rotations (see the query above for N=3). 因此,对于长度为N的列表,足以产生对应于N个残差类别的N个旋转,以便覆盖所有可能的旋转(参见上面对N = 3的查询)。 On the other hand, a given N > list length can be easily computed by taking the smallest non-negative member of its residue class. 另一方面,通过取其残差等级的最小非负成员,可以容易地计算给定的N>列表长度 For example, given a list with three elements, a rotation by 2 or 5 steps respectively yields the same result: 例如,给定一个包含三个元素的列表,分别通过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].

And of course you could also left rotate the list by a negative number of steps, that is rotating it in the other direction: 当然,您也可以通过负数旋转列表,即将其旋转到另一个方向:

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

On a side note: Since this constitutes rotation to the right, you could easily define right rotation by simply writing: 在旁注:由于这构成向右旋转,您可以通过简单地编写来轻松定义右旋转:

list_n_rrot(L,N,R) :-
   list_n_lrot(L,-N,R).

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

The predicate list_n_heads_tail/4 is quite similar to splitAt/4 in Willem's post. 谓词list_n_heads_tail/4颇为相似splitAt/4在威廉的职位。 However, due to the use of if_/3 the predicate succeeds deterministically (no need to hit ; after the only answer since no unnecessary choicepoints are left open), if one of the lists and the second argument of list_n_lrot/3 are ground: 但是,由于使用了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].

You can observe another nice effect of using CLP(FD) with the second solution of the most general query: 您可以观察到使用CLP(FD)与最常见查询的第二个解决方案的另一个很好的效果:

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

This answer states, that for a list with one element any rotation by an arbitrary number of steps yields the very same list again. 这个答案指出,对于具有一个元素的列表,任意数量的步骤的任何旋转都会再次产生相同的列表。 So in principle, this single general answer summarizes an infinite number of concrete answers. 所以原则上,这个单一的一般性答案总结了无数的具体答案。 Furthermore, you can also ask questions like: What lists are there with regard to a rotation by 2 steps? 此外,您还可以提出以下问题:通过2个步骤进行旋转的列表是什么?

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

To finally come back to the example in your question: 最后回到你问题中的例子:

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

Note how this more general approach to define arbitrary rotations on lists subsumes your use case of relocating the first N elements to the end of the list. 请注意,在列表上定义任意旋转的这种更通用的方法包含了将前N个元素重定位到列表末尾的用例。

Try this 尝试这个

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

The first predicate moves one element to the end of the list, then the only thing I do is "repeat" that N times. 第一个谓词将一个元素移动到列表的末尾,然后我唯一要做的就是“重复”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].

We can do this with a predicate that works in two phases: 我们可以使用一个分为两个阶段的谓词来做到这一点:

  • a collect phase: we collect the first N items of the list; 收集阶段:我们收集列表的前N项; and
  • an emit phase: we construct a list where we add these elements at the tail. 发射阶段:我们构造一个列表,我们在尾部添加这些元素。

Let is construct the two phases with separate predicate. 让我们用单独的谓词构造两个阶段。 For the collect phase, we could use the following predicate: 对于收集阶段,我们可以使用以下谓词:

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

Now for the emit phase, we could use append/3 . 现在对于发射阶段,我们可以使用append/3 So then the full predicate is: 那么完整的谓词是:

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

This gives: 这给出了:

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

The algorithm works in O(n) . 该算法在O(n)中工作

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM