将某个键的重复值从散列数组合并到数组中

时间:2014-02-18 23:06:30

标签: ruby arrays hash inject

我有一系列哈希:

connections = [
{:name=>"John Doe", :number=>"5551234567", :count=>8},
{:name=>"Jane Doe", :number=>"5557654321", :count=>6},
{:name=>"John Doe", :number=>"5559876543", :count=>3}
]

如果:name值是重复的,就像John Doe一样,它应该将:number值组合成一个数组。计数不再重要,因此输出应采用以下格式:

{"John Doe"=>["5551234567","5559876543"],
"Jane Doe"=>["5557654321"]}

到目前为止我所拥有的是:

k = connections.inject(Hash.new{ |h,k| h[k[:name]] = [k[:number]] }) { |h,(k,v)| h[k] << v ; h }

但这只是输出

{"John Doe"=>["5559876543", nil], "Jane Doe"=>["5557654321", nil]}

3 个答案:

答案 0 :(得分:2)

在一行中:

k = connections.each.with_object({}) {|conn,result| (result[conn[:name]] ||= []) << conn[:number] }

更具可读性:

result = Hash.new {|h,k| h[k] = [] }
connections.each {|conn| result[conn[:name]] << conn[:number] }

result #=> {"John Doe"=>["5551234567", "5559876543"], "Jane Doe"=>["5557654321"]}

答案 1 :(得分:1)

names = {}
connections.each{ |c| names[c[:name]] ||= []; names[c[:name]].push(c[:number]) }
puts names

答案 2 :(得分:1)

这有效:

connections.group_by do |h| 
  h[:name] 
end.inject({}) do |h,(k,v)| 
  h.merge( { k => (v.map do |i| i[:number] end) } ) 
end
# => {"John Doe"=>["5551234567", "5559876543"], "Jane Doe"=>["5557654321"]}

一步一步......

connections与您的帖子相同:

connections
# => [{:name=>"John Doe", :number=>"5551234567", :count=>8}, 
#     {:name=>"Jane Doe", :number=>"5557654321", :count=>6}, {:name=>"John Doe",
#      :number=>"5559876543", :count=>3}]

首先,我们使用group_by将哈希条目与相同的:name

组合在一起
connections.group_by do |h| h[:name] end
# => {"John Doe"=>[{:name=>"John Doe", :number=>"5551234567", :count=>8}, 
#                  {:name=>"John Doe", :number=>"5559876543", :count=>3}], 
#     "Jane Doe"=>[{:name=>"Jane Doe", :number=>"5557654321", :count=>6}]}

这很好,但我们希望结果哈希的值只是显示为:number键值的数字,而不是完整的原始条目哈希值。

只给出一个列表值,我们就可以通过这种方式获得所需的结果:

[{:name=>"John Doe", :number=>"5551234567", :count=>8}, 
 {:name=>"John Doe", :number=>"5559876543", :count=>3}].map do |i| 
  i[:number] 
end
# => ["5551234567", "5559876543"]

但我们希望一次对所有列表值执行此操作,同时保持与其键的关联。它基本上是一个嵌套的map操作,但外部映射跨越Hash而不是Array。

事实上你可以用map来做。唯一棘手的部分是Hash上的map不返回哈希,而是一组嵌套的[,]数组。通过将调用包装在Hash[ ... ]构造函数中,您可以将结果转回哈希:

Hash[ 
  connections.group_by do |h|
    h[:name] 
  end.map do |k,v| 
    [ k, (v.map do |i| i[:number] end) ] 
  end
]

返回与上面原始完整答案相同的结果,并且可以说更清晰,因此您可能只想使用该版本。

但我使用的机制是inject。它类似于map,但它不是只返回块中返回值的数组,而是让您完全控制如何从单个块调用中构造返回值:

connections.group_by do |h| 
  h[:name] 
end.inject({}) do |h,(k,v)| 
  h.merge( { k => (v.map do |i| i[:number] end) } ) 
end

这会创建一个新的Hash,它开始为空({}传递给inject),并将其传递给do块(显示为h })以及group_by返回的哈希中的第一个键/值对。该块创建另一个新的Hash,其中传入单个键,并且如上所述转换值的结果,并将其合并到传入的值中,返回新值 - 基本上,它添加一个新的键/值对哈希,内部map将值转换为所需的形式。新的Hash从块中返回,因此它将在下一次通过块时成为h的新值。

(我们也可以直接用h将条目分配到h[k] = v.map ...,但随后该块需要return h作为单独的语句,因为它是返回值块,而不是块执行结束时h的值,传递给下一次迭代。)

顺便说一句:我在我的街区周围使用了do ... end代替{ ... },以避免与{混淆... }用于哈希文字。没有语义差异;这完全是一种风格问题。在标准的Ruby样式中,您可以将{ ... }用于单行块,并将do ... end限制为跨越多行的块