Let's look at another example of accumulators on trees.  Recall the
binary number trees from the exam.  Let's write a program to sum up
the numbers in a numtree.  As usual, we start with the structural
solution:

;; sumtree : bintree -> number
;; sum the numbers in a binary tree
(define (sumtree abt)
  (cond [(empty? abt) 0]
	[else (+ (bintree-num abt)
		 (sumtree (bintree-left abt))
		 (sumtree (bintree-right abt)))]))

We write the accumulator template as usual:

(define (sumtree abt)
  (local [;; accum : ...
	  (define (sum-accum atree accum)
	    (cond [(empty? atree) ...]
		  [else (sum-accum ...
			 (bintree-left atree) ...
			 (bintree-num atree) ...
			 accum ...)
			(sum-accum ...
			 (bintree-right atree) ...
			 (bintree-num atree) ...
			 accum ...)]))]
    (sum-accum abt ...)))

What should we use as the accumulator invariant?  Let's let the
accumulator represent the sum of the numbers in the part of the tree
that we've already visited.  Parts of this are easy to fill in:

(define (sumtree abt)
  (local [;; accum : sum of numbers in part of abt that sum-accum
	  ;;   has already visited
	  (define (sum-accum atree accum)
	    (cond [(empty? atree) accum]
		  [else (sum-accum ...
			 (bintree-left atree) ...
			 (bintree-num atree) ...
			 accum ...)
			(sum-accum ...
			 (bintree-right atree) ...
			 (bintree-num atree) ...
			 accum ...)]))]
    (sum-accum abt 0)))

What about the rest?  Notice that we have two trees, not one, to
process in the recursive call.  We could process each tree separately
and add the results.  That defeats the purpose of the accumulator,
though, because it still leaves a pending computation (the addition)
after the recursive calls.  We really want to pass the accumulated sum
from one subtree to the other, as follows:

(define (sumtree abt)
  (local [;; accum : sum of numbers in part of abt that sum-accum
	  ;;   has already visited
	  (define (sum-accum atree accum)
	    (cond [(empty? atree) accum]
		  [else (sum-accum 
			 (bintree-left atree) 
			 (sum-accum (bintree-right atree) 
				    (+ (bintree-num atree) 
				       accum)))]))]
	 (sum-accum abt 0)))

This style, where we pass the result of calling the accumulator on one
branch of the tree to the call to process the other branch of the
tree, is the standard method for writing accumulator programs over
trees.  When you write an accumulator program over trees, you should
expect to pass the result of processing one subtree to the result of
processing the other subtree(s).  This style of programming is
sometimes called threading.

Let's look at one more program on binary trees.  We want to gather a
list of all elements in a binary tree that are larger than 5.  The
structural program looks as follows:

;; get-larger : binary-tree -> (listof number)
;; returns list of all numbers in tree that are larger than 5
(define (get-larger abt)
  (cond [(empty? abt) empty]
	[else
	 (local [(define inrest (append (get-larger (bintree-left abt))
					(get-larger (bintree-right abt))))]
		(cond [(> (bintree-num abt) 5)
		       (cons (bintree-num abt) inrest)]
		      [else inrest]))]))

This time, however, let's accumulate something other than the list of
numbers in other subtrees that are larger than 5.  Recall that
accumulators carry knowledge about how a program executes to other
calls to that program.  In previous examples, we've accumulated the
output of a program.  This time, let's accumulate the other trees that
are yet to be processed.  This demonstrates another way that we might
use an accumulator in a program.  As usual, we start with the template.

(define (get-larger abt)
  (local [;;accum : list of subtrees of abt remaining to be processed
	  (define (get-larger-accum atree accum)
	    (cond [(empty? atree) ...]
		  [else
		   (local ((define inrest
			     (get-larger-accum
			      (bintree-left atree) ...
			      (bintree-right atree) ...
			      (bintree-num atree) ... accum)))
		     ... inrest ...)]))]
    (get-larger-accum abt empty)))

How do we fill in the program?  The else case is fairly easy -- it
follows the same strategy as in the structural program.  The empty
case here is harder.  Once the tree is empty, we begin to process the
accumulated trees that have not yet been processed.  The following
code shows the final program:

(define (get-larger abt)
  (local [;;accum : list of subtrees of abt remaining to be processed
	  (define (get-larger-accum atree accum)
	    (cond [(empty? atree)
		   (cond [(empty? accum) empty]
			 [else (get-larger-accum
				(first accum) (rest accum))])]
		  [else
		   (local ((define inrest
			     (get-larger-accum
			      (bintree-left atree)
			      (cons (bintree-right atree) accum))))
			  (cond [(> (bintree-num atree) 5)
				 (cons (bintree-num atree) inrest)]
				[else inrest]))]))]
    (get-larger-accum abt empty)))

