Вопрос

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.

Это было полезно?

Решение

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

Другие советы

In a single iteration over initial array:

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

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

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

@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.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top