Lecture 13, July 17

The lecture notes I post serve two purposes: to remind you of the topics we discussed and to provide any interesting code examples I did during lecture. I would describe these notes as an outline, not a summary, of what I talked about during lecture. You are not expected to be able to learn the material simply from examining these notes, nor is reading these notes a reasonable substitute for attending lecture.

Efficiency

We discussed reducing duplicated computations. This can often be done with a local definition. We also discussed tail recursion.
#lang typed/racket

(require "../include/uchicago151.rkt")

(: rel->abs : (Listof Number) -> (Listof Number))
(define (rel->abs l)
  (local
    {(: add-to-each : Number (Listof Number) -> (Listof Number))
     (define (add-to-each n l)
       (cond
         [(empty? l) '()]
         [else (cons (+ n (first l)) (add-to-each n (rest l)))]))}
    (cond
      [(empty? l) '()]
      [else (cons (first l)
                  (add-to-each (first l) (rel->abs (rest l))))])))
(: rel->abs2 : (Listof Number) -> (Listof Number))
(define (rel->abs2 l)
  (local
    {(: helper : Number (Listof Number) -> (Listof Number))
     (define (helper n l)
       (cond
         [(empty? l) '()]
         [else (cons (+ n (first l)) (helper (+ n (first l)) (rest l)))]))}
    (helper 0 l)))

(: reverse : (All (A) (Listof A) -> (Listof A)))
(define (reverse l)
  (local
    {(: add-to-end : A (Listof A) -> (Listof A))
     (define (add-to-end i l)
       (cond
         [(empty? l) (list i)]
         [else (cons (first l) (add-to-end i (rest l)))]))}
  (cond
    [(empty? l) '()]
    [else (add-to-end (first l) (reverse (rest l)))])))
    ;[else (append (reverse (rest l)) (list (first l)))])))

(: reverse2 : (All (A) (Listof A) -> (Listof A)))
(define (reverse2 l)
  (local
    {(: helper : (Listof A) (Listof A) -> (Listof A))
     ; l is the input list, which we are dividing into first and rest
     ; m is the list we are building up
     (define (helper l m)
       (cond
         [(empty? l) m]
         [else (helper (rest l) (cons (first l) m))]))}
    (helper l '())))

(: append : (All (A) (Listof A) (Listof A) -> (Listof A)))
(define (append l m)
  (cond
    [(empty? l) m]
    [else (cons (first l) (append (rest l) m))]))

(: listmin : (Listof Real) -> Real)
(define (listmin l)
  (cond
    [(empty? l) +inf.0]
    [(< (first l) (listmin (rest l))) (first l)]
    [else (listmin (rest l))]))

(: listmin2 : (Listof Real) -> Real)
(define (listmin2 l)
  (local
    {(: helper : Real (Listof Real) -> Real)
     (define (helper min-so-far nums-left)
       (cond
         [(empty? nums-left) min-so-far]
         [else (helper (min min-so-far (first nums-left))
                       (rest nums-left))]))}
    (helper +inf.0 l)))

(: fact : Nonnegative-Integer -> Positive-Integer)
(define (fact n)
  (cond
    [(= n 0) 1]
    [else (* n (fact (sub1 n)))]))

(: fact2 : Nonnegative-Integer -> Positive-Integer)
(define (fact2 n)
  (local
    {(: helper : Positive-Integer Nonnegative-Integer -> Positive-Integer)
     (define (helper product n)
       (cond
         [(= n 0) product]
         [else (helper (* product n) (sub1 n))]))}
    (helper 1 n)))

; Write sum using tail recursion
(: sum : (Listof Number) -> Number)
(define (sum l)
  (local
    {(: helper : Number (Listof Number) -> Number)
     (define (helper n l)
       (cond
         [(empty? l) n]
         [else (helper (+ n (first l)) (rest l))]))}
    (helper 0 l)))

; The version of merge sort that we did in lecture had a lot of duplicated
; computations.  The take and drop functions do the same things twice:
;(define (take n l)
;  (cond
;    [(= n 0) '()]
;    [else (cons (first l) (take (sub1 n) (rest l)))]))
;(define (drop n l)
;  (cond
;    [(= n 0) l]
;    [else (drop (sub1 n) (rest l))]))
; Rewrite merge sort so that it only walks down the list one time to split the
; list into two pieces.
(define-struct (Pair A B)
  ([a : A]
   [b : B]))
(: msort : (All (A) (A A -> Boolean) (Listof A) -> (Listof A)))
(define (msort f l)
  (cond
    [(or (empty? l)
         (empty? (rest l))) l]
    [else
     (local
       {(: half-len : Nonnegative-Integer)
        (define half-len (quotient (length l) 2))
        (: split : Nonnegative-Integer (Listof A) (Listof A) -> (Pair (Listof A) (Listof A)))
        (define (split n l m)
          (cond
            [(= n 0) (Pair l m)]
            [else (split (sub1 n) (rest l) (cons (first l) m))]))
        (: splits : (Pair (Listof A) (Listof A)))
        (define splits (split half-len l '()))
        (: merge : (Listof A) (Listof A) -> (Listof A))
        (define (merge l m)
          (cond
            [(empty? l) m]
            [(empty? m) l]
            [(f (first l) (first m)) (cons (first l) (merge (rest l) m))]
            [else (cons (first m) (merge l (rest m)))]))}
       (merge (msort f (Pair-a splits))
              (msort f (Pair-b splits))))]))
(msort < (build-list 30 (λ ([n : Integer]) (random 100))))
; The version of quick sort that we did in lecture also had duplicated computations
; Rewrite it so that, instead of calling filter twice, it only processes the list
; once.  Similar to the merge sort problem above, write a helper function
; that will output a pair of lists
(: qsort : (All (A) (A A -> Boolean) (Listof A) -> (Listof A)))
(define (qsort f l)
  (cond
    [(empty? l) l]
    [else
     (local
       {(: pivot : A)
        (define pivot (first l))
        (: split : (Listof A) (Listof A) (Listof A) -> (Pair (Listof A) (Listof A)))
        (define (split l smaller greater)
          (cond
            [(empty? l) (Pair smaller greater)]
            [(f (first l) pivot) (split (rest l) (cons (first l) smaller) greater)]
            [else (split (rest l) smaller (cons (first l) greater))]))
        (: splits : (Pair (Listof A) (Listof A)))
        (define splits (split (rest l) '() '()))}
       (append (qsort f (Pair-a splits))
               (cons pivot (qsort f (Pair-b splits)))))]))
(qsort >= (build-list 30 (λ ([n : Integer]) (random 100))))