In the lab on vectors, we developed some
procedures that took vectors as arguments and constructed and returned
other vectors as results -- for instance, the
double-every-element procedure:
(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))))))))
Instead of constructing an entirely new vector, it is often more efficient to replace the elements of the original vector with the newly computed elements:
(define double-every-element!
(lambda (vec)
(let ((size (vector-length vec)))
(let loop ((position 0))
(if (< position size)
(begin
(vector-set! vec position (* 2 (vector-ref vec position)))
(loop (+ position 1))))))))
> (define sample-vector (vector 3 1 4 1 5 9))
> sample-vector
#(3 1 4 1 5 9)
> (double-every-element! sample-vector)
> sample-vector
#(6 2 8 2 10 18)
As the exclamation point at the end of double-every-element!
indicates, calling the procedure causes an irreversible change of state in
its argument, sample-vector. The original contents of that
vector are gone; each of the original elements has been replaced by its
double. This ``destructive'' procedure is more efficient than
double-every-element, because it is unnecessary to allocate
storage for a result vector, but it is also somewhat trickier
to use. The timing is important: one must not replace the elements of the
vector too soon, at a time when one still needs some of the old values for
other computations.
This pattern of computation is common enough that it is useful to define a
destructive version of vector-map:
(define vector-map!
(lambda (proc vec)
(let ((size (vector-length vec)))
(let loop ((position 0))
(if (< position size)
(begin
(vector-set! vec position (proc (vector-ref vec position)))
(loop (+ position 1))))))))
> (define sample-vector (vector 3 1 4 1 5 9))
> (vector-map! (lambda (n) (* n n)) sample-vector)
> sample-vector
#(9 1 16 1 25 81)
Using vector-map!, define a Scheme procedure
convert-negatives-to-zero! that takes any vector of real
numbers as its argument and, as a side effect, replaces any negative value
in that vector with 0.
> (define example (vector -3.8 17 0.14 -0.14 -113/4)) > (convert-negatives-to-zero! example) > example #(0 17 0.14 0 0)
Another way of using the vector-set! procedure is illustrated
by our implementation of 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)))))))))
> ((vector-generator (lambda (x) (+ x 1))) 6)
#(1 2 3 4 5 6)
Using vector-generator, define a procedure
factorial-table that takes any natural number as its argument
and produces a vector of the specified length, each element of which is the
factorial of its position number.
> (factorial-table 8) #(1 1 2 6 24 120 720 5040)
An accumulation procedure is one that traverses a vector from left to right, replacing each element with the result of performing some given operation on all of the elements up to and including that one. For example, here is a procedure that accumulates partial sums in a vector of numbers:
(define accumulate-sum!
(lambda (vec)
(let ((size (vector-length vec)))
(let loop ((position 0)
(sum 0))
(if (< position size)
(let ((new-sum (+ sum (vector-ref vec position))))
(vector-set! vec position new-sum)
(loop (+ position 1) new-sum))))))))
> (define sample-vector (vector 3 1 4 1 5 9))
> (accumulate-sum! sample-vector)
> sample-vector
#(3 4 8 9 14 23)
And here is one that accumulates strings by concatenation:
(define accumulate-strings!
(lambda (vec)
(let ((size (vector-length vec)))
(let loop ((position 0)
(so-far ""))
(if (< position size)
(let ((new-value (string-append so-far
(vector-ref vec position))))
(vector-set! vec position new-value)
(loop (+ position 1) new-value))))))))
> (define string-vector (vector "To " "be " "or " "not " "to " "be "))
> (accumulate-strings! string-vector)
> string-vector
#("To "
"To be "
"To be or "
"To be or not "
"To be or not to "
"To be or not to be ")
Define a Scheme procedure accumulate! that takes two arguments
-- a procedure proc of arity 2 and a ``seed value''
seed -- and returns an accumulated procedure that, when
invoked, applies proc across a given vector, starting by
applying it to seed and the initial element of the vector,
then to the result of the first application and the next element of the
vector, and so on. Like the accumulation procedures above, the one
returned by accumulate! should, as a side effect, replace all
the elements of the vector it is given with the intermediate results.
(define accumulate-sum! (accumulate! + 0)) (define accumulate-strings! (accumulate! string-append "")) > (define powers (make-vector 12 1)) > ((accumulate! (lambda (x y) (* 2 x y)) 1) powers) > powers #(2 4 8 16 32 64 128 256 512 1024 2048 4096)
Other built-in Scheme data structures have mutator procedures as well:
The string-set! procedure takes two three arguments -- a
string str, a natural number k less than the
length of str, and a character ch -- and, as a
side effect, replaces the character at position k in
str with ch:
> (define sample-string (string #\s #\a #\m #\p #\l #\e)) > sample-string "sample" > (string-set! sample-string 1 #\i) > sample-string "simple"
Like vectors named by quoted mesh-and-parentheses expressions, strings
named by constants beginning and ending with double quotation marks are
``immutable,'' and it is an error to apply string-set! to
them. (Chez Scheme does not object to the erroneous operation, but some
other implementations of Scheme do.)
The set-car! procedure takes two arguments -- a pair
pr and a Scheme value obj -- and, as a side
effect, replaces the car of pr with obj.
> (define sample-pair (cons 'alpha 'beta)) > sample-pair (alpha . beta) > (set-car! sample-pair 'gamma) > sample-pair (gamma . beta)
Similarly, the set-cdr! procedure takes pr and
obj as arguments and, as a side effect, replaces the cdr of
pr with obj.
Pairs that are named by quoted list or dotted-pair expressions are
immutable, and it is an error to apply set-car! or
set-cdr! to such a pair.
The set-cdr! procedure in particular should be used with great
caution, since it is all too easy to create (by accident) a data structure
for which the box-and-pointer diagram contains a cycle -- for instance, a
structure in which box A contains a pointer to box B, which contains a
pointer back to box A. List-traversal procedures often enter infinite
loops when confronted with such a structure.
Standard Scheme does not supply a list-set! procedure, for
replacing the element in a specified position in a given list. Define one,
using set-car!.
This document is available on the World Wide Web as
http://www.math.grin.edu/courses/Scheme/spring-1998/structure-mutation.html
created November 8, 1997
last revised June 21, 1998