|
Comp210: Principles of Computing and Programming
Fall 2004 -- Lab #6:
Mutually Recursive Data
|
Files and Directories
The following are data definitions for one possible (simplified) representation
of files and directories. Observe the mutual recursion between files and
list-of-files.
; A file is one of
; - a symbol
; representing a "simple" file's name
; - a directory
; (make-dir name contents)
; where name is a symbol, and contents is a l-o-f.
; A list-of-files (l-o-f) is one of
; - empty
; - (cons f lofd)
; where f is a file, and lofd is a l-o-f
This is very similar to the descendant trees data structure seen in class.
Tree-based data structures are very common!
Directory exercises
-
Create some sample data for the above types.
-
Write the templates for the above types.
-
Develop a function
; find? : symbol file -> boolean
; Returns whether the filename is anywhere in the
; tree of files represented by the file.
An aside
Note that this is a vast simplification of find, the
mother-of-all everything-but-the-kitchen-sink UNIX directory
traversing command. If you're curious, at a UNIX prompt, enter
man find to see what it can do.
|
-
Use DrScheme's stepper to step through an example use of find? .
Following the templates leads to an overall strategy known as
depth-first search. I.e., it explores each tree branch to the
end before moving on to the next branch.
-
Develop a function
; any-duplicate-names? : file -> boolean
; Returns whether any (sub)directory directly or indirectly contains
; another directory or file of the same name. It does NOT check
; for duplicated names in separate branches of the tree.
There's a straightforward way that just follows the template.
-
For the curious...
Develop a program to check for duplicated names among
all directories and files in the given tree, not just
subdirectories.
Here's a hint.
-
Develop the following function:
; flatten-dir-once : symbol file -> (file or l-o-f)
; Returns a structure like the original file, except that any
; (sub)directory with that name is removed and its contents
; moved up one level.
Here are two pictorial examples, in both cases removing the directory
named to-remove . These illustrate why this function can
return either a file or a list of files.
|
Input |
Output |
Example 1: |
foo
/ | \
bar baz to-remove
/ \
one two
|
foo
/ / \ \
bar baz one two
|
Example 2: |
to-remove
/ | \
foo bar baz
|
foo bar baz
|
Follow the templates and think about a single case at a time.
If you do that, it shouldn't be too difficult. If you don't, you'll
probably have real trouble.
|
Sample solutions.
|
Descendant Family Trees
Review:
In class, we originally discussed Ancestor
Family Trees (where each person had exactly two parents), and today turned
that on its head and introduced Descendent
Family Trees (where each person has any number of children).
Note on Family Trees
Pragmatically, we would have a representation of family trees that had the
advantages of both the ancestor- and descendant-based versions. That is
indeed possible. For that, we want a graph, a kind of data
structure that we will look at later in the course.
|
The descendant trees entailed two separate interrelated types:
;; A Person is a structure
;; where name is their name, year is their birth year
;; and kids is a list the person's children.
;; (make-Person symbol num list-of-Persons)
(define-struct Person (name year kids))
;; A list-of-Persons is either
;; - empty
;; - (cons Person list-of-Persons)
As a reminder, here are their templates:
;; Template for Person
(define (f-Person a-person)
...(Person-name a-person)...
...(Person-year a-person)...
...(f-lop (Person-kids a-person))...)))
;; Template for list-of-Persons
(define (f-lop a-lop)
(cond
[(empty? a-lop) ...]
[(cons? a-lop) ...(f-Person (first a-lop))...
...(f-lop (rest a-lop))...)]))
Some examples of this data might be
(define Bart (make-Person 'Bart 1979 empty))
(define Lisa (make-Person 'Lisa 1981 empty))
(define Maggie (make-Person 'Maggie 1988 empty))
(define Homer (make-Person 'Homer 1955 (list Bart Lisa Maggie)))
(define Marge (make-Person 'Marge 1956 (list Bart Lisa Maggie)))
(define Selma (make-Person 'Selma 1950 empty))
(define Patty (make-Person 'Patty 1950 empty))
(define Jackie (make-Person 'Jackie 1926 (list Selma Patty Marge)))
(define Herbert (make-Person 'Herbert 1950 empty))
(define Mona (make-Person 'Mona 1929 (list Homer Herbert)))
(define Abe (make-Person 'Abe 1920 (list Homer Herbert)))
Descendant Family Tree warm-up exercises
These are variations on the in-class example. Do some to make sure you
understand the template, before moving on to the more difficult next
exercise.
-
Note how we're using define to save ourselves typing. How
would we write the descendent tree for Homer, if we hadn't have used
define ? How about for Jackie?
-
Develop a function that counts the number of decendants in a person's
family tree. Include the person in the count.
-
Develop a function that returns whether there is someone in a family
tree with a certain given name.
|
Descendant Family Tree pretty-printing exercises
Note how DrScheme displays the value Abe : nicely indented, but in
Scheme format, using "make-Person ", "list ", etc.
This is the preferred way for us programmers, because it shows the structure
of the data. But suppose we want to show our data to non-programmers. We
still want things indent appropriately, but not necessarily as internalized
structure, e.g.,
Jackie (b. 1926)
|_ Selma (b. 1950)
|_ Patty (b. 1950)
|_ Marge (b. 1956)
|_ Bart (b. 1979)
|_ Lisa (b. 1981)
|_ Maggie (b. 1988)
or
Abe (b. 1920)
|_ Homer (b. 1955)
|_ Bart (b. 1979)
|_ Lisa (b. 1981)
|_ Maggie (b. 1988)
|_ Herbert (b. 1950)
We will write a function Person->string
produces that output as a string. This will be similar to the corresponding
function for Ascestor Family Trees shown in
class.
Like the other pretty-printing examples, this program will involve two
separate conceptual strategies: data-driven mutual recursion and
problem-driven accumulation (of the prefix string).
-
What are a couple of other sample outputs (e.g., for Abe and Bart)?
Assume that a list of children should print out in the same order as
listed in the data structure. That does not necessarily correspond
to birth order.
-
Group discussion:
How do the solutions to the sub-problem (e.g., the string version of
Marge) relate to the total solution (e.g., the string version of
Jackie)?
-
Group discussion:
What info do we need to keep track of, when writing these examples on
the board? We need to keep track of what to output at the beginning
of each line -- at first nothing, but for each generation, some
additional space. This is an accumulator!
-
Okay, write the function!
A couple of built-in functions you'll need to use are
symbol->string and
number->string . Also, load the teachpack
/home/comp210/Labs/Lab06/lab06-lib.ss
via DrScheme's Language.. menu. This provides you with the
following constant and function:
;; newline: string
;; The one-character string with a carriage-return.
;; display-string: string -> true
;; Display the given string in a text-box.
;; Always returns true.
; Example:
(define limerick (string-append "There once was a man from Kali,"
newline
"Whose limerick stopped at line three."
newline
"You'd think it just started --"))
limerick
(display-string limerick)
-
For the curious...
For similarity to the pretty-printing with ancestor trees, we'd prefer
the output for Abe to be
Abe (b. 1920)
|_ Homer (b. 1955)
| |_ Bart (b. 1979)
| |_ Lisa (b. 1981)
| |_ Maggie (b. 1988)
|_ Herbert (b. 1950)
without changing the output for Jackie. Write that version.
Hint: Note that the last child is treated differently than the others
in that his/her children are not prefixed by a vertical bar. You'll
need to check for that, while not breaking encapsulation.
|
Sample solution.
|
A couple of notes on strings and I/O
-
Some people thing that input and output are intrinsic to programming.
They're not -- they're just impediments to get around, before you
can even begin to solve the problem you're interested in. DrScheme
makes I/O fairly transparent via the calculator-like behavior of the
read-eval-print loop.
Formatting output is more important when writing programs that other
non-programmers will want to use. It's rarely relevant to solving
the problem itself.n Thus, it's necessary, but uninteresting. (Some
language environments don't have default output so nice; in those
cases, programmers usually have to spend time, for each structure,
writing how to format it, just so they can debug more easily.)
-
Converting data to strings throws away the data's structure.
You should never convert your high-level, structured data down into
a string, pass that string to some other function, and force that
function to parse the string back into a structure.
Working within a single language, it's easy to pass your high-level
structures immediately. Saving structured data to a file or
communicating across a web-link is more problematic. The recent
solution is to encode structure as a series of "tags" in a language
called XML. XML is a standard way to convert (nested) lists into
strings, in a way that makes it trivial to reconstitute the original
structure. If you know some XML (or HTML), you may be interested in
DrScheme's menu item Insert XML box.
|
Last Revised
Thursday, 30-Sep-2004 13:13:42 CDT
©2004 Stephen Wong and Dung Nguyen