In lab last week, you saw your first sorting program: a program which consumes a list of numbers and produces a list of the same numbers, only in sorted order. Today, we want to look at another sorting algorithm, called quicksort (so called because it is ... quicker than other sorting algorithms on large examples). The algorithm works as follows: - call the first element of the list the pivot - divide the rest of the list into two pieces: those that are smaller than the pivot, and those that are larger than the pivot. - sort these two new lists - form a new list from the sorted list of smaller elementss, the pivot, and the sorted list of larger elements. Let's work through two examples: 1. (list 11 8 14 7) 2. (list 1 5 3 6) See section 25 of the text for a detailed walk-through of how quicksort operates on the first example. The second example is similar. Now, we want to write the program. As always, we start with the contract, purpose, header, and template: ;; qsort : (listof number) -> (listof number) ;; sort the numbers in the list in increasing order (define (qsort alon) (cond [(empty? alon) ...] [else ... (first alon) ... (qsort (rest alon)) ...])) We've written out the steps in English. Can you fill in the rest of the program? Parts of this are easy, based on the description: (define (qsort alon) (cond [(empty? alon) empty] [else (local ((define pivot (first alon))) ... (qsort (rest alon)) ...)])) What's strange here is that the template isn't doing what we want it to do. We don't want to run qsort on the rest of the list, but on the list of numbers smaller than the pivot and on the list of numbers larger than the pivot. In other words, our program needs to do something like the following: (define (qsort alon) (cond [(empty? alon) empty] [else (local ((define pivot (first alon))) ... (qsort (smaller-items (rest alon) pivot)) ... (qsort (larger-items (rest alon) pivot)) ...)])) (Assume for the moment that we've written the helper functions smaller-items and larger-items.) Let's first finish the program and look at another example, then come back and discuss what went wrong with the template: (define (qsort alon) (cond [(empty? alon) empty] [else (local ((define pivot (first alon))) (append (qsort (smaller-items (rest alon) pivot)) (list pivot) (qsort (larger-items (rest alon) pivot))))])) (define (smaller-items alon threshold) (filter (lambda (n) (< n threshold)) alon)) (define (larger-items alon threshold) (filter (lambda (n) (> n threshold)) alon)) Let's consider a second example. Have you heard of fractals? Fractals are objects with fractal dimension. They are interesting to us because they show similar structure on several different scales. Let's consider a simple example of a fractal, known as the Sierpinski triange (see the section on fractals, page 367 in the text, for the picture of the Sierpinski triangle). We want to write a program that consumes the three vertices of the original (equilateral) triangle and draws the Sierpinski triangle, returning true. The program only draws triangles whose sides are longer than a certain small threshold, say 3. Assume you have a function draw-triangle, which consumes three points, draws a triangle between them, and returns true. The data definition for points (posns, in the text and libraries) is ;; A posn is a structure ;; (make-posn x y) ;; where x and y are numbers (define-struct posn (x y)) How do we get started? Well, our description of the problem suggests two cases: one if a side of the triangle is smaller than three and one if it is not. Let's defer the size check to a helper function for now: (define (sierpinski p1 p2 p3) (cond [(too-small? p1 p2 p3) true] [else ... (draw-triangle p1 p2 p3) ...])) How do we fill in the else case? The problem said that we need to find the midpoints of the three sides, then fill in the three outer triangles. The following program does this: (define (sierpinski p1 p2 p3) (cond [(too-small? p1 p2 p3) true] [else (local ((define p1-p2 (mid-point p1 p2)) (define p2-p3 (mid-point p2 p3)) (define p3-p1 (mid-point p1 p3))) (and (draw-triangle p1 p2 p3) (sierpinski p1 p1-p2 p3-p1) (sierpinski p2 p1-p2 p2-p3) (sierpinski p3 p3-p1 p2-p3)))])) Let's step back and look at these two programs, qsort and sierpinski. They are both recursive, yet neither follows the templates that we have discussed. What's the difference here? Where did the recursion come from in the programs that we've been writing all semester? From the data. We used recursively defined data, so we needed recursive programs to process that data. Where does the recursion come from in today's programs? From the problems that we are writing programs to solve (even though the data is also recursively defined, as in the case of qsort). Both of today's programs use a common problem solving technique called "divide-and-conquer": we take a problem, break it into smaller instances of the same problem, solve the smaller instances, and combine the results. We use recursion because we are solving instances of the same problem, but we cannot break the problem into pieces based solely on the structure of the data. Instead, we break the problem into pieces based on the insights that we have about the problem itself. Thus, today we introduce a new form of recursion called "generative recursion". In generative recursion, we generate new instances of a problem based on some creative insight and solve them recursively. Our prior problems use what we call "structural recursion": the recursion comes solely from the recursive structure of the data. We know the design recipes for structural recursion. What about generative recursion? Let's look at qsort and sierpinski and try to find some commonality in their organization. Notice that both programs use a cond, with one case for "the problem is small enough to solve" and another for "decompose the problem into smaller problems". As a first approximation to a template, we can therefore write the following: (define (gen-recur-func arg1 .. argn) (cond [(easy-enough-to-handle? arg1 ... argn) (handle-easy-case arg1 ... argn)] [else (combine (gen-recur-func smaller-problem1) ... ... (gen-recur-func smaller-problemk) ... )])) We'll continue refining this template and the design recipe for generative recursion in the next class.