{"id":775,"date":"2009-05-27T16:36:35","date_gmt":"2009-05-27T16:36:35","guid":{"rendered":"http:\/\/scientopia.org\/blogs\/goodmath\/2009\/05\/27\/finally-finger-trees\/"},"modified":"2009-05-27T16:36:35","modified_gmt":"2009-05-27T16:36:35","slug":"finally-finger-trees","status":"publish","type":"post","link":"http:\/\/www.goodmath.org\/blog\/2009\/05\/27\/finally-finger-trees\/","title":{"rendered":"Finally: Finger Trees!"},"content":{"rendered":"<p><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/scientopia.org\/img-archive\/goodmath\/img_384.jpg?resize=106%2C130\" width=\"106\" height=\"130\" alt=\"stone fingers_478fc739a1585.jpg\" class=\"inset right\" \/><\/p>\n<p> For ages, I&#8217;ve been promising to write about finger trees. Finger trees are<br \/>\nan incredibly elegant and simple structure for implementing sequence-based<br \/>\ndata structures. They&#8217;re primarily used in functional languages, but there&#8217;s nothing<br \/>\nstopping an imperative-language programmer from using them as well.<\/p>\n<p> In functional programming languages, lists are incredibly common. They show up everywhere; they&#8217;re just so easy to use for recursive iteration that they&#8217;re ubiquitous. I can&#8217;t think of<br \/>\nany non-trivial program that I&#8217;ve written in Haskell, Lisp, or OCaml that doesn&#8217;t use lists. (Heck, I&#8217;ve wound up using cons-cell libraries a couple of times in C++.)<\/p>\n<p> Of course, lists aren&#8217;t perfect. In fact, for some things, they absolutely suck. Suppose I&#8217;ve got a really long list &#8211; like a list of names and phone numbers from a telephone book. My local phone book has 600 pages of names; and the pages are incredibly dense &#8211; there&#8217;ve got to be at least a couple of hundred names per page. So in a phone book for a suburban region of New York City, the list of phone numbers will have 120,000 names. Now imagine that I want to look<br \/>\nup one name in that list &#8211; on average, I&#8217;m going to have to chase through 60,000 pointers. 60,000 isn&#8217;t a huge number for a computer, but still, chasing 60,000 pointers is a pretty big deal. If I stored them in an array, it would be a lot less convenient for some tasks, but I could use<br \/>\nbinary search to find names, and I could find any name in no more than 16 comparisons &#8211; and the whole shebang would be contiguous, so I wouldn&#8217;t even be chasing pointers.<\/p>\n<p> What finger trees do is give me a way of representing a list that has both the convenience<br \/>\nof the traditional cons list, and the search efficiency of the array based method.<\/p>\n<p><!--more--><\/p>\n<p> The basic idea of the finger tree is amazingly simple. It&#8217;s a balanced tree where you store<br \/>\nall of the data in the leaves. The internal nodes are just a structure on which you can hang<br \/>\n<em>annotations<\/em>, which you can use for optimizing search operations on the tree. What makes<br \/>\nthe finger tree so elegant is the way that some very smart people have generalized the idea of<br \/>\nannotations to make finger trees into a single, easily customizable structure that&#8217;s useful for so<br \/>\nmany different purposes: you customize the annotations that you&#8217;re going to store in the internal nodes according to the main use of your tree. <\/p>\n<p> Let&#8217;s look at a simple example &#8211; a sorted list, like the phone book. Below is a two-three tree<br \/>\nwith values in the leafs. The list is the sequence of leafs from left to right.<\/p>\n<div><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/scientopia.org\/img-archive\/goodmath\/img_385.png?resize=352%2C217\" width=\"352\" height=\"217\" alt=\"leaf-tree.png\" \/><\/div>\n<p> In a finger tree, we label the internal nodes of the tree with a <em>count<\/em>: the number of<br \/>\nvalues stored in the subtree rooted at an internal node is stored in the node &#8211; like in the image below. So, now, to look up<br \/>\nthe seventh element of the list, we look at the left child of the root: it&#8217;s got five children. So<br \/>\nwe know that the 7th element is in the right subtree. Then we search the right subtree: the left<br \/>\nchild has 2 values &#8211; so the leaf we want is going to be in the left subtree: since that node has<br \/>\nleaves as children, it&#8217;s the second child of the leaf.<\/p>\n<div><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/scientopia.org\/img-archive\/goodmath\/img_386.png?resize=352%2C217\" width=\"352\" height=\"217\" alt=\"counted-finger-tree.png\" \/><\/div>\n<p> By doing that labeling, we&#8217;ve got a list where an array-like index operator takes O(lg n) steps. It&#8217;s also O(lg n) for arbitrary inserts. And the time to maintain the counts doesn&#8217;t change the algorithmic complexity, and it&#8217;s extremely fast in practice.<\/p>\n<p> But there&#8217;s more to like about it than that. It&#8217;s downright easy to keep <em>every<\/em> version of the list that ever occurred in the history of the program. If you think about it, each operation to modify the list of values has a bubble-up effect, changing the internal nodes of the tree above it. Those internal nodes are very small and simple: if you copy them instead of mutate them, then you get very lightweight persistence. A great example of how this works is: if you use a finger tree to represent the text buffer of an editor, you don&#8217;t need to implement undo: you just keep the old versions of the text buffer! You can do that by just copying a small number of internal nodes as things change; the text itself, you never change &#8211; so the total memory for text is the size of the original buffer plus whatever you inserted. In terms of memory use, the<br \/>\nmemory performance of a finger-tree based persistent history is roughly the same as the amount of memory needed to create an undo history. (Depending on your exact implementation, and the particular usage pattern of the editor, either one can come out ahead.)<\/p>\n<p> But we still haven&#8217;t even gotten to the most beautiful thing about finger trees!<\/p>\n<p> The idea of a finger tree is that the internal nodes store some kind of data that makes it<br \/>\npossible for you to find a particular desired value very quickly. In the example we looked at, the<br \/>\nthing we wanted to be able to quickly was array style indexing. But finger trees are good for much<br \/>\nmore than that. Another canonical example is a priority queue. In a priority queue, you&#8217;ve got a<br \/>\ncollection of tasks that need to be performed. Each task has a priority, representing how<br \/>\nimportant it is. Whenever a processor becomes available, the highest priority task should be<br \/>\nexecuted. Priorities are generally handled in reverse order: priority 1 is the highest, 2 is lower<br \/>\npriority than 1; 3 is lower than 1 and 2, and so on. (It&#8217;s done that way so that it&#8217;s always easy<br \/>\nto add something at a lower priority.) The way priorities are handled, the highest priority always<br \/>\ngoes first; you should never start a priority 2 when there&#8217;s a priority 1 in the queue; but if<br \/>\nyou&#8217;ve got two priority 2 tasks, they should be executed in the order in which they were<br \/>\ninserted.<\/p>\n<p> We can create a priority queue as a list of tasks, using a finger-tree based list, just like we did for the array indexing. When we add a new task to the queue, we&#8217;ll always add it at the<br \/>\nend of the list. What we want to do is to try to pick the leftmost high-priority task in the<br \/>\nlist.<\/p>\n<p> How can we annotate the tree to make it easy to quickly find the leftmost highest priority<br \/>\ntask? We just label each internal node with the <em>highest<\/em> priority containing in any of its<br \/>\nchildren. Then we search the tree, looking for the highest-value child at each level; if there&#8217;s a<br \/>\ntie, we go to the leftmost. Presto: O(lg n) to find and remove the highest priority task; O(lg n)<br \/>\nto insert a new task. <\/p>\n<p> Now, the real question is: what do these two have in common? That is, if we wanted to<br \/>\nimplement a <em>generic<\/em> finger tree, what do the annotations have in common?<\/p>\n<p> The annotations are <em>monoids<\/em> over some kind of number. If you remember abstract<br \/>\nalgebra, a monoid is a set of values with an associative operation and an identity element.<br \/>\nAs long as the annotations are a monoid, you can make a finger tree based on the annotations.<br \/>\n(Actually, you don&#8217;t even strictly need them to be monoids over numbers; you need to be able<br \/>\nto do an ordered comparison between the annotation values &#8211; so what you really need is<br \/>\njust a monoid over ordered values.)<\/p>\n<p> In the list of integers, the set of annotation values are natural numbers, the operation<br \/>\nis addition, and the identity is (obviously) 0. In the priority queue, the set of annotations are also natural numbers, but the operation is min; identity is the maximum priority value &#8211; which will be the maximum size of the integer type used for priority.<\/p>\n<p> If you parameterize your finger-tree implementation on the monoid, you can use<br \/>\n<em>exactly<\/em> the same finger-tree code for both the indexable list and the priority queue. How<br \/>\ncan that be? This is where things get subtle: we need to dive in a bit more and see just what the<br \/>\nfact that the annotation is a monoid really <em>means<\/em>.<\/p>\n<p> As I said above, a monoid is a set of values with a single, associative operation. (For<br \/>\nthis discussion, I&#8217;m going to use &diams; for the monoid operation.)  What associativity<br \/>\nmeans is that given any sequence of values (v<sub>1<\/sub>, &#8230;, v<sub>n<\/sub>),<br \/>\nif you apply &diams; to the set, any way of parenthesizing it will produce the<br \/>\nsame result: you can group the list any way you want.<\/p>\n<p> v<sub>1<\/sub> &diams; v<sub>2<\/sub> &diams; v<sub>3<\/sub> &diams; v<sub>4<\/sub> &diams; v<sub>5<\/sub> &diams; v<sub>6<\/sub> &diams; v<sub>7<\/sub> &diams; v<sub>8<\/sub> &diams; v<sub>9<\/sub> =<br \/>\n((v<sub>1<\/sub> &diams; v<sub>2<\/sub>) &diams; (v<sub>3<\/sub> &diams; v<sub>4<\/sub>)) &diams; ((v<sub>5<\/sub> &diams; v<sub>6<\/sub>) &diams; ((v<sub>7<\/sub> &diams; v<sub>8<\/sub>) &diams; v<sub>9<\/sub>)) =<br \/>\n((((((((v<sub>1<\/sub> &diams; v<sub>2<\/sub>) &diams; v<sub>3<\/sub>) &diams; v<sub>4<\/sub>) &diams; v<sub>5<\/sub>) &diams; v<sub>6<\/sub>) &diams; v<sub>7<\/sub>) &diams; v<sub>8<\/sub>) &diams; v<sub>9<\/sub>)\n<\/p>\n<p> What that means in terms our finger tree is that the value at the root of any<br \/>\nsubtree is a <em>measure<\/em> of its subtree &#8211; and that the grouping of the children<br \/>\nof that subtree doesn&#8217;t matter. You can structure a tree any way that you like: so long as you preserve the order of the leaves, it doesn&#8217;t affect the annotation value of its root; and the annotation value of the root of any subtree tells you a crucial bit of information<br \/>\nabout what&#8217;s in the subtree.<\/p>\n<p> And there&#8217;s the key: what does it tell you?<\/p>\n<p> When we go to search a finger tree for some value, we&#8217;re searching for a<br \/>\nvalue with a particular measure. What the monoid does is allow us to describe<br \/>\nthat measure <em>not just<\/em> in terms of the measure of individual nodes, but<br \/>\nin terms of <em>sets<\/em> of nodes. And the tree gives us a natural way of<br \/>\ndividing the list into a set of candidates, and not-candidates. Put the two<br \/>\ntogether, and you&#8217;ve got a structure that is custom designed for enabling binary<br \/>\nsearch (or, rather, tree based search; if you use a 2\/3 tree as the basis of<br \/>\nyour finger tree, you&#8217;ve got a 2\/3 tree search; if you use a balanced binary<br \/>\ntree, then you&#8217;ve got binary search.)<\/p>\n<p> Let&#8217;s look at a couple of examples. First, let&#8217;s pull out our array tree.<br \/>\nLook back at the example tree towards the top of the post. We&#8217;re going to look<br \/>\nfor the seventh element of the list. We start at the root: the measure of the<br \/>\nroot is<br \/>\n((1&diams;1&diams;1)&diams;(1&diams;1))&diams;((1&diams;1)&diams;(1&diams;1)) &#8211;<br \/>\nor (1&diams;1&diams;1&diams;1&diams;1&diams;1&diams;1&diams;1&diams;1) &#8211; which<br \/>\nis 9. <\/p>\n<p> This tells us that the entire tree has 9 leafs. Doesn&#8217;t matter how they&#8217;re<br \/>\narranged: every tree with 9 leaves will produce the same measure.<\/p>\n<p> We want to find the 7th value. But we don&#8217;t know where it is. So we take the<br \/>\nsequence of values represented by the tree, and split it, and then use the<br \/>\nmonoid measure of the two subtrees to compute the measures for the <em>sets<\/em><br \/>\nof values contained by their leafs. We split according to the tree: we could<br \/>\ntake either the left half, or the right half. To figure out which, we look at<br \/>\nthe measures. The combined measure of the nodes on the left is 5; the combined<br \/>\nmeasure on the right is 4. So we know that our target is on the right, and we&#8217;re<br \/>\nlooking for element 2 on the right. And again, we want to split the<br \/>\nremaining set of values, and only search the half that might contain<br \/>\nour target. We again look at subtrees, and find that our<br \/>\ntarget is on the left. We then have a subtree whose measure is 1&diams;1=2, so<br \/>\nthe target is on the right. And we&#8217;ve narrowed it to one node.<\/p>\n<p> If you look at that process &#8211; it&#8217;s <em>precisely<\/em> a binary search of the list.<\/p>\n<p> Now, let&#8217;s try looking at a priority queue. Here&#8217;s a binary tree priority<br \/>\nqueue, which contains a set of tasks A (pri 3), B (pri 9), C(pri 4), D(pri 3),<br \/>\nE(pri 1), F(pri 3), and G (pri 4). As a finger tree, that would look like the<br \/>\nfollowing:<\/p>\n<div><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/scientopia.org\/img-archive\/goodmath\/img_387.png?resize=490%2C223\" width=\"490\" height=\"223\" alt=\"finger-pqueue.png\" \/><\/div>\n<p> The monoid set for the priority queue is the set of possible priority<br \/>\nvalues; the operation is natural-number minimum. So for this queue, it&#8217;s<br \/>\n3&diams;9&diams;4&diams;3&diams;1&diams;3&diams;4 &#8211; or min(3, 9, 4, 3, 1, 3, 4) =<br \/>\n1.<\/p>\n<p> Now, we want to to split the queue into two subqueues, and only search the<br \/>\nsubqueue that contains the highest priority task. We do that using the tree:<br \/>\nwe look at the measure of the left subtree (which is annotated with the measure of the left subset), and the right subtree (which is annotated with the measure of the right subset). The left subset has measure 1, the right has measure 4. So the highest priority task is in the left subset (or subtree).<\/p>\n<p> Again, we want to divide the set of tasks into two subsets, and only search<br \/>\nthe subset that contains the highest priority task. The measure of the left<br \/>\nsubtree-subset is 3; the right is 1. So it&#8217;s in the right subtree. And so on,<br \/>\nuntil we wind up at E.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>For ages, I&#8217;ve been promising to write about finger trees. Finger trees are an incredibly elegant and simple structure for implementing sequence-based data structures. They&#8217;re primarily used in functional languages, but there&#8217;s nothing stopping an imperative-language programmer from using them as well. In functional programming languages, lists are incredibly common. They show up everywhere; they&#8217;re [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[83],"tags":[],"class_list":["post-775","post","type-post","status-publish","format-standard","hentry","category-data-structures"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p4lzZS-cv","jetpack_sharing_enabled":true,"jetpack_likes_enabled":true,"_links":{"self":[{"href":"http:\/\/www.goodmath.org\/blog\/wp-json\/wp\/v2\/posts\/775","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/www.goodmath.org\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.goodmath.org\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.goodmath.org\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/www.goodmath.org\/blog\/wp-json\/wp\/v2\/comments?post=775"}],"version-history":[{"count":0,"href":"http:\/\/www.goodmath.org\/blog\/wp-json\/wp\/v2\/posts\/775\/revisions"}],"wp:attachment":[{"href":"http:\/\/www.goodmath.org\/blog\/wp-json\/wp\/v2\/media?parent=775"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.goodmath.org\/blog\/wp-json\/wp\/v2\/categories?post=775"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.goodmath.org\/blog\/wp-json\/wp\/v2\/tags?post=775"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}