COMP 210 Lab 12: More State, Including Vectors

Vector basics, Searching in vectors, Imperative programming


Vector Basics (Review)

Vectors are another abstraction for collections of data. Like a structure, each element of a vector is named, in this case with a number. I.e., they are named 0,1,2,3,... in order. Vectors can be of any length, although you cannot change the length of a particular vector. Some important functions are the following:

Remember that vector elements are indexed starting with 0, not 1! That means you should never use code like

   (vector-ref vec (vector-length vec))
because there is no such element in the vector.

Vector exercises
  1. Develop a function that, given a vector of numbers, changes the vector to contain the squares of the old elements.
         (define v (vector 1 3 5 2))
         (vector-square! v)
         v                              returns (vector 1 9 25 4)
         
  2. Develop a function that, given a vector of numbers, returns a new vector of the same length containing the squares of the numbers in the original vector.
         (define v (vector 1 3 5 2))
         (define w (vector-square v))
         v                              returns (vector 1 3 5 2)
         w                              returns (vector 1 9 25 4)
         

Searching in vectors

At first glance, it seems like we cannot use a recursive template for vectors because vectors are not defined recursively. So, how do we approach writing interesting functions on vectors? When using vectors, you usually use the template for natural numbers. E.g., to search for an element find-element in vector v, we would write a helper function (search find-element v i), which recurs on number i.

Searching downwards

Vector exercises
As a group, finish and test the following programs to search a vector. In each, the helper function follows the natural number recursive template. However, note the slightly different purposes of the helper functions!
  1.      ; vec-search-down1? : (any -> boolean) (vectorof any) -> boolean
         ; Purpose: Returns whether any element of the vector meets the given
         ;          predicate.
         ; Examples:
         ;   (vec-search-down1? number? (vector true false))   = false
         ;   (vec-search-down1? number? (vector true 3 false)) = true
         ;   (vec-search-down1? number? (vector))              = false
         (define (vec-search-down1? pred? a-vec)
            (local [; search : natnum -> boolean
                    ; Purpose: Returns whether any element 0..num_elts-1 of a-vec
                    ;          meets predicate pred?.
                    (define (search num_elts)
                       (cond
                          [(zero? num_elts)
                           ...]
                          [else
                           ...(search (sub1 num_elts))...]))]
               (search ...)))
         
  2.      ; vec-search-down2? : (any -> boolean) (vectorof any) -> boolean
         ; Purpose: Returns whether any element of the vector meets the given
         ;          predicate.
         ; Examples:
         ;   (vec-search-down2? number? (vector true false))   = false
         ;   (vec-search-down2? number? (vector true 3 false)) = true
         ;   (vec-search-down2? number? (vector))              = false
         (define (vec-search-down2? pred? a-vec)
            (local [; search : natnum -> boolean
                    ; Purpose: Returns whether any element 0..index of a-vec
                    ;          meets predicate pred?.
                    (define (search index)
                       (cond
                          [(zero? index)
                           ...]
                          [else
                           ...(search (sub1 index))...]))]
               (search ...)))
         
Solutions.

Note the few small differences in your solutions. A common mistake is to produce code that looks partly like each of those solutions. Depending on the details, that typically results in not accessing the first or last element, or trying to access an element before the first or after the last. These are all known as "off-by-1 errors".

Searching upwards

For the curious...

Sometimes we prefer to count upwards instead of downwards. For example, when searching a vector, we could want to return the lowest-indexed element matching our predicate. This doesn't follow our natural number template -- we want to define a number template that counts in the opposite order, so that we recur in the opposite order.

More vector exercises
  1. Define the integers up to some n.
  2. Using that data definition, develop the program vec-search-up2? which searches up through the elements. This will be very similar to vec-search-down2?.
  3. Using the standard natural number data definition, develop the program vec-search-up1?. I.e., as we recur on smaller numbers, we want to look at higher-indexed elements. We need a simple formula to convert between the recurred-on number and the desired index. This will be very similar to vec-search-down1?.
Solutions.

