University of Virginia Computer Science CS150: Computer Science, Fall 2005

# Exam 1 — Comments

### Counting with Procedures

1. Define a procedure counteq that takes two inputs, a list lst and a value val, and produces as output a count of the number of elements in lst that are equal to (use eq? as your comparison function) the value val. For example:
(counteq (list 1 2 3 4 5) 3) should evaluate to 1
(counteq (list 7 7 7) 7) should evaluate to 3
(counteq (list 1 2 3 4 5) 7) should evaluate to 0
There are many good ways to define counteq. Here's one:
```(define (counteq lst val)
(if (null? lst) 0
(+ (if (eq? (car lst) val) 1 0)
(counteq (cdr lst)))))
```
Another approach is to use filter and length:
```(define (counteq lst val)
(length (filter (lambda (el) (eq? el val)) lst)))
```
2. Define a procedure count that takes two inputs, a list lst and a procedure p, and produces as output a count of the number of elements in lst for which p applied to that element is not #f. For example, (count (list 1 2 3 4 5) odd?) should evaluate to 3.
We can modify the two definitions above:
```(define (count lst p)
(if (null? lst) 0
(+ (if (p (car lst)) 1 0)
(counteq (cdr lst)))))
```
```(define (count lst p) (length (filter p lst)))
```
3. Using the count procedure you defined in question 2, define a procedure count3s that takes a list as its input and evaluates to the number of 3 elements in the list. For example, (count3s (list 1 2 3 4 5)) should evaluate to 1. Your count3s procedure should be very short and may not use if.
```(define (count3s lst) (count lst (lambda (el) (eq? el 3))))
```

### Language

4. Draw a recursive transition network that describes the same language as the Backus-Naur Form grammar below produces starting from Expression:
Expression ::= ( Expression ExpressionList )
Expression ::= Name
ExpressionList ::= Expression ExpressionList
ExpressionList ::=
The terminals are Name and the parentheses.
This was done in Class 19.

### Complexity

For the next two questions (5 and 6), you are given two functions, f and g that both take a single, non-negative integer parameter n. Explain which of the following are true:
1. f is O (g)
2. f is Ω (g)
3. f is Θ (g)
In some cases, note that several of these may be true. A good answer will identify which of these are true, and will argue convincingly why those are true but the others are not.

5. f: n; g: n2

(1) f is O (g) since n < n2 for all n > 1 (choosing c = 1).

