Functional Problem Solving (CSC 151 2015F) : Labs

Laboratory: Merge Sort

Summary: In this laboratory, we consider merge sort, a more efficient technique for sorting lists of values.

Exercise 0: Preparation

Make a copy of `mergesort-lab.rkt`, the code for this lab.

Exercises

Exercise 1: Reflecting on Merging

a. What will happen if you call `merge` with unsorted lists as the two list parameters?

b. Check your answer by experimentation. To help you understand what is happening, you may wish to modify `merge` so that it displays the values of `sorted1` and `sorted2`.

c. What will happen if you call `merge` with sorted lists of very different lengths as the first two parameters?

Exercise 2: Sorting

a. Uncomment the following line in the `repeat-merge` helper in `new-merge-sort`.

```                 (write list-of-lists) (newline)
```

b. What output do you expect to get if you run your updated `new-merge-sort` on the list from the reading's self-check 3, step b?

d. Rerun `new-merge-sort` on a list of twenty integers.

Exercise 3: Special Cases

As we've seen, in exploring any algorithm, it's a good idea to check a few special cases that might cause the algorithm difficulty. Here are some to start with.

a. Run both versions of merge sort on the empty list.

b. Run both versions of merge sort on a one-element list.

c. Run both versions of merge sort on a list with duplicate elements.

Exercise 4: Steps in Merge Sort

We've claimed that merge sort takes approximately nlog2n steps. Let's explore that claim experimentally. We'll focus on the number of calls to `may-precede?`.

a. Briefly review the definitions of `counter-new`, `counter-count!`, `counter-reset!`, and `counter-print!` in the definitions pane.

b. Define a counter named `may-precede-counter` and a counter called `merge-counter`.

```(define int-may-precede?
(lambda (left right)
(counter-count! may-precede-counter)
(<= left right)))
```

d. Add the following line to the beginning of the `merge` procedure.

```  (counter-count! merge-counter)
```

```(define experiment
(lambda (lst)
(counter-reset! may-precede-counter)
(counter-reset! merge-counter)
(let ([result (merge-sort lst int-may-precede?)])
(counter-print! may-precede-counter)
(counter-print! merge-counter)
result)))
```

f. Using these counters, count the number of calls to `merge` and `may-precede?` in sorting a few lists of size 8, 16, 32, and 64. (Try a few lists of each size. You should use `random-numbers` to generate the lists.)

g. Is the number of calls to `merge` similar or different for different lists of the same size? Is the number of calls to `may-precede?` similar or different for different lists of the same size? What explains the similarities or differences?

h. Does the running time seem to grow slower than n2? (In such functions, when you double the input size, you should quadruple the number of steps.)

For Those with Extra Time

Extra 1: More Complex Merges

Assume that we represent names as lists of the form `(last-name first-name)`. Write an expression to merge the following two lists:

```(define mathstats-faculty
(list (list "Blanchard" "Jeff")
(list "Chamberland" "Marc")
(list "Fellers" "Pamela")
(list "French" "Chris")
(list "Jonkman" "Jeff")
(list "Kuiper" "Shonda")
(list "Mileti" "Joseph")
(list "Moore" "Emily")
(list "Moore" "Tom")
(list "Olsen" "Chris")
(list "Paulhus" "Jennifer")
(list "Shuman" "Karen")
(list "Wolf" "Royce")))

(define more-faculty
(list (list "Moore" "Chuck")
(list "Moore" "Ed")
(list "Moore" "Gordon")
(list "Moore" "Roger")))
```

Extra 2: Splitting, Revisited

Some computer scientists prefer to define `split` something like the following.

```(define split
(lambda (ls)
(let kernel ([rest ls]
[left null]
[right null])
(if (null? rest)
(list left right)
(kernel (cdr rest) (cons (car rest) right) left)))))
```

a. How does this procedure split the list?

b. Why might you prefer one version of split over the other?

Extra 3: Checking Permutations

We've written a procedure that checks whether a list is sorted, which is one of the postconditions of a list-based sorting routine. However, we have not yet written a procedure to check whether two lists are permutations of each other.

Write a procedure, ```(permutation? lst1 lst2)```, that determines if `lst2` is a permutation of `lst1`.

You might find the following strategy, which involves iterating through the first list, an appropriate strategy for testing for permutations.

Base case: If both lists are empty, the two lists are permutations of each other.

Base case: If the first list is empty and the second is not (i.e., it's a pair), the two lists are not permutations of each other.

Base case: If the first list is nonempty and car of the first list is not contained in the second, the two lists are not permutations of each other.

Recursive case: If the first list is not empty and the first element of the first list is in the second list, then the two lists are permutations of each other only if the cdr of the first list is a permutation of what you get by removing the car of the first list from the second list.

You may also find the following two procedures useful.

```;;; Procedure:
;;;   list-contains?
;;; Parameters:
;;;   lst, a list
;;;   val, a value
;;; Purpose:
;;;   Determines if lst contains val.
;;; Produces:
;;;   contained?, a Boolean
;;; Preconditions:
;;; Postconditions:
;;;   If there is an i such that (list-ref lst i) equals val,
;;;     then contained? is true (#t).
;;;   Otherwise,
;;;     contained? is false.
(define list-contains?
(lambda (lst val)
(and (not (null? lst))
(or (equal? (car lst) val)
(list-contains? (cdr lst) val)))))

;;; Procedure:
;;;   list-remove-one
;;; Parameters:
;;;   lst, a list
;;;   val, a value
;;; Purpose:
;;;   Remove one copy of val from lst.
;;; Produces:
;;;   newlst
;;; Preconditions:
;;;   lst contains at least one value equal to val.
;;; Postconditions:
;;;   (length newlst) = (length lst) - 1
;;;   lst is a permutation of (cons val newlst).
;;;   Ordering is preserved.  That is, if val1 and val2 appear in
;;;     both lst and newlst, and val1 precedes val2 in lst, then
;;;     val1 precedes val2 in newlst.
(define list-remove-one
(lambda (lst val)
(cond
((null? lst)
null)
((equal? val (car lst))
(cdr lst))
(else
(cons (car lst) (list-remove-one (cdr lst) val))))))
```