![]() |
|
So, let's kick it up a notch, shall we?
A file system in a computer could be modeled as such:
This leads to the following templates:
(define (f-directory aDir) ...(Directory-name aDir)...(f-LoFD Directory-contents aDir)...) (define (f-LoFD aLoFD) (cond [(empty? aLoFD) ...] [(symbol? (first aLoFD)) ...(f-sym (first aLoFD))...(f-LoFD (rest aLoFD))...] [(Directory? (first aLoFD)) ...(f-directory (first aLoFD))...(f-LoFD (rest aLoFD))...]))
There are a couple of issues with this model:
To remedy these issues, let's consider another model of a computer file system where an abstraction of a file and a directory is explicitly defined (this is was done in Lab06):
![]() |
Reminder on the features of a UML class 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).
Notes:
|
Here an AFile is either a simple concrete File or a collection of AFiles (a Directory). Notice that a Directory is essentially a named collection of abstract AFiles. Thus, the contents list only contains one type of Cons. This type of structure 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. Note that the design from lab is not a whole-part hierarchy because a file and a directory are not abstractly equivalent.
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 is equivalent to the following data definitions:
;; An AFile (abstract file) is ;; -- File, or ;; -- Directory ;; A File is structure with a name and a size. ;; (make-File string num) (define-struct File (name size)) ;; A Directory is ;; a name and a contents, which is a list of AFiles. ;; (make-Directory symbol list-of-AFile) (define-struct Directory (name contents)) ;; list-of-AFile (loAF) is ;; -- empty ;; -- (cons AFile loAF)
Given a UML diagram, one should be able to write the Scheme data definitions and vice versa.
And of course, these definitions give rise to some templates. Our new top-level AFile abstraction enables clearly defined delegations that preserve encapsulations:
;; Template for AFile (define (f-AFile aFile ...) (cond [(File? aFile) (f-File aFile ...)] [(Directory? aFile) (f-Directory aFile ...)])) ;; Template for File (define (f-File file ...) (...(File-name file)...(File-size file)...)) ;; Template for Directory (define (f-Directory dir ...) (...(Directory-name dir)...(f-LoAF (Directory-contents dir))...)) ;; Template for List-of-AFile (define (f-LoAF loAF ...) (cond [(empty? loAF) ...] [(cons? loAF) (...(f-AFile (first loAF))...(f-LoAF (rest loAF)))]))
Could the above templates be compressed without really violating templates as we have defined them in the past?.
Sure -- in the past, we wouldn't have explicitly separated out the File and Directory functions from the AFile 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 Directory:
(define myFS (make-Directory "folder0" (list (make-File "file1" 100) (make-File "file2" 200) (make-Directory "folder1" (list (make-File "file3" 300) (make-Directory "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 AFile which includes any sub-AFile, if they exist. This is a straight-forward plugging into the template:
;; totalSize-AFile: AFile --> num ;; returns the total size of an AFile and all sub-AFile. (define (totalSize-AFile aFile) (cond [(File? aFile) (totalSize-File aFile )] [(Directory? aFile) (totalSize-Directory aFile)])) ;;totalSize-File: File --> num ;; Returns size the File. (define (totalSize-File file ) (File-size file)) ;; This is redundant -- could have called this in totalSize-AFile. ;; totalSize-Directory: Directory --> num ;; Returns the total size of all AFiles in the contents list. (define (totalSize-Directory dir) (totalSize-LoAF (Directory-contents dir))) ;; totalSize-LoAF: list-of-AFile --> num ;; Calculates the total size of all the AFiles in the list. (define (totalSize-LoAF loAF) (cond [(empty? loAF) 0 ] [(cons? loAF) (+ (totalSize-AFile (first loAF)) (totalSize-LoAF (rest loAF)))])) "totalSize-AFile test cases:" (= 1300 (totalSize-AFile myFS)) (= 300 (totalSize-AFile (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-AFile: AFile string --> list-of-string ;; Returns the full pathname of all aFiles found that ;; match the given name ;; Empty list returned if no matches (define (find-AFile aFile name) (cond [(File? aFile) (find-File aFile name)] [(Directory? aFile) (find-Directory aFile name)])) ;; find-File: AFile 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-Directory: Directory string --> list-of-string ;; Returns a list of the full pathname of all AFiles found that ;; match the given name. Directorys have "[d]" after their names. ;; Empty list returned if no matches (define (find-Directory dir name) (append (cond [(equal? name (Directory-name dir)) (list (string-append (Directory-name dir) " [d]"))] [else empty]) (find-LoAF (Directory-contents dir) name (Directory-name dir)))) ;; find-LoAF: Dir string --> list-of-string ;; Returns a list of the full pathname of all AFiles found that ;; match the given name ;; Empty list returned if no matches (define (find-LoAF loAF name dir-name) (cond [(empty? loAF) empty] [(cons? loAF) (append (find-LoAF (rest loAF) name dir-name) (prepend-name dir-name (find-AFile (first loAF) 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)))])) "find-AFile test cases:"
(equal? (list "folder0/file2") (find-AFile myFS "file2"))
(equal? (list "folder0/file3" "folder0/folder1/folder1/file3" "folder0/folder1/file3") (find-AFile myFS "file3"))
(equal? (list "folder0/folder1 [d]" "folder0/folder1/folder1 [d]") (find-AFile myFS "folder1"))
(empty? (find-AFile myFS "zz"))
(equal? (list "zz") (find-AFile (make-File "zz" 300) "zz"))
Notes:
I will leave the above three functions as "at home" exercises.
©2003 Stephen Wong