我有一系列像这样的哈希:
[{: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!
方法,但不知道从哪里开始
答案 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
(对于“旧”)是o
和k
中h
的值(对于“新”) )是n
中k
的值。 (我们在块中没有使用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"]}