views:

293

answers:

2

I have a tree structure in a .csv file (nodes are of type text), and after reading the csv i want to store the data in a ruby object. I went through a few tree plugins and i think nested_set would serve the purpose for me. However, i am facing problem with fixing a format of csv file, so that i can read it and convert into tree object. Is there any direct way of converting a csv file or 2 dimensional array, into a tree data structure??

+2  A: 

After you clarified that you don't need to store this tree in database, I suggest to throw away NestedSets (they're to store nested sets of objects on RDBMS, which you don't need). What you need i simple tree

class Node
  attr_accessor :parent, :children, :text

  def initialize(text)
    @text = text
    @children = []
  end
end

As I have right to choose format of CSV file, then I suggest sth like this:

id,parent,text
1,,"1"
2,1,"1.1"
3,1,"1.2"
3,2,"1.1.1"

Tree root is first row, without parent, and there is always order that parent is declared before its children. That way you can build tree

def build_tree(rows)
  nodes = {}
  rows.each do |row|
    node = Node.new(row[:text])
    nodes[row[:id]] = node

    node.parent = nodes[row[:parent]]
    nodes[row[:parent]].children << node if row[:parent]
  end

  nodes.values.find {|node| node.parent.nil? }
end

root = build_tree(rows)
root.text #=> "1"
root.children.map(&:text) #=> ["1.1", "1.2"]
root.children[0].children.map(&:text) #=> ["1.1.1"]

If you need to get all texts from subnodes, then you need to use more tricks

def get_nodes(tree_node)
  [ tree_node, tree_node.children.map{|node| get_nodes(node)} ].flatten
end

get_nodes(root).map(&:text) #=> ["1", "1.1", "1.1.1", "1.2"]
MBO
I dont want to persist the object in database. It will be saved in the session, and destroyed after session expires.
Nitin Garg
Hey dude, you stole my answer... ;)
Mladen Jablanović
thanks a lot for helping me out...
Nitin Garg
+1 for improving already good idea... ;) One remark, though: your function expects nodes in top-down order, i.e. if you try to add nodes in random order (add child first, and later add parent), it won't work.
Mladen Jablanović
@Mladen Jablanović That's right. I could choose format of CSV file, so I chosen easier approach - nodes in top-down order. Version for randomly ordered rows is in first version of answer
MBO
The get_nodes method and usage just saved my life. Call it "recursive flatten" at least in my book. I have a generic object tree hierarchy and you allowed me to search/flatten by attribute, through many recursive levels. Thank you.
milkfilk
+1  A: 

Seems that you don't need to use ORM at all. Why don't you make your tree logic yourself, with a dynamic language such as Ruby, it's pretty easy:

require 'set'

# expects an array of [parent, child] pairs, returns the root element of a tree
def make_tree a
  tree = {}

  a.each do |p, c|
    tree[p] ||= {:value => p}
    tree[p][:children] ||= Set.new
    tree[c] ||= {:value => c}
    tree[c][:parent] = tree[p]
    tree[p][:children] << tree[c]
  end

  tree.values.find{|e| e[:parent].nil?}
end

root = make_tree [[1,2],[3,4],[1,3],[4,5]]

puts root.inspect
puts root[:value]

Or, if you want it more OO, you can make TreeNode class instead of Hash above.

Oh, and if you need to access particular tree nodes directly by their key (in this case the integer value itself), change the method to return tree hash instead of just the root element.

Mladen Jablanović
I wanted a tree data structure, where each node has a name, id and value. I decided to use nested_net because the value field of nodes of an entire subtree changes on run time, and nested tree provides this functionality. I also thought about designing a tree myself but thought the rails plugins might give better performance. What do you suggest??
Nitin Garg
I haven't worked with nested_set, so I can't really tell you if you could or should use it. If you want to adapt my answer to your needs, 1) either add name and id keys to the node's hash when creating or go with Node class that MBO suggested, 2) create method to traverse a subtree for given node and execute a provided block, that will enable you to e.g. set value for each subtree node.
Mladen Jablanović
thanks a lot for your help :)
Nitin Garg
+1 for same idea as mine :-)
MBO