We show you this to emphasize a point: accumulators do not always
gather partial computations of the result of a program.  Accumulators
simply pass knowledge about one computation to another computation.
The challenge in writing accumulator-style programs is figuring out
what the accumulator represents, and what invariant holds of that
accumulator.  Developing that invariant is the key to writing good
accumulator style programs.

Let's summarize.  We've now seen three different uses for an
accumulator:

 1. To introduce knowledge into our programs so that they can work
    properly (find-route).
 2. To make a program more efficient (reverse, available-days)
 3. To make programs easier to understand (more subjective)

Of these reasons, 1 is the most important to you right now.  Reason 2
is semi-important, because you can notice the efficiency problems on
small to medium sized examples.  Reason 3 is somewhat a matter of
personal taste.  Sometimes writing accumulator stype programs is easy
(as for sum).  Sometimes, as in get-larger, its a bit harder.  What
you should take from these lectures on accumulators is that writing
accumulator programs is easier if you write the structural solution
first, then follow the recipes to add accumulators carefully to your
programs.

Finally, let's look at one more problem on file systems.  Most
operating systems let you create shortcuts (or links) between files,
so that one file can appear in multiple directories.  Let's write a
program to detect whether there are any shared files in our
directories.  We'll assume that a file is shared if its name appears
more than once in the directory hierarchy.

;; shared-files? : dir -> boolean
;; determine whether some file name appears more than once in a
;;  directory hierarchy
(define (shared-files? adir) ...)

As usual, we start with a template.  We know that the accumulator must
hold the files that we've already seen, so let's also write the
accumulator invariant down now.

(define (shared-files? adir) 
  (local [;accum1 : list of files seen so far
	  (define (shared-accum a-dir accum1)
	    ...
	    (share/list-accum (dir-dirs a-dir) ...
			      (dir-files a-dir) ... accum1))
	  ; accum2 : list of files seen so far
	  (define (share/list-accum alod accum2)
	    (cond [(empty? alod) ...]
		  [else
		   ... (shared-accum (first alod) accum2) 
		   ... (share/list-accum (rest alod) ...
					 (first alod) ... accum2)]))]
   (shared-accum adir ...)))

As a first attempt, we might try to fill in the program as follows:

(define (shared-files? adir) 
  (local [;accum1 : list of files seen so far
	  (define (shared-accum a-dir accum1)
	    (or (overlap? (dir-files a-dir) accum1)
		(share/list-accum (dir-dirs a-dir)
				  (append (dir-files a-dir) accum1))))
	  ; accum2 : list of files seen so far
	  (define (share/list-accum alod accum2)
	    (cond [(empty? alod) false]
		  [else
		   (or (shared-accum (first alod) accum2)
		       (share/list-accum (rest alod)
					 (append (dir-files (first alod))
						 accum2)))]))]
	 (shared-accum adir empty)))

However, this doesn't work, as evidenced by the following test case.
What happens is that when we traverse (first alod) in
share/list-accum, we don't accumulate the files seen in the whole
(first alod) tree on the recursive call; we only accumulate the files
in the individual directory (first alod).  Therefore this program
fails to work.

(define d2 (make-dir 'Home empty
		     (list (make-dir 'Papers empty
				     (list (make-dir 'Fall99 (list 'p1 'p4) empty)))
			   (make-dir 'Courses empty
				     (list (make-dir 'Huma101 (list 'p1) empty)
					   (make-dir 'Comp210 (list 'p2) empty))))))


What's the solution?  To write this program fully in
accumulator-style, we need to accumulate both the files we've seen AND
the directories remaining to process.  This way, we can accumulate
files while processing one subdirectory, then pass those along to the
next subdirectory.  We therefore need a program with two accumulators,
as shown below.

(define (shared-files? adir)
  (local [;accum-f1 : list of files seen so far
	  ;accum-t1 : list of trees left to process
	  (define (shared-accum a-dir accum-f1 accum-t1)
	    (or (overlap? (dir-files a-dir) accum-f1)
		(share/list-accum (dir-dirs a-dir)
				  (append (dir-files a-dir) accum-f1)
				  accum-t1)))
	  ;accum-f2 : list of files seen so far
	  ;accum-t2 : list of trees left to process
	  (define (share/list-accum alod accum-f2 accum-t2)
	    (cond [(empty? alod)
		   (cond [(empty? accum-t2) false]
			 [else (shared-accum (first accum-t2)
					     accum-f2
					     (rest accum-t2))])]
		  [else
		   (shared-accum (first alod) accum-f2
				 (append accum-t2 (rest alod)))]))]
   (shared-accum adir empty empty)))

Certainly, you could write this program without using accumulators
(this is a good practice problem for structural recursion on trees if
you need the practice).  We showed you this version as a challenge,
and to demonstrate that working with accumulators isn't always as easy
as what we've seen in class all week.