CSC 270: Survey of Programming Languages
Scheme (Racket) Lecture 10 - An Extended Example: Sorting
An Extended Example: Sorting
-
One task of an investment advisor is to tell customers how their investments
performed. If an investment advisor has many clients, each of whom
have many holdings, this task is a natural candidate for a program.
-
We want to rank investments by their profit, with the more profitable ones
first and then decreasing in profit (and eventually increasing in losses).
-
To do this, we need to write a program that can sort a list of numbers.
Starting the Sort Program
-
Initially, it is best to represent the portfolio as a list of numbers,
each of which represents the profit of a stock holding. Next, we
proceed to the second step: the formulation of a contract, a header, and
a purpose statement:
;; sort : list-of-numbers -> list-of-numbers
;; to create a list of numbers that
contains
;; the same
numbers as alon in descending
;; order
(define (sort alon) ...)
-
The name sort is appropriate because it consumes a more or less random
list of numbers and produces a list of numbers that is sorted.
Examples of Sorts
-
Here are two examples:
(sort empty) = empty
(sort (list 1297.04 20000.00 -505.25))
= (list 20000.00 1297.04 -505.25 )
-
The first one covers the base clause of the data definition for list-of-numbers;
after all, empty list is the sorted version of empty.
-
The second example is an instance of the second and more interesting case.
The input contains many different numbers; the output contains the same
numbers but in descending order.
Templates for the Sort Program
-
Next we must translate this data description into a program template. We
can simply adapt the template we have used before:
(define (sort alon)
(cond
[(empty? alon)
...]
[else . (first
alon) . (sort (rest alon)) . ]
)
)
-
Let's consider each case of the cond-expression separately, starting with
the simple case. When sort's input is empty, its output is empty.
The Simple Case for the Sort Program
-
When sort's input is empty, its output is empty.
define (sort alon)
(cond
[(empty? alon)
empty]
[else . (first
alon) . (sort (rest alon)) . ]
)
)
Developing the non-empty case
-
So let's assume that the input is not empty, and deal with the second cond-clause.
-
It contains two expressions and, we must understand what they compute:
-
(first alon) extracts the first number from the input
-
(sort (rest alon)) extracts the rest of the list and, according to the
purpose statement of sort, produces a sorted version of (rest alon).
-
This observation suggests the key question of this program design:
.What do we do with the first element in alon when we already have
a sorted version of the rest of the list? .
-
Let's take a closer look at the second program example:
( sort (cons 1297.04 (cons 20000.00
(cons -505.25 empty))))
= (cons 20000.00 (cons 1297.04 (cons -505.25 empty)))
-
When sort consumes this input,
-
(
first alon)
evaluates to 1297.04,
-
(rest alon)
is (cons 20000.00
(cons -505.25 empty)),
and
-
(sort (rest alon))
produces (cons
20000.00 (cons -505.25 empty)).
-
To produce the desired answer, we must somehow insert the first number
in between the two numbers of this last list.
-
In general, the answer in the second cond-line must insert
(first
alon)
into its proper place into the sorted list (sort
(rest alon))
.
-
Inserting a number into a sorted list requires processing the entire list.
We need a separate program to do the insertion. Let us call this program
insert and let us formulate a clear purpose statement:
;; insert : number list-of-numbers
-> list-of-numbers
;; to create a list of numbers from
n and the numbers
;; on alon that is sorted in
descending order; alon is
;; already sorted
(define (insert n alon) . )
-
Let's assume for the moment that we can develop insert. Then it is easy
to complete the definition of sort:
(define (sort alon)
(cond
[(empty?
alon) empty]
[else (insert
(first alon)
(sort (rest alon)))]
)
)
Writing insert
-
Of course, we are not really finished until we have developed insert.
-
We already have a contract, a header and a purpose statement.
-
Next we need to make up program examples.
(insert 5 empty) = (list 5 )
(insert 1297.04(list 20000.00 -505.25))
= (list 20000.00 1297.04 -505.25 empty)
-
In contrast to sort, the program insert consumes two inputs. But we know
that the first one is a number and atomic (indivisible). However, we can
focus on the second argument, which is a list of numbers and suggests that
we use the list-processing template one more time:
(define (insert n alon)
(cond
[(empty? alon)
...]
[else . (first
alon) .
(insert n (rest alon)) . ]))
-
The only difference between this template and the one for sort is that
this one needs to take into account the additional argument.
-
To fill the gaps in the template of insert, we again proceed on a case-by-case
basis. Even if the input list is empty, insert must construct a list with
one number: its first input. Hence, the answer in the first case must be
(cons n alon)
-
The second case is more complicated. When alon is not empty, we know that
(first alon)
is the first number
on alon and
(insert n (rest alon))
produces
a sorted list consisting of n and all numbers on (rest
alon)
.
-
The problem is how to combine these pieces of data to get the answer. Let
us consider some examples again. Consider
( insert 3 (list 4 5 6))
-
Here n is 3 and smaller than any of the numbers in the second
input. Hence, it suffices if we just cons 3 onto
(list
4 5 6)
. In contrast, when the application is something like
(insert 3 (list-1 1 2 6))
the first argument n must indeed be inserted into the rest of the list.
More concretely,
1. (first alon)
is -1
2. (insert n (rest alon))
produces
the sorted list (list 1 2 3 6)
.
-
By consing -1 onto this last list, we get the desired answer.
-
The problem clearly requires a further case distinction.
-
If the (
first alon)
<
n, all the items in alon are smaller than n because alon
is already sorted. The result is (cons n alon) for this case.
-
If, however, the
(first alon)
>
n, then we have not yet found the proper place to insert
n into alon. Instead, we know that the first item of the result must be
the (first alon)
and that n must
be inserted into (rest alon)
. The
final result in this case is
(cons (first alon) (insert n
(rest alon)))
because this list contains n and all items of alon in sorted order--which
is what we need.
-
The translation of these ideas into Scheme requires writing a conditional
expression that distinguishes between the two possible cases.
-
The condition for the first clause determines whether n is larger than
(first alon):
(cond
[(> n (first alon))
...]
[else ...])
The final sort program
;; sort : list-of-numbers
-> list-of-numbers
;; to create a list of numbers with
the same
;; numbers
;; as alon sorted in descending order
(define (sort alon)
(cond
[(empty? alon)
empty]
[(cons? alon) (insert
(first alon)
(sort (rest alon)))]))
The final insert program
;; insert : number sorted-number-list
-> number-list
;; to create a list of numbers from
n and alon that is
;; sorted in descending order; alon
is sorted
(define (insert n alon)
(cond
[(empty? alon)
(cons n empty)]
[else (cond
[(> n (first alon)) (cons n alon)]
[else (cons (first alon)
(insert n (rest
alon)))])]))
[Back to the Notes Index]