[previous] [up] [next]     [index]
Next: A Problem with Generative Up: The Loss of Knowledge Previous: The Loss of Knowledge

A Problem with Structural Processing

Suppose we are given the relative distances between a series of points, starting at the origin, and suppose we are to compute the absolute distances from the origin. For example, we might be given a line such as this:

relative distance

Each number specifies the distance between two dots. What we need is the following picture, where each dot is annotated with the distance to the left-most dot:

absolute distance


;; relative-2-absolute : (listof number) -> (listof number)
;; to convert a list of relative distances to a list of absolute distances 
;; the first item on the list represents the distance to the origin 
(define (relative-2-absolute alon) 
  (cond 
    [(empty? alon) empty] 
    [else (cons (first alon) 
                (add-to-each (first alon) (relative-2-absolute (rest alon))))]))

;; add-to-each : number (listof number) -> (listof number) ;; to add n to each number on alon (define (add-to-each n alon) (cond [(empty? alon) empty] [else (cons (+ (first alon) n) (add-to-each n (rest alon)))]))

Figure: Converting relative distances to absolute distances


Developing a program that performs this calculation is at this point an exercise in structural function design. Figure [cross-reference] contains the complete Scheme program. When the given list is not empty, the natural recursion computes the absolute distance of the remainder of the dots to the first item on (rest alon). Because the first item is not the actual origin and has a distance of (first alon) to the origin, we must add (first alon) to each and every item on the result of the recursive application. This second step, adding a number to each item on a list of numbers, requires an auxiliary function.

While the development of the program is straightforward, using it on larger and larger lists reveals a problem. Consider the evaluation of the following definition:[footnote]

(define x (relative-2-absolute (list 0 ... N)))
As we increase N, the time needed grows even faster:[footnote]

tabular38273

Instead of doubling as we go from 100 to 200 items, the time quadruples. This is also the approximate relationship for going from 200 to 400, 300 to 600, and so on.


Exercises

Exercise 30.1.1

Reformulate add-to-each using map and lambdaSolution

Exercise 30.1.2

Determine the abstract running time of relative-2-absolute.

Hint: Evaluate the expression

(relative-2-absolute (list 0 ... N))
by hand. Start by replacing N with 1, 2, and 3. How many natural recursions of relative-2-absolute and add-to-each are required each time? Solution

Considering the simplicity of the problem, the amount of ``work'' that the two functions perform is surprising. If we were to convert the same list by hand, we would tally up the total distance and just add it to the relative distances as we take another step along the line.

Let's attempt to design a second version of the function that is closer to our hand method. The new function is still a list-processing function, so we start from the appropriate template:

(define (rel-2-abs alon)
  (cond 
    [(empty? alon) ...] 
    [else ... (first alon) ... (rel-2-abs (rest alon)) ...])) 
Now imagine an ``evaluation'' of (rel-2-abs (list 3 2 7)):
  (rel-2-abs (list 3 2 7))

= (cons ... 3 ... (convert (list 2 7)))

= (cons ... 3 ... (cons ... 2 ... (convert (list 7))))

= (cons ... 3 ... (cons ... 2 ... (cons ... 7 ... (convert empty))))

The first item of the result list should obviously be 3, and it is easy to construct this list. But, the second one should be (+ 3 2), yet the second instance of rel-2-abs has no way of ``knowing'' that the first item of the original list is 3. The ``knowledge'' is lost.

Put differently, the problem is that recursive functions are independent of their context. A function processes the list L in (cons N L) in the exact same manner as L in (cons K L). Indeed, it would also process L in that manner if it were given L by itself. While this property makes structurally recursive functions easy to design, it also means that solutions are, on occasion, more complicated than necessary, and this complication may affect the performance of the function.

To make up for the loss of ``knowledge,'' we equip the function with an additional parameter: accu-dist. The new parameter represents the accumulated distance, which is the tally that we keep when we convert a list of relative distances to a list of absolute distances. Its initial value must be 0. As the function processes the numbers on the list, it must add them to the tally.

Here is the revised definition:

(define (rel-2-abs alon accu-dist)
  (cond 
    [(empty? alon) empty] 
    [else (cons (+ (first alon) accu-dist) 
                (rel-2-abs (rest alon) (+ (first alon) accu-dist)))])) 
The recursive application consumes the rest of the list and the new absolute distance of the current point to the origin. Although this means that two arguments are changing simultaneously, the change in the second one strictly depends on the first argument. The function is still a plain list-processing procedure.

Evaluating our running example with rel-2-abs shows how much the use of an accumulator simplifies the conversion process:

= (rel-2-abs (list 3 2 7) 0)
= (cons 3 (rel-2-abs (list 2 7) 3)) 
= (cons 3 (cons 5 (rel-2-abs (list 7) 5))) 
= (cons 3 (cons 5 (cons 12 (rel-2-abs empty 12)))) 
= (cons 3 (cons 5 (cons 12 empty))) 
Each item in the list is processed once. When rel-2-abs reaches the end of the argument list, the result is completely determined and no further work is needed. In general, the function performs on the order of N natural recursion steps for a list with N items.

One minor problem with the new definition is that the function consumes two arguments and is thus not equivalent to relative-2-absolute, a function of one argument. Worse, someone might accidentally misuse rel-2-abs by applying it to a list of numbers and a number that isn't 0. We can solve both problems with a function definition that contains rel-2-abs in a local definition: see figure [cross-reference]. Now, relative-2-absolute and relative-2-absolute2 are indistinguishable.


;; relative-2-absolute2 : (listof number) -> (listof number)
;; to convert a list of relative distances to a list of absolute distances 
;; the first item on the list represents the distance to the origin 
(define (relative-2-absolute2 alon) 
  (local ((define (rel-2-abs alon accu-dist) 
            (cond 
              [(empty? alon) empty] 
              [else (cons (+ (first alon) accu-dist) 
                          (rel-2-abs (rest alon) (+ (first alon) accu-dist)))]))) 
    (rel-2-abs alon 0))) 

Figure: Converting relative distances with an accumulator



[previous] [up] [next]     [index]
Next: A Problem with Generative Up: The Loss of Knowledge Previous: The Loss of Knowledge

PLT