Ruby数组哈希。一行代码中进行group_by和修改

19

我有一个哈希数组,类似于

[ {:type=>"Meat", :name=>"one"}, 
  {:type=>"Meat", :name=>"two"}, 
  {:type=>"Fruit", :name=>"four"} ]

我希望你能把它转换成这样

{ "Meat" => ["one", "two"], "Fruit" => ["Four"]}

我尝试了group_by,但是出现了这个问题

{ "Meat" => [{:type=>"Meat", :name=>"one"}, {:type=>"Meat", :name=>"two"}],
  "Fruit" => [{:type=>"Fruit", :name=>"four"}] }

然后我无法修改它以仅保留名称而不是完整哈希值。 我需要在一行中完成此操作,因为这是用于Rails表单上的grouped_options_for_select


1
有一个 option_groups_from_collection_for_select 助手。 - Stefan
6个回答

22
array.group_by{|h| h[:type]}.each{|_, v| v.replace(v.map{|h| h[:name]})}
# => {"Meat"=>["one", "two"], "Fruit"=>["four"]}

根据steenslag的建议:

array.group_by{|h| h[:type]}.each{|_, v| v.map!{|h| h[:name]}}
# => {"Meat"=>["one", "two"], "Fruit"=>["four"]}

5
最后一个代码块可写为 {|_, v| v.map!{|h| h[:name]}} - steenslag
@steenslag 谢谢。我忘了它。 - sawa
...或者 each_value...,我不明白为什么要加上 ! - Cary Swoveland
.each_value {|v| v.map!{|h| h[:name]}} 可以正常运行,但我认为需要加上 ! - Cary Swoveland

16
在初始数组的单次迭代中:
arry.inject(Hash.new([])) { |h, a| h[a[:type]] += [a[:name]]; h }

不错!(但是你的前言听起来像一个鼓点。 :-) ) - Cary Swoveland
2
你的回答让我更深入地了解了 inject 的酷炫之处。谢谢! - Cary Swoveland

3

使用 ActiveSuport 的 Hash#transform_values

array.group_by{ |h| h[:type] }.transform_values{ |hs| hs.map{ |h| h[:name] } }
#=> {"Meat"=>["one", "two"], "Fruit"=>["four"]}

1
transform_values 现在已经成为 Ruby 的一部分(我认为是从 2.4 开始)。 - Henrik N

2
array = [{:type=>"Meat", :name=>"one"}, {:type=>"Meat", :name=>"two"}, {:type=>"Fruit", :name=>"four"}]
array.inject({}) {|memo, value| (memo[value[:type]] ||= []) << value[:name]; memo}

2
我会按照以下方式操作:
hsh =[{:type=>"Meat", :name=>"one"}, {:type=>"Meat", :name=>"two"}, {:type=>"Fruit", :name=>"four"}]
p Hash[hsh.group_by{|h| h[:type] }.map{|k,v| [k,v.map{|h|h[:name]}]}]

# >> {"Meat"=>["one", "two"], "Fruit"=>["four"]}

1

@ArupRakshit回答,稍有修改(为了在最终示例中更清晰地表达加入了该函数):

def group(list, by, at)
  list.group_by { |h| h[by] }.map { |k,v| [ k , v.map {|h| h[at]} ] }.to_h
end

sample =[
  {:type=>"Meat",  :name=>"one",  :size=>"big"   },
  {:type=>"Meat",  :name=>"two",  :size=>"small" },
  {:type=>"Fruit", :name=>"four", :size=>"small" }
]

group(sample, :type, :name) # => {"Meat"=>["one", "two"], "Fruit"=>["four"]}
group(sample, :size, :name) # => {"big"=>["one"], "small"=>["two", "four"]}

请注意,虽然问题中没有提到,但您可能希望保留原始的 sample。一些答案对此进行了保留,而其他答案则没有。

在分组 (list.group_by {...}) 后,执行转换的部分(不修改原始样本的值)如下:

.map { |k,v| [ k , v.map {|h| h[at]} ] }.to_h

一些提示:
  1. 迭代组的哈希(第一个“map”)中的对(pairs),其中
  2. 对于每次迭代,我们接收 |group_key,array] 并返回 [group_key,new_array] Array (外部 block ),
  3. 最后, to_h Array of Array 转换为 Hash (将此 [[gk1,arr1],[gk2,arr2] ...] 转换为此 {gk1 => arr1,gk2 => arr2,...}
步骤(2)中未解释的一个遗漏步骤。 new_array 是由 v.map {| h | h[at]} 制成的,它只是将原始 Hash 的每个元素 array (因此我们从 Array of Hash 转移到元素的 Array )。
希望这能帮助其他人理解这个例子。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接