Using reduce over a tree in Lisp

后端 未结 3 427
后悔当初
后悔当初 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:14

    I've provided an implementation of a treeduce function in Counting elements of a list and sublists, and although it's for Scheme, the same principles apply here. Wikipedia, in the Fold (higher-order function), says:

    In functional programming, fold – also known variously as reduce, accumulate, aggregate, compress, or inject – refers to a family of higher-order functions that analyze a recursive data structure and recombine through use of a given combining operation the results of recursively processing its constituent parts, building up a return value. Typically, a fold is presented with a combining function, a top node of a data structure, and possibly some default values to be used under certain conditions. The fold then proceeds to combine elements of the data structure's hierarchy, using the function in a systematic way.

    The list data structure can be described as an algebraic datatype:

    List ::= Cons(Object, List)
           | Nil
    

    When we call reduce with a function a list, we're essentially turning each use of Cons into an application of the function, and each use of Nil with some constant value. That is, we take the list

    Cons(x,Cons(y,Cons(z,Nil)))
    

    and turn it into

    Fn(x,Fn(y,Fn(z,init)))
    

    Alternatively, you can imagine Nil and init as as a zero-argument functions, in which case the list is turned into

    Fn(x,Fn(y,Fn(z,init())))
    

    For trees, we can do the same thing, but it's a little bit more complex. For a tree, the algebraic datatype is:

    Tree ::= Node(Tree,Tree)
           | Leaf(Object)
    

    To do a reduce for a tree, then, we need two functions: one to replace Node and one to replace Leaf. The definition is pretty straightforward, though:

    TreeReduce(nodeFn,leafFn,tree) =
      case tree of 
        Node(left,right) => nodeFn(TreeReduce(nodeFn,leafFn,left),TreeReduce(nodeFn,leafFn,right)
        Leaf(object) => leafFn(object)
    

    In Common Lisp, that's simply:

    (defun tree-reduce (node-fn leaf-fn tree)
      (if (consp tree)
          (funcall node-fn 
                   (tree-reduce node-fn leaf-fn (car tree))
                   (tree-reduce node-fn leaf-fn (cdr tree)))
          (funcall leaf-fn 
                   tree)))
    
    (tree-reduce 'cons
                 (lambda (x) 
                   (if (numberp x) (1+ x) x))
                 '(1 (2 3) (4 5 6)))
    ;=> (2 (3 4) (5 6 7))
    

    We can use tree-reduce to compute the sum that you asked about:

    (tree-reduce '+
                 (lambda (x)
                   (if (null x) 0 x))
                 '(1 (2) (3 (4) 5)))
    ;=> 15
    

    The reason that we need all of these null guards is that when we're viewing a cons-based structure as a tree, nil isn't really anything special. That is, we could consider the tree (1 (2 . 3) 4 . 5) as well as (1 (2 3) 4 (5)) (which is the same as (1 (2 3 . nil) 4 (5 . nil) . nil), of course).

提交回复
热议问题