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.
In addition, a vector is a mutable data structure: It is possible to replace an element of a vector with a different value, just as one can take out the contents of a container and put in something else instead. It's still the same vector after the replacement, just as the container retains its identity no matter how often its contents are changed.
The particular values that a vector contains at some particular moment constitute its state. One could summarize the preceding paragraph by saying that the state of a vector can change and that state changes do not affect the underlying identity of the vector.
One way to name a vector, in Scheme, is to name each of its elements,
enclose all of those names in parentheses, and add a mesh character,
#, in front of the left parenthesis. For instance, here's a name
for the 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.
When writing a Scheme program or typing commands and definitions into the Scheme interactive interface, one must place a single quotation mark before the name, so that Scheme will not try to evaluate the vector as if it were an exotic kind of procedure call. (Chez Scheme will not make this mistake even if you forget the single quotation mark, but most implementations of Scheme are not so generous.)
Most Scheme implementations choose also to display vectors using the mesh-and-parentheses representation (without the quotation mark). Chez Scheme 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:
#3(alpha beta gamma)
However, you can instruct the Chez Scheme interactive interface 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.
Start Chez Scheme and tell it not to print the lengths of vectors.
Give Chez Scheme the (quoted) name of a vector containing just the two elements 3.14159 and 2.71828. How does it display the value of this vector?
Standard Scheme provides the following fundamental procedures for creating vectors and selecting and replacing their elements:
The constructor vector takes any number of arguments and
assembles them into a vector, which it returns.
> (vector 'alpha 'beta 'gamma)) #(alpha beta gamma) > (vector) ; the empty vector -- no elements! #() > (vector 'alpha "beta" '(gamma 3) '#(delta 4) (vector 'epsilon)) #(alpha "beta" (gamma 3) #(delta 4) #(epsilon))
As the last example shows, Scheme vectors can be heterogeneous, containing elements of various types, just like Scheme lists.
The make-vector procedure takes two arguments, a natural
number k and a Scheme value obj, and returns a
k-element vector in which each position is occupied by
obj.
> (make-vector 12 'foo) #(foo foo foo foo foo foo foo foo foo foo foo foo) > (make-vector 4 0) #(0 0 0 0) > (make-vector 0 4) ; the empty vector again #()
The second argument is optional; if you omit it, the value that initially
occupies each of the positions in the array is left unspecified. Various
implementations of Scheme have different ways of filling them up, so you
should omit the second argument of make-vector only when you
intend to replace the contents of the vector right away.
The type predicate vector? takes any Scheme value as argument
and determines whether it is a vector.
> (vector? '#(alpha beta gamma)) #t > (vector? '(alpha beta gamma)) ; a list, not a vector #f > (vector? "alpha beta gamma") ; a string, not a vector #f > (vector? '#(#f)) ; a one-element vector -- the element is #f #t
The vector-length procedure takes one argument, which must be
a vector, and returns the number of elements in the vector.
> (vector-length '#(3 1 4 1 5 9)) 6 > (vector-length (vector 'alpha 'beta 'gamma)) 3 > (vector-length '#()) 0
The selector vector-ref takes two arguments -- a vector
vec and a natural number k (which must be less
than the length of vec). It returns the element of
vec that is preceded by exactly k other elements.
(In other words, if k is 0, you get the element that begins
the vector; if k is 1, you get the element after that; and so on.)
> (vector-ref '#(3 1 4 1 5 9) 4) 5 > (vector-ref (vector 'alpha 'beta 'gamma) 0) alpha > (vector-ref (vector 'alpha 'beta 'gamma) 3) Error in vector-ref: 3 is not a valid index for #(alpha beta gamma). Type (debug) to enter the debugger.
The mutator vector-set! takes three arguments -- a vector
vec, a natural number k (which must be less than
the length of vec), and a Scheme value obj -- and
replaces the element of vec that is currently in the position
indicated by k with obj. This changes the state
of the vector irreversibly; there is no way to find out what used to be in
that position after it has been replaced. It is a Scheme convention to
place an exclamation point at the end of the name of any procedure that
makes such an irreversible change in the state of an object; the
exclamation point basically means ``Proceed with caution!''
The value returned by vector-set! is unspecified; one calls
vector-set! only for its side effect on the state of its first
argument.
> (define sample-vector (vector alpha beta gamma delta epsilon))
> (vector-set! sample-vector 2 'zeta)
> sample-vector ; same vector, now with changed contents
#(alpha beta zeta delta epsilon)
> (vector-set! sample-vector 0 "foo")
> sample-vector ; changed contents again
#("foo" beta zeta delta epsilon)
> (vector-set! sample-vector 2 -38.72)
> sample-vector ; and again
#("foo" beta -38.72 delta epsilon)
Vectors introduced into a Scheme program by means of the
mesh-and-parentheses notation are ``immutable'' -- applying
vector-set! to such a vector is an error, and the contents of
such vectors are therefore constant. (Some implementations of Scheme,
including Chez Scheme, don't enforce this rule.)
The vector->list takes any vector as argument and returns a
list containing the same elements in the same order; the
list->vector procedure performs the converse operation.
> (vector->list '#(31 27 16)) (31 27 16) > (vector->list (vector)) () > (list->vector '(#\a #\b #\c)) #(#\a #\b #\c) > (list->vector (list 31 27 16)) #(31 27 16)
The vector-fill! procedure takes two arguments, the first of
which must be a vector. It changes the state of that vector, replacing
each of the elements it formerly contained with the second argument.
> (define sample-vector (vector 'rho 'sigma 'tau 'upsilon)) > (vector-fill! sample-vector 'kappa) > sample-vector ; same vector, now with changed contents #(kappa kappa kappa kappa)
The vector-fill! procedure is invoked only for its side effect
and returns an unspecified value.
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:
(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.
(define our-vector->list
(lambda (vec)
(let loop ((remaining (vector-length vec))
(result '()))
(if (zero? remaining)
result
(let ((position (- remaining 1)))
(loop 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,
with the number of elements remaining reduced by 1.
(define our-vector-fill!
(lambda (vec obj)
(let ((size (vector-length vec)))
(let loop ((position 0))
(if (< position size)
(begin
(vector-set! vec position obj)
(loop (+ position 1))))))))
In other words: Let size be the number of elements in the
vector vec. Starting at position 0, put obj into
each position within vec, overwriting the value previously
stored in that position. Increase position by 1 after each
such overwriting step; when position becomes equal to
size, stop.
With these procedures, we can write all of the other vector operations
described in chapter 9 of the textbook. For example, here is the
vector-generator procedure:
(define vector-generator
(lambda (proc)
(lambda (size)
(let ((result (make-vector size)))
(let loop ((position 0))
(if (= position size)
result
(begin
(vector-set! result position (proc position))
(loop (+ position 1)))))))))
Write a vector-sum procedure that takes one argument, a vector
of numbers, and returns the sum of the elements of that vector. (You can
use our-vector->list as a pattern for
vector-sum -- only a few judicious changes are needed.)
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.
(define double-every-element
(lambda (vec)
(let* ((size (vector-length vec))
(result (make-vector size)))
(let loop ((position 0))
(if (= position size)
result
(begin
(vector-set! result position (* 2 (vector-ref vec position)))
(loop (+ position 1))))))))
> (double-every-element '#(3 1 4 1 5 9))
#(6 2 8 2 10 18)
In English: Let size be the length of the vector
vec, and let result be a new vector of the same
length (contents unspecified). Start at position 0. If the position
number is equal to size, all of the elements of
vec have already been processed; return the
result vector. Otherwise, double the element stored in the
current position of vec and put the doubled number into the
corresponding position in result; then proceed to the next
position.
An alternative defintion of double-every-element uses
vector-generator:
(define double-every-element
(lambda (vec)
((vector-generator (lambda (position)
(* 2 (vector-ref vec position))))
(vector-length vec))))
Write a Scheme procedure length-of-every-element that takes as
argument a vector of strings and returns a vector containing the lengths of
those strings.
> (length-of-every-element '#("red" "white" "and" "blue"))
#(3 5 3 4)
One can abstract the control 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:
(define vector-map
(lambda (proc vec)
(let* ((size (vector-length vec))
(result (make-vector size)))
(let loop ((position 0))
(if (= position size)
result
(begin
(vector-set! result position (proc (vector-ref vec position)))
(loop (+ position 1))))))))
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,
(define vector-map
(lambda (proc first-vec . rest-of-vecs)
(let* ((size (vector-length first-vec))
(result (make-vector size)))
(let ((all-vecs (cons first-vec rest-of-vecs)))
(let loop ((position 0))
(if (= position size)
result
(begin
(vector-set! result position
(apply proc (map (lambda (vec)
(vector-ref vec position))
all-vecs)))
(loop (+ position 1)))))))))
> (vector-map / '#(6 7 8 9 10) '#(1 2 3 4 5))
#(6 7/2 8/3 9/4 2)
Define a similar procedure vector-for-each, which takes a
procedure as its first argument and one or more vectors of equal length as
its remaining arguments and applies the procedure to corresponding elements
of the vectors, for the side effects only (like the built-in
for-each procedure for lists), discarding the results.
> (vector-for-each display '#("con" "duct" "or"))
conductor
This document is available on the World Wide Web as
http://www.math.grin.edu/~stone/courses/scheme/vectors.html
created November 7, 1997
last revised June 4, 1998