迭代哈希数组并合并

时间:2015-04-06 22:39:50

标签: ruby arrays hashmap

我有一系列像这样的哈希:

[{: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!方法,但不知道从哪里开始

3 个答案:

答案 0 :(得分:4)

我也会使用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)

要合并来自:a=>"a"的{​​{1}},g会看到update包含相同的密钥h。因此,它遵循块来确定合并散列中:a的值。它将以下值传递给块:

:a

其中k = :a o = "a" n = "a" 是关键,k(对于“旧”)是okh的值(对于“新”) )是nk的值。 (我们在块中没有使用g,因此我将块变量命名为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"} 的第三个元素中的:c=>"cc"时,会将以下值传递给enum的块:

update

由于_ :c o = ["c", "notc"] n = "cc" 是一个数组,我们执行o语句的以下行:

case

并为o.include?(n) ? o : o + [n] #=> ["c", "notc", "cc"] 赋值。其余的计算也是类似的。

答案 1 :(得分:0)

我会做这样的事情:

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)将确保一个哈希的值是arary。 push(right)将其他哈希值添加到该数组中。 uniq.compact删除nil个值并重复。

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

答案 2 :(得分:-1)

假设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对数组进行排序,以便所有键都排列在一起#39;

[{: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) }.

此时我们已经按键将所有数据组合在一起,并且需要对密钥的所有副本进行重复删除,我们可以删除nils并将值设为uni:

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