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
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).