Using reduce over a tree in Lisp

后端 未结 3 426
后悔当初
后悔当初 2021-01-15 18:54

To fold a flat list in Lisp you use reduce:

* (reduce #\'+ \'(1 2 3 4 5))
15

But what if I have an arbitrarily complex tree, a

3条回答
  •  礼貌的吻别
    2021-01-15 19:37

    In addition to developing a tree-reduce, a useful exercise is to try to repair your existing code so that it is more generally applicable.

    That is, we take what you have:

    (defun tree+ (a b)
      (cond ((null b) 0)
            ((atom b) (+ a b))
            (t (+ (tree+ a (car b))
                  (tree+ 0 (cdr b))))))
    
    (reduce #'tree+ '(1 (2) (3 (4) 5)) :initial-value 0)
    

    Note how we are just passing #'tree+ into reduce, and tree+ is hard-coded for addition. The obvious fix is is to curry the + function as a functional argument.

    To achieve this, we can very simply transform the bulk your tree+ into a function that returns a function.

    We don't use lambda, because then we would need a Y-combinator or other silly hack to handle the recursion, which is much more easily achieved by using labels to our function a local name:

    (defun tree-reducer (binary-func &optional initial-val)
      (labels ((tr-red (a b)
                 (cond ((null b) initial-val) 
                       ((atom b) (funcall binary-func a b))
                       (t (+ (tr-red a (car b))
                             (tr-red initial-val (cdr b)))))))
         #'tr-red))
    

    Now this is used like this:

    (reduce (tree-reducer #'+ 0) '(1 (2) (3 (4) 5)) :initial-value 0)  -> 15
    

    Unfortunately, the initial value is specified twice, the reason for this being that the function returned by tree-reducer takes on some of the responsibility of carrying out the reduce logic! Note that when we add a level of nesting to the tree and call:

    (reduce (tree-reducer #'+ 0) '((1 (2) (3 (4) 5))) :initial-value 0) -> 15
    

    who is doing the work of producing 15? Not the reduce function! All it does is call the function once, with the arguments ((1 (2) ...))) and 0, which then does all the work.

    Also, the initial-value argument of tree-reducer will seriously misbehave if it is not the identity element for the given function (like zero to addition).

    (reduce (tree-reducer #'+ 0) '(1 (2) (3 (4) 5)) :initial-value 1) -> 16  ;; OK
    
    (reduce (tree-reducer #'+ 1) '(1 (2) (3 (4) 5)) :initial-value 0) -> 20  ;; Whoa!
    

提交回复
热议问题