|
Comp210: Principles of Computing and Programming
|
Consider the following program to find the maximum depth of a binary tree:
;; A BinTree is either ;; -- EmptyTree ;; -- NETree ;; A NETree ;; (make-NETree any BinTree BinTree) (define-struct NETree (first left right)) ;; EmptyTree is empty (define-struct EmptyTree ());;Looks kind of like a Family Tree doesn't it? (define EMPTYTREE (make-EmptyTree)) (define bt (make-NETree 3 (make-NETree 5 EMPTYTREE (make-NETree 6 EMPTYTREE EMPTYTREE)) (make-NETree 2 EMPTYTREE EMPTYTREE))) ;; maxDepth_long: BinTree --> num ;; Returns the maximum depth of the tree. (define (maxDepth_long binTree) (cond [(EmptyTree? binTree) 0] [(NETree? binTree) (cond [(< (maxDepth_long (NETree-left binTree)) (maxDepth_long (NETree-right binTree))) (add1 (maxDepth_long (NETree-right binTree)))] [else (add1 (maxDepth_long (NETree-left binTree)))])])) "maxDepth_long test cases: " (= 0 (maxDepth_long EMPTYTREE)) (= 1 (maxDepth_long (make-NETree 42 EMPTYTREE EMPTYTREE)))
(= 2 (maxDepth_long (make-NETree 42 (make-NETree 3 EMPTYTREE EMPTYTREE) (make-NETree 7 EMPTYTREE EMPTYTREE))))
(= 3 (maxDepth_long bt))
This is not a particularly efficient program -- it calculates the everything twice, once for the conditional and once for the actual answer. There's got to be a better way.
How bad is it? Let's analyze how many times
maxDepth_long
gets called during the recursion :
(note: this is not an exact calculation)
Thus if there are N nodes on the tree, maxDepth_long
gets
called about Nlog23 = N1.585 times. (For
now, just believe us. Later courses will explain how to get that.)
Suppose we could arrange to only call maxDepth_long
once on
each sub-tree? Then, instead of 3 calls per node in the tree, we would only
have 2 calls. The total number of calls would thus only be about Nlog22
= N calls. The savings would be a multiplicative factor of N1.585.
If N is any size at all, this is a huge savings. It behooves us to find a
way to accomplish this.
Let's look at some new syntax in Scheme, called "local
".
To use local, set the language level to at least "Intermediate Student"
We'll check out what it does with some examples here:
(define x 2) "x, before local" x "x, inside local" (local [(define x 5)] x) ;; x has a different definition in here. "x, after local" x
The output from the above code is
x, before local 2 x, inside local 5 x, after local 2
Local has enabled a "local" redefinition of x.
Scope
Visibility
Note that it is possible to be in scope but not visible. To be visible however, one must be in scope. |
localSyntax: (local [(define ...) (define ...) ...] expression) Effects:
Purposes:
|
Let's look at another example:
(define z 10) (define (local_test1 x) (cond [(< x 5) (local [(define a 2)] (+ z (* x a)))] [else (+ z (* x a))])) " local_test1: This test works: " (= 16 (local_test1 3)) ;"local_test1: This test will not execute at all." ;(= 22 (local_test1 6))
Scoping and visibility of the various placeholders:
The second test gives an error because a is not scoped to the else clause.
So what would our maxDepth function look like using a local clause?
;; maxDepth: BinTree --> num ;; Returns the maximum depth of the tree. (define (maxDepth binTree) (cond [(EmptyTree? binTree) 0] [(NETree? binTree) (local [(define leftDepth (maxDepth (NETree-left binTree))) (define rightDepth (maxDepth (NETree-right binTree)))] (cond [(< leftDepth rightDepth) (add1 rightDepth)] [else (add1 leftDepth)]))])) "maxDepth test cases:" (= 0 (maxDepth EMPTYTREE)) (= 1 (maxDepth (make-NETree 42 EMPTYTREE EMPTYTREE)))
(= 2 (maxDepth (make-NETree 42 (make-NETree 3 EMPTYTREE EMPTYTREE) (make-NETree 7 EMPTYTREE EMPTYTREE))))
(= 3 (maxDepth bt))
Piece of cake.
For the record, the actual savings for the number of calls to the function are:
Tree |
# of calls to
maxDept_long
|
# calls to
maxDepth
|
EMPTYTREE |
1
|
1
|
(make-NETree 42 EMPTYTREE EMPTYTREE) |
4
|
3
|
(make-NETree 42 (make-NETree 3 EMPTYTREE EMPTYTREE) (make-NETree 7 EMPTYTREE EMPTYTREE))) |
13
|
7
|
bt |
25
|
9
|
local is also handy when you have many sub-expressions building up your answer.
As per the First Law of Programming, the calculation is now expressed in terms that make sense, not magic numbers.;; earth-pull: given a mass which is "height" meters above the ground (mean radius of the Earth), ;; return the force of gravity between it and the earth, in Newtons.
;; (define (earth-pull m height) (local [(define G 6.67e-11) ; Universal gravitational constant in SI units. (define earth-mass 5.97e24) ; in kg (define earth-radius 6380000) ; in m (define dist-from-center (+ earth-radius height))] (/ (* G earth-mass m) (square dist-from-center)))) "earth-pull test cases (1% accuracy):" (> .01 (abs (/ (- 9.8 (earth-pull 1 0)) 9.8))) (> .01 (abs (/ (- 166.30 (earth-pull 17 23)) 166.30)))
Let's see how local can help tame the proliferation of helper functions we are experiencing:
;; sum: lon --> num
;; sums the list of numbers
(define (sum lon)
(cond
[(empty? lon) 0]
[(cons? lon)
(local
(;;helper: lon num --> num
;; returns the sum of the lon plus the sum-so-far.
(define (helper lon sum-so-far)
(cond
[(empty? lon) sum-so-far]
[(cons? lon) (helper (rest lon) (+ (first lon) sum-so-far))])))
(helper (rest lon) (first lon)))]))
"sum test cases:" (= 0 (sum empty)) (= 15 (sum (list 1 2 3 4 5)))
The helper function is not visible to the outside world -- all that is seen is a nicely encapsulated sum function.
Today's code can be downloaded here: local.scm
Both Prof. Barland and Prof. Cooper wrote nice lectures on local. I recommend that you go read them:
Prof. Barland's Fall 2001 local lecture
Prof. Cooper's Spring 2002 local lecture
Last Revised Tuesday, 24-Aug-2004 13:49:00 CDT
©2004 Stephen Wong and Dung Nguyen