10

I need to define divide so that List [1,2,3,4,5] divides into:

a = [1,2,3}

b = [4,5]

I'm getting an error that says "Arguments are not sufficiently instantiated", and I don't know enough about the language to figure out what my problem is, or if my design is even right. Any guidance would be appreciated.

So here's what I have so far:

append([],L2,L2).
append([H|T],L2,[H|L3]) :- append(T,L2,L3).

lengthIs([],N).
lengthIs([H|T],N) :- lengthIs(T,M), N is M+1.

divide([],[],[]).
divide([H|T],L2,L3) :-
   (  lengthIs(L2, M) < lengthIs(L1,N)/2
   -> divide(T, append(L2, H, X), L3)
   ;  divide(T, L2, append(L3,H,Y))
   ).
MD XF
  • 7,860
  • 7
  • 40
  • 71
bdmflyer
  • 145
  • 11

9 Answers9

9

Let's give the predicate a more relational name: list_half_half/3

list_half_half(Xs, Ys, Zs) :-
   length(Xs, N),
   H is N - N // 2,
   length(Ys, H),
   append(Ys, Zs, Xs).

length/2 and append/3 are predefined in practically all recent Prologs.

This is GNU Prolog:

| ?- append(L,_,[a,b,c,d]), list_half_half(L,H1,H2).

H1 = []
H2 = []
L = [] ? ;

H1 = [a]
H2 = []
L = [a] ? ;

H1 = [a]
H2 = [b]
L = [a,b] ? ;

H1 = [a,b]
H2 = [c]
L = [a,b,c] ? ;

H1 = [a,b]
H2 = [c,d]
L = [a,b,c,d]
false
  • 10,264
  • 13
  • 101
  • 209
5

This is the most efficient solution conforming to your specification for most Prolog implementations:

divide(L, A, B) :-
    divide1(L, L, A, B).

divide1([], L, [], L).
divide1([_|T], [H|L], [H|A], B) :-
    divide2(T, L, A, B).

divide2([], L, [], L).
divide2([_|T], L, A, B) :-
    divide1(T, L, A, B).

If you don't mind which elements go into the sublists as far as they are of similar length (as in the solution from Konstantin Weitz post), then you can use :

divide([], [], []).
divide([H|T], [H|A], B) :- divide(T, B, A).
salva
  • 9,943
  • 4
  • 29
  • 57
  • 1
    Your solution terminates if the first or the second list's length is known. So `divide(L,A,B) terminates_if bound(L) ; bound(A).` But it still does not terminate for `divide(L,A,[]).` Two answers are expected here! `L = [], A = [] ; L = [X], A = [X]` – false Nov 21 '11 at 23:47
  • yes, that predicate is expected to be used as `divide(+, -, -)`. – salva Nov 22 '11 at 08:48
  • 1
    You are better than `+, -, -` already! There is `divide(L,[a],H)` and even `divide([a|_],[b|_],_)` nicely terminates. – false Nov 22 '11 at 10:23
  • yes, I know, but this is just by coincidence, not because the predicate was designed to handle that case and it leaves a choice point behind so even if it terminates it does it suboptimally. – salva Nov 22 '11 at 10:36
  • I cannot see the choicepoint you are mentioning. It's perfect for `+,-,-` and `-,+,-` – false Nov 22 '11 at 16:34
  • I have tested it on SWI-Prolog (though not with one of the newer versions implementing JIT indexing) and `divide(L, [a], H)` leaves a choice point there. – salva Nov 23 '11 at 08:29
  • `divide(L,[a],H)` has to produce two answers! After the second answer, no choice point is left in recent versions. But you are right: Older non-JIT indexing versions leave a choicepoint. – false Nov 23 '11 at 12:42
4

append is a pre-defined predicate, so that might be the issue: http://en.wikibooks.org/wiki/Prolog/Lists#The_append_predicate

You also never defined 'N' in lengthIs - you need to set the empty list as 0, not N/ There's likely also a size function

The underscore tells Prolog we don't care about that bit in that predicate definition.

Something like this should work

divide(L1,L2,L3):- append(L2,L3,L1), 
                   samesize(L2,L3).
divide(L1,L2,L3):- append(L2,L3,L1), 
                   onebigger(L2,L3).
samesize(A,B):-    size(A,N),
                   size(B,N).
onebigger(A,[_|T]):-   size(A,N), 
                   size(T,N).
size([],0).
size([H|T],N):-    size(T,M+1).
Philip Whitehouse
  • 4,293
  • 3
  • 23
  • 36
2

Surely the effect of this code (lengthIs(L2, M) < lengthIs(L1,N)/2 -> ...) isn't what you expect: it doesn't compare numbers, but terms. You should write it this way:

lengthIs(L2, M), lengthIs(L1, N), M < N/2 -> ...

