If order doesn't matter, then just:
partition( [] , [] , [] ) .
partition( [Y] , [Y] , [] ) .
partition( [Y,Z|Xs] , [Y|Ys] , [Z|Zs] ) :- partition(Xs,Ys,Zs) .
If order is important, then the easiest way would be to simply
partition( Xs , Ys , Zs ) :-
length(Xs,N),
M is N div 2,
length(Ys,M),
append(Ys,Zs,Xs) .
[Though probably not what your instructor is looking for.]
Something like this would work:
partition( Xs , Ys, Zs ) :-
length(Xs,N0),
N is N0 div 2 ,
partition( N , Xs, Ys, Zs )
.
partition( 0 , Zs , [] , Zs ) .
partition( N , [X|Xs] , [X|Ys] , Zs ) :- N1 is N-1, partition(N1,Xs,Ys,Zs).
It runs in O(N) time — O(1.5N) actually. It traverses the source list once to determine its length, and then again to partition it.
As would this:
partition( Xs , Ys , Zs ) :- partition(Xs,Xs,Ys,Zs) .
partition( [] , Xs , [] , Xs ) .
partition( [_] , [X|Xs] , [X|Ys] , Xs ) .
partition( [_,_|Ts] , [X|Xs] , [X|Ys] , Zs ) :- partition(Ts,Xs,Ys,Zs ) .
Here you pass the source list to the helper predicate twice. From the one list, we strip two items on each iteration, and from the other, a single item. Once the list we're stripping two items from it exhausted, we're done.
This runs in O(N) time as well — O(N/2) actually, so it's somewhat more time efficient.