遍历哈希数组并合并

Iterate through array of hashes and merge

我有一个像这样的哈希数组:

[{:a=>"a", :b=>"b", :c=>"c", :d=>"d"}, 
 {:a=>"a", :b=>nil, :c=>"notc", :d=>"d"}]

我想遍历散列并在特定键相同的地方合并它们,例如 :a。如果下一个键元素相同 - 忽略它,但如果键不同,则创建一个数组。结果将如下所示:

{:a=>"a", :b=>"b", :c=>["c","notc"], :d=>"d"}

我想我已经对哈希数组进行了 for 循环,然后使用 merge! 方法,但不确定从哪里开始

假设 hs = [{:a=>"a", :b=>"b", :c=>"c", :d=>"d"}, {:a=>"a", :b=>nil, :c=>"notc", :d=>"d"}],你可以用一个(不可否认,非常密集的)单线来做到这一点:

Hash[hs.map { |h| h.map { |k,v| [k, v] } }.sort_by(&:first).reduce { |left,right| left.zip(right) }.map { |a| [a.first.first, a.map(&:last).compact.uniq] }]

解开这里发生的事情:

首先我们使用map将哈希数组转换成对数组的数组,然后使用sort_by对数组进行排序,使得所有的键都是'lined up'.

[{:a=>"a", :b=>"b", :c=>"c", :d=>"d"}, {:a=>"a", :b=>nil, :c=>"notc", :d=>"d"}] 变为

[[[:a, "a"], [:b, "b"], [:c, "c"], [:d, "d"]], [[:a, "a"], [:b, nil], [:c, "notc"], [:d, "d"]]]

就是这部分:

 hs.map { |h| h.map { |k,v| [k, v] } }.sort_by(&:first).

然后,我们使用 reduce 并将所有数组压缩在一起,zip 只需要两个数组 [1,2,3][:a,:b,:c] 并输出如下数组:[[1,:a],[2,:b],[3,:c]]

    reduce { |left,right| left.zip(right) }.

至此我们已经将所有数据通过key组合在一起,需要对key的所有副本进行去重,我们可以去除nils并对值进行uniq:

    map { |a| [a.first.first, a.map(&:last).compact.uniq] }]

这是一个来自窥探会话的示例:

[31] pry(main)> Hash[hs.map { |h| h.map { |k,v| [k, v] } }.sort_by(&:first).reduce { |left,right| left.zip(right) }.map { |a| [a.first.first, a.map(&:last).compact.uniq] }]
=> {:a=>["a"], :b=>["b"], :c=>["c", "notc"], :d=>["d"]}

我也会使用 Hash#merge!(又名 update),像这样(让 a 表示哈希数组的名称):

a.each_with_object({}) do |g,h|
  h.update(g) do |_,o,n|
    case o
    when Array
      o.include?(n) ? o : o + [n]
    else
      o.eql?(n) ? o : [o,n] 
    end
  end
end
  #=> {:a=>"a", :b=>["b", nil], :c=>["c", "notc"], :d=>"d"} 

o为数组时,如果不想合并nil的值,将下面这行改为:

(o.include?(n) || n.nil?) ? o : o + [n]

步骤:

a = [{:a=>"a",  :b=>"b", :c=>"c",    :d=>"d"}, 
     {:a=>"a",  :b=>nil, :c=>"notc", :d=>"d"},
     {:a=>"aa", :b=>"b", :c=>"cc",   :d=>"d"},
]

enum = a.each_with_object({})
  #-> #<Enumerator: [{:a=>"a",  :b=>"b", :c=>"c",    :d=>"d"},
  #                  {:a=>"a",  :b=>nil, :c=>"notc", :d=>"d"},
  #                  {:a=>"aa", :b=>"b", :c=>"cc",   :d=>"d"}]
  #    :each_with_object({})> 

我们可以通过将枚举数转换为数组来查看枚举数(将传递到块中)的值:

enum.to_a
  #=> [[{:a=>"a",  :b=>"b", :c=>"c",    :d=>"d"}, {}],
  #    [{:a=>"a",  :b=>nil, :c=>"notc", :d=>"d"}, {}],
  #    [{:a=>"aa", :b=>"b", :c=>"cc",   :d=>"d"}, {}]] 

传入第一个值并将其分配给块变量:

g,h = enum.next
  #=> [{:a=>"a", :b=>"b", :c=>"c", :d=>"d"}, {}] 
g #=> {:a=>"a", :b=>"b", :c=>"c", :d=>"d"} 
h #=> {} 

update 的块用于确定要合并的两个散列中存在的键的值。由于 h 目前为空 ({}),因此不用于此合并:

h.update(g)
  #=> {:a=>"a", :b=>"b", :c=>"c", :d=>"d"} 

返回 h 的新值。

现在将 enum 的第二个值传递到块中:

g,h = enum.next
  #=> [{:a=>"a", :b=>nil, :c=>"notc", :d=>"d"},
  #    {:a=>"a", :b=>"b", :c=>"c",    :d=>"d"}] 
g #=>  {:a=>"a", :b=>nil, :c=>"notc", :d=>"d"} 
h #=>  {:a=>"a", :b=>"b", :c=>"c",    :d=>"d"} 

并执行:

h.update(g)

当要合并 g 中的 :a=>"a" 时,update 发现 h 包含相同的键 :a。因此,它遵从块来确定合并哈希中 :a 的值。它将以下值传递给块:

k = :a
o = "a"
n = "a"

其中 k 是键,o(对于 "old")是 hnk 的值( "new") 是 gk 的值。 (我们没有在块中使用 k,所以我将块变量命名为 _ 来表示。)在 case 语句中,o 不是数组,所以:

o.eql?(n) ? o : [o,n]
  #=> "a".eql?("a") ? "a" : ["a","a"] 
  #=> "a"

返回到 update 作为 :a 的值。即值不变。

当键为:b时:

k = :b
o = "b"
n = nil

同样,o 不是一个数组,所以我们再次执行:

o.eql?(n) ? o : [o,n] 
  #=> ["b", nil] 

但是这次返回的是一个数组。合并 enum 的第二个元素的其余计算过程类似。合并后:

h #=> {:a=>"a", :b=>["b", nil], :c=>["c", "notc"], :d=>"d"} 

合并enum的第三个元素中的:c=>"cc"时,将以下值传递给update的块:

_ :c
o = ["c", "notc"]
n = "cc"

因为o是一个数组,我们执行case语句的下面一行:

o.include?(n) ? o : o + [n]
  #=> ["c", "notc", "cc"]

并且 :c 的值被赋予该值。其余计算同理。

我会这样做:

array = [{ :a=>"a", :b=>"b", :c=>"c",    :d=>"d" }, 
         { :a=>"a", :b=>nil, :c=>"notc", :d=>"d" }]

result = array.reduce({}) do |memo, hash|
  memo.merge(hash) do |_, left, right|
    combined = Array(left).push(right).uniq.compact
    combined.size > 1 ? combined : combined.first
  end
end

puts result
#=> { :a=>"a", :b=>"b", :c=>["c", "notc"], :d=>"d" }

Array(left) 将确保 one hash 的值是一个数组。 push(right) 将另一个散列的值添加到该数组中。 uniq.compact 删除 nil 个值和重复值。

如果数组只包含一个元素,combined.size > 1 ? combined : combined.first 行 returns 只是元素。