Another typo like mistake: the first clause of lengthIs/2 should read

lengthIs([],0).
CapelliC
  • 59,646
  • 5
  • 47
  • 90
1

Here's a sort of "rabbit and hare" approach.

divide(Xs,Ys,Zs) :-
    divide(Xs,Xs,Ys,Zs).

divide(Ls,[],[],Ls).
divide([H|Ts],[_],[H],Ts).
divide([H|Ts],[_,_|Zs],[H|Us],Rs)
    :- divide(Ts,Zs,Us,Rs).

The divide/4 predicate works but stripping both a single element and a double element from the initial list. By the time the second argument gets to the empty list or a list of one element then we are effectively halfway through the list passed as the first argument.

It's effectively O(n/2) complexity.

Here's a sample output:

?- divide([a,b,c,d,e,f,g,h],X,Y).
X = [a, b, c, d],
Y = [e, f, g, h] .

?- divide([a,b,c,d,e,f,g],X,Y).
X = [a, b, c, d],
Y = [e, f, g] .
Enigmativity
  • 113,464
  • 11
  • 89
  • 172
1

No need to check sizes. Just do it like this:

div([],[],[]).
div([A],[A],[]).
div([A,B|T],[A|X],[B|Y]) :- div(T,X,Y).
Konstantin Weitz
  • 6,180
  • 8
  • 26
  • 43
  • 2
    note that this solutions does not really conform to the OP specification as it does `div([1,2,3,4,5], [1, 3, 5], [2, 4])` instead of `div([1, 2, 3, 4, 5], [1, 2, 3], [4, 5])`. – salva Nov 18 '11 at 07:55
  • 2
    @KonstantinWeitz: this splits `[1,2,3,4,5]` into `[1,3,5]` and `[2,4]`. It splits it into 2 list with the correct lengths, but not the correct *contents*. – Nicholas Carey Nov 18 '11 at 18:14
  • Yes, this doesn't give the correct results. – Enigmativity Jan 03 '22 at 03:57
0

Improved head-and-tail iteration, guarding against infinity:

split_list_half([A,B|T], Half1, Half2) :-
    split_list_half_([A,B|T], [A,B|T], Half1, Half2, Half2).
    
split_list_half_([], H2, [], _, H2).
% H2D prevents infinity with: split_list_half(L, H1, [c, d]).
split_list_half_([_|T], [H|Sgl], H1, H2D, H2) :-
    split_list_half_dbl_(T, H, Sgl, H1, H2D, H2).

split_list_half_dbl_([], H, H2, [H], _, H2).
split_list_half_dbl_([_|T], H, Sgl, [H|H1], [_|H2D], H2) :-
    split_list_half_(T, Sgl, H1, H2D, H2).

Results in swi-prolog:

?- split_list_half([a,b,c,d], H1, H2).
H1 = [a, b],
H2 = [c, d].

?- split_list_half([a,b,c,d,e], H1, H2).
H1 = [a, b, c],
H2 = [d, e].

?- split_list_half(LF, H1, [d, e]).
LF = [_A, _B, d, e],
H1 = [_A, _B] ;
LF = [_A, _B, _C, d, e],
H1 = [_A, _B, _C] ;
false.
brebs
  • 3,462
  • 2
  • 3
  • 12
0

Another answer, uses Backtracking a lot, isn't very performant, though. append and length are assumed to be predefined:

divide(A,B,C):-
    append(B,C,A),
    length(B,B_Length),
    length(C,C_Length),
    (B_Length = C_Length;
        B_Length =:= C_Length +1).

Oh, sorry, just have seen that this is sort of a rephrasing of the answer from Philip Whitehouse.

Patrick J. S.
  • 2,885
  • 19
  • 26
0

This is how I did it. Almost no built-ins:

split_list_in_half( Xs , H , T ) :-
  list_length( X , L ) ,
  LL = L - (L // 2) ,
  take_first( Xs , LL , H , T ) ,
  .

list_length( L , N ) :-
  list_length( L , 0 , N )
  .

list_length( [] , N , N ).
list_length( [X|Xs] , T , N ) :-
  T1 is T+1 ,
  list_length( Xs , T1 , N )
  .

take_first( Xs , N , Pfx , Sfx ) :-
  take_first( Xs , N , [] , P1 , Sfx ) ,
  reverse( P1 , Pfx )
  .

take_first( []     , _ , H  , H , []     ).
take_first( [X|Xs] , 0 , H  , H , [X|Xs] ).
take_first( [X|Xs] , N , H1 , H , T      ) :-
  N > 0    ,
  N1 = N-1 ,
  take_first( Xs , N1 , [X|H1] , H , T )
  .
Nicholas Carey
  • 71,308
  • 16
  • 93
  • 135