【问题标题】:Adjacency data structure to nested hash嵌套哈希的邻接数据结构
【发布时间】:2013-12-28 14:45:03
【问题描述】:

我在 rails 中有以下模型:

class Model < ActiveRecord::Base
  # id — integer
  # name — string
  # model_id — integer

  belongs_to :parent, class_name: 'Model', foreign_key: 'model_id'
  has_many :children, class_name: 'Model', foreign_key: 'model_id'
end

我正在使用邻接结构,它可以有无限的深度。我在使用递归选择的 Postgres 数据库中。

获取对象的嵌套散列最明智的方法是什么?我尝试选择 Model 的实例并对其进行排序,但无法将其带到任何可用的结果。

假设我的数据库中保存了四个 Model 实例:Model_1Model_2Model_3Model_4Model_3Model_2 的孩子,Model_4Model_3 的孩子。

这是我想要实现的输出(Model 实例的嵌套哈希):

{
  #<Model_1...> => {},
  #<Model_2...> => {
    #<Model_3...> => {
      #<Model_4...> => {}
    }
  }
}

有什么想法吗?

更新: 树已经恢复——可以是 CollectionProxy、Relation 或任何其他数组式数据结构。我不想将该树排序为嵌套散列的散列。

【问题讨论】:

  • 省去一些麻烦并使用祖先宝石。
  • MPP 在这种情况下不是一个选项——我需要无限深度和廉价的读/写(所以闭包表和嵌套树也不是一个选项)。
  • 你想做什么?在尽可能少的查询中恢复元素树?每次加载父项时,您是要使用所有子项,还是使用延迟加载更好?
  • 可以说树已经恢复了——可以是 CollectionProxy、Relation 或任何其他数组式数据结构。我不想将该树排序为嵌套哈希的哈希。
  • 你使用的是什么数据库?

标签: ruby-on-rails ruby nested-lists adjacency-list


【解决方案1】:

我将其命名为 parent_id 字段。

  belongs_to :parent, class_name: "Model"
  has_many :children, class_name: "Model", foreign_key: "parent_id"

当你有哈希时,你会使用sortsort_by

http://www.ruby-doc.org/core-2.1.0/Enumerable.html#method-i-sort_by

def sort(hash)
  hash.sort { |m1, m2| m1.id <=> m2.id }
  sort(hash.children)
end

【讨论】:

  • 那么树(被展平)被恢复为类似数组(或类似枚举)的数据结构 - 即[#&lt;Model_1...&gt;, #&lt;Model_2...&gt;]。我不能开箱即用地调用children 方法;为此,我首先需要将其分类为嵌套结构;这就是我的首要目标。此外,排序只会对数组/枚举中的对象进行排序,但我需要嵌套它们。
【解决方案2】:

首先,在 Model 类中定义 ff 方法:

def to_hash
  if children.empty?
    {self => {}}
  else
    {self => children.inject({}) {|hash, model| hash.merge(model.to_hash) } }
  end
end

然后做 ff 得到你想要的输出:

top_level_models = #code that queries top-level models while eager-loading all nested children
hash_of_nested_models = top_level_models.inject({}) {|hash, ancestor| hash.merge(ancestor.to_hash) }

您传递给包含的哈希参数应该涵盖您的嵌套深度。上面传递给includes 的参数将是深度为3 个后代的孩子的嵌套。只要您在 where 查询中包含所有嵌套子项,生成哈希就不会再执行任何数据库查询。

希望有帮助!

【讨论】:

  • 感谢您的回复。它可以工作,但这会产生 N+1 个查询;再加上树的深度可以无限大,它很快就会成为瓶颈。
  • 您没有将其指定为要求,所以只是想出了一些可行的方法。也许做一个includes
  • Rails 不支持递归查询;这样includes 将产生 M+1 个查询,其中 M 是树的多个级别。我认为将其保留在一个查询中的唯一方法是首先选择整个树which is not a problem in my case,然后才开始将其排序为嵌套哈希。
  • 我刚刚对上面的代码进行了更改,这样哈希构建代码就不会再进行任何数据库查询了。
  • 感谢您的回答,但不幸的是,正如问题本身所述,树可以有无限深度;所以在这种情况下 3 个后代不会这样做。
【解决方案3】:

在没有 N+1 个查询的情况下让 AR 做到这一点会很困难。将不得不编写一些与内存中的数据一起使用的东西。

你必须编写一个看起来像这样的自定义函数:

 def to_hash
  root_hash = {}
  # Load all the model into memory and into a hash to allow faster access when we have the id
  models = Hash[Model.all.collect {|m| [m.id, m]}]
  # The resultant hash for each child
  models_with_hash = Hash[map.values.collect {|m| [m.id, {}]} ]
  # Stitch them together
  models.each do |id, m| 
    if m.model_id.nil?
      root_hash.merge! m => models_with_hash[m.id]
    else
      # should name model_id to parent_id
      models_with_hash[m.model_id].merge! m => models_with_hash[m.id]
    end  
  end
  root_hash
end

【讨论】:

    猜你喜欢
    • 2011-09-08
    • 2016-06-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-30
    • 1970-01-01
    • 2010-12-07
    • 1970-01-01
    相关资源
    最近更新 更多