/* --- 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. */