Ruby通过子父关系排序哈希数组

时间:2015-02-20 08:29:43

标签: ruby iteration

所以我们有哈希数组

array = [
  {id: 1, parent_id: 0},
  {id: 2, parent_id: 1},
  {id: 3, parent_id: 0},
  {id: 4, parent_id: 2}
]
target_array = []

将该数组映射/排序为以下结果的最有效和红宝石方法是什么:

target_array = [
  {id:1,children:
    [{id: 2, children: [
      {id:4, children:[]}]}]},
  {id: 3, children:[]}  
]

p.s.我能做的最多就是为每个项目迭代整个事物,并从已映射到target_array的数组哈希中排除。

4 个答案:

答案 0 :(得分:3)

你可以通过递归来解决这个问题:

@array = [
  {id: 1, parent_id: 0},
  {id: 2, parent_id: 1},
  {id: 3, parent_id: 0},
  {id: 4, parent_id: 2}
]


def build_hierarchy target_array, n
    @array.select { |h| h[:parent_id] == n }.each do |h|
      target_array << {id: h[:id], children: build_hierarchy([], h[:id])}
    end
    target_array
end


build_hierarchy [], 0

输出:

=> [{"id"=>1, "children"=>[{"id"=>2, "children"=>[{"id"=>4, "children"=>[]}]}]}, {"id"=>3, "children"=>[]}] 

这个红宝石小提琴http://rubyfiddle.com/riddles/9b643

中的实例

答案 1 :(得分:2)

我会使用递归,但以下内容很容易转换为非递归方法。

首先构建一个哈希,将父母与子女联系起来(p2c)。为此,请使用Hash#update(aka merge!)的形式,该形式使用块来确定合并的两个哈希中存在的键的值:

@p2c = array.each_with_object({}) { |g,h|
  h.update(g[:parent_id]=>[g[:id]]) { |_,ov,nv| ov+nv } }
  #=> {0=>[1, 3], 1=>[2], 2=>[4]} 

还有许多其他方法可以构建此哈希。这是另一个:

@p2c = Hash[array.group_by { |h| h[:parent_id] }
                 .map { |k,v| [k, v.map { |g| g[:id] }] }]

现在构造一个递归方法,其单个参数是父类:

def family_tree(p=0)
  return [{ id: p, children: [] }] unless @p2c.key?(p)
  @p2c[p].each_with_object([]) { |c,a|
    a << { id:c, children: family_tree(c) } }
end

我们获得:

family_tree
  #=> [ { :id=>1, :children=>
  #               [
  #                { :id=>2, :children=>
  #                          [
  #                            { :id=>4, :children=>[] }
  #                          ]
  #                }
  #              ]
  #    },
  #    { :id=>3, :children=>[] }
  #  ] 

最初构造哈希@p2c应该会非常有效。

答案 2 :(得分:1)

这是我尝试使用哈希

的方法
array = [
  {id: 1, parent_id: 0},
  {id: 2, parent_id: 1},
  {id: 3, parent_id: 0},
  {id: 4, parent_id: 2}
]

  target_hash = Hash.new { |h,k| h[k] = { id: nil, children: [ ] } }

  array.each do |n|
      id, parent_id = n.values_at(:id, :parent_id)
      target_hash[id][:id] = n[:id]
      target_hash[parent_id][:children].push(target_hash[id])  
  end
 puts target_hash[0]

<强>输出:

{:id=>nil, :children=>[{:id=>1, :children=>[{:id=>2, :children=>[{:id=>4, :children=>[]}]}]}, {:id=>3, :children=>[]}]}

答案 3 :(得分:1)

我认为最好的一个最多会有O(nlog(n))时间复杂度。我给出了我的非哈希值:

array = [
  {id: 1, parent_id: 0},
  {id: 2, parent_id: 1},
  {id: 3, parent_id: 0},
  {id: 4, parent_id: 2}
]

# This takes O(nlog(n)).
array.sort! do |a, b|
    k = (b[:parent_id] <=> b[:parent_id])
    k == 0 ? b[:id] <=> a[:id] : k
end

# This takes O(n)
target_array = array.map do |node|
    { id: node[:id], children: [] }
end

# This takes O(nlog(n))
target_array.each_with_index do |node, index|
    parent = target_array[index + 1...target_array.size].bsearch do |target_node|
        target_node[:id] == array[index][:parent_id]
    end
    if parent
        parent[:children] << node
        target_array[index] = nil
    end
end

# O(n)
target_array.reverse.compact
# =>
# [{:id => 1, :children =>[{:id=>2,:children=> [ {:id=>4, 
# :children=>[]}]}]},
# {:id=>3, :children=>[]} ] 

因此我一般使用O(nlog(n))。

顺便说一句,当我简单地测试现有的解决方案时,我发现Gagan Gami的效率最高(略高于我的),我相信它也是O(nlog(n)),虽然不明显。但目前接受的解决方案需要O(n ^ 2)时间。