get-word which extracts the first word or number from a string -- skipping any initial whitespace and taking the following information until more whitespace appears, and
chop-word which removes the initial word or number from a string.
When such generally useful procedures are identified and implemented, it is common to collect them into a file. That file then can be loaded into new applications as they arise, so the useful procedures can be reused without having to write then again. Such a file or a collection of files sometimes is called a procedure library.
Create a new file string-lib.ss with your editor, and place copies of get-word and chop-word into the file. Then save the file, so these functions can be loaded and used in future applications.
Check that your string-lib.ss file works correctly by starting up Scheme in a dtterm window, loading string-lib.ss, and trying the following tests:
(get-word " this is a test ") ==> "this" (chop-word " this is a test ") ==> " is a test ") (get-word " 2.718281828459") ==> "2.718281828459" (chop-word " 2.718281828459") ==> ""
Procedure read-n reads a given number of characters from a file and collects those characters into a string. If the current line of the file does not contain enough characters, the end of the string is padded with spaces.
Procedure clear-line reads successive characters from a file until it encounters the end of the current line. clear-line also should read the #\newline from the end of the line. successive characters from the end of a line, clearing the #\newline from the end of the line. Thus, clear-line is like read-line in the lab on line-by-line processing, except that clear-line returns the empty string rather than returning a string of all characters read.
(define read-n
(lambda (source number-chars)
(let loop ((n number-chars) (char-list '()))
(if (zero? n)
(list->string (reverse char-list))
(let ((next-char (read-char source)))
(cond ((eof-object? next-char) next-char)
((char=? next-char #\newline)
(string-append (list->string (reverse char-list))
(make-string n #\space)))
(else (loop (- n 1) (cons next-char char-list)))
)
)
)
)
)
)
To test this program, one might utilize the following simple test procedure:
(define test-read-n
(lambda (file-name)
(let ((source (open-input-file file-name)))
(display "First 10 characters: ")
(write (read-n source 10))
(newline)
(display "Next 16 characters: ")
(write (read-n source 16))
(newline)
(display "Next 8 characters: ")
(write (read-n source 8))
(newline)
(display "Next 40 characters: ")
(write (read-n source 40))
(newline)
(close-input-port source)
)
)
)
Run test-read-n on file "/home/walker/151s/labs/file4.dat" contains one line, with the characters abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ. Thus, the call
(test-read-n "/home/walker/151s/labs/file4.dat")
should return the following:
First 10 characters: "abcdefghij" Next 16 characters: "klmnopqrstuvwxyz" Next 8 characters: "ABCDEFGH" Next 40 characters: "IJKLMNOPQRSTUVWXYZ "
In this output, note that the last line reads the final newline character and then pads the string with space characters in order to obtain a string of the correct length.
Check that the last line has the correct number of spaces at the end.
Run test-read-n on another file that you have constructed, so you can check some additional cases.
Implement procedure clear-line, as described above. Note that this code may follow the same general format of read-line from the previous lab, except that no characters have to be stored in the main loop.
Collect the file-reading procedures described thus far into a file library, file-lib.ss. Thus, file-lib.ss should contain:
Note that all of these procedures assume that the source file already been opened in a separate main program.
Run a few tests to be sure that file-lib.ss works correctly.
The previous lab analyzed the sixty largest cities and towns in Iowa to determine their percentage increase or decrease from 1980 to 1990. We now consider how to modify the previous program to determine the name of the Iowa city with the largest 1990 population (i.e, Des Moines).
The approach for finding the largest city is similar, although now the program must read the city name, the 1990 population, and the 1980 population for each city. (While the 1980 population is not needed, we still read it, so we can get to the next data in the file.) In what follows, we have modified the line-by-line processing so that we can remember the maximum identified so far.
(load "file-lib.ss")
(define find-max-city
(lambda (source-file-name)
(let* ((source (open-input-file source-file-name)) ; Open the input file.
(first-city (read-n source 16))
(first-pop-90 (read source))
(first-pop-80 (read source))
(first-remainder (clear-line source)))
(let loop1 ((ch (peek-char source)) ; Peek at the next character.
(max-city first-city)
(max-90 first-pop-90))
(if (eof-object? ch) ; If you get the eof-object,
(begin
(display "The largest city is ")
(display max-city)
(display " with a population of ")
(display max-90)
(newline)
(close-input-port source)) ; close the input file
(begin
;; process-line in the previous code is expanded here
(let* ((next-city (read-n source 16))
(next-pop-90 (read source))
(next-pop-80 (read source))
(next-remainder (clear-line source)))
(if (< max-90 next-pop-90)
(loop1 (peek-char source)
next-city
next-pop-90)
(loop1 (peek-char source)
max-city
max-90)
)
)
)
)
)
)
)
)
Test this program on the Iowa cities file:
(find-max-city "/home/walker/151s/labs/ia-cities.dat")
Explain why two let* statements are needed in this procedure. Also, why is let* used rather than let?
Does the procedure still work if the lines for first-pop-80 and next-pop-80 are deleted? Explain why or why not?
Why does the output of this program leave a space after the city name?
Modify find-max-city to find the city with the maximum percentage increase from 1980 to 1990.
This document is available on the World Wide Web as
http://www.math.grin.edu/~walker/courses/151.fa00/lab-file-library.html