将2元素数组的数组转换为散列,其中重复键附加其他值

时间:2012-02-14 02:38:29

标签: ruby arrays hash

例如

给定一个数组:

array = [[:a,:b],[:a,:c],[:c,:b]]

返回以下哈希:

hash = { :a => [:b,:c] , :c => [:b] }

hash = Hash[array]会覆盖以前的关联,产生:

hash = { :a => :c , :c => :b }

4 个答案:

答案 0 :(得分:74)

使用功能性婴儿步骤:

irb:01.0> array = [[:a,:b],[:a,:c],[:c,:b]]
#=> [[:a, :b], [:a, :c], [:c, :b]]

irb:02.0> array.group_by(&:first)
#=> {:a=>[[:a, :b], [:a, :c]], :c=>[[:c, :b]]}

irb:03.0> array.group_by(&:first).map{ |k,a| [k,a.map(&:last)] }
#=> [[:a, [:b, :c]], [:c, [:b]]]

irb:04.0> Hash[ array.group_by(&:first).map{ |k,a| [k,a.map(&:last)] } ]
#=> {:a=>[:b, :c], :c=>[:b]}

使用命令式样式编程:

irb:10.0> h = Hash.new{ |h,k| h[k]=[] }
#=> {}

irb:11.0> array.each{ |k,v| h[k] << v }
#=> [[:a, :b], [:a, :c], [:c, :b]]

irb:12.0> h
#=> {:a=>[:b, :c], :c=>[:b]}

作为必要的单行代码:

irb:13.0> h = Hash.new{ |h,k| h[k]=[] }.tap{ |h| array.each{ |k,v| h[k] << v } }
#=> {:a=>[:b, :c], :c=>[:b]}

或者使用每个人最喜欢的inject

irb:14.0> array.inject(Hash.new{ |h,k| h[k]=[] }){ |h,(k,v)| h[k] << v; h }
#=> {:a=>[:b, :c], :c=>[:b]}

如果您确实希望单个值不与数组冲突,可以将它们作为后处理步骤取消排列,或者使用不同的哈希累积策略,该策略仅在碰撞时创建数组。或者,绕过这个:

irb:17.0> hashes = array.map{ |pair| Hash[*pair] } # merge many mini hashes
#=> [{:a=>:b}, {:a=>:c}, {:c=>:b}]

irb:18.0> hashes.inject{ |h1,h2| h1.merge(h2){ |*a| a[1,2] } }
#=> {:a=>[:b, :c], :c=>:b}

答案 1 :(得分:29)

编辑:在Ruby 2.1+中,您可以使用Array#to_h

pry(main)> [[:a,:b],[:a,:c],[:c,:b]].to_h
=> {:a=>:c, :c=>:b}

结束编辑

Hash类上的public []方法接受一个键值对数组,并返回一个散列,其中数组的第一个元素为键,第二个元素为值。

当存在密钥重复时,键值对中的最后一个值将是实际值。

Hash[[[:a,:b],[:a,:c],[:c,:b]]]
    => {:a=>:c, :c=>:b}

此语法在1.9.3+中有效;我不确定早期的Ruby版本(它在1.8.7中无效)

参考:http://www.ruby-doc.org/core-2.1.0/Hash.html#method-c-5B-5D

另一个有趣的方法是使用注入方法:(显然上面的方法更简洁,并推荐用于此特定问题)

[ [:a, :b], [:a, :c], [:c, :b] ].inject({}) { |memo, obj| 
   memo[obj.first] = obj.last
   memo 
}

=> {:a=>:c, :c=>:b}

在这种情况下,对可枚举的数组进行迭代,从注入的参数开始,在本例中为空哈希{}。

对于可枚举中的每个对象,使用变量memo和obj:

调用块
  • obj是数组中的当前对象

  • 备忘录是您的块最后一次迭代返回的值(对于第一次迭代,它是您注入的内容)

答案 2 :(得分:1)

使用each_with_object可以非常简洁地完成此操作。

array.each_with_object({}) { |(k, v), h| h[k] = (h[k] || []) + [v] }

irb中进行演示:

irb(main):002:0> array = [[:a,:b],[:a,:c],[:c,:b]]
=> [[:a, :b], [:a, :c], [:c, :b]]
irb(main):003:0> array.each_with_object({}) { |(k, v), h| h[k] = (h[k] || []) + [v] }
=> {:a=>[:b, :c], :c=>[:b]}

答案 3 :(得分:0)

这种操作在我们的项目中很常见,因此我们在to_group_h中添加了Enumerable。我们可以像这样使用它:

[[:x, 1], [:x, 2], [:y, 3]].to_h
# => { x: 2, y: 3 }

[[:x, 1], [:x, 2], [:y, 3]].to_group_h
# => { x: [1, 2], y: [3] }

以下是Enumerable#to_group_h的实现:

module Enumerable
  if method_defined?(:to_group_h)
    warn 'Enumerable#to_group_h is defined'
  else
    def to_group_h
      hash = {}
      each do |key, value|
        hash[key] ||= []
        hash[key] << value
      end
      return hash
    end
  end
end