ruby如何生成树形结构表单数组?

时间:2013-09-16 13:30:06

标签: ruby arrays performance algorithm tree

我有一个数组,其中包含像这样的项目列表

arr = [
  {:id=>1,  :title=>"A",      :parent_id=>nil}, 
  {:id=>2,  :title=>"B",      :parent_id=>nil},
  {:id=>3,  :title=>"A1",     :parent_id=>1}, 
  {:id=>4,  :title=>"A2",     :parent_id=>1},
  {:id=>5,  :title=>"A11",    :parent_id=>3}, 
  {:id=>6,  :title=>"12",     :parent_id=>3},
  {:id=>7,  :title=>"A2=121", :parent_id=>6}, 
  {:id=>8,  :title=>"A21",    :parent_id=>4},
  {:id=>9,  :title=>"B11",    :parent_id=>2}, 
  {:id=>10, :title=>"B12",    :parent_id=>2},
   ...
]

如果parent_idnil,那么它应该是父节点,如果parent_id不是nil那么它应该属于特定的父节点。

根据idparent_id,我想提供这样的回复:

-A
  -A1
    -A11
    -A12
      -A123
  -A2
    -A21
-B
  -B1
    -B11
    -B12

我怎样才能生成上面提到的回复?

由于

3 个答案:

答案 0 :(得分:7)

你可以使用像Closure_tree这样的宝石:

  

hash_tree提供了一种将子树渲染为有序的方法   嵌套哈希:

Tag.hash_tree
#=> {a => {b => {c1 => {d1 => {}}, c2 => {d2 => {}}}, b2 => {}}}

Ancestry

  

Ancestry可以将整个子树排列成嵌套的哈希,以方便使用   从数据库中检索后导航。 TreeNode.arrange可以   例如return:

{ #<TreeNode id: 100018, name: "Stinky", ancestry: nil>
  => { #<TreeNode id: 100019, name: "Crunchy", ancestry: "100018">
    => { #<TreeNode id: 100020, name: "Squeeky", ancestry: "100018/100019">
      => {}
    }
  }
}

有关其他宝石,请参阅https://www.ruby-toolbox.com/categories/Active_Record_Nesting

更新

如果你必须在内存中这样做,这样的事情应该有效:

nested_hash = Hash[arr.map{|e| [e[:id], e.merge(children: [])]}]
nested_hash.each do |id, item|
  parent = nested_hash[item[:parent_id]]
  parent[:children] << item if parent
end
tree = nested_hash.select { |id, item| item[:parent_id].nil? }.values

require 'pp'
pp tree

输出

[{:id=>1,
  :title=>"A",
  :parent_id=>nil,
  :children=>
   [{:id=>3,
     :title=>"A1",
     :parent_id=>1,
     :children=>
      [{:id=>5, :title=>"A11", :parent_id=>3, :children=>[]},
       {:id=>6,
        :title=>"12",
        :parent_id=>3,
        :children=>
         [{:id=>7, :title=>"A2=121", :parent_id=>6, :children=>[]}]}]},
    {:id=>4,
     :title=>"A2",
     :parent_id=>1,
     :children=>[{:id=>8, :title=>"A21", :parent_id=>4, :children=>[]}]}]},
 {:id=>2,
  :title=>"B",
  :parent_id=>nil,
  :children=>
   [{:id=>9, :title=>"B11", :parent_id=>2, :children=>[]},
    {:id=>10, :title=>"B12", :parent_id=>2, :children=>[]}]}]

答案 1 :(得分:4)

一个例子:

#!/usr/bin/env ruby

root = {:id => 0, :title => '', :parent_id => nil}

arr = arr = [
  {:id=>1,  :title=>"A",      :parent_id=>nil}, 
  {:id=>2,  :title=>"B",      :parent_id=>nil},
  {:id=>3,  :title=>"A1",     :parent_id=>1}, 
  {:id=>4,  :title=>"A2",     :parent_id=>1},
  {:id=>5,  :title=>"A11",    :parent_id=>3}, 
  {:id=>6,  :title=>"12",     :parent_id=>3},
  {:id=>7,  :title=>"A2=121", :parent_id=>6}, 
  {:id=>8,  :title=>"A21",    :parent_id=>4},
  {:id=>9,  :title=>"B11",    :parent_id=>2}, 
  {:id=>10, :title=>"B12",    :parent_id=>2},
]

map = {}

arr.each do |e|
  map[e[:id]] = e
end

@@tree = {}

arr.each do |e|
  pid = e[:parent_id]
  if pid == nil || !map.has_key?(pid)
    (@@tree[root] ||= []) << e
  else
    (@@tree[map[pid]] ||= []) << e
  end
end

def print_tree(item, level)
  items = @@tree[item]
  unless items == nil
    indent = level > 0 ? sprintf("%#{level * 2}s", " ") : ""
    items.each do |e|
      puts "#{indent}-#{e[:title]}"
      print_tree(e, level + 1)
    end
  end
end

print_tree(root, 0)

输出:

-A
  -A1
    -A11
    -12
      -A2=121
  -A2
    -A21
-B
  -B11
  -B12

答案 2 :(得分:2)

并不是要取代经过验证的宝石,但根据您的需要,您可以使用以下简单的东西:

groups = arr.group_by{ |x| x[:parent_id] }
groups.default = []

build_tree = 
  lambda do |parent| 
    [parent[:title], groups[parent[:id]].map(&build_tree)]
    # or
    # { parent[:title] => groups[parent[:id]].map(&build_tree) }
  end

p build_tree[:id => nil][1] # :id => nil is not required, empty hash will work too
# => [["A", [["A1", [["A11", []], ["A12", [["A122", []]]]]], ["A2", [["A21", []]]]]], ["B", [["B11", []], ["B12", []]]]]