Reason for using "Hash.new { Hash.new(false) }" - ruby

Encountered some code which sets an instance variable like so:
#square_array = Hash.new { Hash.new(false) }
Is there a reason to do it this way?
Rather then one of the following:
#square_array = Hash.new
# or
#square_array = {}

Yes, there is a reason. If you define hash with {} and try to get value of non-existing key, you get nil
hash = {}
#=> {}
hash[:a]
#=> nil
But if you define it with Hash.new you can set define value for non-existing key
hash = Hash.new { Hash.new(false) }
#=> {}
hash[:a]
#=> {}
hash[:a][:a]
#=> false

Related

What is an elegant Ruby way to have a condition of 'nil'?

Is there a more elegant way to write this code?
def create_a_hash_from_a_collection
my_hash = {}
collection_of_hashes.each do |key, value|
my_hash[key] = {} if my_hash[key].nil?
my_hash[key] = value
end
my_hash
end
The line that seems clumsy to me is this:
my_hash[key] = {} if my_hash[key].nil?
Is there a shorthand way of expressing it?
If you want to have a brand new hash for each key in your initial hash you need to initialise it with a block:
hash = Hash.new { |hash, key| hash[key] = {} }
hash[:foo].object_id == hash[:bar].object_id #=> false
Otherwise, if you do this, It will be always the same default hash
hash = Hash.new({})
hash[:foo].object_id == hash[:bar].object_id #=> true
You can use ||= operator which does exactly what you want
my_hash[key] ||= {}
The rest of my answer here is because I'm not sure what a "collection of hashes" is, so my best guess is that it would be an array of hashes. If I'm wrong, let me know and disregard the rest of this answer.
It seems that the rest of your method may not do what it sounds like you're trying to do. Consider:
#collection_of_hashes = [{foo: 'bar'}, {baz: 'qux'}]
def create_a_hash_from_a_collection
my_hash = {}
#collection_of_hashes.each do |key, value|
# this is not actually doing anything here and returns same with or
# without the following line
# my_hash[key] ||= {}
my_hash[key] = value
end
my_hash
end
#=> {{:foo=>"bar"}=>nil, {:baz=>"qux"}=>nil}
But what you probably want is
def create_a_hash_from_a_collection
my_hash = {}
#collection_of_hashes.each do |hash|
hash.keys.each do |k|
my_hash[k] = hash[k]
end
end
my_hash
end
#=> {:foo=>"bar", :baz=>"qux"}
But also keep in mind, if any of your "collection of hashes" which we would tend to assume would be an array of hashes, contain the same key, which one wins? This code, it would be the last item in the array's key value. What is the actual goal of your method?
I guess what you want is to initialise your my_hash with a default value, so then you don't need to check if it's nil or not.
That can be done using the Hash.new constructor, compare:
my_hash = {}
puts my_hash['no_existing_key'] #=> nil
my_hash = Hash.new({})
puts my_hash['no_existing_key'] #=> {}
You then can reduce your code to:
def create_a_hash_from_a_collection
my_hash = Hash.new({})
collection_of_hashes.each do |key, value|
my_hash[key] = value
end
my_hash
end
Since you are assigning value anyway, maybe you could use my_hash[key] = value || {}?
So, if value to assign is nil, the value of that key becomes {}.

How to assign to deeply nested Hash without using many 'nil' guards

