【问题标题】:Slick way to transform this data structure hash of hashes? [closed]转换这种哈希数据结构哈希的巧妙方法? [关闭]
【发布时间】:2014-04-06 22:14:49
【问题描述】:

我有以下 Ruby 数据结构:

data_struct = {
  item1 => { attr1: :word_a, attr2: :word_b, attr3: :word_a},
  item2 => { attr1: :word_b, attr2: :word_a },
  item3 => {                 attr2: :word_b, attr3: :word_c}
}

我想把它改成:

new_data_struct = {
  attr1 => { word_a: item1, word_b: item2 },
  attr2 => { word_b: [item1, item3], word_a: item2 },
  attr3 => { word_a: item1, word_c: item3 }
}

我的印象是我应该使用inject,也许还有fetch 和Hash.new() 默认值。

【问题讨论】:

  • item1、item2 和 item3 是什么?你的意思是让它们成为符号或字符串?另外,你能描述一下这个转变吗?
  • 如果子哈希的所有值都是数组,而不是单个元素和数组的混合,我认为您会发现使用转换后的哈希会更容易。
  • 处理数据结构转换最巧妙的方法是不要从数据结构开始。
  • 不知道为什么除了没有极其详细地描述转换之外,它为什么会因为不清楚而被关闭。我说'我有一个带有示例的数据结构 X,我需要带有示例转换的数据结构 Y,我如何转换它? attr1、word_a、item1 等的细节并不重要,可以是任何东西。斯沃夫兰先生的回答内容丰富,令人大开眼界。我很想知道更多关于他的思考过程来得出这个答案。当然,托马斯先生的评论对谈话没有任何帮助。其他 2 个答案也很有帮助 - 谢谢!
  • 并尝试添加到我的初始评论以使其更有帮助:虽然在许多语言中处理数据结构是处理数据的最简单方法,但在 Ruby 中,我会考虑将数据结构重构为值对象。通常这会使代码更容易阅读(= 更易于维护)

标签: ruby data-structures hash transform inject


【解决方案1】:
data_struct = {
  'item1' => { attr1: :word_a, attr2: :word_b, attr3: :word_a},
  'item2' => { attr1: :word_b, attr2: :word_a },
  'item3' => {                 attr2: :word_b, attr3: :word_c}
}


new_data_sturct = data_struct.inject({}) { |merged, (item, h)|
  h.each do |attr, word|
    h2 = merged[attr] ||= {}
    if h2[word]
      h2[word] = [h2[word]] unless h2[word].is_a? Array
      h2[word] << item
    else
      h2[word] = item
    end
  end
  merged
}

# => {:attr1=>{:word_a=>"item1", :word_b=>"item2"},
#     :attr2=>{:word_b=>["item1", "item3"], :word_a=>"item2"},
#     :attr3=>{:word_a=>"item1", :word_c=>"item3"}}

替代使用Enumerable#map、Enumerable#inject、Hash#merge!:

new_data_sturct = data_struct.map {|item, h|
  Hash[h.map { |attr, word| [attr, {word => item}] }]
}.inject { |h1, h2|
  h1.merge!(h2) { |_, old, new| 
    old.merge!(new) { |_, a, b|
      (a.is_a?(Array) ? a : [a]) << b
    }
  }
}

【讨论】:

    【解决方案2】:

    我会这样做:

    data_struct.each_with_object(Hash.new { |h,k| h[k]={} }) { |(k,v),h|
      v.each {|k1,v1| h[k1].merge!({v1=>k}) { |key,ov,_|
        (ov.is_a? Array) ? ov << k : [ov,k] } } }
    

    什么时候

    data_struct = {
      :item1 => { attr1: :word_a, attr2: :word_b, attr3: :word_a },
      :item2 => { attr1: :word_b, attr2: :word_a },
      :item3 => {                 attr2: :word_b, attr3: :word_c }
      }
    

    这会产生:

      {:attr1=>{:word_a=>:item1,           :word_b=>:item2},
       :attr2=>{:word_b=>[:item1, :item3], :word_a=>:item2},
       :attr3=>{:word_a=>:item1,           :word_c=>:item3}}
    

    说明

    考虑data_struct的第一个元素,由外块中的(k,v)表示:

    k => :item1
    v => { attr1: :word_a, attr2: :word_b, attr3: :word_a }
    

    内部块遍历v的元素,其中第一个是:

    k1 => :att1
    v1 => :word_a
    

    最初是h =&gt; {},

    h[:attr1].merge!({word_a: :item1})
      => {}.merge!({word_a: :item1})
      => {word_a: :item1} 
    

    h[:attr1] =&gt; {} 因为定义了散列,所以 {} 是添加键时的默认值。

    merge! 后面的块仅在两个具有相同 key 的哈希合并时有效,因此不适用于此操作。不过后来,当我们想merge!

    {word_b: :item3}
    

    与

    h[:attr2]=>{...,word_b: :item1,...}
    

    我们有

    {|:word_b,:item1,_| (:item1.is_a? Array) ? :item1<<:item3 : [:item1,:item3]}
      #=> [:item1, :item3]
    

    因为:item1 不是数组。 word_b 是否有另一个合并,但是,ov 将是一个数组,因此将执行 op &lt;&lt; ...。

    替代品

    表达式可以改为:

    data_struct.each_with_object({}) { |(k,v),h| v.each { |k1,v1|
      (h[k1] ||= {}).merge!({v1=>k}) { |key,ov,_|
        (ov.is_a? Array) ? ov << k : [ov,k] } } }
    

    我同意@theTinMan 的观点,即生成值数组的所有值(包括包含单个元素的值)可能会更有用。它还稍微简化了计算:

    data_struct.each_with_object(Hash.new {|h,k| h[k]={}}) { |(k,v),h|
      v.each { |k1,v1| h[k1].merge!({v1=>[k]}) { |key,ov,_| ov << k } } }
    
      #=> {:attr1=>{:word_a=>[:item1],         :word_b=>[:item2]},
      #    :attr2=>{:word_b=>[:item1, :item3], :word_a=>[:item2]},
      #    :attr3=>{:word_a=>[:item1],         :word_c=>[:item3]}}
    

    【讨论】:

    • 这部分很棒(h[k1] ||= {}).merge!({v1=&gt;k}).... +1 整体代码,这里有很多东西要学习
    【解决方案3】:

    使用*splat 运算符可以使其更具可读性:

    data_struct.each_with_object({}) do |(item, attrs), new_data_struct|
      attrs.each do |attr, word|
        (new_data_struct[attr] ||= {}).merge!(word => item) do |_, old_items, new_item| 
          [*old_items, new_item]
        end
      end
    end
    => {:attr1=>{:word_a=>:item1, :word_b=>:item2}, 
        :attr2=>{:word_b=>[:item1, :item3], :word_a=>:item2}, 
        :attr3=>{:word_a=>:item1, :word_c=>:item3}}
    

    【讨论】:

      猜你喜欢
      • 2021-07-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-11
      • 1970-01-01
      • 2019-10-27
      • 2019-05-23
      • 2015-10-06
      相关资源
      最近更新 更多