Related
I am new to Prolog, and have been having some problems figuring out the syntax of the language. I want to create a method which takes 3 arguements of (X,Y,Z).
X is a list of all of the numbers in between Y and Z. I know that the base case should only return Y since Y+1 = Z. Then the recursive should keep incrementing Y until it is equal to Z while putting the numbers into a list.
Since I am new, I wanted to avoid using built-in libraries and predicates.
This is what I am working off of right now.
range(X,Y,Z):-
%If Y + 1 == Z, X is just Y
range(X,Y,Z) :-
Y =< Z,
D is Y+1,
range(X,D,Z).
%use recursion to go from Y to Z, then collect it in X
I realized a bit later that my expected results should look like this:
range(X,1,10) should return [1,2,3,4,5,6,7,8]
Here a simple solution:
range([],A,B):-
A > B.
range([A|T],A,B):-
A =< B,
A1 is A + 1,
range(T,A1,B).
First clause: if A is greater than B, the resulting list is empty ([]). Second clause: if A is less or equal than B, increment A by 1 and store the result in A1, unify the head of the list with A (see [A|T]), and recursively call the predicate with the remaining part of the list (T), A1, and B.
?- range(L,1,10).
L = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
false
?- range(L,10,1).
L = []
false
There are multiple use cases for something like this:
testing that an integer lies within a given range, and
generating a specified sequence of integers.
This covers both ascending ranges and descending ranges, so
range(1,5,X) will, on backtracking, unify X with 1,2,3,4,5.
range(5,1,X) will, on backtracking, unify X with 5,4,3,2,1.
range(1,5,9) tests whether or not 9 lies within the ascending range 1-5 (it does not).
range(5,1,3) tests whether or not 3 lies within the descending range 5-1 (it does).
% ==========================================================
% range/3: Tests that Z lies with the range X-Y,
% or generates the sequence of integers X-Y.
% Both ascending ranges (1-5) and descending ranges (5-1)
% are supported. X and Y must both be integers. Z must be
% either an integer or an unbound variable.
% ==========================================================
range( X , Y , Z ) :-
integer(X),
integer(Y),
range0(X,Y,Z).
% ==========================================================
% Private (helper) predicates)
% ==========================================================
% --------------------------
% range0/3: Traffic director
% --------------------------
range0( X , Y , Z ) :- integer(Z), !, range1(X,Y,Z) .
range0( X , Y , Z ) :- var(Z), !, range2(X,Y,Z) .
% ----------------------------------
% range1/3: Z is bound to an integer
% ----------------------------------
range1(Lo,Hi,N) :- N >= Lo, N =< Hi, !.
range1(Hi,Lo,N) :- N >= Lo, N =< Hi, !.
% --------------------------------------------
% range2/3: Z is unbound. Generate a sequence.
% --------------------------------------------
range2(X,Y,N) :- X =< Y, !, range_asc(Lo,Hi,N) .
range2(X,Y,N) :- X > Y, !, range_dsc(Lo,Hi,N) .
% --------------------------------------------------------
% range_asc/3: Generates an ascending sequence of integers
% --------------------------------------------------------
range_asc( Hi , Hi , Hi ) :- !.
range_asc( Lo , _ , Lo ) .
range_asc( Lo , Hi , N ) :- L1 is Lo+1, range_asc(L1,Hi,N) .
% --------------------------------------------------------
% range_dsc/3: Generates a descending sequence of integers
% --------------------------------------------------------
range_dsc( Lo , Lo , Lo ) :- !.
range_dsc( Hi , _ , Hi ) .
range_dsc( Hi , Lo , N ) :- H1 is Hi-1, range_dsc(H1,Lo,N) .
I'm trying to implement a predicate that replaces NumElm elements of a list after (and including) a given Index. For that I use another predicate that replaces an element of a list.
My code so far is:
replace([_|T], 0, X, [X|T]).
replace([H|T], I, X, [H|R]):-
I > -1,
NI is I-1,
replace(T, NI, X, R), !.
replace(L, _, _, L).
replaceX(_,_,0,_,_).
replaceX(Line,Index,NumElm,Elm,NLine) :-
replace(Line,Index,Elm,BLine),
Index1 is Index+1,
NumElm1 is NumElm-1,
replaceX(BLine,Index1,NumElm1,Elm,Nline).
With that code, only the first element, the one of order Index, is replaced.
Any ideas?
Thanks in advance.
[EDIT] The result of the previous code was just 'yes'. The one that replaced the first element was the following:
replace([_|T], 0, X, [X|T]).
replace([H|T], I, X, [H|R]):-
I > -1,
NI is I-1,
replace(T, NI, X, R), !.
replace(L, _, _, L).
replaceX(_,_,0,_,_).
replaceX(Line,Index,NumElm,Elm,NLine) :-
replace(Line,Index,Elm,NLine),
Index1 is Index+1,
NumElm1 is NumElm-1,
replaceX(NLine,Index1,NumElm1,Elm,Nline).
I think you need to define the first clause of replaceX with:
replaceX(A,_,0,_,A):-!.
The reasoning behind this is explained by how _ works: it names an anonymous variable, and each instance of _ names a different variable. Here's an explanation. The cut, on the other hand, helps you get rid of undesired backtracking.
I think Nline in the last line should be NLine. Also you're lacking a dot at the end of the second replace clause.
Edit:
replace([_|T], 0, X, [X|T]).
replace([H|T], I, X, [H|R]):-
I > -1,
NI is I-1,
replace(T, NI, X, R), !.
replace(L, _, _, L).
replaceX(A,_,0,_,A):- !.
replaceX(Line,Index,NumElm,Elm,NLine) :-
replace(Line,Index,Elm,BLine),
Index1 is Index+1,
NumElm1 is NumElm-1,
replaceX(BLine,Index1,NumElm1,Elm,NLine).
Example input:
?- replaceX([0,1,2,3,4,5,6],2,4,a,R).
R = [0,1,a,a,a,a,6].
Isn't that the expected result?
handling numeric indexing can be tedious in Prolog, I would write
replace(L, From, To, X, R) :-
findall(S, (nth0(I,L,E), (I>=From, I<To ->S = X ; S = E)), R).
that yields
?- replace([0,1,2,3,4,5,6], 2,4,a, R).
R = [0, 1, a, a, 4, 5, 6].
Here's one way:
replace( As , _ , 0 , _ , As ) . % special case: nothing left to replace.
replace( [A|As] , P , N , X , [A|Bs] ) :- % otherwise,
P > 0 , % - if the position counter is greater than zero,
P1 is P-1 , % - decrement it, stow the current item to the result list
replace( As , P1 , N , X , Bs ) % - and recurse down
. %
replace( [_|As] , 0 , N , X , [X|Bs] ) :- % otherwise, if the position count is decremented to zero
N > 0 , % - but the length counter is greater than zero,
N1 is N-1 , % - decrement it, stow X in the result list
replace( As , 0 , N1 , X , Bs ) % - and recurse down.
. %
Could you help me solve the following?
Write a ternary predicate delete_nth that deletes every n-th element from a list.
Sample runs:
?‐ delete_nth([a,b,c,d,e,f],2,L).
L = [a, c, e] ;
false
?‐ delete_nth([a,b,c,d,e,f],1,L).
L = [] ;
false
?‐ delete_nth([a,b,c,d,e,f],0,L).
false
I tried this:
listnum([],0).
listnum([_|L],N) :-
listnum(L,N1),
N is N1+1.
delete_nth([],_,_).
delete_nth([X|L],C,L1) :-
listnum(L,S),
Num is S+1,
( C>0
-> Y is round(Num/C),Y=0
-> delete_nth(L,C,L1)
; delete_nth(L,C,[X|L1])
).
My slightly extravagant variant:
delete_nth(L, N, R) :-
N > 0, % Added to conform "?‐ delete_nth([a,b,c,d,e,f],0,L). false"
( N1 is N - 1, length(Begin, N1), append(Begin, [_|Rest], L) ->
delete_nth(Rest, N, RestNew), append(Begin, RestNew, R)
;
R = L
).
Let's use clpfd! For the sake of versatility and tons of other good reasons:
:- use_module(library(clpfd)).
We define delete_nth/3 based on if_/3 and (#>=)/3:
delete_nth(Xs,N,Ys) :-
N #> 0,
every_tmp_nth_deleted(Xs,0,N,Ys).
every_tmp_nth_deleted([] ,_ ,_,[] ). % internal auxiliary predicate
every_tmp_nth_deleted([X|Xs],N0,N,Ys0) :-
N1 is N0+1,
if_(N1 #>= N,
(N2 = 0, Ys0 = Ys ),
(N2 = N1, Ys0 = [X|Ys])),
every_tmp_nth_deleted(Xs,N2,N,Ys).
Sample query:
?- delete_nth([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15],2,Ys).
Ys = [1,3,5,7,9,11,13,15] % succeeds deterministically
Ok, how about something a little more general?
?- delete_nth([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15],N,Ys).
N = 1 , Ys = []
; N = 2 , Ys = [1, 3, 5, 7, 9, 11, 13, 15]
; N = 3 , Ys = [1,2, 4,5, 7,8, 10,11, 13,14 ]
; N = 4 , Ys = [1,2,3, 5,6,7, 9,10,11, 13,14,15]
; N = 5 , Ys = [1,2,3,4, 6,7,8,9, 11,12,13,14 ]
; N = 6 , Ys = [1,2,3,4,5, 7,8,9,10,11, 13,14,15]
; N = 7 , Ys = [1,2,3,4,5,6, 8,9,10,11,12,13, 15]
; N = 8 , Ys = [1,2,3,4,5,6,7, 9,10,11,12,13,14,15]
; N = 9 , Ys = [1,2,3,4,5,6,7,8, 10,11,12,13,14,15]
; N = 10 , Ys = [1,2,3,4,5,6,7,8,9, 11,12,13,14,15]
; N = 11 , Ys = [1,2,3,4,5,6,7,8,9,10, 12,13,14,15]
; N = 12 , Ys = [1,2,3,4,5,6,7,8,9,10,11, 13,14,15]
; N = 13 , Ys = [1,2,3,4,5,6,7,8,9,10,11,12, 14,15]
; N = 14 , Ys = [1,2,3,4,5,6,7,8,9,10,11,12,13, 15]
; N = 15 , Ys = [1,2,3,4,5,6,7,8,9,10,11,12,13,14 ]
; N in 16..sup, Ys = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15].
Please follow aBathologist instructive answer and explanation (+1). I just post my own bet at solution since there is a problem in ditto solution for ?‐ delete_nth([a,b,c,d,e,f],0,L)..
delete_nth(L,C,R) :-
delete_nth(L,C,1,R).
delete_nth([],_,_,[]).
delete_nth([_|T],C,C,T1) :- !, delete_nth(T,C,1,T1).
delete_nth([H|T],N,C,[H|T1]) :- C<N, C1 is C+1, delete_nth(T,N,C1,T1).
yields
1 ?- delete_nth([a,b,c,d,e,f],2,L).
L = [a, c, e].
2 ?- delete_nth([a,b,c,d,e,f],1,L).
L = [].
3 ?- delete_nth([a,b,c,d,e,f],0,L).
false.
A minor (?) problem: this code is deterministic, while the samples posted apparently are not (you have to input ';' to get a false at end). Removing the cut will yield the same behaviour.
An interesting - imho - one liner variant:
delete_nth(L,C,R) :- findall(E, (nth1(I,L,E),I mod C =\= 0), R).
but the C==0 must be ruled out, to avoid
ERROR: mod/2: Arithmetic: evaluation error: `zero_divisor'
Edited, correcting the mistake pointed out by #CapelliC, where predicate would succeed on N = 0.
I can see where you're headed with your solution, but you needn't bother with so much arithmetic in this case. We can delete the Nth element by counting down from N repeatedly until the list is empty. First, a quick note about style:
If you use spaces, line breaks, and proper placement of parenthesis you can help your readers parse your code. Your last clause is much more readable in this form:
delete_nth([X|L], C, L1):-
listnum(L, S),
Num is S+1,
C>0 -> Y is round(Num/C),
Y=0 -> delete_nth(L, C, L1)
; delete_nth(L, C, [X|L1]).
Viewing your code now, I'm not sure whether you meant to write
( C>0 -> ( Y is round(Num/C),
Y=0 -> delete_nth(L, C, L1) )
; delete_nth(L, C, [X|L1])
).
or if you meant
C>0 -> Y is round(Num/C),
( Y=0 -> delete_nth(L, C, L1)
; delete_nth(L, C, [X|L1])
).
or perhaps you're missing a ; before the second conditional? In any case, I suggest another approach...
This looks like a job for auxiliary predicates!
Often, we only need a simple relationship in order to pose a query, but the computational process necessary to resolve the query and arrive at an answer calls for a more complex relation. These are cases where it is "easier said than done".
My solution to this problem works as follows: In order to delete every nth element, we start at N and count down to 1. Each time we decrement the value from N, we move an element from the original list to the list of elements we're keeping. When we arrive at 1, we discard the element from our original list, and start counting down from N again. As you can see, in order to ask the question "What is the list Kept resulting from dropping every Nth element of List?" we only need three variables. But my answer the question, also requires another variable to track the count-down from N to 1, because each time we take the head off of List, we need to ask "What is the Count?" and once we've reached 1, we need to be able to remember the original value of N.
Thus, the solution I offer relies on an auxiliary, 4-place predicate to do the computation, with a 3-place predicate as the "front end", i.e., as the predicate used for posing the question.
delete_nth(List, N, Kept) :-
N > 0, %% Will fail if N < 0.
delete_nth(List, N, N, Kept), !. %% The first N will be our our counter, the second our target value. I cut because there's only one way to generate `Kept` and we don't need alternate solutions.
delete_nth([], _, _, []). %% An empty list has nothing to delete.
delete_nth([_|Xs], 1, N, Kept) :- %% When counter reaches 1, the head is discarded.
delete_nth(Xs, N, N, Kept). %% Reset the counter to N.
delete_nth([X|Xs], Counter, N, [X|Kept]) :- %% Keep X if counter is still counting down.
NextCount is Counter - 1, %% Decrement the counter.
delete_nth(Xs, NextCount, N, Kept). %% Keep deleting elements from Xs...
Yet another approach, following up on #user3598120 initial impulse to calculate the undesirable Nth elements away and inspired by #Sergey Dymchenko playfulness. It uses exclude/3 to remove all elements at a 1-based index that is multiple of N
delete_nth(List, N, Kept) :-
N > 0,
exclude(index_multiple_of(N, List), List, Kept).
index_multiple_of(N, List, Element) :-
nth1(Index, List, Element),
0 is Index mod N.
I want to write a predicate that an integer and a list of digits, and succeed if Digits contain the digits of the integer in the proper order, i.e:
?-digit_lists( Num, [1,2,3,4] ).
[Num == 1234].
Here is what I have so far:
my_digits( 0, [] ).
my_digits(N,[A|As]) :- N1 is floor(N/10), A is N mod 10, my_digits(N1, As).
I think this is easier:
numToList(NUM,[LIST|[]]):-
NUM < 10,
LIST is NUM,
!.
numToList(NUM,LIST):-
P is NUM // 10,
numToList(P,LIST1),
END is (NUM mod 10),
append(LIST1,[END] ,LIST).
As already suggested, consider using finite domain constraints:
:- use_module(library(clpfd)).
number_digits(Number, 0, [Number]) :- Number in 0..9.
number_digits(Number, N, [Digit|Digits]) :-
Digit in 0..9,
N #= N1 + 1,
Number #= Digit*10^N + Number1,
Number1 #>= 0,
N #> 0,
number_digits(Number1, N1, Digits).
This predicate can be used in all directions. Examples with either argument instantiated:
?- number_digits(215, _, Ds).
Ds = [2, 1, 5] ;
false.
?- number_digits(N, _, [4,3,2,1]).
N = 4321 ;
false.
And two more general queries:
?- number_digits(N, _, [A,B]).
N in 10..99,
_G2018+B#=N,
_G2018 in 10..90,
A*10#=_G2018,
A in 0..9,
B in 0..9 ;
false.
?- number_digits(N, _, Ds).
Ds = [N],
N in 0..9 ;
Ds = [_G843, _G846],
N in 0..99,
_G870+_G846#=N,
_G870 in 0..90,
_G843*10#=_G870,
_G843 in 0..9,
_G846 in 0..9 ;
etc.
Here comes yet another variant based on clpfd... Based on (#=)/3 and if_//3 we define:
n_base_digits(N, R, Ds) :-
N #> 0, % positive integers only
R #> 1, % smallest base = 2
Ds = [D|_], % leading digit may not be 0
D #> 0,
phrase(n_base_digits_aux(N, R, Ds), Ds).
n_base_digits_aux(N, Base, [_|Rs]) -->
{ D #= N mod Base,
M #= N // Base },
if_(M #= 0,
{ Rs = [] },
n_base_digits_aux(M, Base, Rs)),
[D].
Query using SICStus Prolog 4.3.3:
| ?- n_base_digits(1234, 10, Ds).
Ds = [1,2,3,4] ? ;
no
Works the other way round, too!
| ?- n_base_digits(I,10,[1,2,3]).
I = 123 ? ;
no
Note that the above is faster than number_digits/3 as proposed by #mat in his answer.
You could also avoid recursion and use in-built predicates for type conversions:
my_digits(Number, List) :-
atomic_list_concat(List, Atom),
atom_number(Atom, Number).
The first line converts the list to an atom, and the second line converts this atom to a number, which will give true if that number is the same as that passed in.
I don't know if there is an even more direct way to convert the list into a number (don't think so..), in which case it could be achieved in a single line.
I don't agree with #ssBarBee. After all, you should get 4321 if you supply your list and their allegation is correct; but instead you get this:
?- my_digits(Num, [1,2,3,4]).
ERROR: is/2: Arguments are not sufficiently instantiated
We could try it with clpfd:
my_digits( 0, [] ).
my_digits(N,[A|As]) :- N1 #= N/10, A #= N mod 10, my_digits(N1, As).
We get this:
?- my_digits(Num, [1,2,3,4]), label([Num]).
Num = -6789 ;
Num = 4321.
I find all that pretty curious, but tracing with clpfd is not pleasant.
If you just wanted to parse a list of numbers I would be inclined to make it tail recursive like so:
my_digits(Num, List) :- my_digits(0, List, Num).
my_digits(Num, [], Num).
my_digits(N, [A|As], Num) :- N1 is N * 10 + A, my_digits(N1, As, Num).
This gives us:
?- my_digits(Num, [1,2,3,4]).
Num = 1234 ;
false.
But it doesn't generate:
?- my_digits(1234, X).
ERROR: is/2: Arguments are not sufficiently instantiated
If I were solving this without clpfd, I'd be inclined at this point to just inspect my arguments and have separate predicates. Gross, I know, but that's what I'd do.
my_digits(Num, List) :-
nonvar(List),
my_digits_p(0, List, Num).
my_digits(Num, List) :-
var(List),
my_digits_g(Num, ListRev),
reverse(ListRev, List).
my_digits_p(Num, [], Num).
my_digits_p(N, [A|As], Num) :- N1 is N * 10 + A, my_digits(N1, As, Num).
my_digits_g(0, []) :- !.
my_digits_g(N, [A|As]) :- A is N mod 10, N1 is floor(N / 10), my_digits_g(N1, As).
This can parse or check, or generate if the number is a non-variable:
?- my_digits(1234, X).
X = [1, 2, 3, 4].
?- my_digits(X, [1,2,3,4]).
X = 1234 ;
false.
?- my_digits(1234, [1,2,3,4]).
true;
false.
If you try and generate with both arguments as variables you'll get a pretty unhelpful result though:
?- my_digits(X, Y).
X = 0,
Y = [].
So we can try and generate by adding another special case to my_digits:
my_digits(Num, List) :-
var(Num), var(List),
my_digits_g_from(0, Num, ListRev),
reverse(ListRev, List).
my_digits(Num, List) :-
nonvar(List),
my_digits_p(0, List, Num).
my_digits(Num, List) :-
var(List),
my_digits_g(Num, ListRev),
reverse(ListRev, List).
my_digits_g_from(N, N, List) :- my_digits_g(N, List).
my_digits_g_from(N, Num, List) :- succ(N, N1), my_digits_g_from(N1, Num, List).
That's a lot of code, and a good demonstration of the kind of acrobatics one has to do when not using clp(fd). It's an unfortunate fact that when doing arithmetic in Prolog one must work around the fact that is does not unify, but the complexity of clp(fd) is good proof of why that is.
I hope someone else has a more elegant solution!
For a class assignment? What the professor is probably looking for is something like the following. A a general rule, your analysis of the problem statement should first identify the special cases (in this case, zero and negative values) and then the general case.
: -- int_2_digits/2 ------------------------------------------------------------
:
: The public api.
:
: we've got 2 special cases here:
:
: * zero, and
: * negative numbers
:
: and, of course, the general case: a positive value.
:
: ------------------------------------------------------------------------------
int_2_digits( 0 , [0] ) . : zero is a special case
int_2 digits( X , ['-'|Ds] ) :- : negative numbers are a special case
X < 0 , : which we handle (YMMV) by prepending the
X1 is - X , : sign and than processing the absolute value
int_2_digits(X1,Ds) . :
int_2_digits( X , Ds ) :- : the general case is a positive value
X > 0 , : just invoke the worker predicate.
int_2_digits(X,[],Ds) . :
: -- int_2_digits/3 ------------------------------------------------------------
:
: The guts of the operation.
:
: We're using an accumulator here because we compute the result right-to-left,
: from least significant digit to most significant digit. Using the accumulator
: builds the list in the correst sequence, so we don't have to reverse it at
: the end.
: ------------------------------------------------------------------------------
int_2_digits( 0 , Ds , Ds ) . : if we hit zero, we're done. Unify the accumulator with the result
int_2_digits( X , Ts , Ds ) :- : otherwise...
D is mod(X,10) , : - get the current digit (X modulo 10)
T is div(X,10) , : - get the next value via integer division
int_2_digits( X1 , [T|Ts] , Ds ) : - recurse down
. : Easy!
I'm trying to divide a list in Prolog into 3 equal parts (...well, as equal as possible).
My algorithm is the following:
Find out the size of the initial list.
Call the procedure with two extra parameters (the size of the list and a counter that will tell me when I should stop adding elements to one list and start adding to another)
The procedure looks like this:
With 4 parameters:
div3(InitialList,FirstNewList,SecondNewList,ThirdNewList).
With 2 extra parameters:
div3(InitialList,FirstList,SecondList,ThirdList,InitialListSize,Counter).
Here's my code:
div3([],[],[],[]).
div3([X],[X],[],[]).
div3([X,Y],[X],[Y],[]).
div3([X,Y,Z],[X],[Y],[Z]).
div3([X | Y],A,B,C) :- length([X | Y],Sz),
Sz1 is 0,
div3([X | Y],A,B,C,Sz,Sz1).
div3([X | Y],A,B,C,Sz,Sz1) :- Sz1 < Sz//3, % am I done adding to the 1st list?
append(X,L,A), % add to the 1st list
Sz2 is Sz1+1, % increment the counter
div3(Y,L,B,C,Sz,Sz2),!.
div3([X | Y],A,B,C,Sz,Sz1) :- Sz1 < 2*Sz//3, % am I done adding to the 2nd list?
append(X,L,B), % add to the 2nd list
Sz2 is Sz1+1, % increment the counter
div3(Y,A,L,C,Sz,Sz2),!.
div3([X | Y],A,B,C,Sz,Sz1) :- Sz1 < Sz, % am I done adding to the 3rd list?
append(X,L,C),% add to the 3rd list
Sz2 is Sz1+1, % increment the counter
div3(Y,A,B,L,Sz,Sz2),!.
I think the first part of your code was almost right...
What you are looking for is a recursive predicate with 3 base cases and just one recursive clause.
div3([], [], [], []).
div3([X], [X], [], []).
div3([X,Y], [X], [Y], []).
div3([X,Y,Z|Tail], [X|XTail], [Y|YTail], [Z|ZTail]):-
div3(Tail, XTail, YTail, ZTail).
there are no end-cases in the code for the recursive predicate div3/5, the first 3 clauses are applied only for div3/3 calls (that's why calls like div3([4,2,42],X,Y,Z) succeed)
also, you call append/3 with an element, not a list, so it fails (unless you have a list of lists but even in that case, it's not what you want)
i would suggest switching to a more "declarative" approach, maybe with a predicate like get_N_elements(List,List_N,Rest) to avoid code repetition too
if maintaining source order does not matter, the following should suffice.
divide( [] , [] , [] , [] ) . % we're done when the source list is exhausted, OR ...
divide( [X] , [X] , [] , [] ) . % - it's only got 1 element, OR ...
divide( [X,Y] , [X] , [Y] , [] ) . % - it's only got 2 elements
divide( [X,Y,Z|T] , [X|Xs] , [Y|Ys] , [Z|Zs] ) :- % otherwise, split three elements amount the result lists and
divide(T,Xs,Ys,Zs) % - recurse down.
. %
The code above partitions the list
[a,b,c,d,e,f,g]
into
[a,d,g]
[b,e]
[c,f]
If you wish to maintain the order, this would work, describing what constitutes a correct solution (e.g., lists of lengths as equal as possible) and letting append/3 find the correct solution(s):
divide( L , X , Y , Z ) :-
append(X,T,L) , % split X off as a prefix of the source list L
append(Y,Z,T) , % divide the remainder (T) into a prefix Y and suffix Z
length(X,X1) , % compute the length of X
length(Y,Y1) , % compute the length of Y
length(Z,Z1) , % compute the length of Z
min_max([X1,Y1,Z1],Min,Max) , % determine the shortest and longest such length
Max - Min =< 1 , % and ensure that the delta is 1 or less
.
min_max([],0,0) .
min_max([H|T],Min,Max) :-
min_max(T,H,H,Min,Max)
.
min_max([],Min,Max,Min,Max) .
min_max([H|T], T1 , T2 , Min , Max ) :-
( H < T1 -> T3 = H ; T3 = T1 ) ,
( H > T2 -> T4 = H ; T4 = T2 ) ,
min_max(T,T3,T4,Min,Max)
.
The above basically says
Divide list L into 3 sublists X, Y and Z such that the delta between the lengths of
each sublist does not exceed 1.
In this case, you should see the list
[a,b,c,d,e,f,g]
divided into
[a,b]
[c,d]
[e,f,g]
One should note that this is non-deterministic and backtracking will find all possible such solutions.