(2) f is not Ω (g) since there is no c value that makes c * n > n2 for all but a finite number of n values. For any choice of c, once nc the inequality is unsatisfied, and there are infinitely many n values greater than c (for any constant c.

(3) f is not Θ (g) since n is not Ω (n2).

6. f: the time it takes to apply the counteq procedure (you defined in question 1) to a list of length n; g: the time it takes to apply the best possible sorting procedure to a list of length n
For both definitions, the complexity of counteq is Θ(n) where n is the length of the input list. For the first definition, we can see this since it cdrs down the list (calling counteq once for each element), and does constant work in the body each time. For the second definition, filter is Θ(m) where m is the length of the input list, and length is Θ(p) where p is the length of the input list. We know m = n, so the filter application is Θ(n) work. We know the input to length is the output of the filter application, which must be less than or equal to the original length, n. Hence, we are doing two operations that are both less than or equal to Θ(n) which is Θ(n).

The best possible sorting procedure is Θ(n log n) where n is the length of the input list. (See Class 13.)

The actual time it takes to apply each procedure is unknown, but because we know the growth rate, we can fold the actual time into the constant. That is, we know the time it takes to evaluate counteq is an + k where a and k are constants, and the time it takes to evaluate the best sorting procedure is b (n log n) + t where b and t are constants.

So, we have:

(1) f is O (g) since n < n log n for all n > 1 (choosing c = 1).

(2) f is not Ω (g) since there is no c value that makes c * n > n log n for all but a finite number of n values. For any choice of c, once log nc the inequality is unsatisfied, and there are infinitely many n values where log n is greater than c (for any constant c).

(3) f is not Θ (g) since n is not Ω (n log n).

7. For any two functions f and g is it always the case that at least one of the two possibilities, f is O (g) or g is O (f) is true? Prove or disprove using a precise argument.
The statement is false. Consider if f and g are defined by the procedures below:
```   (define (f n) (if (even? n) 0 1))
(define (g n) (if (even? n) 1 0))
```
Then, f is not O (g) since for any choice c there are infinitely many value for which c * f (n) is not less than g (n). That is, all the even values since f(n) is 0 and g (n) is 1 when n is even.

But, g is also not O (f) since for any choice c there are infinitely many value for which c * g (n) is not less than f (n). That is, all the odd values since f(n) is 1 and g (n) is 0 when n is even.

Another example of choices for f and g where neither is upper bounded by the other are sin and cos.

8. Describe the complexity of the select-mosaic-tiles procedure from PS1 (provided in mosaic.ss, but simplified slightly here):
```(define (select-mosaic-tiles samples tiles color-comparator)
(map2d (lambda (sample)
(tile-name (find-best-match sample tiles
color-comparator)))
samples))

(define (map2d f ll)
(map (lambda (inner-list) (map f inner-list)) ll))

(define (find-best-match sample tiles color-comparator)
(if (null? tiles)
(error "No tiles to match!")
(if (= (length tiles) 1)
(car tiles)
(pick-better-match
sample (first tiles)
(find-best-match sample
(cdr tiles) color-comparator)
color-comparator))))

(define (pick-better-match sample tile1 tile2 color-comparator)
(if (color-comparator sample (tile-color tile1) (tile-color tile2))
tile1
tile2))

```
For good credit, your answer must explain your reasoning clearly. Remember to carefully define the meaning of all variables you use in your answer and state all the assumptions you make about procedures used whose code is not shown here.
We should start by defining variables that capture all important properties about the input size. The select-mosaic-tiles procedure has three inputs, samples a list of lists, tiles a list of tiles, and color-comparator, a color comparison procedure. We will assume the procedure passed in as color-comparator is Θ(1). We introduce variables to refer to the other sizes:
• s — the number of elements of the samples list
• r — the average number of elements in each sublist of the samples list
• t — the number of tiles (length of the tiles)
The select-mosaic-tiles procedure applies map2d to samples. The map2d procedure applies the input procedure to every element of the inner lists. Here, there are s outer lists, each containing r elements. So, there are sr total applications of the procedure.

The proceedure passed to map2d is (lambda (sample) (tile-name (find-best-match sample tiles color-comparator))). The code for tile-name was not included here, we assume it is Θ(1). (If we look at the mosaic.ss code, that confirms this assumption, since it just applies car once.) So, we only need to worry about the find-best-match procedure.

The body of find-best-match applies pick-better-match to the result of the recursive call to find-best-match using (cdr tiles). This means there are t calls to find-best-match, one for each tile. The work for each call is Θ(1) since pick-better-match is Θ(1) as are all the other operations in find-best-match.

Hence, the overall complexity is Θ(srt). Note that if instead of defining the variables as we did, we defined p as the total number of samples, then this would just be Θ (pt).

### Trees

9. Using the tree procedures from Class 12 (extended with make-leaf:
```   (define (make-tree left el right) (list left el right))
(define (make-leaf el) (make-tree null el null))
(define (get-left tree) (first tree))
(define (get-element tree) (second tree))
(define (get-right tree) (third tree))
```
define a procedure count-tree that takes two inputs, a tree and a test procedure, and evaluates to the number of elements in the tree for which the test procedure applied to the element does not evaluate to #f. For example,
(count-tree (make-tree (make-leaf 3) 7 (make-tree (make-leaf 8) 9 (make-leaf 3))) even?) should evaluate to 1.
The easy way is to just turn the tree into a list of its elements and then use count from question 2. We can do this using extract-elements from Class 12:
```(define (count-tree tree p)
(count (extract-elements tree) p)
```
Without using extract-elements, we can do this by walking through the tree:
10. What is the complexity of your count-tree procedure? Explain your reasoning clearly and carefully. (You may assume the tree is well balanced for this question.)
Both count-tree procedures are Θ(n) where n is the number of elements in the input tree. The easy way to see this is that they both must consider each element in the tree exactly once.
11. Do you feel your performance on this exam will fairly reflect your understanding of the course material so far? If not, explain why.
Several people commented that they were surprised the exam did not have more or harder programming questions on it. For students with good understanding of the programming concepts we have covered so far, the programming questsions were indeed quite easy (and all could be answered with only a line or two of code). On the other hand, I think the programming questions were enough for me to evaluate well if you understand the most important concepts of recursive definitions, higher order procedures, and programming with lists that we have covered so far.

The other common comment was that it would be better if you were allowed to use DrScheme on the programming questions. Its true that it is easy to make simple mistakes (especially leaving out parentheses) when you don't have an interpreter to try evaluating your code. On the other hand, you don't lose points for those types of mistakes on exams, and the problem sets overemphasize getting those kinds of details write (which is undesirable, but necessary to get your code to run). So, I would rather see what you are able to do without an interpreter on the exam, and not have you spend a lot of time using "trial-and-error" guessing to get a procedure that works that you might not really understand. Exam 2 may have some questions that require you to use the Scheme interpreter, but I'm not sure if this is a good idea yet.