Skip to main content

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.