Functional Problem Solving (CSC 151 2015F) : Readings

Implementing Standard Vector Procedures


Some older implementations of Scheme may lack the list->vector, vector->list, and vector-fill! procedures, but it is straightforward to define them in terms of the others.

list->vector

Since we are writing this procedure ourselves, we should begin by documenting it.

;;; Procedure:
;;;   list->vector
;;; Parameters:
;;;   lst, a list.
;;; Purpose:
;;;   Convert the list to a vector.
;;; Produces:
;;;   vec, a vector
;;; Preconditions:
;;;   lst is a list
;;; Postconditions:
;;;   vec is a vector.
;;;   The length of vec equals the length of lst.
;;;   The ith element of vec equals the ith element of lst for
;;;     all "reasonable" i.

In most implementations, we will need to recurse over the list, adding elements to a corresponding vector. We will also need to build that vector first. Since we only want to build one vector, we should use the husk-and-kernel technique. The husk creates the vector and tells the kernel and then calls the kernel.

However, we may need other parameters for the kernel, so it is time to think a little bit about the kernel. Since we want to copy all the elements of the list to the vector, we probably need to repeat some basic step, and the only way we know how to do repetition is recursion. To keep track of what we are copying, we will probably need a counter that we pass to the helper. Let us call that counter pos, since it keeps track of a position in the list or vector.

What should we copy from at each step? We could copy the element at position pos of the list into position pos of the vector. However, that is somewhat inefficient because it requires us to step through to the ith element of the list.

Since we no longer need a value from the list once we have copied it, we can cdr through the list as we step through the vector. Now, our goal is to copy the initial element of the list into the appropriate position of the vector.

When do we stop? When we run out of elements to copy.

Given all of the above, here is how to set things up.

(define list->vector
  (lambda (lst)
    (list->vector-kernel! lst ; Copy the whole list
                          0   ; Starting at position 0
                          (make-vector (length lst) null)
                              ; Into a new vector of the appropriate length
                          )))

The kernel is a little bit more complicated. We need to keep track of where we are in the vector. We may also want to carefully specify what this kernel is supposed to do. (Such specification is not always necessary for helpers, but I think it clarifies things in this case.)

;;; Procedure:
;;;   list->vector-kernel!
;;; Parameters:
;;;   lst, a list to copy
;;;   pos, a position in the vector
;;;   vec, a vector
;;; Purpose
;;;   Copy values from lst into positions pos, pos+1, ...
;;; Produces:
;;;   vec, the same vector but with different contents.
;;; Preconditions:
;;;   The length of vec = pos + the length of list. [Unverified]
;;;   pos >= 0. [Unverified]
;;; Postconditions:
;;;   Element pos of vec now contains element 0 of list.
;;;   Element pos+1 of vec now contains element 1 of lst.
;;;   Element pos+2 of vec now contains element 2 of lst.
;;;   ...
;;;   The last element of vec now contains the last element of lst.
(define list->vector-kernel!
  (lambda (lst pos vec)
    ; We don't bother to verify the preconditions because this
    ; procedure should only be called by something that has already
    ; verified the preconditions (explicitly or implicitly).

    (cond 
      ; If there's nothing left to copy, stop.
      ((null? lst) 
       vec)
      ; Otherwise, copy the initial element of the list and
      ; then copy the remaining elements
      (else
       (vector-set! vec pos (car lst))
       (list->vector-kernel! (cdr lst) (+ pos 1) vec)))))

Is this the only way to write the list->vector procedure? No. More advanced students might know about the apply procedure which makes life significantly easier.

(define list->vector
  (lambda (lst)
    (apply vector lst)))

In other words: Call the vector procedure, giving it the elements of lst as its arguments. Don't worry if you don't understand this now. We will return to the idea later in the semester.

For now, we should probably collect the helper kernel into a local procedure, which we'll do with a named let. Note how we can use a regular let before the recursive named let to bind values that do not change as the repetition proceeds.

(define list->vector
  (lambda (lst)
    (let ([vec (make-vector (length lst))]) ; a new vector of appropriate length
      (let kernel! ([remaining lst] ; Copy the whole list into the vector
                    [pos 0])         ; Starting at position 0
        (cond 
         ; If there's nothing left to copy, stop.
         [(null? remaining) 
          vec]
         ; Otherwise, copy the initial element of the list and
         ; then copy the remaining elements
         [else
          (vector-set! vec pos (car remaining))
          (list->vector-kernel! (cdr remaining) (+ pos 1)))]))))

vector->list

Now let us consider how to go in the opposite direction. Once again, we will probably need to recurse to step through positions and need to keep track of the position with an extra variable. Should we create a list first and then populate it? No. We do not typically mutate lists. So, we will update the list “on the fly”, adding elements and extending the list at each step. Let's start with the helper. The helper will build a list from a subrange of the vector.

;;; Procedure:
;;;   vector->list-kernel
;;; Parameters:
;;;   vec, a vector
;;;   start, an integer
;;;   finish, an integer
;;; Purpose:
;;;   Builds a list that contains elements start ... finish-1 of vec
;;; Produces:
;;;   lst, a new list.
;;; Preconditions:
;;;   start >= 0
;;;   finish >= start
;;;   finish <= length of vec
;;; Postconditions:
;;;   The element at position i of lst is the element at position
;;;     i+start of vec for all reasonable i.
;;;   Does not change vec.
(define vector->list-kernel
  (lambda (vec start finish)
    ; We stop at the finish.
    (if (= start finish)
        ; There are no more elements to put into a list, so use
        ; the empty list.
        null
        ; Otherwise, add the element at position start to a list of
        ; the remaining elements.
        (cons (vector-ref vec start)
              (vector->list-kernel vec (+ 1 start) finish)))))

Now, we just have to set things up for this kernel. We start at position 0. We end just before the length of the vector. So, here goes ...

;;; Procedure:
;;;   vector->list
;;; Parameters:
;;;   vec, a vector
;;; Purpose:
;;;   Builds a list that contains the elements of vec in the same order.
;;; Produces:
;;;   lst, a new list.
;;; Preconditions:
;;;   [None]
;;; Postconditions:
;;;   The element at position i of lst is the element at position
;;;     i of vec for all reasonable i.
;;;   Does not change vec.
(define vector->list
  (lambda (vec)
    (vector->list-kernel vec 0 (vector-length vec))))

Once again, it is more elegant to collect the helper into a local procedure with a named let. Likewise note how we can use a regular let before the recursive named let to bind values that do not change as the repetition proceeds.

(define vector->list
  (lambda (vec)
    (let ([finish (vector-length vec)]) ; Define recursion stopping point
      (let kernel ([pos 0])
        ; We stop at the finish.
        (if (= pos finish)
            ; There are no more elements to put into a list, so use
            ; the empty list.
            null
            ; Otherwise, add the element at position start to a list of
            ; the remaining elements.
            (cons (vector-ref vec pos)
                  (kernel (+ 1 start))))))))

Self Checks

Check 1: Understanding the Design

The design of list->vector is a bit subtle. Explain what the kernel does. If you're not completely sure, you may want to add a call to display inside the kernel, as in

     (display (list 'l2v-kernel! lst pos vec)) (newline)