With family trees, we saw our first real example of a problem domain
with many candidate models.  The process we went through on family
trees, in which we refined the models as we worked with them, is an
important process in any real programming project.

Let's consider another example: models of filesystems.  Filesystems
contain two kinds of items: files and directories, which can
themselves contain both files and directories.  If you wanted a very
simple model of a filesystem, what information would you maintain
about files and directories?  For files, we would maintain just their
names.  For directories, we would just maintain lists of their
contents.

A file is a symbol

A directory is one of
  - empty,
  - (cons f d), where f is a file and d is a directory, or
  - (cons d1 d2), where d1 and d2 are directories

The template for a program that processes directories is as follows: 

(define (file-func a-file)
   ...)

(define (dir-func a-dir)
   (cond
       [(empty? a-dir) ...]
       [(file? (first a-dir))
        ... (file-func (first a-dir)) ... (dir-func (rest a-dir)) ...]
       [(cons? (first a-dir))
        ... (dir-func (first a-dir)) ... (dir-func (rest a-dir)) ...]))

What are the shortcomings of this model?  In the real world. both
files and directories have many more attributes.  For example,
directories have names and files have sizes and contents.  How would
we redo the data definition to account for this?

First, let's enhance the definition of directories.  We need to make a
structure to hold the additional information about directories.  In
the previous model, a directory was a list containing its contents.
Now, the list of contents will need to move into the structure.

A directory is a structure
  (make-dir n c)
where n is a symbol and c is a list of files and directories

(define-struct dir (name contents))

A list-of-files-and-directories (l-o-f-d) is one of
  - empty,
  - (cons f lofd), where f is a file, and lofd is a l-o-f-d, or
  - (cons d lofd), where d is a directory, and lofd is a l-o-f-d.

Consider what happens if we enhance the definition of files: does it
change the shape of the above data definition?  No.  Therefore, let's
leave files as symbols for now (you can extend them to something more
complicated at home for practice if you'd like).

The template:

(define (file-func a-file)
   ...)

(define (dir-func a-dir)
   ... (dir-name a-dir) ... (lofd-func (dir-contents a-dir)) ...)

(define (lofd-func a-lofd)
   (cond
       [(empty? a-lofd) ... ]
       [(file? (first a-lofd))
        ... (file-func (first a-lofd))
        ... (lofd-func (rest a-lofd)) ...]
       [(dir? (first a-lofd))
        ... (dir-func (first a-lofd))
        ... (lofd-func (rest a-lofd)) ...]))

Write a program file-in-dir? which consumes a directory and a file
name and returns true if the file exists in the directory and false
otherwise.

;; file-in-dir? dir sym -> boolean
;; checks whether file exists in directory
(define (file-in-dir? a-dir name)
  (file-in-lofd? (dir-contents a-dir) name))

;; file-in-lofd? : l-o-f-d sym -> boolean
;; checks whether file exists in a list of files and directories
(define (file-in-lofd? a-lofd name)
   (cond
       [(empty? a-lofd) false]
       [(file? (first a-lofd))
        (or (is-file?? (first a-lofd) name)
            (file-in-lofd? (rest a-lofd) name))]
       [(dir? (first a-lofd))
        (or (file-in-dir? (first a-lofd) name)
            (file-in-lofd? (rest a-lofd) name))]))

;; is-file? : file sym -> boolean
;; indicates whether this file is the one we're looking for
(define (is-file?? a-file name) 
   (symbol=? a-file name))

Write a program depth-dir, which consumes a directory and returns a
number indicating how many levels of directories are in the directory
tree.

;; depth-dir : directory -> num
;; determine how many levels of directories are in a directory
(define (depth-dir a-dir)
  (+ 1 (depth-lofd (dir-contents a-dir))))

;; depth-lofd : l-o-f-d -> number
;; determines maximum number of levels of directories within a
;; list of files and directories
(define (depth-lofd a-lofd)
   (cond
       [(empty? a-lofd) 0]
       [(file? (first a-lofd))
        (depth-lofd (rest a-lofd))]
       [(dir? (first a-lofd))
        (max (depth-dir (first a-lofd))
             (depth-lofd (rest a-lofd)))]))