我有一系列哈希如下:
hashes = [
{id: 1, value: 'something', source: 'a'},
{id: 1, value: 'something', source: 'b'},
{id: 2, value: 'something', source: 'a'},
{id: 3, value: 'something', source: 'c'}
]
我需要这个:
new_hashes = [
{id: 1, value: 'something', source: ['a', 'b']},
{id: 2, value: 'something', source: ['a']},
{id: 3, value: 'something', source: ['c']}
]
我该怎么做呢?我尝试过以下方法:
merge_array = -> x,y { x.merge(y){|key, old, new| Array(old).push(new)} }
hashes.reduce &merge_array
但这将所有东西合并在一起。我不想合并具有不同ID的哈希。
hashes.
group_by { |e| [e[:id], e[:value]] }.
map { |_, g|
g.first.clone.
tap { |t|
t[:source] = g.reduce([]) { |a, e| a << e[:source] }
}
}
首先按应该相同的部分对哈希进行分组。我们不再关心钥匙了;但是每个组本身都会映射到与该组的第一个元素非常相似的东西。克隆它,使原来的hashes
元素不会发生变异;然后将其:source
替换为所有组元素':source
值的积累。
@Amadan在他的回答中使用了Enumerable#group_by。 group_by
和Enumerable#update
(又名merge!
)是可以互换的,当使用一个时,另一个可以
平时
改为使用。我将展示如何在这里使用update
。
hashes.each_with_object({}) do |g,h|
h.update(g[:id] => g.merge(source: [g[:source]])) do |_,oh,nh|
oh.merge(source: oh[:source] + nh[:source])
end
end.values
#=> [{:id=>1, :value=>"something", :source=>["a", "b"]},
# {:id=>2, :value=>"something", :source=>["a"]},
# {:id=>3, :value=>"something", :source=>["c"]}]
首先,请注意h.update(k=>v)
是h.update({ k=>v })
的简写。这使用了Hash#update(aka merge!
)的形式,它使用一个块来确定合并的两个哈希中存在的键的值。该块有三个块变量,公共密钥(_
),正在更新的哈希值(oh
,“o”代表“old”,“h”因为值是哈希值)和哈希值被合并(nh
,“n”表示“新”)。
步骤如下。
e = hashes.each_with_object({})
#=> #<Enumerator: [
# {:id=>1, :value=>"something", :source=>"a"},
# {:id=>1, :value=>"something", :source=>"b"},
# {:id=>2, :value=>"something", :source=>"a"},
# {:id=>3, :value=>"something", :source=>"c"}
# ]:each_with_object({})>
生成此枚举数的第一个元素,传递给块并分配给块变量。
g,h = e.next
#=> [{:id=>1, :value=>"something", :source=>"a"}, {}]
g #=> {:id=>1, :value=>"something", :source=>"a"}
h #=> {}
然后执行块计算。
h.update(g[:id] => g.merge(source: [g[:source]]))
#=> h.update(1 => g.merge(source: ["a"]))
#=> h.update(1 =>{:id=>1, :value=>"something", :source=>["b"]})
#=> {1=>{:id=>1, :value=>"something", :source=>["b"]}}
在执行此合并之前,h
为空,这意味着合并的两个哈希没有公共密钥。因此没有调用update
的价值分辨率块。
现在e
由update
执导以生成其下一个值并将其传递给该块。块变量被分配给该值并执行块计算。
g,h = e.next
#=> [{:id=>1, :value=>"something", :source=>"b"},
# {1=>{:id=>1, :value=>"something", :source=>["a"]}}]
g #=> {:id=>1, :value=>"something", :source=>"b"}
h #=> {1=>{:id=>1, :value=>"something", :source=>["a"]}}
请注意,h
已更新。现在计算:
h.update(g[:id] => g.merge(source: [g[:source]])) do |_,oh,nh|
oh.merge(source: oh[:source] + nh[:source])
end
#=> {1=>{:id=>1, :value=>"something", :source=>["a", "b"]}}
如
g[:id]
#=> 1
和
g.merge(source: [g[:source]])
#=> g.merge(source: ["b"])
#=> {:id=>1, :value=>"something", :source=>["b"]}
上面的表达式减少到
h.update(1 => {:id=>1, :value=>"something", :source=>["b"]}) do |_,oh,nh|
oh.merge(source: oh[:source] + nh[:source])
end
由于两个哈希合并都有一个1
的公共密钥,因此调用该块来确定合并哈希中1
的值:
_ = 1
oh = h[1]
#=> {:id=>1, :value=>"something", :source=>["a"]}
nh = g.merge(source: [g[:source]])
#=> g.merge(source: ["b"])
#=> {:id=>1, :value=>"something", :source=>["b"]}
我使用了下划线(一个有效的局部变量名称)来表示公共密钥,以向读取器发出信号,表明它未在块计算中使用。块计算如下。
oh.merge(source: oh[:source] + nh[:source])
#=> oh.merge(source: ["a", "b"])
#=> {:id=>1, :value=>"something", :source=>["a", "b"]}
e
生成的其余元素的计算类似。我们因此得到:
f = hashes.each_with_object({}) do |g,h|
h.update(g[:id] => g.merge(source: [g[:source]])) do |_,oh,nh|
oh.merge(source: oh[:source] + nh[:source])
end
end
#=> {1=>{:id=>1, :value=>"something", :source=>["a", "b"]},
# 2=>{:id=>2, :value=>"something", :source=>["a"]},
# 3=>{:id=>3, :value=>"something", :source=>["c"]}}
最后一步是返回f.values
。