#lang racket (require htdp/testing) ;; this file: ucErrorProp.rkt ;; name: Chris Stone and Christine Alvarado ;; approximate time spent: Just enough. ;; other comments? Yes. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; SAMPLE SOLUTION ;; ;; DO NOT COPY, DISTRIBUTE, OR PRINT ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; this next module provides the list unicalc-db and UDB ;; these are two names for the same association list of units... (require "unicalc-db.rkt") ;; Export these to other modules (provide add subtract multiply divide power normalize normalize-unit) ;; ;; The unit-calculator application - in Racket ;; ;; Provided functions - for comparing and sorting symbols ;; ;; comparing symbols alphabetically requires conv. to strings (define (symstring sym1) (symbol->string sym2))) ;; a function for sorting lists of symbols: sortsym (define (sortsym L) (sort L symstring (first L)) (symbol->string (first M))) (cancel (rest L) M)) ((string-cistring (first M)) (symbol->string (first L))) (cons (first M) (cancel L (rest M)))) (else (cancel (rest L) (rest M))))) ;; function: simplify ;; input: A quantity list ;; output: A simplified quantity list where ;; units shared between the numerator and denominator ;; have been eliminated. (define (simplify QL) (make-QL (get-quant QL) (cancel (get-den QL) (get-num QL)) (cancel (get-num QL) (get-den QL)))) ;; function: conv-unit ;; input: a unit, s ;; output: looks up a single unit in the database ;; and returns a QL for that unit if it is basic, ;; or the conversion entry in the database if it is not. (define (conv-unit s) (cond ((assoc s unicalc-db) (second (assoc s unicalc-db))) (else (make-QL 1.0 (list s) '())))) ;; function: normalize-unit ;; input: a unit, s ;; output: A normalized quantity list equivalent to the ;; input unit. (define (normalize-unit s) (cond ((not (assoc s unicalc-db)) (make-QL 1.0 (list s) '())) (else (normalize (conv-unit s))))) ;; function: conversion-factor ;; input: a unit, s ;; output: the dimensional-analysis conversion factor for ;; converting this unit into normalized units. ;; i.e., given input 'inch, the output would be the quantity ;; representing "0.0254 meters/inch." (define (conversion-factor s) (let ((QL (normalize-unit s))) (make-QL (get-quant QL) (get-num QL) (cons s (get-den QL))))) ; function: normalize ; input: a Quantity list, QL ; output: A normalized version of the input QL ; Multiplies the given QL by the conversion factors for the given ; numerator units, divides by the conversion factors for the given ; denominator units, and then cancels out any normalized units ; appearing in both the numerator and denominator. (define (normalize QL) (let* ((numerator-conversions (map conversion-factor (get-num QL))) (denominator-conversions (map conversion-factor (get-den QL)))) (simplify ;; Unfortunately, divide cares about the order of its arguments. ;; and foldr provides list values as first arguments and ;; "running totals" as the second argument. Since we want ;; to divide the running total by each list element in turn, ;; this means swapping the order of arguments before calling divide. (foldr (lambda (x y) (divide y x)) (foldr multiply QL numerator-conversions) denominator-conversions)))) ; An alternate implementation, using direct recursion. ; There are many other possibilities as well. ; ;(define (normalize QL) ; (cond ((null? (append (get-num QL) (get-den QL))) QL) ; ((null? (get-num QL)) ; (divide (make-QL (get-quant QL) '() '()) ; (normalize (make-QL 1.0 (get-den QL) '())))) ; (else (multiply (normalize-unit (first (get-num QL))) ; (normalize (make-QL (get-quant QL) ; (rest (get-num QL)) ; (get-den QL))))))) ;;;;;;;;;;;;;;;; ;; Arithmetic ;; ;;;;;;;;;;;;;;;; ;; function: multiply ;; input: two quantity lists. If the inputs are normalized ;; then the output will be as well ;; output: the QL QL1*QL2 (define (multiply QL1 QL2) (simplify (make-QL (* (get-quant QL1) (get-quant QL2)) (append (get-num QL1) (get-num QL2)) (append (get-den QL1) (get-den QL2))))) ;; function: divide ;; input: two quantity lists. If the inputs are normalized ;; then the output will be as well ;; output: the QL QL1/QL2 (define (divide QL1 QL2) (simplify (make-QL (/ (get-quant QL1) (get-quant QL2)) (append (get-num QL1) (get-den QL2)) (append (get-den QL1) (get-num QL2))))) ;; function: power ;; input: A quantity list QL1 and an integer p. ;; output: the quantity QL1^p. If QL1 is normalized ;; then the output will be as well ;; Takes advantage of the existing code for multiply and divide. (define (power QL1 p) (cond ((< p 0) (power (divide (make-QL 1.0 '() '()) QL1) (* -1 p))) ((= p 0) (make-QL 1.0 '() '())) (else (multiply QL1 (power QL1 (- p 1)))))) ;; function: units-match ;; input: two *normalized* QLs, QL1 and QL2 ;; output: #t if the units in QL1 are identical to the units ;; in QL2, #f otherwise. (define (units-match QL1 QL2) (and (equal? (get-num QL1) (get-num QL2)) (equal? (get-den QL2) (get-den QL2)))) ;; function: add ;; input: Two quantity lists. This version does not ;; assume the inputs are normalized, but it works ;; even if they are. ;; output: The QL resulting from adding the two input QLs, ;; or an error if the QLs are not in terms of the same units. ;; The output is always normalized. (define (add Q1 Q2) (let ((norm-Q1 (normalize Q1)) (norm-Q2 (normalize Q2))) (if (units-match norm-Q1 norm-Q2) (make-QL (+ (get-quant norm-Q1) (get-quant norm-Q2)) (get-num norm-Q1) (get-den norm-Q1)) (list 'error "illegal add" Q1 Q2)))) ;; function: subtract ;; input: Two quantity lists. This version does not ;; assume the inputs are normalized, but it works ;; even if they are. ;; output: The QL resulting from subtracting the two input QLs, ;; or an error if the QLs are not in terms of the same units. ;; The output is always normalized. ;; It might be an even better idea to define "subtract" in terms of "add", ;; rather than copying and pasting the code, but then subtractions could ;; produce an "illegal add" error message. Best of all would be to ;; put the common parts of the two functions into a third helper ;; function (i.e., where the operation to perform on quantities ;; and the appropriate error message get passed in as extra arguments). (define (subtract Q1 Q2) (let ((norm-Q1 (normalize Q1)) (norm-Q2 (normalize Q2))) (if (units-match norm-Q1 norm-Q2) (make-QL (- (get-quant norm-Q1) (get-quant norm-Q2)) (get-num norm-Q1) (get-den norm-Q1)) (list 'error "illegal subtract" Q1 Q2)))) ;; End of your unicalc functions and tests... (generate-report)