Ruby array of hash. group_by and modify in one line

强颜欢笑 提交于 2019-11-29 11:44:35

问题


I have an array of hashes, something like

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

and I want to convert it to this

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

I tried group_by but then i got this

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

and then I can't modify it to leave just the name and not the full hash. I need to do this in one line because is for a grouped_options_for_select on a Rails form.


回答1:


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

Following steenslag's suggestion:

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



回答2:


In a single iteration over initial array:

arry.inject(Hash.new([])) { |h, a| h[a[:type]] += [a[:name]]; h }



回答3:


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



回答4:


I would do as below :

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



回答5:


Using ActiveSuport's Hash#transform_values:

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



回答6:


@ArupRakshit answer, slightly modified (the function has been added for sake of clarity in the final example):

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

Please, notice that, although not mentioned in the question, you may want to preserve the original sample as it is. Some answers kept provision on this, others not as.

After grouping (list.group_by {...}) the part that does the transformation (without modifying the original sample's values) is:

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

Some hints:

  1. iterating the pairs of the Hash of groups (first map), where
  2. for each iteration, we receive |group_key, array] and return an Array of [group_key, new_array] (outer block),
  3. and finally to_h transforms the Array of Arrays into the Hash (this [[gk1,arr1],[gk2,arr2]...] into this { gk1 => arr1, gk2 => arr2, ...})

There is one missing step not explained at step (2) above. new_array is made by v.map {|h| h[at]}, which justs casts the value at of each original Hash (h) element of the array (so we move from Array of Hashes to an Array of elements).

Hope that helps others to understand the example.



来源:https://stackoverflow.com/questions/18920877/ruby-array-of-hash-group-by-and-modify-in-one-line

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!