Problem: In a class, students each have taken two tests, as described in the following define statement:
(define class
'( ("Egbert Bacon" 88 85)
("William Hemingway" 79 63)
("Frances Homer" 94 86)
("Po Wer MacHinery" 84 91)
("R. McDonald" 82 96)
("L. Bo Peep" 76 78)
("A. N. Onymous" 72 81)
("Henry Shakespeare" 90 92)
("I. M. Silly" 87 70)
("P. Arty Time" 62 59)
("Mac K. Walker" 93 87))
)
Given this data, we would like to write a program to fill in the following
table:
Name Test 1 Test 2 Average
Egbert Bacon 88 85 ...
William Hemingway 79 63 ...
...
...
...
Averages: ... ...
Solution: A previous lab described how to use the display
procedure to print output to the screen and how to use newline to
move from one line of output to the next. In what follows, we use these
procedures repeatedly to print the table desired for the current problem.
The structure of our solution will parallel the form of the table:
We now consider each of these output elements in turn.
Printing the Title: Overall, we want a class-print procedure which takes a class of the above form as a parameter. Since the display procedure prints text, we can produce the title with a simple display and newline at the start of class-print. This suggests the following start for our solution:
(define class-print ;;; version 1 with title
(lambda (class-list)
;;; Print Table Header
(display "Name Test 1 Test 2 Average")
(newline)
)
)
Printing Names, Their Scores, and Their Averages:
To print successive names and their scores, we move recursively down
the class -- extracting the name and each score from each student entry.
Since we follow the same work for each successive name, we use a local
procedure print-student for each person on the class list.To compute an average, we extract the scores for test 1 and test 2 from the information about a student and we average these values.
To print all-students, we proceed down the class list -- starting with the entire list and continuing recursively until we have only the null class list remaining. This yields the next version of class-print.
(define class-print ;;; version 2 with first cut at student entries
(lambda (class-list)
;;; Print Table Header
(display "Name Test 1 Test 2 Average")
(newline)
;;; Print successive class entries
(let ((print-student ;;; local procedure for individual student
(lambda (student-entry)
(let ((student (car student-entry))
(test-1 (cadr student-entry))
(test-2 (caddr student-entry)))
(display student)
(display test-1)
(display test-2)
(display (/ (+ test-1 test-2) 2.0))
(newline)
)
)
))
;;; recursive procedure to run through class
(let all-students ((class class-list))
(if (null? class)
"End of table"
(begin
(print-student (car class))
(all-students (cdr class))
)
)
)
)
)
)
When this procedure is run with the data set we first described, the
following output is obtained:
> (class-print class) Name Test 1 Test 2 Average Egbert Bacon888586.5 William Hemingway796371.0 Frances Homer948690.0 Po Wer MacHinery849187.5 R. McDonald829689.0 L. Bo Peep767877.0 A. N. Onymous728176.5 Henry Shakespeare909291.0 I. M. Silly877078.5 P. Arty Time625960.5 Mac K. Walker938790.0 "End of table"While this output shows the correct information, all values are compressed together. Thus, we will need to add some spaces between the test scores and between the name and the first score.
Adding space between the test scores is reasonably straightforward: we might print 6 spaces between one number and the next.
Adding space after the name is slightly more complicated, however, as the names have different lengths. One simple approach to handle this task is to pad each name with a reasonably large number of spaces. Then, we use the substring procedure to select the desired number of characters for the name part of the table. The next version of the table program follows:
(define class-print ;;; version 3 with formatted student entries
(lambda (class-list)
;;; Print Table Header
(display "Name Test 1 Test 2 Average")
(newline)
;;; Print successive class entries
(let ((print-student ;;; local procedure for individual student
(lambda (student-entry)
(let ((student (car student-entry))
(test-1 (cadr student-entry))
(test-2 (caddr student-entry)))
;;; take 22 characters of padded name
(display (substring
(string-append student " ")
0 21))
(display test-1)
(display " ") ;;;spaces printed here
(display test-2)
(display " ") ;;; more spaces here
(display (/ (+ test-1 test-2) 2.0))
(newline)
)
)
))
;;; recursive procedure to run through class
(let all-students ((class class-list))
(if (null? class)
"End of table"
(begin
(print-student (car class))
(all-students (cdr class))
)
)
)
)
)
)
To allow tail recursion within all-students, we keep these totals as additional parameters -- adding each new student's data during the recursion. When all students have been processed (e.g., when we reach the null list as the base case), the final average information can be printed. This motivates the following program:
(define class-print ;;; version 4 -- the almost completed program
(lambda (class-list)
;;; Print Table Header
(display "Name Test 1 Test 2 Average")
(newline)
;;; Print successive class entries
(let ((print-student ;;; local procedure for individual student
(lambda (student-entry)
(let ((student (car student-entry))
(test-1 (cadr student-entry))
(test-2 (caddr student-entry)))
;;; take 22 characters of padded name
(display (substring
(string-append student " ")
0 21))
(display test-1)
(display " ") ;;;spaces printed here
(display test-2)
(display " ") ;;; more spaces here
(display (/ (+ test-1 test-2) 2.0))
(newline)
)
)
))
;;; recursive procedure to run through class
(let all-students ((class class-list)
(test-1-total 0)
(test-2-total 0)
(number-students 0))
(if (null? class)
(begin ;;; print final averages
(display "Averages ")
(display (/ test-1-total number-students))
(display " ")
(display (/ test-2-total number-students))
(newline)
)
(begin
(print-student (car class))
(all-students (cdr class)
(+ test-1-total (cadar class))
(+ test-2-total (caddar class))
(+ number-students 1))
)
)
)
)
)
)
When we run this program, we obtain the following:
> (class-print class) Name Test 1 Test 2 Average Egbert Bacon 88 85 86.5 William Hemingway 79 63 71.0 Frances Homer 94 86 90.0 Po Wer MacHinery 84 91 87.5 R. McDonald 82 96 89.0 L. Bo Peep 76 78 77.0 A. N. Onymous 72 81 76.5 Henry Shakespeare 90 92 91.0 I. M. Silly 87 70 78.5 P. Arty Time 62 59 60.5 Mac K. Walker 93 87 90.0 Averages 907/11 888/11This gives correct results, except that we might have preferred to have the class averages in decimal form rather than printed as fractions. Further, we might like the results printed to two decimal places.
One way to accomplish this is to initialize test-1-total and
Describe why does this approach returns the value of R to the
desired number of decimal places.
Use this approach to rounding to adjust the printing of averages in the
previous program. Adjust spacing, so that columns remain aligned.
Modify the expanded program from part 5, changing print-student
so that these three values are passed as parameters. Then, declare and
bind these local variables within the else clause of
all-students and use them to call print-student and
within the local computations as necessary.
Problem: Write a procedure that reads 3 scores from the keyboard
and averages them:
Approach 1: The first approach explicitly calls read 3
times in its computation:
When the program runs, the first display procedure prompted the
user to enter some numbers. Then, before the next display can
finish, three values must be read and averaged. Each call to procedure
read fetches the next value, and the average then is computed.
Comment upon whether the resulting code seems simpler or more complex than
the version given above.
Write an average procedure which reads successive numbers until 0
is entered and computes and prints the average of these numbers. Be sure
you do not include 0 in your computation of the average.
Note: In this problem, the number 0 is called a sentinel.
Generally, a sentinel is a value read that tells the computer something
special about how processing should proceed.
This document is available on the World Wide Web as
Reading and Writing:
Sometimes, in addition to printing, it is convenient to read values from
the keyboard. This is accomplished with the read procedure with
returns with the value of the next item typed at the keyboard. The use of
this procedure is illustrated with two approaches to the following simple
problem
(/ (round (* R 100)) 100.0)
will give a result to two decimal places.
(define average-3
(lambda ()
(display "Enter three numbers: ")
(display (/ (+ (read) (read) (read)) 3.0))
(display " is the average of the three numbers")
(newline)
)
)
When we run this program to average the numbers 3, 5, and 10, we might have
the following interaction with the computer:
> (average-3)
Enter three numbers: 3 5 10
6.0 is the average of the three numbers
Here, we began processing by calling the procedure with
(average-3). Note that this procedure does not require any
parameters.
Approach 2: We might proceed recursively, binding each value read
to a local variable. A separate parameter within a kernel procedure could
record how many values have been read:
(define average-3
(lambda ()
(display "Enter three numbers: ")
(let kernel ((sum 0)
(number-to-process 3))
(if (= number-to-process 0)
(begin ;;; base case -- all numbers read
(display (/ sum 3.))
(display " is the average of the three numbers")
(newline)
)
;;; else -- read another number and add it
(let ((value-read (read))) ;; read and bind value
(kernel (+ sum value-read)
(- number-to-process 1)))
)
)
)
)
Work to be turned in:
http://www.math.grin.edu/~walker/courses/151/lab-i-o.html
created March 5, 1997
last revised March 22, 1997