# 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 `n`

th 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`

.