/*
--- CSU213 Spring 2006 Lecture Notes ---------
Copyright 2006  Viera K. Proulx

Lecture 13: Sorting out Sorting

Goals:

 - Review of sorting - comparison with Scheme.

Introduction:

The main reason for this lecture is to review sorting of lists of items.
You have seen this in Scheme - and in ProfessorJ, but here we will focus
not only on getting the task done, but reflect on the connections
and differences between Scheme and Java, and illustrate how using the
design recipe show us the way to solution.
------------------------------------------------------------------------

Here is a definition of a list of numbers in Scheme:

A List of Numbers (LON) is one of
-- empty
-- (cons Number LON)

Remember that (cons X Y) means we have a structure with two parts,
first and rest, with the following functions defined:

constructor: 
;; construct a new list from f and r
;; Number LON --> LON
(cons f r) 

;; predicate -- is the given datum a cons? (of any kind of data)
;; Any --> Boolean
(cons? a-list)

;; selector for the first element of the list
;; LON --> Number
(first a-list)

;; selector for the remainder of the list
;; LON --> LON
(rest a-list)

Compare this with Java definitions. The class diagram is:

           +------+              
           | ILoN |<------------+
           +------+             |
           +------+             |
             / \                |
             ---                |
              |                 |
      ----------------          |
      |              |          |
  +-------+    +-----------+    |
  | MTLoN |    | ConsLoN   |    |
  +-------+    +-----------+    |
  +-------+    | int first |    |
               | ILoN rest |----+ 
               +-----------+ 

The class definitions are straightforward:

// to represent a list of numbers
interface ILoN { }

// to represent an empty list of numbers
class MTLoN implements ILoN {
  MTLoN() { }
}

// to represent a nonempty list of numbers
class ConsLoN implements ILoN {
  int first;
  ILoN rest;

  ConsLoN(int first, ILoN rest) {
    this.first = first;
    this.rest = rest;
  }
}

We now design the function that produces a sorted list from the given
list of numbers. Here is the purpose, contract, and the header:

;; produce a sorted list of numbers from the given list of numbers
;; LON --> [LON (sorted)]
(define (sort a-list) ...)

Next we make examples:
(sort empty) --> empty
(sort (list 4 8 2 6)) -->  (list 2 4 6 8) 

So far, the examples did not give us any hint how to proceed, other than 
the fact that an empty list is always sorted. Let us look at the 
template:

;; Template
;; ... (empty? a-list) ...                          -- Boolean
;; ... (cons? a-list) ...                           -- Boolean
;;     ... (first a-list) ...                       -- Number
;;     ... (rest a-list) ...                        -- LON

;;     ... (sort (rest a-list)) ...                 -- [LON (sorted)]

The last entry reminds us that we can apply the sort function to the rest 
of the given list - and reading the purpose statement we see that it is
'sorted list of numbers from the rest of of our list of numbers'. For our
example above, we have:

;;     ... (first a-list) ...                       4
;;     ... (rest a-list) ...                        (list 8 2 6)

;;     ... (sort (rest a-list)) ...                 (list 2 6 8)

This shows us that all that remains to be done is to insert 4 into the
sorted (list 2 6 8). This looks to be a complex task, so we will defer 
the solution to a helper function. Here is the purpose, contract and
header for the helper:

;; produce a sorted list by inserting a number into a sorted list
;; [LON (sorted)] Number --> [LON (sorted)]
(define (insert s-list num) ...)

Now we can add the following to our template:

;;     ... (insert (sort (rest a-list)) Number) ... -- [LON (sorted)]

With this in place, we can complete the body of the sort function:

;; produce a sorted list of numbers from this list of numbers
;; LON --> [LON (sorted)]
(define (sort a-list)
  (cond
    [(empty? a-list) empty]
    [else (insert (sort (rest a-list)) (first a-list))]))

Before we go on to design the insert function, let us see how these steps
look when working in ProfessorJ. The sort method needs no additional
data other than the list that invoked the method. The purpose and header
will be:

  // produce a sorted list of numbers from this list of numbers
  ILoN sort(){

The function argument 'a-list' used in the Scheme function has become
'this' --- the object that invokes our method.

Next we make examples:

ILoN empty = new MTLoN();
ILoN sList = new ConsLoN(2,
             new ConsLoN(4,
             new ConsLoN(6,
             new ConsLoN(8, this.empty))));

ILoN aList = new ConsLoN(4,
             new ConsLoN(8,
             new ConsLoN(2,
             new ConsLoN(6, this.empty))));

// tests for the sort method
boolean testSort1 = this.empty.same(this.empty.sort());
boolean testSort2 = this.sList.same(this.aList.sort());

Again, only the empty case gives us a suggestion on how to proceed.
We easily complete the method for the MTLoN class:

  // produce a sorted list of numbers from this list of numbers
  ILoN sort(){ return this; }

and turn to the ConsLoN class. Again, we look at the template:

  ... this.first ...                         -- int
  ... this.rest ...                          -- ILoN
  ... this.rest.sort() ...                   -- [ILoN (sorted)] 

As before, we remind ourselves of the meaning of the data in the template
in the context of our example 'alist.sort()'

this.first                      4

this.rest                       new ConsLoN(8,
                                new ConsLoN(2,
                                new ConsLoN(6, this.empty)))

this.rest.sort()                new ConsLoN(2,
                                new ConsLoN(6,
                                new ConsLoN(8, this.empty))))

Again we see that all that remains to be done is to insert number 4
into the sorted list that contains the numbers 2, 6, and 8. The method
'insert' is invoked by a sorted ILoN object, and has to be given the 
number to insert. The purpose and the header for the method is:

  // produce a sorted list by inserting the given number 
  // into this sorted list of numbers
  ILoN insert(int num);

and we can add to the template for 'sort' the following:

  ... this.rest.sort().insert(..int..) ...   -- [ILoN (sorted)] 

Reading the purpose statement for this piece of data we see that it 
represents 'insert the given number into the sorted rest of this list'.
So, inserting the first number will do the job, and the method body becomes:

  // produce a sorted list of numbers from this list of numbers
  ILoN sort(){
    return this.rest.sort().insert(this.first);
  }

It is clear that the design process and the resulting solution in both 
cases is nearly identical. The differences are in how the two languages 
handle the union of data. In Scheme we were responsible for differentiating 
between the empty and cons clauses by using the 'cond'. In ProfessorJ this
is done by the compiler that dispatches the method invocation to the class
to which the object that invoked the method belongs. So, an empty list
invokes the method in the MTLoN class, while for the nonempty list the 
method invocation is dispatched to the ConsLoN class. The remainder of the
differences is just in the syntax.

We will design the function/method insert by looking at the two languages 
in parallel.

Here are the two purpose statements:

;; produce a sorted list by inserting a number into a sorted list
;; [LON (sorted)] Number --> [LON (sorted)]
(define (insert s-list num) ...)

  // produce a sorted list by inserting the given number 
  // into this sorted list of numbers
  ILoN insert(int num);

The only difference is that the explicit argument to the insert function
in Scheme has become the implicit 'this' argument for the insert method
in ProfessorJ.

We compare the examples:

In Scheme we have:

(equal? (list 2 4 6 8) (insert (list 4 6 8) 2))
(equal? (list 2 4 6 8) (insert (list 2 6 8) 4))
(equal? (list 2 4 6 8) (insert (list 2 4 6) 8))

vs. ProfessorJ:

ILoN empty = new MTLoN();
ILoN sList = new ConsLoN(2,
             new ConsLoN(4,
             new ConsLoN(6,
             new ConsLoN(8, this.empty))));

ILoN list1 = new ConsLoN(4,
             new ConsLoN(6,
             new ConsLoN(8, this.empty)));

ILoN list2 = new ConsLoN(2,
             new ConsLoN(6,
             new ConsLoN(8, this.empty)));

ILoN list3 = new ConsLoN(2,
             new ConsLoN(4,
             new ConsLoN(6, this.empty)));

// tests for the insert method
boolean testInsert1 = (new ConsLoN(2, this.empty)).same(this.empty.insert(2));
boolean testInsert2 = this.sList.same(this.list1.insert(2));
boolean testInsert3 = this.sList.same(this.list2.insert(4));
boolean testInsert4 = this.sList.same(this.list3.insert(8));

The main difference is that we do not have list abbreviations in ProfessorJ,
otherwise the examples represent the same problems and results.

Next we compare the templates for the two languages:

;; Template
;; ... (empty? a-list) ...                    -- Boolean
;; ... (cons? a-list) ...                     -- Boolean
;;     ... (first a-list) ...                 -- Number
;;     ... (rest a-list) ...                  -- [LON (sorted)]

;;     ... (insert (rest a-list)) Number) ... -- [LON (sorted)]

  TEMPLATE:
  ... this.first ...                  -- int
  ... this.rest ...                   -- [ILoN (sorted)] 
  ... this.rest.insert(..int..) ...   -- [ILoN (sorted)] 

Again the distinction between the empty and nonempty case in Scheme has
to be handled explicitly using 'cond', while in ProfessorJ the method 
invocation is dispatched automatically to the correct class. The remaining
differences are just in the syntax:

in Scheme we have:

(define (insert s-list num)
  (cond 
    [(empty? s-list) (cons num s-list)]
    [else
     (cond
       [(< num (first s-list)) (cons num s-list)]
       [else (cons (first s-list) (insert (rest s-list) num))])]))

in ProfessorJ - in the MTLoN class;

  ILoN insert(int num){
    return new ConsLoN(num, this);
  }

and in ProfessorJ - in the ConsLoN class;

  ILoN insert(int num){
    if (num < this.first)
      return new ConsLoN(num, this);
    else
      return new ConsLoN(this.first, this.rest.insert(num));
  }

Of course, we should now run the tests.

The complete code for each language is provided.

*/
