![]() |
|
So far we have from the last lecture
Now we want to automate the building process to build a gasket of arbitrary size:
The process we used is called a "depth-first traversal" because the functions we wrote worked by recurring all the way to each base case along each branch of the "virtual tree" of Sierpinski's before processing the next branch of the tree. Depth-first traversals are not limited to virtual trees that are generated on the fly--one can do depth-first traversals of regular trees as well. In fact, most, if not all, of the searching-type algorithms (e.g. "find such-and-such an element" ) we have written on trees have been depth-first traversals. Depth-first traversals tend to be the easiest and most natural way to navigate around a tree-type structure.
Today's code: lec28.scm
Helper code: sierpinski.scm
Previously, we showed that we could display an n'th order Sierpinski gasket by taking a base case gasket, growing in it n times and then drawing the resultant gasket onto the screen.
But is there any other way we could have done it?
:Let's consider the depth-first traversals in general. This generative recursion process looks basically like this:
Can this be utilized for drawing fractals? That is, just draw the fractal without ever making an instance of a fractal structure?
Of course! They're recursive after all, aren't they?
What's the difference from what we did before? In our previous techniques, we actually made an entire fractal object which was then displayed. In this generative process, we will mimic the process of making a fractal object, but never actually create one, only a picture of one.
Let's see what we've got:
Here's a standard templated version of the depth-first traversal to draw a fractal:
;; A factory to make a fractal, which has a ;; base case function and an inductive case function. ;; The base case takes a base case fractal and draws it ;; fBase: SierpBase --> boolean ;; The inductive function takes a SierpBase and returns a list of SierpBases ;; finduct: SierpBase --> list-of-Fractal (define-struct FractalFactory (fBase fInduct)) ;; draw-Fractal: FractalBase FractalFactory number --> boolean ;; Regular template version to draw a Sierpinski gasket of a given level (define (draw-gasket frac fracFac level) (cond [(zero? level) ((FractalFactory-fBase fracFac) frac)] [else (map (lambda (f) (draw-gasket f fracFac (sub1 level))) ((FractalFactory-fInduct fracFac) frac))]))
Notice that the above code doesn't care exactly what the FractalFactory does, so we're not going to worry about that for a bit.. The factory is slightly different that what we've used before in that instead of constructing a base case fractal, it takes a base case fractal and draws it. This is because our base case is now a drawing rather than a construction, since we only want to draw and not create a fractal structure.
But I want to get rid of that annoying cond statement, so I want to convert the above code into a visitor to a natural number (NVisitor).
But here's a problem: draw-Fractal above is a function of 3 variables. Now the level input is no problem since that natural number will become the host of our visitor. But that leaves two more inputs and a visitor only allows one input parameter besides the host. We could make a specialized container structure for those two variables, but that's a pain and just hides what is going on.
Let's analyze the situation in terms of the most important analysis criteria in good programming: The separation of the variant and the invariant.
Variant entities must be passed as input variables because we never know what they are. Invariant entities don't need to be passed at all, just somehow accessible. Global accessiblility is a no-no however, because the invariant nature of the FractalFactory is relevant only to this algorithm and not necessarily to anything else in our system.
The FractalFactory needs to be in the closure of the functions that use it!
Some observations:
Our visitor code thus becomes:
;; make-drawFractalVisitor: FractalFactory --> NVisitor ;; makes a NatNum Visitor that will draw a n'th level ;; fractal using the functions in the supplied FractalFactory ;; The parameter is a base case fractal. (define (make-drawFracVisitor fracFac) (local [(define this (make-NVisitor ;; Uses the base case function of the factory ;; to draw the given fractal (lambda (n frac) ((FractalFactory-fBase fracFac) frac)) ;; Uses the inductive case function of the factory ;; to generate the next level of the fractal (lambda (n frac) (map (lambda (f) (nExecute (sub1 n) this f)) ((FractalFactory-fInduct fracFac) frac)))))] this))
See how the factory enables us to create a closure that encompasses both lambdas in the visitor (note that the local is just so we can name the visitor and thus allow us to be able to make the recursive call to it).
Note that this is still a depth-first traversal process.
The FractalFactory for Sierpinski gaskets is just two function we already have: draw-sierpBase, which draws a base case Sierpinski, and make-nextSierp, which takes a base case Sierpinski and creates the next level gasket from it.
Our function call to draw the Sierpinski gasket thus looks something like this:
(nExecute nLevels (make-drawFracVisitor aSierpFac) aBaseSierp)
Can the make-drawFracVisitor be used for other fractals? You know the answer -- especially since you've done your homework!
Get the code for generative recursion-built Sierpinski gaskets here: gensierpinski.scm
Build-data-structure-and-draw-it technique:
- Data-driven methodology.
- Considered to be in the realm of the "object-orient programming" paradigm of computing by many.
- Results in a fully constructed data object that can be used in other places for other purposes.
- Can be costly in terms of time and space.
Generate-and-draw-on-the-fly technique
- Process-driven methodology
- Considered to be in the realm of the "functional programming" paradigm of computing by many.
- No resultant data structure.
- Smaller code, faster and less memory intensive.
Bottom line: It depends on what exactly your goals are.
Revelation: Object-oriented programming and functional programming are not so different. Some say one is a subset of the other -- it depends on who you ask as to which is the superset and which is the subset!
Principles of Computer Science are principles of Computer Science, no matter what language or paradigm in which you express them.
©2003 Stephen Wong