简体   繁体   中英

Prolog - Split a list in two halves, reversing the first half

I am asked to take a list of letters into two lists that are either equal in size (even sized original list I guess) or one is larger than the other by one element (odd sized list), and reverse the first one while I'm at it.

Query and output example:

?- dividelist2([a,b,c,d,e,f], L1,L2).
L1 = [c,b,a]
L2 = [d,e,f]

?- dividelist2([a,b,c,d,e], L1,L2).
L1 = [c,b,a]
L2 = [d,e]
% OR
L1 = [b,a]
L2 = [c,d,e]

I've reached this solution, but I have a feeling something is wrong with it. Please let me know if I'm missing something... How do I get the last output? (two options for output, I mean)

dividelist2(L, X, B) :-
  (  append(A, B, L),
     length(A, O),
     length(B, N),
     (  (O-1) =:= N
     ;  (O+1) =:= N
     ;  O     =:= N
     ),
     !
  ), reverse(A, X).

reverse(List, Rev) :-
        reverse(List, Rev, []).

reverse([], L, L).
reverse([H|T], L, SoFar) :-
        reverse(T, L, [H|SoFar]).

Thanks in advance!

Thanks @WillNess, I have a full solution here using the tortoise/hare trick. The idea behind that trick is that you are traversing the list simultaneously, with the tortoise going element by element and the hare going by two elements at a time. When the hare list is empty, you know you're at the middle of the list. This inlines a common accumulator pattern for reversing a list in one pass, which is how we produce the first half of the result. The second half of the result is just the remainder of the list--which is to say, the tortoise list.

divide(List, Left, Right) :-
    reverse(List, List, [], Left, Right).

% reverse(Tortoise, Hare, ReverseAccumulator, RevFirstHalf, SecondHalf)
reverse(Right, [], Left, Left, Right).
reverse([X|Xs], [_,_|Rem], Acc, Left, Right) :-
    reverse(Xs, Rem, [X|Acc], Left, Right).

As you can see, the hare is just here to be traversed two-at-a-time until it becomes empty, which triggers our base case. This works fine for even-length lists.

For odd-length lists we must handle them by noticing that the hare is a single-item list. Then we need to produce two more solutions, so we have two clauses for the single-item list case: one that places the next tortoise item into the first half list, one that places it into the second half:

reverse([X|Xs], [_], Acc, Left, Right) :-
    reverse([X|Xs], [], Acc, Left, Right).
reverse([X|Xs], [_], Acc, Left, Right) :-
    reverse(Xs, [], [X|Acc], Left, Right).

This produces the results you want. It's a little hard to understand what is going on at first, but it is more efficient than the accepted answer, probably because it doesn't allocate as much. For comparison, I ran both on a list of 5 million entries; this version ran somewhat faster:

?- length(L, 5000000), time(splitflip(L, Left, Right)).
% 5,000,014 inferences, 0.806 CPU in 0.806 seconds (100% CPU, 6200835 Lips)
L = [_58, _64, _70, _76, _82, _88, _94, _100, _106|...],
Left = [_15000052, _15000046, _15000040, _15000034, _15000028, _15000022, _15000016, _15000010, _15000004|...],
Right = [_15000058, _15000064, _15000070, _15000076, _15000082, _15000088, _15000094, _15000100, _15000106|...].

?- length(L, 5000000), time(divide(L, Left, Right)).
% 2,500,001 inferences, 0.567 CPU in 0.568 seconds (100% CPU, 4405613 Lips)
L = [_850, _856, _862, _868, _874, _880, _886, _892, _898|...],
Left = [_15000844, _15000838, _15000832, _15000826, _15000820, _15000814, _15000808, _15000802, _15000796|...],
Right = [_15000850, _15000856, _15000862, _15000868, _15000874, _15000880, _15000886, _15000892, _15000898|...] .

Edit : Original answer follows.

This is only a partial solution. I'm missing something obvious about how to convert this to the full solution, and I don't have a lot of time at the moment to finish it, but I thought it might be useful to share anyway. And probably another answerer will see immediately where the problem is.

My plan here is basically to be reversing the list and tracking the index I'm at from the start on the way into the recursive call, and be tracking the index I'm at from the end on the way out of the recursive call. With that in-hand, if the indexes match, we take the reversed prefix and the suffix and that is the result. We can define "match" as the indexes are within 1 of each other, with a separate predicate.

reverse(_, [], 0, Z, Z).
reverse(I, [X|Xs], J1, Z, Acc) :-
    succ(I, I1),
    reverse(I1, Xs, J, Z, [X|Acc]),
    succ(J, J1).

The combination of pieces that I think we need are visible in this code when viewed in the trace. output:

[trace]  ?- reverse(0, [a,b,c,d], _, R, []).
   Call: (8) reverse(0, [a, b, c, d], _694, _696, []) ? creep
   Call: (9) succ(0, _964) ? creep
   Exit: (9) succ(0, 1) ? creep
   Call: (9) reverse(1, [b, c, d], _972, _696, [a]) ? creep
   Call: (10) succ(1, _970) ? creep
   Exit: (10) succ(1, 2) ? creep
   Call: (10) reverse(2, [c, d], _978, _696, [b, a]) ? creep
   Call: (11) succ(2, _976) ? creep
   Exit: (11) succ(2, 3) ? creep
   Call: (11) reverse(3, [d], _984, _696, [c, b, a]) ? creep
   Call: (12) succ(3, _982) ? creep
   Exit: (12) succ(3, 4) ? creep
   Call: (12) reverse(4, [], _990, _696, [d, c, b, a]) ? creep
   Exit: (12) reverse(4, [], 0, [d, c, b, a], [d, c, b, a]) ? creep
   Call: (12) succ(0, _988) ? creep
   Exit: (12) succ(0, 1) ? creep
   Exit: (11) reverse(3, [d], 1, [d, c, b, a], [c, b, a]) ? creep
   Call: (11) succ(1, _988) ? creep
   Exit: (11) succ(1, 2) ? creep
   Exit: (10) reverse(2, [c, d], 2, [d, c, b, a], [b, a]) ? creep
   Call: (10) succ(2, _988) ? creep
   Exit: (10) succ(2, 3) ? creep
   Exit: (9) reverse(1, [b, c, d], 3, [d, c, b, a], [a]) ? creep
   Call: (9) succ(3, _694) ? creep
   Exit: (9) succ(3, 4) ? creep
   Exit: (8) reverse(0, [a, b, c, d], 4, [d, c, b, a], []) ? creep
R = [d, c, b, a] .

Note particularly this line:

   Exit: (10) reverse(2, [c, d], 2, [d, c, b, a], [b, a]) ? creep

As you can see, at this moment we have I = J = 2, and we have a list [b,a] and a list [c,d] . This gives me hope that this is close, but for whatever reason I'm not seeing the bridge to the complete solution. Maybe you will!

You're on the right track:

splitflip(In, Out1, Out2) :-
    length(In, L), % length of the list
    FL is L//2, % integer division, so half the length, Out1 will be 1 shorter than Out2 if L is odd
    ( \+ (FL*2 =:= L), % is odd
      FLP is FL + 1 % odd case, choicepoint left
    ; FLP = FL % odd and even case, no choicepoint left
    ), % the one place we need a choicepoint for the odd length case of an odd input, note order prunes choicepoint for even case
    length(FirstHalf, FLP), % generate an list of vars to the right length
    append(FirstHalf, Out2, In), % unify with the first half and generate second half of correct length
    reverse(FirstHalf, Out1). % do the reverse

Just need to reorder things a bit and use integer division.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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