%%%%%%%%%%%%%%
% Sorting:
% We'll just provide the 'spec':
% L2 is a sorted version of L1 if:
% - L1 is a permutation (re-arrangement) of L2, and
% - L2 is in ascending order (er, non-decreasing).
scrapSort(L1,L2) :- permutation(L1,L2), nondecreasing(L2).
% Here're the helper functions:
nondecreasing([]).
nondecreasing([_]).
nondecreasing([F,S|R]) :- F=<S, nondecreasing([S|R]).
badPerm1([],[]).
badPerm1([F|R],Y) :- append2(Y1,[F|Y2],Y),
append2(Y1,Y2,PY),
badPerm1(R,PY).
% This is a correct *specification* of permutation.
% However, it has several drawbacks:
% (a) written non-intuitively,
% (b) using 'append' is a sledgehammer (poor performance too),
% (c) it only half-works, in Prolog:
% badPerm1([3,4,5],[5,3,4]) is true
% but
% badPerm1([3,4,5],X) gives only *one* answer
% and then loops forever.
% However, it works 'in reverse':
% badPerm1(X,[3,4,5]) gives all solutions.
badPerm2([],[]).
badPerm2([F|R],Y) :- remove(F,Y,Z), badPerm2(Z,R).
%
% This is also a correct spec,
% and it fixes drawbacks (a) and (b),
% but still suffers from (c).
% In fact, it's worse: badPerm2 no longer
% works in reverse -- badPerm2(X,[3,4,5]) gives
% *two* (?!) solutions, and then backtracks
% into la-la land.
% Here's a fully-working version:
% like badPerm2 except that it destructures the *return* value.
%
permutation([],[]).
permutation(X,[F|R]) :- remove(F,X,Z), permutation(Z,R).
remove(F,[F|L1],L1).
remove(F,[G|L1],[G|L2]) :- remove(F,L1,L2).
%% Now, back to sorting...
% Reflecting on scrapSort:
% Consider the *order* of solutions
% permutation([3,4,5],X) and permutation([5,4,3],X).
% How many permutations does scrapSort([3,4,5],X) look at?
% How about scrapSort ([5,4,3],X) look at?
%
% Now, consider this problem amplified:
% scrapSort([8,7,6,5,4,3,2,1],X) runs okay. But
% But scrapSort([10,9,8,7,6,5,4,3,2,1],X) takes a while.
%
% The name "scrapSort" was chosen because its performance
% is a piece of scrap: O(n!).
% Ironically, 'quicksort' is more straightforward than 'scrapsort',
% even though we specify *how* to sort rather than *what sorted means*.
%
% QuickSort
% Author: Varesano Fabio
% http://www.varesano.net/blog/fabio/quick+sort+implemented+prolog
/* [+,-] */
quicksort([], []).
quicksort([HEAD | TAIL], SORTED) :- partition(HEAD, TAIL, LEFT, RIGHT),
quicksort(LEFT, SORTEDL),
quicksort(RIGHT, SORTEDR),
append(SORTEDL, [HEAD | SORTEDR], SORTED).
/* [+,+,-,-] */
partition(PIVOT, [], [], []).
partition(PIVOT, [HEAD | TAIL], [HEAD | LEFT], RIGHT) :- HEAD @=< PIVOT,
partition(PIVOT, TAIL, LEFT, RIGHT).
partition(PIVOT, [HEAD | TAIL], LEFT, [HEAD | RIGHT]) :- HEAD @> PIVOT,
partition(PIVOT, TAIL, LEFT, RIGHT).
/* [+,+,-] */
append([], LIST, LIST).
append([HEAD | LIST1], LIST2, [HEAD | LIST3]) :- append(LIST1, LIST2, LIST3).
% Here is a another, more verbose version
% (the version I started writing myself ...
% Note that I'm used to partitioning into three sections,
% so that we always *reduce* the size of a partition
% (even if our pivot is the maximum value).
% Study the above sol'n, to see how the Prolog
% makes sure we always recur on smaller lists (even
% when the pivot is the maximum value *and* when
% it's the minimum value).
qsort([],[]).
qsort([F|R],L2) :- partition([F|R],F,Sm,Eq,Lr),
qsort(Sm,SmSorted),
qsort(Lr,LrSorted),
append3(SmSorted,Eq,LrSorted,L2).
partition([],_,[],[],[]).
partition([F|R],Pivot,Smaller,Equal,Larger) :-
F<Pivot,
Smaller = [F|SmallerRest],
partition(R,Pivot,SmallerRest,Equal,Larger).
partition([F|R],Pivot,Smaller,Equal,Larger) :-
F=Pivot,
Equal = [F|EqualRest],
partition(R,Pivot,Smaller,EqualRest,Larger).
partition([F|R],Pivot,Smaller,Equal,Larger) :-
F>Pivot,
Larger = [F|LargerRest],
partition(R,Pivot,Smaller,Equal,LargerRest).
append3(L1,L2,L3,All) :- append2(L1,L2,L1and2), append2(L1and2,L3,All).
append2([],L2,L2).
append2([F|R],L2,[F|AllRest]) :- append2(R,L2,AllRest).
/*****************
Reflections/thoughts on prolog:
* The idea of declarative programming is cool:
Just by specifying what *constitutes* a correct answer,
you can write a program (and often it's efficient).
+ Sometimes though, it's too inefficient (scrapSort).
+ Worse though: to really write Prolog, you need to
thoroughly understand *how* it implements searching
(badPerm).
+ Seeing how different 'cultures' compute is mind-expanding;
* Pattern matching: Way cool!
This is something *any* language can use
(ML, Haskell; Scheme has a 'match' library).
* The idea of specifying correctness of an answer
(separately from an algorithm)
makes me a better [Java, whatever] programmer:
often having a clean spec of correctness
leads directly to clean, correct code.
*****************/ |