Home > OS >  transform keys and values with an array as one of them
transform keys and values with an array as one of them

Time:12-07

I have the following Ruby hash

a = {
  7 => [1469, 2283],
  5 => [1469, 5464],
  3 => [7561],
  6 => [7952, 8114],
  4 => []
}

and would like to get the keys that a number shows up in as a value

 b = {
   1469 => [7,5],
   2283 => [7],
   5464 => [5]
   ...  
 }

How would I do this? I'm sure there's some super slick way of getting it done.

CodePudding user response:

Given:

a = {
    7 => [1469, 2283],
    5 => [1469, 5464],
    3 => [7561],
    6 => [7952, 8114],
    4 => [1469, 2283], 
    2 => []
}

Use a default value of a new array in b:

b=Hash.new {|hsh, key| hsh[key] = [] }
a.each{|k,v| v.each{|n| b[n] << k} }

Or include the object creation with each_with_object:

b=a.each_with_object(Hash.new {|h,k| h[k] = []}) { |(k,v), h|
    v.each{ |e| h[e] << k } }   

Result b is:

{1469=>[7, 5, 4], 2283=>[7, 4], 5464=>[5], 7561=>[3], 7952=>[6], 8114=>[6]}

CodePudding user response:

Using #each_with_object to iterate over the original hash and the values stored in each array and build up a new hash based on it.

a.each_with_object({}) do |(k, v), h|
  v.each do |val|
    h[val] ||= []
    h[val] << k
  end
end

Result:

{
  1469 => [7, 5], 
  2283 => [7], 
  5464 => [5],
  7561 => [3],
  7952 => [6],
  8114 => [6]
}

CodePudding user response:

@dawg's answer is the way I would go

a.each_with_object(Hash.new {|h,k| h[k] = []}) do |(k,v),obj| 
 v.each{|e| obj[e] << k} 
end

But here are a few other options: (a lot of allocations though)

Array#product and Hash#merge

a.each_with_object({}) do |(k,v),obj|
  obj.merge!(v.product([[k]]).to_h) {|_,o,v| [*o,*v]}
end

#map then #reduce

a.map {|k,v| v.product([[k]]).to_h }
 .reduce {|memo, h| memo.merge(h) {|_,o,n| o.concat(n)}}

This works too (don't ask questions)

a.map {|k,v| v.each_with_object(k)}
 .reduce(&: )
 .group_by(&:shift)
 .transform_values(&:flatten)
  •  Tags:  
  • ruby
  • Related