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, and I want to apply a function over each of the element? So that fold over '(1 (2) (3 (4) 5))
would still give 15
? I tried to use reduce
, but I had to provide a custom function, which kinda defeats the purpose:
(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) ; returns 15
Of course I could flatten the list first, but reduce
is a general function, sometimes you must preserve the structure and order of the original sequence. For example, map
and filter
can be implemented with reduce
. What if I wanted to write an implementation of my-map
, based on reduce
, so that:
(my-map '1+ '(1 (2 (3) 4) 5)) ; outputs '(2 (3 (4) 5) 6)
How to use reduce
over a tree structure? What is the most generic way to apply a binary function over a tree?
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:
The list data structure can be described as an algebraic datatype:
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 ofNil
with some constant value. That is, we take the listand turn it into
Alternatively, you can imagine
Nil
andinit
as as a zero-argument functions, in which case the list is turned intoFor trees, we can do the same thing, but it's a little bit more complex. For a tree, the algebraic datatype is:
To do a reduce for a tree, then, we need two functions: one to replace
Node
and one to replaceLeaf
. The definition is pretty straightforward, though:In Common Lisp, that's simply:
We can use tree-reduce to compute the sum that you asked about:
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).
Common Lisp does not have tree versions of
map
orreduce
. In fact, the only tree functions I can remember off-hand aretree-equal
andsubst
.However, it should not be too hard to do something like:
try it:
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:
Note how we are just passing
#'tree+
intoreduce
, andtree+
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 usinglabels
to our function a local name:Now this is used like this:
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: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) ...)))
and0
, 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).