Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to turn a ruby hash so that the value becomes a key that points to a group of old keys?

Tags:

ruby

hash

I have a hash like this:

t={"4nM"=>"Triangle", "I40"=>"Triangle", "123"=>"Square"}

And I want to turn it into a hash like:

{"Triangle" => ["4nM", "I40"], "Square" => ["123"]}

What is the best way to do this?

I start with group_by but then the code gets to be a bit convoluted....

This is what I did:

t.group_by { |k, v| v }.map { |type, group| {type => group.flatten.reject { |x| x == type } } }
like image 395
Nona Avatar asked Dec 18 '22 11:12

Nona


2 Answers

h = { "4nM"=>"Triangle", "I40"=>"Triangle", "123"=>"Square" }

h.each_with_object({}) { |(k,v),h| (h[v] ||= []) << k }
  #=> {"Triangle"=>["4nM", "I40"], "Square"=>["123"]}

The expression

(h[v] ||= []) << k

expands to

(h[v] = h[v] || []) << k

If h has a key v, h[k] will be truthy, so the expression above reduces to

(h[v] = h[v]) << k

and then

h[v] << k

If h does not have a key v, h[k] #=> nil, so the expression above reduces to

(h[v] = []) << k

resulting in

h[v] #=> [k]

Alternatively, we could write

h.each_with_object(Hash.new { |h,k| h[k] = [] }) { |(k,v),h| h[v] << k }
  #=> {"Triangle"=>["4nM", "I40"], "Square"=>["123"]}

See Hash::new for an explanation of the use of a block for returning the default values of keys that are not present in the hash.

like image 66
Cary Swoveland Avatar answered May 24 '23 19:05

Cary Swoveland


This is the shortest I could write :

t.group_by(&:last).map{|k,v|[k,v.map(&:first)]}.to_h

Still 4 characters longer than @Cary Swoveland's answer.

Note that in Rails, Hash#transform_values makes it a bit easier :

t.group_by{|_,v| v }.transform_values{|v| v.map(&:first) }
like image 30
Eric Duminil Avatar answered May 24 '23 19:05

Eric Duminil