I have a nested hash, to which I need to add more deeply nested property/value pairs.
Sample A:
a = {}
a['x']['y']['z'] << 8
Normally I'd have to do this:
Sample B:
a = {}
a['x'] ||= a['x'] = {}
a['x']['y'] ||= a['x']['y'] = {}
a['x']['y']['z'] ||= a['x']['y']['z'] = []
Otherwise, I will get undefined method '<<' for nil:NillClass.
Is there some type of shorthand or function along the lines of code A instead of code B?
The most elegant solution for the deep-nested hash of any depth would be:
hash = Hash.new { |h, k| h[k] = h.dup.clear }
or, even better (credits to #Stefan)
hash = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }
That way one might access any level:
hash[:a1][:a2][:a3][:a4] = :foo
#⇒ {:a1=>{:a2=>{:a3=>{:a4=>:foo}}}}
The idea is to clone the default_proc within the hash itself.
To give a little backgroud, there's a method Hash#dig which is present since ruby 2.3. With this you can safely attempt to read any number of keys:
{a: {b: {c: 1}}}.dig :a, :b, :c # => 1
{}.dig :a, :b, :c # => nil
of course, this doesn't solve your problem. You're looking for a write version. This has been proposed but rejected in Ruby core in the form of Hash#bury.
This method does almost exactly what you are looking for, however it can only set nested hash values and not append to nested arrays:
# start with empty hash
hash = {}
# define the inner array
hash.bury :x, :y, :z, []
# add to the inner array
hash[:x][:y][:z] << :some_val
You can get this method through the ruby-bury gem, or alternatively you can take their implementation from their source code
Here are a couple of ways that could be done.
arr = [:a1, :a2, :a3, :a4, :foo]
Use Enumerable#reduce (aka inject)
def hashify(arr)
arr[0..-3].reverse_each.reduce(arr[-2]=>arr[-1]) { |h,x| { x=>h } }
end
hashify(arr)
#=> {:a1=>{:a2=>{:a3=>{:a4=>:foo}}}}
Use recursion
def hashify(arr)
first, *rest = arr
rest.size == 1 ? { first=>rest.first } : { first=>hashify(rest) }
end
hashify(arr)
#=> {:a1=>{:a2=>{:a3=>{:a4=>:foo}}}}
You can consider using the get and set methods from the rodash gem in order to set a deeply nested value into a hash with a default value.
require 'rodash'
a = {}
key = ['x', 'y', 'z']
default_value = []
value = 8
current_value = Rodash.get(a, key, default_value)
Rodash.set(a, key, current_value << value)
a
# => {"x"=>{"y"=>{"z"=>[8]}}}
I use the following to deeply set/initialize arrays/hashes using a list of keys. If they keys are ints, it assumes indexing into an array, otherwise it assumes a hash:
def deep_set(target, path, value)
key = path.shift
return target[key] = value if path.empty?
child = target[key]
return deep_set(child, path, value) if child
deep_set(
target[key] = path[0].is_a?(Integer) ? [] : {},
path,
value,
)
end
Here is an example of using it:
target = {first: [:old_data]}
deep_set(target, [:first, 1, 1, :lol], 'foo')
puts target # {:first=>[:old_data, [nil, {:lol=>"foo"}]]}
It uses the fact that ruby allows you to set-and-expand arrays on the fly, which is a bit wonky, but works nicely here.

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: {}}}}}

Is there a way to initialize hash's values lazily?

I have a hash like:
h = {
a: '/users/sign_up',
b: "/user/#{#user.id]}"
}
Later I do h[:b].
Hash values are initialized when hash itself is initialized. But I'd want #user.id to be invoked every time when h[:b] is invoked.
It seems to be not possible to do it with Ruby's hash. But is there some workaround to do it?
You can use lambdas for the values of the hash, and call the lambda when the actual value is needed, e.g.:
h = {
a: ->{'/users/sign_up'},
b: ->{"/user/#{#user.id}"}
}
h[:b].call
h = {}
h.default_proc = proc do |hash, key|
key == :b ? "/user/#{#user.id}" : nil
end
h[:a] #=> nil
h[:b] #=> "/user/<id>"

Is there a way to inject into a hash in coffescript, just like in Ruby?

In Ruby, i can do this:
hash = ['foo', 'bar'].each_with_object({}) { |i, h| h[i] = 0 }
How do I do the same in CoffeeScript, preferably using some elegant one-liner?
One way to do it would be like this:
hash = {}
hash[key] = 0 for key in ["foo", "bar"]
Also, in the Ruby example, you can use each_with_object instead of inject so that you don't need to return the h variable at the end:
hash = ['foo', 'bar'].each_with_object({}) { |i, h| h[i] = 0 }

Resources