Vectors

A vector is a data structure that contains some fixed number of elements and provides random access to them, in the sense that each element, regardless of its position in the vector, can be recovered in the same amount of time. In this respect, a vector differs from a list: The first element of a list is immediately accessible, but subsequent elements are increasingly difficult and time-consuming to get at.

When displaying a vector, Scheme displays each of its elements, enclosed in parentheses, with an extra mesh character, `#', in front of the left parenthesis. For instance, here's how Scheme displays a vector containing the symbols alpha, beta, and gamma, in that order:

#(alpha beta gamma)

The mesh character distinguishes the vector from the list containing the same elements.

We can use the same syntax to specify a vector when writing a Scheme program or typing commands and definitions into the Scheme interactive interface, except that we have to place a single quotation mark at the beginning, just as we do with list literals, so that Scheme will not try to evaluate the vector as if it were some exotic kind of procedure call. (DrScheme will not make this mistake even if you forget the single quotation mark, but not all implementations of Scheme are so generous.)

In the interaction window, when DrScheme displays a vector as the value of some top-level expression that the user supplied, it does something more ambitious, but potentially rather confusing: It inserts a numeral between the mesh character and the left parenthesis to indicate the number of elements in the vector, thus:

> '#(alpha beta gamma)
#3(alpha beta gamma)

However, you can instruct DrScheme to use the straight mesh-and-parentheses representation, with no numeral, by giving the following command at the beginning of your program or interactive session:

> (print-vector-length #f)

-- that is, ``Don't print the lengths of vectors.'' I recommend giving this command at the beginning of each of the labs that deal with vectors.

Standard Scheme provides a number of fundamental procedures for creating vectors and selecting and replacing their elements:

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

;;; our-list->vector: construct a vector of the elements of a given list

;; Given:
;;   LS, a list.

;; Result:
;;   VEC, a vector.

;; Preconditions:
;;   None.

;; Postconditions:
;;   (1) The length of VEC is equal to the length of LS.
;;   (2) Every element of VEC is identical to the element in the
;;       corresponding position in LS.

(define our-list->vector
  (lambda (ls)
    (apply vector ls)))

In other words: Call the vector procedure, giving it the elements of ls as its arguments.

;;; our-vector->list: construct a list of the elements of a given vector

;; Given:
;;   VEC, a vector.

;; Result:
;;   LS, a list.

;; Preconditions:
;;   None.

;; Postconditions:
;;   (1) The length of LS is equal to the length of VEC.
;;   (2) Every element of LS is identical to the element in the
;;       corresponding position in VEC.

(define our-vector->list
  (lambda (vec)
    (let kernel ((remaining (vector-length vec))
                 (result null))
      (if (zero? remaining)
          result
          (let ((position (- remaining 1)))
            (kernel position (cons (vector-ref vec position) result)))))))

In other words: Let remaining initially be the number of elements in the vector. This parameter is used to keep track of how many elements of the vector remain to be transferred to the list. Let result initially be the empty list. We shall add the elements of the vector, one by one, to result. If there are no more elements to be transferred, return the finished list result; otherwise, let position be one less than remaining, so that it indicates the position in the vector that is occupied by the rightmost element that has not yet been transferred. Select that element from the vector (with vector-ref) and add it to the beginning of the result list (with cons); then start over again, reducing the number of elements remaining by 1.

With these procedures, we can write a number of other vector operations. For example, here is a procedure that creates a vector of a specified length, placing at each position the value that a given unary procedure returns when it is given the position number as argument:

;;; vector-generator: given a unary procedure that takes a natural number
;;; as its argument, return a procedure that constructs vectors of any
;;; specified length by applying the given procedure to position numbers

;; Given:
;;   PROC, a unary procedure.

;; Result:
;;   VECTOR-MAKER, a unary procedure.

;; Precondition:
;;   PROC can be applied to any natural number and returns one value when
;;   so applied.

;; Postconditions:
;;   (1) VECTOR-MAKER, when applied to any natural number SIZE, returns a
;;       vector VEC containing SIZE elements.
;;   (2) For every natural number POS less than SIZE, the element at
;;       position POS in VEC is the result of applying PROC to POS.

(define vector-generator
  (lambda (proc)
    (lambda (size)
      (list->vector (let kernel ((position 0))
                      (if (= position size)
                          null
                          (cons (proc position)
                                (kernel (+ position 1)))))))))

The double-every-element procedure takes one argument, a vector vec of numbers, and returns a new vector just like vec except that each of the elements is twice the corresponding element of vec.

;;; double-every-element: construct a vector similar to a given vector of
;;; numbers, but with each element doubled

;; Given:
;;   VEC, a vector of numbers.

;; Result:
;;   DOUBLE-VEC, a vector of numbers.

;; Preconditions:
;;   None.

;; Postconditions:
;;   (1) The length of DOUBLE-VEC is equal to the length of VEC.
;;   (2) Every element of DOUBLE-VEC is twice the corresponding element of
;;       VEC.

(define double-every-element
  (lambda (vec)
    ((vector-generator (compose double (left-section vector-ref vec))))
     (vector-length vec))))
> (double-every-element '#(3 1 4 1 5 9))
#(6 2 8 2 10 18)

The operator section (left-section vector-ref vec) takes a position number as its argument and returns the value at the specified position in vec. Composing this operator section with double gives a procedure that takes a position number and returns the double of value at the specified position in vec. The vector-generator procedure converts this composite procedure into one that fills up a vector given its length; applying that to the length of vec, therefore, produces a new vector, the same length as vec, in which each position is occupied by the double of the corresponding element of vec.

Similarly, the length-of-every-element procedure takes a vector of strings as its argument and returns a vector of their lengths:

;;; length-of-every-element: construct a vector similar to a given vector
;;; of strings, containing the lengths of the corresponding strings

;; Given:
;;   VEC, a vector of strings.

;; Result:
;;   LENGTH-VEC, a vector of exact integers.

;; Preconditions:
;;   None.

;; Postconditions:
;;   (1) The length of LENGTH-VEC is equal to the length of VEC.
;;   (2) Every element of LENGTH-VEC is the length of the corresponding
;;       element of VEC.

(define length-of-every-element
  (lambda (vec)
    ((vector-generator (compose string-length
                                (left-section vector-ref vec))))
     (vector-length vec))))

One can abstract the structure that double-every-element and length-of-every-element share to obtain a general vector-map procedure, for constructing the vector that results from applying the same procedure proc to each element of a given vector vec:

;;; vector-map: apply a procedure to each element of a vector, collecting
;;; the results in a vector

;; Given:
;;   PROC, a unary procedure.
;;   VEC, a vector.

;; Result:
;;   RESULT, a vector.

;; Precondition:
;;   Every element of VEC meets the preconditions that PROC imposes on its
;;   argument.

;; Postconditions:
;;   (1) The length of RESULT is equal to the length of VEC.
;;   (2) Each element of RESULT is the result of applying PROC to the
;;       corresponding element of VEC.

(define vector-map
  (lambda (proc vec)
    ((vector-generator (compose proc (left-section vector-ref vec)))
     (vector-length vec))))

or, if one wants to be able to apply it to a procedure of arity n followed by n vectors of equal length, in the manner of the built-in map procedure for lists,

;;; vector-map: apply a procedure to corresponding elements of one or more
;;; given vectors, collecting the results in a vector

;; Given:
;;   PROC, a procedure.
;;   VECS, one or more vectors.

;; Result:
;;   RESULT, a vector.

;; Preconditions:
;;   (1) All of VECS have the same length.
;;   (2) PROC can accept as many arguments as there are VECS.
;;   (3) Any group of corresponding elements of VECS meet the preconditions
;;       that PROC imposes on its arguments.

;; Postconditions:
;;   (1) The length of RESULT is equal to the common length of VECS.
;;   (2) Each element of RESULT is the result of applying PROC to the
;;       corresponding elements of VECS.

(define vector-map
  (lambda (proc first-vec . rest-of-vecs)
    (let ((all-vecs (cons first-vec rest-of-vecs)))
      ((vector-generator
         (lambda (position)
           (apply proc (map (right-section vector-ref position)
                            all-vecs))))
       (vector-length first-vec)))))
> (vector-map / '#(6 7 8 9 10) '#(1 2 3 4 5))
#(6 7/2 8/3 9/4 2)

This document is available on the World Wide Web as

http://www.cs.grinnell.edu/~stone/courses/scheme/readings/vectors.xhtml

Validated as XHTML 1.1 by the World Wide Web Consortium Cascading Style Sheet validated by the World Wide Web Consortium

created November 7, 1997
last revised September 19, 2001

John David Stone (stone@cs.grinnell.edu) and Ben Gum (gum@cs.grinnell.edu)