2

How to create hash object with keys created from values and grouped by them?

cars = [{
  id: 1,
  properties: {
    name: "audi",
    type: "petrol"
  },
},{
  id: 1,
  properties: {
    name: "ford",
    type: "petrol"
  }
},{
  id: 1,
  properties: {
    name: "tesla",
    type: "electric"
  }
}]

Desired effect:

{
  petrol: [{name: "audi"}, {name: "ford"}],
  electric: [{name: "tesla"}]
}

My current function gives desired effect but it is too long, How can I get the same effect with shorter code?

cars.map { |c| Hash[c[:properties][:type], c[:properties][:name]] }.group_by{|h| h.keys.first}.each_value{|a| a.map!{|h| h.values.first}}

4 Answers 4

4

I came up with something like this. The grouped_cars variable should be extracted to a separate method.

grouped_cars = cars.inject({}) do |result, car|
  result[car[:properties][:type]] ||= []
  result[car[:properties][:type]] << { name: car[:properties][:name] }
  result
end

{ cars: grouped_cars }
Sign up to request clarification or add additional context in comments.

1 Comment

thanks for your answer, now I know how to use inject({})
2

My variant:

{
  cars: cars.inject({}) do |hash, data|
      type, name = data[:properties].values_at(:type, :name)
      hash[type] ||= []
      hash[type] << {name: name}
      hash
    end
}

And even shorter:

{
  cars: cars.inject(Hash.new([])) do |hash, car|
      type, name = car[:properties].values_at(:type, :name);
      hash[type] += [{ name: name }];
      hash
    end
}

Comments

2

inject method have to return the memo each time, I think each_with_object is better.

cars.each_with_object({}) do |item, hash|
  (hash[item[:properties][:type]] ||= []) << { name: item[:properties][:name] }
end

=> {"petrol"=>[{:name=>"audi"}, {:name=>"ford"}], "electric"=>[{:name=>"tesla"}]}

Comments

0

I suggest the following:

cars.map { |h| h[:properties] }.each_with_object({}) { |g,h|
  h.update(g[:type]=>[{ name: g[:name] }]) { |_,o,n| {name: o+n } } }
  #=> {"petrol"=>{:name=>[{:name=>"audi"}, {:name=>"ford"}]},
  #    "electric"=>[{:name=>"tesla"}]}  

Firstly, we obtain:

cars.map { |h| h[:properties] }
  #=> [{:name=>"audi",  :type=>"petrol"},
  #    {:name=>"ford",  :type=>"petrol"},
  #    {:name=>"tesla", :type=>"electric"}]

We then use the form of Hash#update (aka merge!) that uses the block:

{ |_,o,n| { name: o+n } }

to determine the values of keys that are present in both hashes being merged.

Initially, the hash:

{ "petrol"=>[{ :name=>"audi"] }] }

is merged into an empty hash represented by the block variable h. This merge does not make use of the block because h is empty and therefore does not have a key "petrol".

Next, the hash:

{ "petrol"=>[{ :name=>"ford" }] }

is merged into h. Since both this hash and h have keys "petrol", the value of "petrol" is given by the block:

{ |_,o,n| { name: o+n } }
  #=> {|_,[{ :name=>"audi" }] , [{ :name=>"ford" }]| 
  #      [{ name: "audi" }] + [{ name: "ford" }] }
  #=> [{ name: "audi" }, { name: "ford" }]

Lastly, the hash:

{ "electric"=>{ :name=>"tesla" } }

is merged into h. Since h does not have a key "electric", the block is not not needed to determine the value of that key.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.