光滑的方式来改变哈希的这个数据结构哈希?

时间:2014-03-05 03:23:16

标签: ruby data-structures hash transform inject

我有以下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()默认设置。

3 个答案:

答案 0 :(得分: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 => {}

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

h[:attr1] => {}因为定义了哈希,所以{}是添加密钥时的默认值。

merge!之后的块仅在合并具有相同键的两个哈希时生效,因此在此操作中不适用。但是,当我们希望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 << ...

<强>替代

表达式可以写成:

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]}}

答案 1 :(得分: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#mapEnumerable#injectHash#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 :(得分:1)

使用*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}}