4

I have a tree:

a :: Tree Double
a = 
    Node 1 
       [Node 20 
           [Node 300 
               [Node 400 [], 
               Node 500 []], 
           Node 310 []], 
       Node 30 [], 
       Node 40 []]

I want to apply it an scan operation similar to lists - except that, instead of returning a list, it should return a tree with the travelled paths. For example:

scan (+) 0 a

Should reduce to:

Node 1 
    [Node 21 
        [Node 321 
            [Node 721 [], 
            Node 821 []], 
        Node 331 []], 
    Node 31 [], 
    Node 41 []]

Which is accumulated the sums through the tree. Is there a standard function for this?

MaiaVictor
  • 51,090
  • 44
  • 144
  • 286

3 Answers3

3

There is no standard library function that does this. In the case of lists: Haskell has just about any function you can think of already in Data.List, but Data.Tree is actually pretty sparse.

Fortunately the function you want is quite simple.

scan f ~(Node r l) = Node r $ map (fmap (f r) . scan f) l

--edit--

The above function has a problem: in the example given by the OP it calculates the "721" as "1 + (20 + (400 + 300))", which prevents the "1 + 20" calculation from being re-used in the other branches.

The below function doesn't have this problem, but I'm leaving the original one in place because it can still be useful depending on what function is passed as the first argument.

scan f ~(Node r l) = Node r $ map (scan' r) l where
  scan' a ~(Node n b) = let a' = f a n in Node a' $ map (scan' r) b 
Jeremy List
  • 1,756
  • 9
  • 16
  • 1
    Actually with the `Traversable` class and some helper functions you can do this task generically using only standard library functions. – Thomas M. DuBuisson Jan 05 '15 at 00:01
  • 1
    I knew about `Traversable`, but it doesn't seem to have a function that does this specific task. – Jeremy List Jan 05 '15 at 05:21
  • the Functor instance isn't shown and if it does what it's supposed to do, this would mean repeated re-entering and rescanning of whole subtrees (quadratic behaviour). – Will Ness Jan 05 '15 at 14:55
  • My mistake, I misunderstood the goal. – Thomas M. DuBuisson Jan 05 '15 at 15:08
  • @WillNess This code doesn't re-enter or re-scan whole subtrees, but it does apply more iterations of `f` to deeper nodes in the tree as is specified in the OP. You can check the actual behavior with the `:step` and `:list` commands in ghci – Jeremy List Jan 06 '15 at 10:03
  • @JeremyList doesn't `fmap` (which isn't shown...) has to do the rescanning (i.e. repeated traversal) of the result trees (of calling `scan f` on each subtree), to produce the results as shown in the Q? check out the two ideone entries from the other answer here, which clearly show a quadratic behaviour for your code. Each node is first recreated with the same value, then is modified by `fmap (f r)` from the node immediately above it, then by `fmap (f r)` from the node above *that*, etc. Quadratic. – Will Ness Jan 06 '15 at 10:13
  • (contd.) for `f = (+)` this means, the chain of functions is applied on each node, e.g. `(+1) . (+20) . (+300) $ 400` for the leftmost bottom node. I don't expect the conversion is performed by compiler into `(+321) $ 400`. Which would make it linear, and *would* be extremely nice, if it were, performed. (I can remove my downvote if you'd add a note explaining this - or I could do that myself, with your permission...) – Will Ness Jan 06 '15 at 10:21
  • (replace "quadratic behaviour" with "dramatic slowdown" in the comment above last. to see whether it's quadratic, at least two size points need be measured.) – Will Ness Jan 06 '15 at 10:28
  • @WillNess Not exactly. A single application of fmap would scan the whole subtree if the whole result is evaluated, but because of lazy evaluation you can use fmap in a nested way and still only traverse each node once. The repeated applications of `f` at each node are part of the OP's problem definition. – Jeremy List Jan 06 '15 at 10:32
  • @willNess You haven't explained your reasoning clearly enough for me to know exactly where you went wrong, but the `:step` and `:list` commands in ghci clearly show that you went wrong somewhere. – Jeremy List Jan 06 '15 at 10:46
2

If you want to pass an accumulator, then the definition is

scan f a (Node x ns) = Node a' $ map (scan f a') ns where a' = f a x

This version is also quite more efficient, compare this and this.

effectfully
  • 12,325
  • 2
  • 17
  • 40
  • @Will Ness, the `Tree` datatype has only one constructor, so I put it automatically. But yes, `map` anyway forces `ns`. Fixed, thanks. [Also](http://ideone.com/C6SnCA). – effectfully Jan 05 '15 at 15:23
  • Hm, but it doesn't force `x`, hence `treeHead $ scan const "pointless" undefined` returns `"pointless"`. Which indeed is pointless. – effectfully Jan 05 '15 at 15:29
  • 1
    you meant, *with the lazy pattern* it returns "pointless". (just to clarify) – Will Ness Jan 06 '15 at 08:10
2

update: this produces different results than requested; but it shows a valuable general approach that can be helpful where applicable, and is helpful here as well, as a counterpoint.

It is entirely possible to do this sort of traversal generically using base and GHC. The class you are looking for is Traversable and the mapAccum{R,L} functions along with fst or snd:

Lets avoid writing our own instances:

{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveFunctor #-}

Now we can derive the necessary parts:

import Data.Traversable
import Data.Foldable

data Tree a = Node a [Tree a]
            deriving (Functor, Traversable, Foldable, Show)

Then the use is quite easy. If you don't want the final accumulator then just use snd.

main :: IO ()
main = print $ mapAccumL (\acc e -> (acc+e,acc+e)) 0 demo

demo :: Tree Int
demo =
   Node 1 [Node 20.
            [Node 300 [Node 400 []
                      , Node 500 []]
            , Node 310 []
            ]
          , Node 30 [], Node 40 []
          ]
Will Ness
  • 70,110
  • 9
  • 98
  • 181
Thomas M. DuBuisson
  • 64,245
  • 7
  • 109
  • 166
  • 1
    This type, and appropriate instances, are defined in `Data.Tree` in the `containers` package, which ships with GHC. – dfeuer Jan 05 '15 at 01:26
  • Yep, but if he is using a custom declaration then it's beneficial for him to know how to derive the instances. The question omitted that part. – Thomas M. DuBuisson Jan 05 '15 at 01:41
  • 1
    I believe this doesn't solve the question. The OP asked for accumulating paths from the root to each node, not for accumulating the whole traversal. Compare your result with the result he is asking for. – Petr Jan 05 '15 at 13:15
  • Exactly... the result is different from what I asked. I've learned something from that answer, though, so I'm thankful you posted it. – MaiaVictor Jan 05 '15 at 14:53
  • Ah, I see. I didn't catch this. – Thomas M. DuBuisson Jan 05 '15 at 15:07