遍历哈希数组并合并
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")是 h
和 n
中 k
的值( "new") 是 g
中 k
的值。 (我们没有在块中使用 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 只是元素。
我有一个像这样的哈希数组:
[{: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")是 h
和 n
中 k
的值( "new") 是 g
中 k
的值。 (我们没有在块中使用 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 只是元素。