discussing various options to initialize hash in ruby
Introduction
- Let’s consider we have an array of the array as input. array elements basically represent order_id and product_id with that. So basically for order_id 1, product_id 2 was bought.
[[1,2],[1,3],[2,3],[2,2],[3,1]]
- Now let’s say we want to calculate for each product_id which products were bought.
- Now this problem basically tells us to output something like Key Value, where the key is order_id and the value is a list of products.
- So for order_id 1 output would be -> { 1 => [2,3] }
Solution using external Hash
- One of the ways to write a solution is to create a Hash and initialize it with an array using a block
- Now when we iterate over our element using each we can add each product to its corresponding order_id which is key in our case and val is product_id.
- Hence for each key, we add value to our array using shovel operator << which is essentially appending or pushing an element to our array.
h = Hash.new { |h, k| h[k] = [] }
[[1,2],[1,3],[2,3],[2,2],[3,1]].each do |(key, val)|
h[key] << val
end
irb(main):021:0> h
=> {1=>[2, 3], 2=>[3, 2], 3=>[1]} #ouput
Improving solution by injecting Hash
- We can make our code more concise by using reduce operator instead of each.
- reduce operator takes an accumulator which we can initialize in reduce.
- Now, this is a good place to initialize our hash since we will need to collect all the product_id for a given key.
- Once we add the product_id, we return a hash so that at the end of the array we will be able to get our output.
[[1,2],[1,3],[2,3],[2,2],[3,1]].reduce(Hash.new { |h,k| h[k] = [] } ) do | hash, (key,val) |
hash[key] << val
hash
end
=> {1=>[2, 3], 2=>[3, 2], 3=>[1]} # ouptut
Some more examples
- Now we can use the above improvement in other use cases.
- For example, let’s say now the input array has product_id and a number of sales made at different points in time.
- Now we want to calculate how many of each product sold.
- Now our solution would require us to add the value of each key.
- We can again inject a hash map and initialize all the keys with the value 0
[[1,2],[1,3],[2,3],[2,2],[3,1]].reduce(Hash.new { |h,k| h[k] = 0 } ) do | hash, (key,val) |
hash[key] +=val
hash
end
=> {1=>5, 2=>5, 3=>1} # output
- We can also initialize hash with 0 simply by injecting Hash.new(0)
[[1,2],[1,3],[2,3],[2,2],[3,1]].reduce(Hash.new(0) ) do | hash, (key,val) |
hash[key] +=val
hash
end
=> {1=>5, 2=>5, 3=>1} # output
- One of the alternative ways of achieving the same thing is using each_with_object method, which also allows us to inject hash with initialized value.
[[1,2],[1,3],[2,3],[2,2],[3,1]].each_with_object(Hash.new(0)) do |(key,val), h|
h[key] += val
end
=> {1=>5, 2=>5, 3=>1} # output
Conclusion
- In this article we looked at different options we have in ruby to initialize with the default value.
- reduce and each_with_object are good target methods to inject initialize objects when needed.
- Let me know in the comments if you know an additional approach.