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).
class NestedSetTreePresenter
include Enumerable
def initialize(items, offset = 0, count = nil)
@items = items
@offset = offset
@count = count || @items.size
end
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
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
I chose not to yield a proxy, container or anything else to minimize the number of objects created.
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))