Recently Eitan Chatav asked in the Programming Haskell group on Facebook
What is the correct way to write breadth first traversal of a ?
He’s thinking of “traversal” in the sense of the class, and gave a concrete declaration of rose trees:
It’s an excellent question.
Breadth-first enumeration
First, let’s think about breadth-first enumeration of the elements of a tree. This isn’t compositional (a fold); but the related “level-order enumeration”, which gives a list of lists of elements, one list per level, is compositional:
Here, is “long zip with”; it’s similar to , but returns a list as long as its longer argument:
(It’s a nice exercise to define a notion of folds for , and to write as a fold.)
Given , breadth-first enumeration is obtained by concatenation:
Incidentally, this allows trees to be foldable, breadth-first:
Relabelling
Level-order enumeration is invertible, in the sense that you can reconstruct the tree given its shape and its level-order enumeration.
One way to define this is to pass the level-order enumeration around the tree, snipping bits off it as you go. Here is a mutually recursive pair of functions to relabel a tree with a given list of lists, returning also the unused bits of the lists of lists.
Assuming that the given list of lists is “big enough”—ie each list has enough elements for that level of the tree—then the result is well-defined. Then is determined by the equivalence
Here, the of a tree is obtained by discarding its elements:
In particular, if the given list of lists is the level-order of the tree, and so is exactly the right size, then will have no remaining elements, consisting entirely of empty levels:
So we can take a tree apart into its shape and contents, and reconstruct the tree from such data.
Breadth-first traversal
This lets us traverse a tree in breadth-first order, by performing the traversal just on the contents. We separate the tree into shape and contents, perform a list-based traversal, and reconstruct the tree.
This trick of traversal by factoring into shape and contents is explored in my paper Understanding Idiomatic Traversals Backwards and Forwards from Haskell 2013.
Inverting breadth-first enumeration
We’ve seen that level-order enumeration is invertible in a certain sense, and that this means that we perform traversal by factoring into shape and contents then traversing the contents independently of the shape. But the contents we’ve chosen is the level-order enumeration, which is a list of lists. Normally, one thinks of the contents of a data structure as simply being a list, ie obtained by breadth-first enumeration rather than by level-order enumeration. Can we do relabelling from the breadth-first enumeration too? Yes, we can!
There’s a very clever cyclic program for breadth-first relabelling of a tree given only a list, not a list of lists; in particular, breadth-first relabelling a tree with its own breadth-first enumeration gives back the tree you first thought of. In fact, the relabelling function is precisely the same as before! The trick comes in constructing the necessary list of lists:
Note that variable is defined cyclically; informally, the output leftovers on one level also form the input elements to be used for relabelling all the lower levels. Given this definition, we have
for any . This program is essentially due to Geraint Jones, and is derived in an unpublished paper Linear-Time Breadth-First Tree Algorithms: An Exercise in the Arithmetic of Folds and Zips that we wrote together in 1993.
We can use this instead in the definition of breadth-first traversal:
Thanks for the shoutout 🙂 Here’s my solution though I guess it may have worse spacetime complexity?
Minor tweaks made.
Eitan, I haven’t studied your solution, but the splitPlaces makes me a bit uneasy about the complexity. However, mine isn’t linear-time either: you would at least need to use an accumulating-parameter version of levels to achieve that.
What is the meaning of the symbol in a circle in the last line of code, replaced by ?? in the following: traverse ft = pure (bflabel (shape t)) ?? traverse f (bf t)
Sorry, that wasn’t meant to be cryptic. It’s a fancy rendering of the “zap” of an Applicative functor, written “
<*>
” in ASCII.Why not fmap? I was under the impression that `pure f x` is the same as `f x`
Perhaps some angle brackets went missing from your question, but yes, it’s a law of the Applicative class (and its Functor superclass) that
pure f <*> x = fmap f x
.Pingback: Tree levels in lazy Python using "Long zip with" – Ask python questions
Pingback: Tree levels in lazy Python using “Long zip with” – Python