Initialize object within Hash - ruby

Which way is better? Or do you know more better way?
data = Hash.new { |h, k| h[k] = Hash.new { |h2, k2| h2[k2] = [] } } # 5 or more times
or
def object_in_hash(&object)
Hash.new { |h, k| h[k] = object.call() }
end
data = object_in_hash do object_in_hash do [] end end

You can create an infinitely nested hash by passing its default_proc to the new instance:
data = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }
This lets you assign values to arbitrary keys:
data[:foo][:bar][:baz] = []
data[:foo][:bar][:qux] = []
data #=> {:foo=>{:bar=>{:baz=>[], :qux=>[]}}}

Related

How to refactor and reduce complexity

I have the following code:
#model = "ford"
#attribute = "name"
def grouped
grouped = {}
#array.each do |r|
field = r.car.send(#model)
if field.is_a? ActiveRecord::Associations::CollectionProxy
field.each do |obj|
key = obj.send(#attribute)
grouped[key] = [] unless grouped.has_key?(key)
grouped[key].push(r)
end
else
key = field.send(#attribute)
grouped[key] = [] unless grouped.has_key?(key)
grouped[key].push(r)
end
end
grouped
end
The result is:
{ford: [a, b, c]}
The codeclimate says that it has cognitive complexity.
How can I refactor this method to something cleaner?
def grouped
#array.each_with_object(Hash.new { |h, k| h[k] = [] }) do |r, grouped|
case field = r.car.send(#model)
when ActiveRecord::Associations::CollectionProxy
field.each do |obj|
grouped[obj.send(#attribute)] << r
end
else
grouped[field.send(#attribute)] << r
end
end
end

Hash default value is a hash with same default value

Setting a default value of a hash like this:
hash = Hash.new { |hsh, key| hsh[key] = {} }
will create (and assign) a new hash for an unknown key, but will return nil for an unknown key of the created hash:
hash[:unkown_key] #=> {}
hash[:unkown_key][:nested_unknown] #=> nil
I could make it work for the second level like this:
hash = Hash.new do |hsh, key|
hsh[key] = Hash.new { |nest_hsh, nest_key| nest_hsh[nest_key] = {} }
end
but, it does not work at the third level:
hash[:unkown_key][:nested_unknown] #=> {}
hash[:unkown_key][:nested_unknown][:third_level] #=> nil
How can I make it work at arbitrary levels?
hash[:unkown_key][:nested_unknown][:third_level][...][:nth_level] #=> {}
Sort of mind bending, but you can pass the hash's default_proc to the inner hash:
hash = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }
hash[:foo] #=> {}
hash[:foo][:bar] #=> {}
hash[:foo][:bar][:baz] #=> {}
hash #=> {:foo=>{:bar=>{:baz=>{}}}}
bottomless_hash = ->() do
Hash.new { |h, k| h[k] = bottomless_hash.call }
end
hash = bottomless_hash.call
hash[:unkown_key][:nested_unknown][:third_level][:fourth] # => {}
You could create method that will do this with Recursion
class Hash
def self.recursive
new { |hash, key| hash[key] = recursive }
end
end
hash = Hash.recursive
hash[:unknown_key] # => {}
hash[:first_unknown_key][:second_unknown_key][...][:infinity]
# hash => {first_unknown_key: {second_unknown_key: {... {infinity: {}}}}}

Best way to check for nil and update hash value

I have a hash where the values are all arrays. I want to look up a key in this hash. If it exists I want to add a value to the array. If it does not exist (hash[key] returns nil) then I need to create the array and and add one value. Currently I have this:
hash[key].push elem unless hash[key].nul?
hash[key] ||= [elem]
This involves 3 lookups. I'm new to ruby so I'm sure there's a better way to do this. What is it?
My original plan was to make the default value for the hash [ ]. Then I can just use:
hash[key].push elem
Unfortunately if the key does not exist, that will only change the default value and not add a new key.
In this case you need to create a hash as below :
hash = Hash.new { |h,k| h[k] = [] }
The above is created to handle situations like your. Look new {|hash, key| block } → new_hash
hash = Hash.new { |h,k| h[k] = [] }
hash[:key1] << 1
hash[:key2] << 2
hash[:key1] << 3
hash # => {:key1=>[1, 3], :key2=>[2]}
You can try:
(hash[key] ||= []) << elem
However Arup's answer is much better.
You should create your hash with a default value.
hash = Hash.new { |h,k| h[k] = [] }
hash[key].push elem

Ruby: What does the line "m = Hash.new {|h,k| h[k] = []}" accomplish that "Hash.new" doesn't?

While studying this Railscast I came across the following bit of source code from Rack:
def self.middleware
#middleware ||= begin
m = Hash.new {|h,k| h[k] = []}
m["deployment"].concat [
[Rack::ContentLength],
[Rack::Chunked],
logging_middleware
]
m["development"].concat m["deployment"] + [[Rack::ShowExceptions], [Rack::Lint]]
m
end
end
My question is about the third line. What does passing the block {|h,k| h[k] = []} to Hash.new accomplish? I tried it in IRB and it doesn't seem to do anything different from a regular Hash.new:
2.0.0p247 :003 > m1 = Hash.new
=> {}
2.0.0p247 :004 > m2 = Hash.new{|h,k| h[k] = []}
=> {}
2.0.0p247 :005 > m1 == m2
=> true
... but I'm going to guess that the guys who wrote Rack know more about Ruby than I do. What's the reasoning behind including that block?
It allows you to define default value as an array
h = Hash.new { |h, k| h[k] = [] }
h[:a] # => {:a=>[]}
h[:b] << 123 # => {:a=>[], :b=>[123]}
More examples are here: Hash.new
This creates default object ([] in this case) as value for every key. It calls h1[:a] = [] implicitly resulting in addition of key in the hash.
h1 = Hash.new {|h,k| h[k] = []}
h1[:a] # = []
h1[:a].push(1) # = [1]
h1[:b] # = []
h1 #= {:a=>[1], :b=>[]}
This returns a single default object as value for all keys.
h1 = Hash.new([])
h1[:a] # = []
h1[:a].push(1) # = [1]
h1[:b] # = [1]
h1 #= {}
This returns default object as value for all keys. But does not add key to the hash.
h1 = Hash.new{[]}
h1[:a] # = []
h1[:a].push(1) # = [1]
h1[:b] # = []
h1 #= {}

Ruby Hash: creating a default value for non-existing elements

I learned from this answer here that this is possible:
h = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }
h['bar'] # => {}
h['tar']['star']['par'] # => {}
Can someone explain how it works?
Hashes have a thing called a default_proc, which is simply a proc that Ruby runs when you try to access a hash key that doesn't exist. This proc receives both the hash itself and the target key as parameters.
You can set a Hash's default_proc at any time. Passing a block parameter to Hash.new simply allows you to initialize a Hash and set its default_proc in one step:
h = Hash.new
h.default_proc = proc{ |hash, key| hash[key] = 'foo' }
# The above is equivalent to:
h = Hash.new{ |hash, key| hash[key] = 'foo' }
We can also access the default proc for a hash by calling h.default_proc. Knowing this, and knowing that the ampersand (&) allows a proc passed as a normal parameter to be treated as a block parameter, we can now explain how this code works:
cool_hash = Hash.new{ |h, k| h[k] = Hash.new(&h.default_proc) }
The block passed to Hash.new will be called when we try to access a key that doesn't exist. This block will receive the hash itself as h, and the key we tried to access as k. We respond by setting h[k] (that is, the value of the key we're trying to access) to a new hash. Into the constructor of this new hash, we pass the "parent" hash's default_proc, using an ampersand to force it to be interpreted as a block parameter. This is the equivalent of doing the following, to an infinite depth:
cool_hash = Hash.new{ |h, k| h[k] = Hash.new{ |h, k| h[k] = Hash.new{ ... } } }
The end result is that the key we tried to access was initialized to a new Hash, which itself will initialize any "not found" keys to a new Hash, which itself will have the same behavior, etc. It's hashes all the way down.
In this code you create hashes by chain, so that any link of chain would have same default_proc
So, default_proc of h and h['bar'] and so far will be the same - it will return new instance of Hash with this default_proc

Resources