【问题标题】:Consolidate duplicate values of a certain key from an array of hashes into array将哈希数组中某个键的重复值合并到数组中
【发布时间】:2014-03-19 00:21:04
【问题描述】:

我有一个哈希数组:

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]}

【问题讨论】:

标签: ruby arrays hash inject


【解决方案1】:

一行:

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"]}

【讨论】:

    【解决方案2】:
    names = {}
    connections.each{ |c| names[c[:name]] ||= []; names[c[:name]].push(c[:number]) }
    puts names
    

    【讨论】:

      【解决方案3】:

      这行得通:

      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 操作,但外部映射跨越一个哈希而不是一个数组。

      实际上,您可以使用map 做到这一点。唯一棘手的部分是哈希上的map 不会返回哈希,而是嵌套的[key,value] 数组的数组。通过将调用包装在 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是从block中返回的,所以它成为h的新值,下次通过block。

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

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

      【讨论】:

        猜你喜欢
        • 2020-07-24
        • 2016-05-29
        • 2022-09-27
        • 1970-01-01
        • 2020-10-23
        • 1970-01-01
        • 2019-03-03
        • 2015-11-28
        • 1970-01-01
        相关资源
        最近更新 更多