CS 313 - Homework 7 - More Scheme

Due: Wednesday 4/15 in class

This week, you are allowed to work by yourself or in groups of two. On problem 3, slightly more work is required when working in a group. Hand in one printout of your code per group. As usual, on the first page of your printout, write your name, the names of the students with whom you collaborated, and how much time it took you.

Implement the following functions. Hand in a printout of your code (no electronic submission). Include sample output for each function!

  1. Sorting of lists of integers.

    1. Implement a function "qsort" that performs quicksort on a list of integers (Sethi, problem 10.6 (a) on page 419). Use the first element of the list as pivot. While scheme has vectors, you should not use them here. To receive full credit on this problem, make sure that your sorting function has an average run-time of O(N log N) when sorting a list with N numbers. This requires your partition function to run in linear time (hint: tail recursion may come in handy here). Note: your sorting function should be non-destructive, i.e., it should construct a new sorted list without modifying the original list. Sample output:
      > (define l '(4 -2 5 3 99 2 6 1))
      
      > l
      (4 -2 5 3 99 2 6 1)
      
      > (qsort l)
      (-2 1 2 3 4 5 6 99)
      
      > l
      (4 -2 5 3 99 2 6 1)
      

    2. Implement a function "msort" that performs mergesort on a list of integers. Again, for full credit, make sure that your sorting function runs in O(N log N) time. This requires both your split and merge functions to run in linear time.

    3. Write a function that returns a list with N random integers (use "(random k)" to get an integer between 0 and k-1), and test your two sorting functions using such random lists. Use the following function to print timing results:
      (define (test-sorting l)
        (display "timing sorting of list of length ")
        (display (length l)) (newline)
        (let* ((rt0 (runtime))
               (lq (qsort l))
               (rt1 (runtime))
               (lm (msort l))
               (rt2 (runtime))
               (tq (round->exact (* 1000 (- rt1 rt0))))
               (tm (round->exact (* 1000 (- rt2 rt1))))
               (eq (if (equal? lq lm) 'yes 'no)))
          (display "quicksort runtime = ") (display tq)
          (display " ms") (newline)
          (display "mergesort runtime = ") (display tm)
          (display " ms") (newline)
          (display "same result: ") (display eq) (newline)
          #t))
      
      What are the largest lists that your sorting algorithms can handle without running out of memory? What are the runtimes? Include sample output showing the runtimes for three lists of integers, each twice as long as the previous (e.g. 2500, 5000, and 10000 integers). Do the observed runtimes reflect the expected O(N log N) model?

     

  2. Symbolic differentiation.

    The basic symbolic differentiator discussed in class implements the following reduction rules:

    dc/dx = 0    if c is a constant or a variable different from x
    
    dx/dx = 1
    
    d(u + v)/dx = du/dx + dv/dx
    
    d(u * v)/dx = u * dv/dx + v * du/dx
    
    Extend the differentiator to handle exponentiation of expressions with constant exponent. Using ^ as exponentiation operator, implement the differentiation rule
    d(u ^ n)/dx = n * (u ^ (n-1)) * du/dx
    
    Add a new clause to the program "deriv.scm" and extend the interface to the data by defining appropriate functions exponentiation? and make-exponentiation. Use the symbol ^ to denote the exponentiation operator. To be able to compute the exponentiation of two numbers, we can define ^ as the built-in exponentiation function expt:
    > (define ^ expt)
    > (^ 3 4)
    81
    

    Be sure that your code performs at least the following basic simplifications: (^ x 0) should be simplified to 1,
    (^ x 1) should be simplified to x,
    (^ a b) should be simplified to its numerical value for numbers a and b.

    Some examples:

    > (deriv '(^ x 3) 'x)
    (* 3 (^ x 2))
    
    > (deriv '(^ x 2) 'x)
    (* 2 x)
    
    > (deriv '(^ y n) 'y)
    (* n (^ y (- n 1)))
    
    > (deriv '(^ x 1) 'x)
    1
    
    > (deriv '(+ (^ x 2) (^ x 3)) 'x)
    (+ (* 2 x) (* 3 (^ x 2)))
    
    > (deriv '(^ x (* y 2)) 'x)
    (* (* y 2) (^ x (- (* y 2) 1)))
    
    > (deriv '(^ (* 2 x) 3) 'x)
    (* (* 3 (^ (* 2 x) 2)) 2)
    
    The differentiation code is given in ~schar/cs313/scheme/deriv.scm on benjerry.

     

  3. Infix printing of expressions.

    Implement a function "(infix exp)" that prints an infix representation of exp. Use "display" to print strings, numbers, and symbols. Parentheses should only be output where necessary. Assume expressions are composed of numbers, symbols, and binary +, -, *, and ^ expressions.

    Sample output:

    1 ]=> (define g '(* (+ (^ x 3) x) (^ x 2)))
    1 ]=> (infix g)
    (x^3 + x) * x^2
    ;Value: #t
    
    1 ]=> (define h (deriv g 'x))
    1 ]=> h
    ;Value: (+ (* (+ (^ x 3) x) (* 2 x)) (* (+ (* 3 (^ x 2)) 1) (^ x 2)))
    
    1 ]=> (infix h)
    (x^3 + x) * 2 * x + (3 * x^2 + 1) * x^2
    ;Value: #t
    
    Be careful when parenthesizing the non-associative operations - and ^. If you are working alone, you can always parenthesize these expressions if you want. If you are working in a group, you should avoid redundant parentheses and match the output below. In both cases, include plenty of sample output so I can see whether your code works.
    1 ]=> (infix '(- (- 1 2) (+ 3 4)))
    1 - 2 - (3 + 4)
    ;Value: #t
    
    1 ]=> (infix '(^ a (^ b c)))
    a^(b^c)
    ;Value: #t
    
    1 ]=> (infix '(^ (^ a b) c))
    a^b^c
    ;Value: #t
    
    1 ]=> (infix '(* (- x 2) (* (^ y (- (+ a b) 3)) 7)))
    (x - 2) * y^(a + b - 3) * 7
    ;Value: #t