How to move first N elements to the end of the list for each list in the list of lists

Viewed 249

Is there any way in Prolog how can I move first N elements from a list and put them at the end. I wanna do this for each list in the list of lists. Example:

Input:

move(3, [[1, 2, 3, 4, 5, 6, 7, 8, 9], [1,2,3], [3, 4, 5, 6, 7, 8, 9, 10, 11, 12]], R)

Result:

R = [[4, 5, 6, 7, 8, 9, 1, 2, 3], [1,2,3], [6, 7, 8, 9, 10, 11, 12, 3, 4, 5]]

I have no idea, does anyone know how to do that?

4 Answers

If you execute the following query, Left and Right will be bound to two lists that, when concatenated together, result in [1,2,3,4,5]:

?- append(Left, Right, [1,2,3,4,5]).

The above query admits 6 solutions (Left being empty, the list [1], the list [1,2], etc.). You can constrain the query by saying that Left exactly has three elements:

?- length(Left, 3), append(Left, Right, [1,2,3,4,5]).
Left = [1, 2, 3]
Right = [4, 5]

This has only one solution.

The rotation consists in producing a list where the Left part is on the right of the Right list:

?- length(Left, 3), 
   append(Left, Right, [1,2,3,4,5]),
   append(Right, Left, Result).
Left = [1, 2, 3]
Right = [4, 5]
Result = [4, 5, 1, 2, 3]

The first predicate you need to write is thus:

rotate_left(List, Offset, Rotated) :-
  length(Left, Offset),
  append(Left, Right, List),
  append(Right, Left, Rotated).

Then you can apply this predicate on a list of lists.


NB. you may consider using this version instead:

rotate_left(List, Offset, Rotated) :-
    append(Left, Right, List),
    length(Left, Offset),
    append(Right, Left, Rotated).

It is a bit better because it allows Offset to be variable:

[eclipse 2]: rotate_left([a, b, c, d], N, [c, d, a, b]).

N = 2
Yes (0.00s cpu, solution 1, maybe more) ? ;

No (0.00s cpu)

With the original version, length(Left, N) has both its arguments variable, and it keeps producing answers with longer and longer lists.

You are doing the same operation on each element of the list-of-lists above, i.e., take first three elements and append them at the back. So if you can define the operation itself first then it will be easy to do it for a list of lists.

% If the predicate op/3 is already defined the you can easily define
% another one for a list the elements
op_for_lists([], _, []).
op_for_lists([X|Xs], N, [Y|Ys]) :-
    operation(X, N, Y), op_for_lists(Xs, N, Ys).

So if you take @coredump's answer and replace operation with rotate_left it will work.

You can also use maplist/3 with lambdas from yall library as follows

op_for_lists(Xs, N, Ys) :-
  maplist({N}/[In, Out]>>operation(In, N, Out), Xs, Ys).

It is easier if the constant argument is at the start, then you can simply use

maplist(operation(N), Xs, Ys)

to get the result for list-of-lists

Here's my approach:

First: Extract the number of starting elements. Here M will work as a counter that will tell us when to stop when extracting the starting element. The 0 in the base case is the indication to stop.

 s3(0,Rest,[], Rest):-!.
s3(M,[H|T],[H|R], Rest):-
    M1 is M-1,
    s3(M1,T,R, Rest).

Second: We need to append Rest (J) [which contains the remaining elements of the list] and R (Q) [which contains the starting elements].

append(J,Q,L)

Third: Putting the code altogether.

s3(0,Rest,[], Rest):-!.
s3(M,[H|T],[H|R], Rest):-
    M1 is M-1,
    s3(M1,T,R, Rest).

q1(M,[H|T],L):-
    s3(M,[H|T],Q,J),
    append(J,Q,L).

Example:

?-q1(3,[3,4,5,6,7,8,9,10,11,12],L).
L = [6, 7, 8, 9, 10, 11, 12, 3, 4, 5]

?-q1(3,[1,2,3,4,5],L).
L = [4, 5, 1, 2, 3]

Now you can from this point on write a predicate that can deal with each list separately and give you the solution.

Another option is to use partition/4 (in SWI Prolog):

test(O):-
    L = [[1, 2, 3, 4, 5, 6, 7, 8, 9], [1,2,3], [3, 4, 5, 6, 7, 8, 9, 10, 11, 12]],
    maplist(move(3),L,O).

greater(N,L,E):-
    nth1(I,L,E),I > N.

move(N,L,O):-
    partition(greater(N,L),L,LO,LP),
    append(LO,LP,O).

?- test(O).
O = [[4, 5, 6, 7, 8, 9, 1, 2, 3], [1, 2, 3], [6, 7, 8, 9, 10, 11, 12, 3, 4, 5]]
Related