RU beehive logo ITEC dept promo banner
ITEC 380
2008fall
ibarland

homeinfolecturesexamsarchive

lect13a
Prolog: sorting

We finish, talking about sorting (and various helper functions). We also wrote nth:
nth(0,[F|_],F).
nth(I,[_|R],Answer) :-
  Iless1 is I-1,
  nth(Iless1,R,Answer).
     % Review:
     
     mem(X,[X|_]).
     mem(X,[_|R]) :- mem(X,R).
     
     % A version of 'reverse' which requires 'removeLast' and 'last':
     reverse([],[]).
     reverse([F|R],L2) :- last(L2,F), removeLast(L2,L2less), reverse(R,L2less).
     
     %  removeLast, Last -- not included in notes, 
     %  since it's part of hw05  !!
     
     %% Alternate version: An accumulator variable:
     reverse(L1,L2) :- reverseHelp( L1, [], L2 ).
     
     reverseHelp([],Acc,Acc).
     reverseHelp([F|R],Acc,Out) :- reverseHelp(R,[F|Acc],Out).
     
     
     filterSmallerThan(Threshold,[],[]).
     filterSmallerThan(Threshold,[F|R],SmallerRest) :- 
           F >= Threshold, 
           filterSmallerThan(Threshold,R,SmallerRest).
     filterSmallerThan(Threshold,[F|R],[F|SmallerRest]) :- 
           F < Threshold, 
           filterSmallerThan(Threshold,R,SmallerRest).
     
     
     %%%%%%%%%%%%%%
     % 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 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).
     
     % 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).
     
     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.
      *****************/

homeinfolecturesexamsarchive


©2008, Ian Barland, Radford University
Last modified 2008.Dec.01 (Mon)
Please mail any suggestions
(incl. typos, broken links)
to iba�rlandrad�ford.edu
Powered by PLT Scheme