So, let's kick it up a notch, shall we?
Consider the following model of a computer file system:
![]() |
Reminder on the features of a UML class diagram:
Note some of the new features on this UML diagram:
For more complete information on UML diagrams, see http://www.exciton.cs.rice.edu/JavaResources/UML (ignore the sections on methods and implementing interfaces). |
Notice that a Folder is essentially a named collection of Items. Thus an Item is either a simple concrete File or a collection of Items (a Folder). This is called a whole-part hierarchy because the "parts" (simple concrete sub-types) are abstractly equivalent to the "whole" (the composite collection).. This is also a variant of the Composite Design Pattern. Whole-part hierarchies are important because they enable us to treat collections of objects equivalently as the inidividual objects.
Here is the general UML diagram for a whole-part hierarchy:
Where else have we seen whole-part hierarchies?
Whole-part hierarchies are ubiquitous and understanding how to process them is crucial.
The above model gives rise to the following data definitions:
;; An Item is ;; -- File ;; -- Folder ;; A File is structure with a name and a size. ;; (make-File string num) (define-struct File (name size)) ;; A Folder is ;; (make-Folder symbol list-of-Items) (define-struct Folder (name loi)) ;; list-of-Items (loi) is ;; -- empty ;; -- (cons Item loi)
And of course, these definitions give rise to some templates:
;; Template for Item (define (f-Item item ...) (cond [(File? item) (f-File item ...)] [(Folder? item) (f-Folder item ...)])) ;; Template for File (define (f-File file ...) (...(File-name file)...(File-size file)...)) ;; Template for Folder (define (f-Folder folder ...) (...(Folder-name folder)...(f-LOI (Folder-loi folder))...)) ;; Template for List-of-Items (define (f-LOI dir ...) (cond [(empty? dir) ...] [(cons? dir) (...(f-Item (first dir))...(f-LOI (rest dir)))]))
Could the above templates be compressed without really violating the templates as we have defined them in the past?.
Sure -- in the past, we wouldn't have explicitly separated out the File and Folder functions from the Item template. But in the interests of maintaining encapsulation, and with coupled with the growing complexity of our problems, it is safer to explicitly separate them out now. We can always recombine them later if it is convenient.
Here's an instantiation of a Folder:
(define myFS (make-Folder "folder0" (list (make-File "file1" 100) (make-File "file2" 200) (make-Folder "folder1" (list (make-File "file3" 300) (make-Folder "folder1" (list (make-File "file3" 300))))) (make-File "file3" 400))))
Write some functions, already!:
Let's write a function that will return the total size of an Item which includes any sub-Items, if they exist. This is a straight-forward plugging into the template:
;; totalSize-Item: Item --> num ;; returns the total size of an Item and all sub-Items. (define (totalSize-Item item) (cond [(File? item) (totalSize-File item )] [(Folder? item) (totalSize-Folder item)])) ;;totalSize-File: File --> num ;; Returns size the File. (define (totalSize-File file ) (File-size file)) ;; This is redundant -- could have called this in totalSize-Item. ;; totalSize-Folder: Folder --> num ;; Returns the total size of all items in the list. (define (totalSize-Folder folder) (totalSize-LOI (Folder-loi folder))) ;; Redundant too-- could have called this in totalSize-Item. ;; totalSize-LOI: list-of-Items --> num ;; Calculates the total size of all the items in the list. (define (totalSize-LOI loi) (cond [(empty? loi) 0 ] [(cons? loi) (+ (totalSize-Item (first loi)) (totalSize-LOI (rest loi)))])) "totalSize-Item test cases:" (= 1300 (totalSize-Item myFS)) (= 300 (totalSize-Item (make-File "zz" 300)))
That was easy. How about something a little harder?
How about a function that finds all the occcurances of a name in the file system, returning a list of the complete "pathnames" to those occurances. A pathname is the string of all the names along the path to the occurance, separated by a slash, "/".
Once again, we just plug into the templates:
;; find-Item: Item string --> list-of-string ;; Returns the full pathname of all items found that ;; match the given name ;; Empty list returned if no matches (define (find-Item item name) (cond [(File? item) (find-File item name)] [(Folder? item) (find-Folder item name)])) ;; find-File: Item string --> list-of-string ;; Returns a list with the name of the file if it ;; matches the given name. ;; Empty list returned if no match (define (find-File file name) (cond [(equal? name (File-name file)) (list (File-name file))] [else empty])) ;; find-Folder: Folder string --> list-of-string ;; Returns a list of the full pathname of all items found that ;; match the given name. Folders have "[d]" after their names. ;; Empty list returned if no matches (define (find-Folder folder name) (append (cond [(equal? name (Folder-name folder)) (list (string-append (Folder-name folder) " [d]"))] [else empty]) (find-LOI (Folder-loi folder) name (Folder-name folder)))) ;; find-LOI: Dir string --> list-of-string ;; Returns a list of the full pathname of all items found that ;; match the given name ;; Empty list returned if no matches (define (find-LOI loi name folder-name) (cond [(empty? loi) empty] [(cons? loi) (append (find-LOI (rest loi) name folder-name) (prepend-name folder-name (find-Item (first loi) name)))])) ;;prepend: string list-of-string --> list-of-string ;;pre-pends the given name string onto all the elements of the ;; given list of strings, with a "/" separator. (define (prepend-name name los) (cond [(empty? los) empty] [(cons? los) (cons (string-append name "/" (first los)) (prepend-name name (rest los)))]))
Notes:
I will leave the above three functions as "at home" exercises.
©2002 Stephen Wong