The answers to the first 4 problems are given below, along with a hint on the last problem (ACL 8.5). Because I think the last problem is an interesting one, but so few people completed it, I am reassigning it. If you did for Assignment 2, that's fine -- just include it in your Assignment 3 as well. Since the assignment was all code this time, I included comments not in red text, but as comments embedded in the code below. You should study the answers, even if you got full credit on a problem because I've posed a few questions regarding the code. If you can answer the questions confidently, that's a good sign. If not, we can discuss the answers, and try to figure them out together.
;;;; Problem 1 (ACL 3.5) (3 pts) ;;; Recursive version ;; Uses &optional so that caller need not specify ;; the zero (0) position on first call ;; Note: This function is not tail-recursive -- why not? (defun pos+ (lst &optional (pos-num 0)) (if (null lst) nil (let ((first-elt (first lst))) (if (numberp first-elt) (cons (+ first-elt pos-num) (pos+ (cdr lst) (+ pos-num 1))) (pos+ (cdr lst) (+ pos-num 1))))))) ;;; Iterative version (defun pos+ (lst) (let ((pos 0) (result-list nil)) (dolist (num-elt lst) (push (+ num-elt pos) result-list) (incf pos)) (reverse result-list))) ;;; Mapcar version ;; Many people wound up using setf because they couldn't ;; figure out how to pass values between successive calls ;; to mapcar. So instead they setf'd a value inside the ;; function they passed to mapcar. Yikes. (defun pos+ (lst) (mapcar #'(lambda (num pos) (+ num pos)) lst (index-list (length lst)))) (defun index-list (len &optional (result-lst nil)) (if (= len 0) (cons 0 result-lst) (index-list (- len 1) (cons len result-lst)))) ;;;; Problem 2 (ACL 4.4) (2 pts) (defun big-to-small (bst &optional (result-list nil)) (if (null bst) result-list (let ((min (node-elt (bst-min bst)))) (big-to-small (bst-remove min bst #'<) (cons min result-list))))) ;;;; Problem 3 (ACL 5.7) (3pts) ;;; Recursive version (defun succ-diff-one-p (lst) (if (null lst) t (let ((one (first lst)) (two (second lst))) (if (or (null one) (null two)) (succ-diff-one-p (cddr lst)) (if (diff-one-p one two) (succ-diff-one-p (cdr lst)) nil))))) (defun diff-one-p (one two) (if (= (abs (- one two)) 1) t nil)) ;;; do version (defun succ-diff-one-p (lst) (do ((lst-len (length lst)) (counter 1 (incf counter))) ((= counter lst-len) t) (unless (diff-one-p (nth (- counter 1) lst) (nth counter lst)) (return nil)))) ;;; Using mapc and return ;; Why is the return here? Wouldn't this work ;; without the block/return? (defun succ-diff-one-p (lst) (block nil (let ((prev (car lst))) (if (null (mapc #'(lambda (curr) (if (diff-one-p curr prev) (setf prev curr) (return nil))) (cdr lst))) nil t)))) ;;;; Problem 4 (ACL 6.8) (3 pts) ;;; Many people used a hash table here, but this is not a ;;; a good candidate for using a hash table. Hash tables are ;;; appropriate when the data is sparse in the key space. ;;; Here's a (classic) example: social security numbers (SSN). ;;; SSN's are 9 digits, which mean that there are 10^10 possible ;;; SSN's. However, there are only about 2.5x10^8 people in the ;;; U.S. right now. If you create an array, then only 1 in 40 ;;; slots will have a value in it. You're wasting 98% of the ;;; array. But with a hash table, you would use only what you ;;; need. The cost is that hash tables are more complex (read ;;; slower to run) to implement. ;;; ;;; Personally, I think the assoc list solution is simplest. ;;; You could also make a good case for using a vector because ;;; you know how many elements. you will have in advance (0-100). ;;; ;;; What *is* bad is setf'ing a hash table (or any other ;;; data structure) at the top-level, and then referring ;;; to it from within your function. ;;; ;;; Note that the setf is okay here (where is it?). Why? ;;; Think about the normal objection to setf, and then ;;; look for what distinguishes this version from the ;;; typical case. (let ((table nil)) (defun frugal (int) (let ((answer (assoc int table))) (if (null answer) (cdar ; What does cdar() do, (push ; and why does it work here? (cons int (expensive int)) table)) (cdr answer))))) ;; expensive() computes int! (defun expensive (int) (do ((j int (- j 1)) (f 1 (* j f))) ((= j 0) f))) ;;;; Problem 5 (ACL 8.5) ;;; This was tricky problem, because you had to think ;;; about how to solve it before writing the code. ;;; How could you recognize that a quote might have ;;; been produced by Henley (the sample code in ;;; section 8.8)? Think about how Henley works: ;;; it's probabilistic, so the ideal answer is to ;;; write code that takes the probabiities into ;;; account. But there's a simpler way that can, in ;;; some cases, say positively that Henley did NOT ;;; generate a piece of text. ;;; The sample code needed is here: (defparameter *words* (make-hash-table :size 10000)) (defconstant maxword 100) (defun read-text (pathname) (with-open-file (s pathname :direction :input) (let ((buffer (make-string maxword)) (pos 0)) (do ((c (read-char s nil :eof) (read-char s nil :eof))) ((eql c :eof)) (if (or (alpha-char-p c) (char= c #\')) (progn (setf (aref buffer pos) c) (incf pos)) (progn (unless (zerop pos) (see (intern (string-downcase (subseq buffer 0 pos)))) (setf pos 0)) (let ((p (punc c))) (if p (see p))))))))) (defun punc (c) (case c (#\. '|.|) (#\, '|,|) (#\; '|;|) (#\! '|!|) (#\? '|?|) )) (let ((prev `|.|)) (defun see (symb) (let ((pair (assoc symb (gethash prev *words*)))) (if (null pair) (push (cons symb 1) (gethash prev *words*)) (incf (cdr pair)))) (setf prev symb))) (defun generate-text (n &optional (prev '|.|)) (if (zerop n) (terpri) (let ((next (random-next prev))) (format t "~A " next) (generate-text (1- n) next)))) (defun random-next (prev) (let* ((choices (gethash prev *words*)) (i (random (reduce #'+ choices :key #'cdr)))) (dolist (pair choices) (if (minusp (decf i (cdr pair))) (return (car pair))))))