Laboratory: Writing your own procedures
Summary: We explore mechanisms for creating procedures in Scheme.
Useful procedures and notation
Define procedures with (define NAME PROCEDURE-EXPRESSION)
Compose procedures with (o f g)
Section (fill in some parameters) of a procedure with
(section PROC <> CONSTANT) and variants thereof.
Preparation
a. If you have not done so already, you may want to open a separate tab or window in your browser for the reading on procedures.
b. We will be updating the csc151 library throughout the semester.
Using the Install Package or Package Manager menu item,
make sure that you update our library to the latest version.
Use https://github.com/grinnell-cs/csc151.git.
c. In your definitions window, require the relevant portions of the
csc151 library.
(require csc151/lists)
(require csc151/hop)
(require csc151/numbers)
(require csc151/square)
d. Add the following lists of numbers to your definitions pane.
(define assorted-integers
(list -10 5 8 -2 18 4 23 16 22 -5 -6 11 42 -42))
(define coffee-prices
(list 2.34 1.50 1.60 2.18 1.11 2.12 1.90 2.50 2.90 2.01 2.02 .89))
e. Make your own list of a dozen or so non-integer inexact real numbers.
Name it tea-prices.
f. Save your definitions on the Desktop as as procedures-lab.rkt.
Exercises
Exercise 0: Self checks
Unless we have done so as a class, discuss with your partner the problems in the self check.
Exercise 1: Averaging
a. The reading contains a definition of the average procedure. Add
that definition to your definitions pane and conduct some experiments to
verify that it works as you expect.
b. The geometric mean of a list of n numbers is the nth root
of the product of those numbers. Write a procedure, geometric-mean,
that takes a list of numbers as input and returns its geometric mean.
Exercise 2: Bounding, revisited
In the self-check, you wrote a procedure that bounded its input between 0 and 100. It is likely that you wrote something like the following.
(define bound-grade
(lambda (grade)
(min (max grade 0) 100)))
Rewrite bound-grade without using lambda.
Hint: Think about the steps involved. First you compute the maximum of 0 and some number. Then you compute the minimum of the result of that expression and some number.
Exercise 3: Exploring transformed data
When working with larger data sets, data scientists will often “clean” the data by, for example, removing partial data points or by simplifying the data. A typical approach to computing some characteristic of a set of data is to (a) filter out the inappropriate or incomplete values, (b) transform any remaining values as appropriate (e.g., rounding), (c) potentially join it with other similar sets of data, and (d) compute or visualize the result. If our final step is averaging, we might write that process as
(define fancy-average
(o average (section append <> other-data) transform filter))
But that’s a lot to think about right now, so let’s look at a simpler version in which we just transform the data and then average it.
(define semi-fancy-average (o average transform))
Let’s explore the simple list of prices you added to your definitions pane in the preparation phase of this assignment. That list contains fictitious prices of a cup of black coffee at a variety of venues.
Let’s suppose we just wanted dollar amounts for the coffee. We have
three basic options: We could round with round, round up with ceiling,
or round down with floor. (Since the prices are all positive, there
is no difference between floor and truncate.)
a. Write a definition of transform1 that takes a list of prices
as input and produces a list of those prices all of which are rounded
to the nearest integer using round.
(define semi-fancy-average-1 (o average transform-1))
(define transform-1 ...)
b. Using that definition, compute the semi-fancy-average of the lists of coffee and tea prices.
> (semi-fancy-average-1 coffee-prices)
> (semi-fancy-average-1 tea-prices)
c. Write a definition of transform that takes a list of prices
as input and produces a list of those prices all of which are rounded
up using ceiling.
(define semi-fancy-average-22 (o average transform-22))
(define transform-2 ...)
d. Using that definition, compute the semi-fancy-average of the lists of coffee and tea prices.
> (semi-fancy-average-2 coffee-prices)
> (semi-fancy-average-2 tea-prices)
e. Write a definition of transform that takes a list of prices
as input and produces a list of those prices all of which are rounded
down using floor.
(define semi-fancy-average-33 (o average transform-33))
(define transform-3 ...)
f. Using that definition, compute the semi-fancy-average of the lists of coffee and tea prices.
> (semi-fancy-average-3 coffee-prices)
> (semi-fancy-average-3 tea-prices)
Exercise 4: Exploring effects of transformations
As you may have noted, we saw some potentially significant effects on the average when we rounded up vs rounding down vs just rounding. We might want to measure those potential effects.
a. Write a procedure, list-rounding-differences that takes a
list of real numbers as input and creates a list that, for each
number, shows the difference between rounding up and rounding down.
You will end up with a list of 0’s and 1’s.
> (list-rounding-differences (list 0.1 0.7 10 11 0.5))
`(1.0 1.0 0.0 0.0 1.0)
b. Write a procedure or procedures that help you determine, for an
arbitrary list of real numbers, whether using round to round
values produces a result closer to rounding up or rounding down.
(Yes, this question is intentionally left vague. It’s to help you
think about the ways in which you might think about your data.)
Exercise 5: Eliminating negative numbers
You may recall that a few problems back, we suggested that we often use multiple steps as we analyze data. Sometimes, our first step is to filter out meaningless or incomplete data. Let’s explore that issue a bit more.
As you may have noted, the list assorted-integers contains both
positive and negative integers. Perhaps we would like to remove
the negative integers, assuming that negative numbers represent
incorrectly entered data.
Write a procedure, filter-out-negatives, that takes a list of
integers as input and removes the negative numbers. Your procedure
need not present the values in the same order.
Here’s one valid output.
> (filter-out-negatives (list -5 10 2 8 5 -1 3))
'(10 2 8 5 3)
Here’s an equally valid output.
> (filter-out-negatives (list -5 10 2 8 5 -1 3))
`(2 3 5 8 10)
Hint: You may find the following ideas helpful. You can put lists in
order from smallest to largest with (sort lst <). You can add another
value to a list with (append (list value) lst). You can find the
position of the first instance of a value with (index-of value lst).
You can remove the first k elements with (drop lst k).
Exercise 6: Median
Write a procedure, median, that takes as input an odd-length list of
real numbers and returns the median of the list.
Exercise 7: Scaling lists
Write a procedure, scale-by-median, that takes as input a list of
real numbers and returns a list of the result of dividing each number
by the median.
> (define numbers (list 4.0 2.0 3.0 4.0 5.0 8.0 1.0))
> (median numbers)
4.0
> (scale-by-median numbers)
'(1.0 0.5 0.75 1.0 1.25 2.0 0.25)
Exercise 8: Putting things together
Now that we have scale-by-median, we are able to convert different kinds
of data to the same general “approach”. For example, if we have multiple
lists of prices, one for each kind of good, we can transform them each
to a list of how much each price varies from the median price. Then we
can put the values together and, perhaps, compute some other interesting
characteristics of those values.
a. Write expressions to compute the average and geometric mean of the variances from the median for all the prices we have, both coffee and tea. You’ll want to separately compute ratio to median for coffee prices and ratio to median for tea prices.
b. Write a procedure that takes two lists and does a similar process to the two lists. That is, your procedure should find the ratio to median for each value in each list, combine those ratios together, and then find the arithmetic or geometric mean.
For Those with Extra Time
Extra 1: Filtering, revisited
Write a procedure, filter-out-of-bounds, that takes three inputs:
a real number representing a lower bound, a real number representing
an upper bound, and a list of real numbers. Your procedure should return
a new list that contains only the values in the list that are between
those two bounds (inclusive). You need not return them in the
same order.
Extra 2: Combining data, revisited
Write a procedure, unify, that takes a list of lists of real
numbers as input. It should then take each list and compute the
ratio of the values in that list to the mean of each list. Afterwards,
it should combine them into a single list.
> (unify (list (list 1.0 2.0 3.0) (list 4.0 10.0 8.0) (list 0.8 0.4 0.1)))
'(0.5 1.0 1.5 0.5 1.25 1.0 2.0 1.0 0.25)
Hint: You can join the resulting lists together with reduce and
append. You should be able to convert each list with an appropriate
call to map1.