Search code examples
ruby-on-railsrubyalgorithmtreeancestry

Ancestry gem "flattened tree"


I've been hitting my head up against this one all day. Time for new eyes.

I have a tree-structured model using the ancestry gem. Works great and calling TreeNode.arrange returns a tidy little hash that is a nested tree. The problem is I'm looking for a "flattened tree" for lack of a better description. For example:

Node1
Node2
Node3
  Node4
  Node5
  Node6
    Node7
    Node8
Node9

As opposed to a more traditional

Node1
  Node2
   Node3...

So in other words I only want to "indent" my tree if there is a branch point (more than one child). I figured the best way to do this is a recursive function. I've tried several variants and I am just drawing a blank on this one :-\

def walk_until_fork(tree_hash,&blk)
   tree_hash.each do |node,children| 
    yield node.title
    if children.keys.length > 1
      #fork point
      children.each do |subnode,grandchilden|
        walk_until_fork(grandchilden,&blk)
        yield subnode.title       
      end
    else
      walk_until_fork(children,&blk)
    end
  end
end

The result of calling that test code is the fork points end up at the bottom of the output :-\

What I'd really like to see is a hash structure like that but the only keys that should have children is where a branching happened (one branch continues at that current level and each n branch after that forks).

I'm not sure if I'm being clear. I'll clarify with any questions if needed.


Solution

  • Deleted everything I and started over with a new approach. I'll refactor this later most likly but here was the basic idea.

    def walk_until_fork(tree_hash, root_node = nil)
      root_node ||= ActiveSupport::OrderedHash.new
      tree_hash.each do |node,children|
        more_than_one = false
        parent_node = root_node[node] = ActiveSupport::OrderedHash.new
        children.each do |k,v|
          fork_node = more_than_one ? parent_node : root_node
          walk_until_fork({k => v},fork_node)
          more_than_one = true
        end
      end
      root_node
    end
    

    Then I just call with:

    walk_until_fork(self.arrange)
    

    The code passes a hash reference around to the current root, changing it to a new hash only there is more then one child.