You should find that these are more complicated programs than for searching downwards. While we can write the program in any of these ways, our recursive strategy is biased towards counting down on the number of elements remaining.

Binary search

When searching sorted data, we can use the orderedness of the data to our advantage. We can adapt our previous idea of binary search. As a reminder, we started with a bounded range of numbers and guessed if the middle one was the desired one. If not, we knew how to reduce the range by half, and then continue. We can do exactly the same thing now, where the bounded range represents the vector indices we need to consider. We repeatedly look at data elements somewhere in the middle, and thus this algorithm is naturally suited for vectors, since vectors have constant-time access to arbitrary elements.

Binary search vector exercises
Develop vec-search-binary?:
     ; vec-search-binary? : number (vectorof number) -> boolean
     ; Purpose: Returns whether any element of the vector is the given
     ;          number.  Assumes the vector elements are sorted in
     ;          ascending order.
     ; Examples:
     ;   (vec-search-binary? 4 (vector 0 2 3 4 5 8 10 11 15 20 40)) = true
     ;   (vec-search-binary? 3 (vector))                            = false
You will need a helper function to keep track of the bounded range.
Sample solution.

Imperative Programming

This course has dealt primarily with a style of programming known as functional programming, or to also include the previous couple weeks, mostly-functional programming. Another style is imperative programming, which focuses primarily on side-effects (e.g., set! and set-structure-component!), rather than passing arguments.

Let's get a hint at the relation between these two styles of programming. In particular, let's see an example of how recursion and looping are related. Some of the corresponding syntactic pieces are highlighted in color.

  1. We start with a simple structurally recursive program:

         (define (sum-list alon)
            (cond [(empty? alon)
                   0]
                  [(cons? alon)
                   (+ (first alon) (sum-list (rest alon)))]))
         

  2. We can then obtain the accumulator version:

         (define (sum-list alon)
            (local [(define (sum-list-acc the-lon accum)
                       (cond [(empty? the-lon)
                              accum]
                             [(cons? the-lon)
                              (sum-list-acc (rest the-lon)
                                            (+ (first the-lon) accum))]))]
               (sum-list-acc alon 0)))
         
    As you've seen, sometimes this step is difficult. Remember that this version adds up the numbers in the oppositive order. Thus, part of the reason it was relatively easy to obtain this version is that addition is associative.

  3. We can rewrite this to avoid passing arguments in the helper function and, instead, change only local variables. This step is similar to the assembly-language code that a compiler would produce. This is a straightforward syntactic translation:

         (define (sum-list alon)
            (local [(define accum 0)
                    (define the-lon alon)
                    (define (sum-list!)
                       (cond [(empty? the-lon)
                              accum]
                             [(cons? the-lon)
                              (begin
                                  (set! accum (+ (first the-lon) accum))
                                  (set! the-lon (rest the-lon))
                                  (sum-list!))]))]
               (sum-list!)))
         

    Question
    Does the order of the two set!s matter? Why or why not?

  4. Lastly, we can rewrite this using one of Scheme's several looping constructs. This is another straightforward syntactic translation:

         (define (sum-list alon)
            (local [(define accum 0)]
               (begin
                  (loop-until alon
                              empty?
                              rest
                              (lambda (the-lon)
                                 (set! accum (+ (first the-lon) accum))))
                  accum)))
         

    loop-until takes four arguments: a starting value to loop on, a test for what value(s) to stop on, a function to get the next loop value from the current one, and a function to evaluate on each loop iteration.

    Question
    What must the contract for loop-until be?

    Most programming languages have a construct similar to this, except with a test for what value(s) to continue looping (i.e., the opposite of what's used here). It is typically called a while loop.

This process is insufficient for all programs, but it shows that recursion and iteration are closely related. They are both useful tools.

Which version is best?

Imperative exercises
If you have time left, write imperative versions of
  • the vector searching functions
  • factorial
  • foldl
  • For the curious... foldr (This is harder than foldl, because it is harder to obtain an accumulator-style version of it.)
  • For the curious... the sum of elements in a tree
You may use the above steps if you find them helpful, or just skip to the solution.