CS 313 - Homework 7 - More Scheme

Due: Wednesday 4/19 at 11:15am

This week, you are allowed to work by yourself or in groups of two. I only need one submission per group, as always with both of your names in a comment at the top. Please submit your code for this homework as a single file hw7.scm containing all your functions; as well as plenty of sample output (copy and paste a terminal session) as a single text file hw7-output.txt. Upload both files via the HW 7 submission page by the beginning of class on Wednesday.

Implement the following functions. Submit 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? Include answers to these questions together with your sample output in a text file.

     

  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 the lab machines.

     

  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. For full credit, 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 want, you can simply always parenthesize these expressions, which will only cost one point penalty. To receive this last point, avoid all 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