我正在尝试在哈希列表中编译给定哈希键的所有值。我有以下工作。
[{'a': 1, 'b': 2, 'c': 3}, {'a': 4, 'b': 5, 'c': 6}, {'a': 7, 'b': 8, 'c': 9}].inject({}) do |hash, item|
item.each do |key, value|
hash[key] = [] if hash[key].nil?
hash[key] << value
end
hash
end
结果很棒:
{:a=>[1, 4, 7], :b=>[2, 5, 8], :c=>[3, 6, 9]}
我的问题:是否有一种更优雅的方式来初始化哈希值,所以我不需要检查以下行中的nil案例?
hash[key] = [] if hash[key].nil?
我已尝试Hash.new(0)
作为inject
方法的默认设置,但它不起作用。我确信有更优雅的方式来做到这一点。感谢。
答案 0 :(得分:4)
是的,您可以执行Hash.new { |hash, key| hash[key] = [] }
默认情况下将值设为空数组。
答案 1 :(得分:4)
您可以使用each_with_object
:
array = [{'a': 1, 'b': 2, 'c': 3}, {'a': 4, 'b': 5, 'c': 6}, {'a': 7, 'b': 8, 'c': 9}]
empty_hash = Hash.new{ |h, k| h[k] = [] }
hash = array.each_with_object(empty_hash) do |small_hash, hash|
small_hash.each do |k, v|
hash[k] << v
end
end
p hash
#=> {:a=>[1, 4, 7], :b=>[2, 5, 8], :c=>[3, 6, 9]}
这里有一个更短但更不寻常的版本:
hash = array.each_with_object(Hash.new{ [] }) do |small_hash, hash|
small_hash.each {|k, v| hash[k] <<= v }
end
p hash
#=> {:a=>[1, 4, 7], :b=>[2, 5, 8], :c=>[3, 6, 9]}
对于{:a=>[1], :b=>[2]}
,返回[{a: 1}, {b: 2}]
,作为指定的OP。
hash[k] <<= v
是一个奇怪的(也可能是效率低下的)技巧。它相当于:
hash[k] = (hash[k] << v)
需要进行赋值,因为哈希默认值未正确初始化,并且正在为每个哈希查找生成一个新数组,而不保存为值:
h = Hash.new{ [] }
p h[:a] << 1
#=> [1]
p h[:a]
#=> []
p h[:a] <<= 1
#=> [1]
p h[:a]
#=> [1]
答案 2 :(得分:2)
执行此操作的一种方法是使用Enumerable#flat_map,Enumerable#group_by和Enumerable#each_with_object(以及一些更熟悉的方法)。
a = arr.flat_map(&:to_a)
#=> [[:a, 1], [:b, 2], [:c, 3], [:a, 4], [:b, nil], [:c, 6], [:a, 7], [:b, 8], [:c, 9]]
b = a.group_by(&:first)
#=> {:a=>[[:a, 1], [:a, 4], [:a, 7]],
# :b=>[[:b, 2], [:b, nil], [:b, 8]],
# :c=>[[:c, 3], [:c, 6], [:c, nil]]}
e = b.each_with_object({})
#=> #<Enumerator: {:a=>[[:a, 1], [:a, 4], [:a, 7]], :b=>[[:b, 2], [:b, nil],
# [:b, 8]], :c=>[[:c, 3], [:c, 6], [:c, 9]]}:each_with_object({})>
步骤如下。 (血腥的细节是为了新的Ruby用户的利益 - 我希望如此。)
(k,v),h = e.next
#=> [[:a, [[:a, 1], [:a, 4], [:a, 7]]], {}]
k #=> :a
v #=> [[:a, 1], [:a, 4], [:a, 7]]
h #=> {}
生成枚举数的第一个值并将其传递给块,并使用 parallel assignment (有时称为多个赋值)分配块变量。
c = v.map(&:last)
#=> [1, 4, 7]
d = c.compact
#=> [1, 4, 7]
h[k] = d
#=> [1, 4, 7]
现在执行块计算。
e
现在生成h
的第二个元素并将其与更新后的(k,v),h = e.next
#=> [[:b, [[:b, 2], [:b, nil], [:b, 8]]], {:a=>[1, 4, 7]}]
k #=> :b
v #=> [[:b, 2], [:b, nil], [:b, 8]]
h #=> {:a=>[1, 4, 7]}
c = v.map(&:last)
#=> [2, nil, 8]
d = c.compact
#=> [2, 8]
值一起传递给块,为块变量赋值并执行块计算。
nil
请注意,上一步中删除了h[k] = d
#=> [2, 8]
值。
(k,v),h = e.next
#=> [[:c, [[:c, 3], [:c, 6], [:c, 9]]], {:a=>[1, 4, 7], :b=>[2, 8]}]
k #=> :c
v #=> [[:c, 3], [:c, 6], [:c, 9]]
h #=> {:a=>[1, 4, 7], :b=>[2, 8]}
c = v.map(&:last)
#=> [3, 6, 9]
d = c.compact
#=> [3, 6, 9]
d #=> [3, 6, 9]
h[k] = d
h #=> {:a=>[1, 4, 7], :b=>[2, 8], :c=>[3, 6, 9]}
然后由枚举器生成第三个和最后一个元素并传递给块。
web.xml
答案 3 :(得分:1)
您还可以将inject
与merge
合并为:
[{'a': 1, 'b': 2, 'c': 3}, {'a': 4, 'b': 5, 'c': 6}, {'a': 7, 'b': 8, 'c': 9}].inject do |result, hash|
result.merge(hash) { |_, first, second| [first, second].flatten }
end # => {:a=>[1, 4, 7], :b=>[2, 5, 8], :c=>[3, 6, 9]}