Zipper in scheme › 附录 [#275]

附录,2006 年 6 月 7 日 [受到 Andrew Wilcox 问题的启发] 更准确地说,zipper 与底层枚举器一样保留了共享。以下是最大共享保留枚举器。这两个函数应该取代本文中的函数。
Addendum, June 7, 2006 [inspired by a question from Andrew Wilcox] To be more precise, the zipper preserves sharing as much as the underlying enumerator does. The following is the maximal sharing preserving enumerator. Those two functions should replace the ones in the article.

; deterministic, left-to-right map
; It preserves sharing as much as possible: that is, if given the pair
; l == (cons h t), (and (eq? h (f h)) (eq? t (map* f t))) holds, then
; (eq? (map* f l) l) holds as well.
(define (map* f l)
  (if (null? l) l
    (let ((h (car l)) (t (cdr l)))
      (let ((h1 (f h)) (t1 (map* f t)))
        (if (and (eq? h1 h) (eq? t1 t)) l
            (cons h1 t1))))))

(define (depth-first handle tree)
  (cond
    ((null? tree) tree)
    ((handle tree) => (lambda (new-tree) new-tree))
    ; the node was not handled -- descend
    ((not (pair? tree)) tree) ; an atom
    (else
      (let ((kids1 
             (map* (lambda (kid) (depth-first handle kid)) (cdr tree))))
        (if (eq? kids1 (cdr tree)) tree
            (cons (car tree) ; node name
                kids1))))))

为了测试新的 depth-first 确实保留了共享,我们求值
To test that the new depth-first indeed preserves sharing, we evaluate

(eq? tree1
     (depth-first (lambda (node) (display node) (newline) #f) tree1))

以深度优先顺序打印所有节点后,给出结果 #t。在这种情况下,深度优先(depth-first)返回的树确实是原始树。
which, after printing all nodes in depth-first order, gives the result #t. The tree returned by depth-first in this case is indeed the original tree as it is.

zipper 代码无需更改,按原样工作,具有相同的结果。 为了测试共享是否保留,我们首先通过替换 tree2 中的第 6 个节点(即 y)来生成一棵树:
The zipper code needs no changes, and it works as it was, with the same results. To test the sharing preservation, we first produce a tree by replacing the 6th node (which is y) in tree2:

(define tree2*
  (let ((desired-node (locate-nth-node 6 tree2)))
  (display "Replacing the node: ")
  (display (z-curr-node desired-node))
  (newline)
  (zip-all-the-way-up ((z-k desired-node) 'newy))))

这是结果:(z (u) (v (w 10 12)) newy)
here's the result: (z (u) (v (w 10 12)) newy)

现在,我们编写一个函数,它接受两棵树,同步遍历它们并打印出节点以及它们是否共享:
Now, we write a function that takes two trees, traverses them in lockstep and prints out the nodes and if they are shared:

(define (tree-compare-sharing t1 t2)
  (do ((cursor1 (zip-tree t1) ((z-k cursor1) #f))
       (cursor2 (zip-tree t2) ((z-k cursor2) #f)))
      ((cond
        ((and (zipper? cursor1) (zipper? cursor2)) #f)
      ((zipper? cursor1) (display "t2 finished early") #t)
      ((zipper? cursor2) (display "t1 finished early") #t)
      (else #t)))
      (let ((n1 (z-curr-node cursor1)) (n2 (z-curr-node cursor2)))
        (cond
          ((eq? n1 n2) (display "shared node: ") (display n1))
          (else (display "t1 node: ") (display n1) (newline)
                (display "t2 node: ") (display n2)))
        (newline))))

(tree-compare-sharing tree2 tree2*)
===>
t1 node: (z (u) (v (w 10 12)) y)
t2 node: (z (u) (v (w 10 12)) newy)
shared node: (u)
shared node: (v (w 10 12))
shared node: (w 10 12)
shared node: 10
shared node: 12
t1 node: y
t2 node: newy