This keeps popping up over and over again. About 2 years ago I mentioned solution on how to build tree out of nested set, but the solution was lost in google groups. I'm going to publish it here.
So, nested set: every item has LEFT and RIGHT numbers. LEFT number is less than any LEFT or RIGHT number of any descendant item. RIGHT number is greater than any LEFT or RIGHT number of any descendant item. To get a subtree, select all items with LEFT greater than given LEFT and RIGHT less than given RIGHT number. You get the flat array of items of that tree structure. Next thing is to convert it to tree which means for any given item you need the ability to iterate over all of it's children.
First thing you need to do is to order items by LEFT number, than by RIGHT number.
A small sidenote: we will assume that LEFT and RIGHT numbers are compact so that if your root item has LEFT=1 and RIGHT=10 then for each number X in interval [1, 10] there is item for which X is the value of LEFT or RIGHT. This allows easy calculation of how many descendants particular item has.
So, say we have an flat array and we need to know how many items in array you need to skip to get to given item's next sibling. And that number is (RIGHT-LEFT) (minus one if you do not count the item itself).
I chose not to yield a proxy, container or anything else to minimize the number of objects created.
# Class that allows performance wise traversing a list of items # that form a part of nested set structure. class NestedSetTreePresenter include Enumerable # Constructs presenter with a items that form part of nested set, # offset of first item on desired level and count - number of items # on current level. # Items should be ordered by their left bound values. def initialize(items, offset = 0, count = nil) @items = items @offset = offset @count = count || @items.size end # For each item of the same level in nested set as the first item # calls block passing corresponding item. def each i = @offset bound = @offset + @count bound = @items.size if bound > @items.size while i<bound item = @items[i] descendants_count = (item.rgt-item.lft)/2 yield item i += 1 + descendants_count end end # For each item of the same level in nested set as the first item # calls block passing corresponding item and collection, that # represent all immediate children of that item. Collection is also # an instance of this class. def each_with_children i = @offset bound = @offset + @count bound = @items.size if bound > @items.size while i<bound item = @items[i] descendants_count = (item.rgt-item.lft)/2 yield item, NestedSetTreePresenter.new(@items, i+1, descendants_count) i += 1 + descendants_count end end end
Here is how to use it:
def print_tree(items, level = 0) items.each_with_children do |item, children| puts " "*level + item.name print_tree(children, level+1) end end print_tree(NestedSetTreePresenter.new(items))