初始化inject方法中的哈希以防止值为nil

时间:2017-01-19 21:16:59

标签: ruby

我正在尝试在哈希列表中编译给定哈希键的所有值。我有以下工作。

[{'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方法的默认设置,但它不起作用。我确信有更优雅的方式来做到这一点。感谢。

4 个答案:

答案 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。

&lt;&lt; =?

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_mapEnumerable#group_byEnumerable#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)

您还可以将injectmerge合并为:

[{'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]}