views:

98

answers:

3

Say I wanted to write an algorithm working on an immutable tree data structure that has a list of leaves as its input. It needs to return a new tree with changes made to the old tree going upwards from those leaves.

My problem is that there seems to be no way to do this purely functional without reconstructing the entire tree checking at leaves if they are in the list, because you always need to return a complete new tree as the result of an operation and you can't mutate the existing tree.

Is this a basic problem in functional programming that only can be avoided by using a better suited algorithm or am I missing something?

Edit: I not only want to avoid to recreate the entire tree but also the functional algorithm should have the same time complexity than the mutating variant.

+1  A: 

This depends on your functional programming language. For instance in Haskell, which is a Lazy functional programming language, results are calculated at the last moment; when they are acutally needed.

In your example the assumption is that because your function creates a new tree, the whole tree must be processed, whereas in reality the function is just passed on to the next function and only executed when necessary.

A good example of lazy evaluation is the sieve of erastothenes in Haskell, which creates the prime numbers by eliminating the multiples of the current number in the list of numbers. Note that the list of numbers is infinite. Taken from here

primes :: [Integer]
primes = sieve [2..]
  where
    sieve (p:xs) = p : sieve [x|x <- xs, x `mod` p > 0]
Ruben
Doesn't that just shift the computational complexity to a later point of the program, if it's really likely that I will access every node of the new tree?
Axel Gneiting
Indeed it shifts the execution of the function to a later point in the program. However it only constructs the new tree on the fly when needed.
Ruben
+2  A: 

You may enjoy reading

http://lorgonblog.spaces.live.com/blog/cns!701679AD17B6D310!248.entry

Brian
I don't think this is of any help, because the article is about a top down algorithm that can exclude entire subtrees going down the tree and therefore can reuse large parts of the old tree. Going down from the root I don't know where the leaves are that will be affected, so I can't reuse the old subtrees.
Axel Gneiting
No, it's not. It walks the entire tree down to the leaves to discover where it needs to make changes, but then only recreates the portions of the tree that change, and keeps the unchanged portion, when constructing the new tree as it climbs back up.
Brian
You are right. I should have read it more carefully. But that doesn't help me, because the time complexity is still the same. I edited the question to be more precise in that point.
Axel Gneiting
O(N) in the size of the tree? The mutating variant has to visit all the nodes too, right?
Brian
No it doesn't. That's the point.
Axel Gneiting
I don't get it. Earlier you said you don't know where the leaves are that will be affected, but now somehow the mutating version can 'find the right leaves' without visiting them all? What is the algorithm here? The question is very vague.
Brian
I know the leaves that are affected, but as this is a functional algorithm I need to build a new tree, because I e.g. can't just remove the leaves by mutating their parent nodes.
Axel Gneiting
+1  A: 

The most promising I have seen so far (which admittedly is not very long...) is the Zipper data structure: It basically keeps a separate structure, a reverse path from the node to root, and does local edits on this separate structure.

It can do multiple local edits, most of which are constant time, and write them back to the tree (reconstructing the path to root, which are the only nodes that need to change) all in one go.

The Zipper is a standard library in Clojure (see the heading Zippers - Functional Tree Editing).

And there's the original paper by Huet with an implementation in OCaml.

Disclaimer: I have been programming for a long time, but only started functional programming a couple of weeks ago, and had never even heard of the problem of functional editing of trees until last week, so there may very well be other solutions I'm unaware of.

Still, it looks like the Zipper does most of what one could wish for. If there are other alternatives at O(log n) or below, I'd like to hear them.

j-g-faustus
That seems to be a solution. Thank you very much :)
Axel Gneiting
You're welcome :)
j-g-faustus