Every programming language has what we call a "memory model", a way of organizing the data that arise while running a program. Scheme's memory model contains two main layers, denoted by [] and () below: x ----> [] -------------> 'a y ----> [] -------------> 4 an-entry ----> [] -------------> () --> (make-entry 'kathi 3) alon ----> [] -------------> () --> (cons 1 (cons 2 empty)) variables environment store Every reference to a piece of scheme data is a box. Therefore, to be correct, we need to refine this picture to use value boxes inside the structured data. x -------------> [] -------------> 'a y -------------> [] -------------> 4 3 <-------------- 'kathi <------ | | | an-entry -------------> [] ------> () --> (make-entry [] []) 2 <-------------- 1 <----- | | | alon -------------> [] ------> () --> (cons [] (cons [] empty)) By this picture, we see that Scheme uses an association between variables (left) and values (right). You need to understand how define, set!, and set-structure! affect this association: - define extends the association: it adds new associations of variables to values. - set! changes the association. After a set!, the path from a variable name to a value reaches a different value. Thus, set! changes the association, but not the values. - set-structure! changes the values. It does not change the association. Here are three examples of how the above picture changes following these operations: 1. (define z 5) : creates a new box for z and makes the box refer to 5 x -------------> [] -------------> 'a y -------------> [] -------------> 4 3 <-------------- 'kathi <------ | | | an-entry -------------> [] ------> () --> (make-entry [] []) 2 <-------------- 1 <----- | | | alon -------------> [] ------> () --> (cons [] (cons [] empty)) z -------------> [] -------------> 5 2. (set! y 7) x -------------> [] -------------> 'a 4 y -------------> [] -------------> 7 3 <-------------- 'kathi <------ | | | an-entry -------------> [] ------> () --> (make-entry [] []) 2 <-------------- 1 <----- | | | alon -------------> [] ------> () --> (cons [] (cons [] empty)) 3. (set-entry-phone! an-entry 4) x -------------> [] -------------> 'a y -------------> [] -------------> 4 <-------------- 3 | 'kathi <------ | | | an-entry -------------> [] ------> () --> (make-entry [] []) 2 <-------------- 1 <----- | | | alon -------------> [] ------> () --> (cons [] (cons [] empty)) New topic : Vectors The international tennis organization provides rankings of the top 100 tennis players. For each player, the organization stores his or her name, home country, and number of matches won. Since people frequently ask for statistics on players according to their rank, the organization wants a program through which they can find the information for a player with a given ranking. Develop a data definition and lookup program for this problem. ;; A player is a structure ;; (make-player name home wins) ;; where name and home are strings and wins is a number (define-struct player (name home wins)) ;; A ranking is a (listof player) containing 100 elements ;; find-by-rank : ranking number[<=100] -> player ;; returns the player with the given rank, starting from rank 1 (define (find-by-rank a-ranking player-num) (local [(define (helper alop at-num) (cond [(= at-num player-num) (first alop)] [else (helper (rest alop) (add1 at-num))]))] (helper a-ranking 1))) You could also have written (define (find-by-rank a-ranking player-num) (cond [(= player-num 1) (first a-ranking)] [else (find-by-rank (rest a-ranking) (sub1 player-num))])) This program is similar to one built-in to Scheme called list-ref. List-ref consumes a list L and a number N and returns the Nth element in L, counting from 0. We could therefore have written find-by-rank using list-ref as follows: (define (find-by-rank a-ranking player-num) (list-ref a-ranking (sub1 player-num))) How long does it take to find a player by her rank? It depends on the rank. Finding the top ranked player requires one call to helper; finding the 100th ranked player requires 100 calls to helper. This is unsatisfying. Shouldn't we be able to access players in the same amount of time, regardless of their rank? To do this, we need a form of compound data which lets us access each component with the same amount of work. This form of compound data is called a vector. A vector is a form of compound data with a fixed number of components. Each component is numbered according to its position in the vector, and we can ask for components by their positions. If I want to create a vector containing my three favorite cuisines, for example, I could do so by writing > (vector 'thai 'indian 'greek) (vector 'thai 'indian 'greek) Given a vector, I can perform several operations on it: (define V (vector ...)) 1. Find out how many components it contains: (vector-length V) 2. Retrieve its nth component (counting from 0) (vector-ref V n) 3. Update its nth component to a new value (vector-set! V n new-val) Here's an example: > (define cuisine (vector 'thai 'indian 'greek)) > (vector-length cuisine) 3 > (vector-ref cuisine 1) 'indian > (vector-set! cuisine 2 'mexican) > cuisine (vector 'thai 'indian 'mexican) Sometimes, we can write a function to describe the values in each position of a vector based on the position number. In such cases, we can use the operator build-vector to create vectors. Build-vector consumes a number (the desired length of the vector) and a function that consumes numbers. It returns a vector in which the value stored in position i is the result of the function on i. > (build-vector 5 (lambda (i) (* i i))) (vector 0 1 4 9 16) Thus (build-vector n f) is equivalent to (vector (f 0) (f 1) ... (f (- n 1)) Unlike list-ref, vector-ref uses the same amount of work to access every element, regardless of its position in the vector. Thus, vectors can be a good data structure to use when: 1. the number of components is fixed, 2. uniform access to components is required, and 3. numbers are a natural way to index the components. Thus, vectors are good for problems involving rankings of fixed numbers of elements, such as our tennis organization problem. However, they are bad for problems such as address books, because the numbers are not a natural way to index the entries. Let's redesign our rankings program using vectors. The data definition for players stays the same. ;; A ranking is a vector of 100 players ;; find-by-rank : ranking number[<=100] -> player ;; returns the player with the given rank, starting from rank 1 (define (find-by-rank a-ranking player-num) (vector-ref a-ranking (sub1 player-num))) How would you create a ranking? We could write a function to create an empty ranking, and another to update a ranking with a particular player in a particular spot: ;; make-ranking : num -> vector ;; creates a vector with all components initialized to false (define (make-ranking size) (build-vector size (lambda (i) false))) ;; rank-player! : ranking num player -> void ;; effect : changes value of ranking in position rank to player (define (rank-player! a-ranking rank a-player) (vector-set! a-ranking rank a-player)) Consider another example. Vectors are common in mathematics. A vector is a k-tuple that specifies some point in a vector space. There are two important operations on vectors in linear algebra (the arrows over variables indicate that they are vectors instead of numbers): --> --> scalar arithmetic : s * v or s + v --> --> --> --> vector arithmetic : v * w or v + w Scalar arithmetic adjusts each element in the vector by the given number using the given operation. Vector arithmetic combines two vectors pairwise according to the given operation. Let's write programs to perform scalar and vector arithmetic: (define (scalar* a-num a-vec) (build-vector (vector-length a-vec) (lambda (i) (* s (vector-ref a-vec i))))) Or, more generally: (define (scalar-arith a-num a-vec an-op) (build-vector (vector-length a-vec) (lambda (i) (an-op s (vector-ref a-vec i))))) For vector arithmetic, we need: (define (vector-arith vec1 vec2 an-op) (build-vector (vector-length vec1) (lambda (i) (an-op (vector-ref vec1 i) (vector-ref vec2 i)))))