Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I refactor converting this array into a Hash

Given this array (generated from a file)

["Yonkers", "DM1210", "70.00 USD"], ["Yonkers", "DM1182", "19.68 AUD"], 
["Nashua", "DM1182", "58.58 AUD"], ["Scranton", "DM1210", "68.76 USD"], 
["Camden", "DM1182", "54.64 USD"]]

I convert it to a hash indexed by the second element (the sku) with the code below:

result = Hash.new([])
trans_data.each do |arr|
  result[arr[1]].empty? ? result[arr[1]] = [[arr[0], arr[2]]] : result[arr[1]] << [arr[0], arr[2]] 
end
result

This outputs the hash in the format I want it:

{"DM1210"=>[["Yonkers", "70.00 USD"], ["Scranton", "68.76 USD"]], "DM1182"=>[["Yonkers", "19.68 AUD"], ["Nashua", "58.58 AUD"], ["Camden", "54.64 USD"]]}

I don't feel like my code is... clean. Is there a better way of accomplishing this?

EDIT: So far I was able to replace it with: (result[arr[1]] ||= []) << [arr[0], arr[2]]

With no default value for the hash

like image 364
Senjai Avatar asked Feb 24 '26 12:02

Senjai


1 Answers

Looks like people need to learn about group_by:

ary = [
  ["Yonkers", "DM1210", "70.00 USD"], ["Yonkers", "DM1182", "19.68 AUD"],
  ["Nashua", "DM1182", "58.58 AUD"], ["Scranton", "DM1210", "68.76 USD"],
  ["Camden", "DM1182", "54.64 USD"]
]
hash = ary.group_by{ |a| a.slice!(1) }

Which results in:

=> {"DM1210"=>[["Yonkers", "70.00 USD"], ["Scranton", "68.76 USD"]], "DM1182"=>[["Yonkers", "19.68 AUD"], ["Nashua", "58.58 AUD"], ["Camden", "54.64 USD"]]}

It's possible to write this fairly succinctly without slice!, allowing ary to remain unchanged, and without the need to pull in any extra classes or modules:

irb(main):036:0> Hash[ary.group_by{ |a| a[1] }.map{ |k, v| [k, v.map{ |a,b,c| [a,c] } ] }]
=> {"DM1210"=>[["Yonkers", "70.00 USD"], ["Scranton", "68.76 USD"]], "DM1182"=>[["Yonkers", "19.68 AUD"], ["Nashua", "58.58 AUD"], ["Camden", "54.64 USD"]]}
irb(main):037:0> ary
=> [["Yonkers", "DM1210", "70.00 USD"], ["Yonkers", "DM1182", "19.68 AUD"], ["Nashua", "DM1182", "58.58 AUD"], ["Scranton", "DM1210", "68.76 USD"], ["Camden", "DM1182", "54.64 USD"]]

Several other answers are using each_with_object, which removes the need to coerce the returned array to a hash using Hash[...]. Here's how I'd use each_with_object to avoid a bunch of line-noise inside the block as they try to initialize unknown keys:

ary.each_with_object(Hash.new{ |h,k| h[k] = [] }) { |(a, b, c), h| 
  h[b] << [a, c] 
}
=> {"DM1210"=>[["Yonkers", "70.00 USD"], ["Scranton", "68.76 USD"]], "DM1182"=>[["Yonkers", "19.68 AUD"], ["Nashua", "58.58 AUD"], ["Camden", "54.64 USD"]]}

This takes advantage of Hash.new taking an initialization block that gets called when a key hasn't been previously defined.

like image 125
the Tin Man Avatar answered Feb 26 '26 03:02

the Tin Man



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!