如何通过组合具有不同值的键来合并具有相同ID的两个哈希值

时间:2019-03-27 06:12:21

标签: ruby hash

我有一系列哈希,如下所示:

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的哈希。

2 个答案:

答案 0 :(得分:4)

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值的累加。

答案 1 :(得分:2)

@Amadan在回答中使用了Enumerable#group_bygroup_byEnumerable#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(也称为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的价值解决模块未被调用。

现在eupdate引导以生成其下一个值并将其传递给该块。将块变量分配给该值,然后执行块计算。

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