% Unary natural numbers z, s(z), s(s(z)), ...

nat(z).
nat(s(N)) :- nat(N).

even(z).
even(s(s(N))) :- even(N).

plus(z, N, N).
plus(s(M), N, s(P)) :- plus(M, N, P).

%% N1 * N2 = N3
times(z, _, z).
times(s(N1), N2, N3_2) :-
	times(N1, N2, N3),
	plus(N3, N2, N3_2).

%% ----------------------------------------------------------------------
%% Problem 1.1: Division

%% To use times for division in a well-moded fashion, it must have one of the
%% following modes:
%%
%% times(+N1,-N2,+N3)
%% The first clause violates this mode because the middle argument is
%% not determined by the first and third.
%%
%% times(-N1,+N2,+N3)
%% The second clause violates this mode: in the inductive call, we use
%% times with mode (-,+,-) because N3 is not yet determined.  However,
%% times can independently be given mode (-,+,-), so it is possible to
%% use times with this mode.
%% However, with mode (-,+,-) times does not terminate on all inputs
%% (the inductive call is on the same number!), enumerating
%% all N1 and N3 such that N1 * N2 = N3.  This is a problem for using
%% times with mode (-,+,+): when N2 does not exactly divide N3, search
%% for a divisor loops.
%%
%% This non-termination is spurious, as reversing the order of the subgoals
%% gives times mode (-,+,+) without the non-terminating search:

%% N1 * N2 = N3
%% -    +    +
%% B    Q  = A
%% i.e., A/Q
divExact(z, _, z).
divExact(s(N1), N2, N3_2) :-
	plus(N3, N2, N3_2),
	divExact(N1, N2, N3).

%% -------------------------------------------------------------------------------
%% Problem 2.1: Binary arithmetic

%% bits
bit(zz).
bit(oo).

%% add3Bits(b1,b2,b3,r,c): bits 1-3, then result and then carry
add3Bits(zz,zz,zz,zz,zz).
add3Bits(oo,zz,zz,oo,zz).
add3Bits(zz,oo,zz,oo,zz).
add3Bits(zz,zz,oo,oo,zz).
add3Bits(zz,oo,oo,zz,oo).
add3Bits(oo,zz,oo,zz,oo).
add3Bits(oo,oo,zz,zz,oo).
add3Bits(oo,oo,oo,oo,oo).


%% binary arithmetic

%% a binary number is represented as a list of bits,
%% with the head of the list being the LMB.
%% zero is represented as the empty list,
%% and there can be no trailing 0s

bitlist([]).
bitlist([H|T]) :- bit(H),bitlist(T).

endsWithOO([oo]).
endsWithOO([_|T]) :- endsWithOO(T).

binaryNumber([]).
binaryNumber(L) :- bitlist(L),endsWithOO(L).

bitToBinary(zz,[]).
bitToBinary(oo,[oo]).

unaryToBinary(z,[]).
unaryToBinary(N, [zz|B]) :-
	plus(NOver2, NOver2, N), %% N is even; slick but probably inefficient way to do division
	unaryToBinary(NOver2, B).
unaryToBinary(N, [oo|B]) :-
	plus(s(NOver2), NOver2, N), %% N is odd; slick but probably inefficient way to do division
	unaryToBinary(NOver2, B).

%% This is logically the same as above, but the subgoal order is reversed,
%% which is necessary for it to have the stated mode. 

binaryToUnary(z,[]).
binaryToUnary(N, [zz|B]) :-
	binaryToUnary(NOver2, B),
	plus(NOver2, NOver2, N). %% N is even; slick but probably inefficient way to do division
binaryToUnary(N, [oo|B]) :-
	binaryToUnary(NOver2, B),
	plus(s(NOver2), NOver2, N). %% N is odd; slick but probably inefficient way to do division
%% ----------------------------------------------------------------------
%% Problem 3: Merge sort

merge(L1, [], L1).
merge([H1 | L1], [H2 | L2], [H1 | L]) :-
	H1 =< H2,
	merge(L1, [H2 | L2], L).
merge([H1 | L1], [H2 | L2], [H2 | L]) :-
	H1 > H2,
	merge([H1 | L1], L2, L).

%% A very slow but kind of cute way to do partitioning:
%%    find two lists that append to the list in question,
%%    and whose lengths differ by no more than one.  

lengthsDifferByNoMoreThan1([], []).
lengthsDifferByNoMoreThan1([_], []).
lengthsDifferByNoMoreThan1([], [_]).
lengthsDifferByNoMoreThan1([_|Xs], [_|Ys]) :- lengthsDifferByNoMoreThan1(Xs,Ys).

partition(In, FirstHalf, SecondHalf) :-
	append(FirstHalf, SecondHalf, In),
	lengthsDifferByNoMoreThan1(FirstHalf, SecondHalf).
	
mergesort([], []).
mergesort([X], [X]).
mergesort(Un, Sorted) :-
	partition(Un, FirstHalf, SecondHalf),
	%% you could put green cut here to prevent trying lots of different partitions
	%% !, 
	mergesort(FirstHalf, FirstHalfSorted),
	mergesort(SecondHalf, SecondHalfSorted),
	merge(FirstHalfSorted, SecondHalfSorted, Sorted).


%% ----------------------------------------------------------------------
%% Problem 4: Dutch National Flag

%% One solution is (morally) to mergesort with the appropriate
%% comparator. I don't want to get into higher-order programming in
%% prolog, though

%% Since we know there are only 3 possible values, a bucket sort is easy

dnfHelp([], [], [], []).

dnfHelp([R|Rest], [R | Red], White, Blue) :-
	red(R),
	dnfHelp(Rest, Red, White, Blue).
dnfHelp([W|Rest], Red, [W | White], Blue) :-
	white(W),
	dnfHelp(Rest, Red, White, Blue).
dnfHelp([B|Rest], Red, White, [B | Blue]) :-
	blue(B),
	dnfHelp(Rest, Red, White, Blue).

dnf(In, Out) :-
	dnfHelp(In, Red, White, Blue),
	append(Red,White,RW),
	append(RW,Blue,Out).

%% seeds
red(ketchup).
red(blood).
red(steel).
white(paper).
white(president).
white(light).
blue(sky).
blue(smurf).
blue(outthecandles).

