Answer
levels :: Tree a -> [[a]] levels t = levels' t [] levels' :: Tree a -> [[a]] -> [[a]] levels' EmptyT rest = rest levels' (NodeT alr) [] = [a] : levels' l (levels r) levels' (NodeT alr) (x : xs) = (a : x) : levels' l (levels' r xs)
A slightly more complicated, but more lazy implementation of levels' :
levels' EmptyT rest = rest levels' (NodeT alr) rest = (a : front) : levels' l (levels' r back) where (front, back) = case rest of [] -> ([], []) (x : xs) -> (x, xs)
Fans of the folds noticed that they are structured as catamorphisms:
cata :: (a -> b -> b -> b) -> b -> Tree a -> b cata ne = go where go EmptyT = e go (NodeT alr) = na (go l) (go r) levels t = cata br id t [] where br alr rest = (a : front) : l (r back) where (front, back) = case rest of [] -> ([], []) (x : xs) -> (x, xs)
As chi points out , there seems to be some kind of connection between this general approach and the result of using Jakub Daniel's solution with difference lists as intermediate forms. It might look something like this:
import Data.Monoid levels :: Tree a -> [[a]] levels = map (flip appEndo []) . (cata br []) where br :: a -> [Endo [a]] -> [Endo [a]] -> [Endo [a]] br alr = Endo (a :) : merge lr merge :: Monoid a => [a] -> [a] -> [a] merge [] ys = ys merge (x : xs) ys = (x <> y) : merge xs ys' where (y,ys') = case ys of [] -> (mempty, []) p : ps -> (p, ps)
I'm not quite sure how this compares to more direct approaches.
Discussion
Kostiantyn Rybnikov answer quotes Okasaki Encryption of the first numbering: small maneuver lessons in the Design algorithm , an excellent article that emphasizes the blind spots of many functional programmers, and offers good arguments for making abstract data types easy enough to use, and they will not be missed. However, the problem described in the article is much more complicated than this; it requires not so much machinery. In addition, the document notes that level-oriented solutions are actually slightly faster than queue-based in ML; I expect to see a big difference in a lazy language like Haskell.
Jakub Daniel's answer tries to focus on the level, but unfortunately has a performance problem. He builds each level, repeatedly adding one list to another, and these lists can have the same length. Thus, in the worst case, if I calculate it correctly, O(n log n) is required to process the tree with elements n .
The approach I chose is level-oriented, but avoids the pain of concatenation by passing the levels of my right brother and cousins ββto each left subtree. Each node / leaf of the tree is processed exactly once. This processing includes O(1) work: pattern matching on this node / leaf and, if it is a node, pattern matching in the list received from the right brother and cousins. Thus, the total time O(n) processes the tree with elements n .