我有以下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()
默认设置。
答案 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#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 :(得分: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}}