Rails source code : initialize hash in a weird way? - ruby-on-rails

in the rails source : https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb
the following can be seen
#load_hooks = Hash.new {|h,k| h[k] = [] }
Which in IRB just initializes an empty hash. What is the difference with doing
#load_hooks = Hash.new

Look at the ruby documentation for Hash
new → new_hash click to toggle source
new(obj) → new_hash
new {|hash, key| block } → new_hash
Returns a new, empty hash. If this hash is subsequently accessed by a key that doesn’t correspond to a hash entry, the value returned depends on the style of new used to create the hash. In the first form, the access returns nil. If obj is specified, this single object will be used for all default values. If a block is specified, it will be called with the hash object and the key, and should return the default value. It is the block’s responsibility to store the value in the hash if required.
Example form the docs
# While this creates a new default object each time
h = Hash.new { |hash, key| hash[key] = "Go Fish: #{key}" }
h["c"] #=> "Go Fish: c"
h["c"].upcase! #=> "GO FISH: C"
h["d"] #=> "Go Fish: d"
h.keys #=> ["c", "d"]

The difference is in handling missing values. First one returns empty Array, second returns nil:
irb(main):001:0> a = Hash.new {|h,k| h[k] = [] }
=> {}
irb(main):002:0> b = Hash.new
=> {}
irb(main):003:0> a[123]
=> []
irb(main):004:0> b[123]
=> nil
Here is the link to documentation: http://www.ruby-doc.org/core-1.9.3/Hash.html#method-c-new

Related

Is there an equivalent method for Ruby's `.dig` but where it assigens the values

Let's say we're using .dig in Ruby like this:
some_hash = {}
some_hash.dig('a', 'b', 'c')
# => nil
which returns nil
Is there a method where I can assign a value to the key c if any of the other ones are present? For example if I wanted to set c I would have to write:
some_hash['a'] = {} unless some_hash['a'].present?
some_hash['a']['b'] = {} unless some_hash['a']['b'].present?
some_hash['a']['b']['c'] = 'some value'
Is there a better way of writing the above?
That can be easily achieved when you initialize the hash with a default like this:
hash = Hash.new { |hash, key| hash[key] = Hash.new(&hash.default_proc) }
hash[:a][:b][:c] = 'some value'
hash
#=> {:a=>{:b=>{:c=>"some value"}}}
Setting nested values in that hash with nested defaults can partly be done with dig (apart from the last key):
hash.dig(:a, :b)[:c] = 'some value'
hash
#=> {:a=>{:b=>{:c=>"some value"}}}

How to build a hash of directory names as keys and file names as values in Ruby?

I have directories with files and I would like to build a hash of directory names as keys and file names as values. Example:
/app/foo/create.json
/app/foo/update.json
/app/bar/create.json
/app/bar/update.json
Output:
{
"foo" => {
"create.json" => {},
"update.json" => {}
},
"bar" => {
"create.json" => {},
"update.json" => {}
}
}
Currently I'd doing this:
OUTPUT ||= {}
Dir.glob('app', '**', '*.json')) do |file|
OUTPUT[File.basename(file)] = File.read(file)
end
But it's not working as expected, I'm not sure how to get the parent directory name.
Dir.glob('*/*.json', base: 'app').each_with_object(Hash.new {|g,k| g[k]={}}) do |fname,h|
h[File.dirname(fname)].update(File.basename(fname)=>{})
end
#=> {"foo"=>{"create.json"=>{}, "update.json"=>{}},
# "bar"=>{"update.json"=>{}, "create.json"=>{}}}
#Amadan explains the use of Dir#glob, which is exactly as in his answer. I have employed the version of Hash::new that invokes a block (here {|g,k| g[k]={}}) when g[k] is executed and the hash g does not have a key k.1. See also Hash#update (aka merge!), File::dirname and File::basename.
The steps are as follows.
a = Dir.glob('*/*.json', base: 'app')
#=> ["foo/create.json", "foo/update.json", "bar/update.json", "bar/create.json"]
enum = a.each_with_object(Hash.new {|g,k| g[k]={}})
#=> #<Enumerator: ["foo/create.json", "foo/update.json", "bar/update.json",
# "bar/create.json"]:each_with_object({})>
The first value is generate by the enumerator and passed to the block, and the block variables are assigned values by the process of array decomposition:
fname, h = enum.next
#=> ["foo/create.json", {}]
fname
#=> "foo/create.json"
h #=> {}
d = File.dirname(fname)
#=> "foo"
b = File.basename(fname)
#=> "create.json"
h[d].update(b=>{})
#=> {"create.json"=>{}}
See Enumerator#next. The next value is generated by enum and passed to the block, the block variables are assigned values and the block calculations are performed. (Notice that the hash being built, h, has been updated in the following.)
fname, h = enum.next
#=> ["foo/update.json", {"foo"=>{"create.json"=>{}}}]
fname
#=> "foo/update.json"
h #=> {"foo"=>{"create.json"=>{}}}
d = File.dirname(fname)
#=> "foo"
b = File.basename(fname)
#=> "update.json"
h[d].update(b=>{})
#=> {"create.json"=>{}, "update.json"=>{}}
Twice more.
fname, h = enum.next
#=> ["bar/update.json", {"foo"=>{"create.json"=>{}, "update.json"=>{}}}]
d = File.dirname(fname)
#=> "bar"
b = File.basename(fname)
#=> "update.json"
h[d].update(b=>{})
#=> {"update.json"=>{}}
fname, h = enum.next
#=> ["bar/create.json",
# {"foo"=>{"create.json"=>{}, "update.json"=>{}}, "bar"=>{"update.json"=>{}}}]
d = File.dirname(fname)
#=> "bar"
b = File.basename(fname)
#=> "create.json"
h[d].update(b=>{})
#=> {"update.json"=>{}, "create.json"=>{}}
h #=> {"foo"=>{"create.json"=>{}, "update.json"=>{}},
# "bar"=>{"update.json"=>{}, "create.json"=>{}}}
1. This is equivalent to defining the hash as follows: g = {}; g.default_proc = proc {|g,k| g[k]={}}. See Hash#default_proc=.
An alternative to regexp:
output =
Dir.glob('*/*.json', base: 'app').
group_by(&File::method(:dirname)).
transform_values { |files|
files.each_with_object({}) { |file, hash|
hash[File.basename(file)] = File.read(file)
}
}
Note the base: keyword argument to File.glob (or Pathname.glob, for that matter) which simplifies things as we don't need to remove app; also that for the purposes of OP's question there only needs to be one directory level, so * instead of **.

Creating a new hash with default keys

I want to create a hash with an index that comes from an array.
ary = ["a", "b", "c"]
h = Hash.new(ary.each{|a| h[a] = 0})
My goal is to start with a hash like this:
h = {"a"=>0, "b"=>0, "c"=>0}
so that later when the hash has changed I can reset it with h.default
Unfortunately the way I'm setting up the hash is not working... any ideas?
You should instantiate your hash h first, and then fill it with the contents of the array:
h = {}
ary = ["a", "b", "c"]
ary.each{|a| h[a] = 0}
Use the default value feature for the hash
h = Hash.new(0)
h["a"] # => 0
In this approach, the key is not set.
h.key?("a") # => false
Other approach is to set the missing key when accessed.
h = Hash.new {|h, k| h[k] = 0}
h["a"] # => 0
h.key?("a") # => true
Even in this approach, the operations like key? will fail if you haven't accessed the key before.
h.key?("b") # => false
h["b"] # => 0
h.key?("b") # => true
You can always resort to brute force, which has the least boundary conditions.
h = Hash.new.tap {|h| ["a", "b", "c"].each{|k| h[k] = 0}}
h.key?("b") # => true
h["b"] # => 0
You can do it like this where you expand a list into zero-initialized values:
list = %w[ a b c ]
hash = Hash[list.collect { |i| [ i, 0 ] }]
You can also make a Hash that simply has a default value of 0 for any given key:
hash = Hash.new { |h, k| h[k] = 0 }
Any new key referenced will be pre-initialized to the default value and this will avoid having to initialize the whole hash.
This may not be the most efficient way, but I always appreciate one-liners that reveal a little more about Ruby's versatility:
h = Hash[['a', 'b', 'c'].collect { |v| [v, 0] }]
Or another one-liner that does the same thing:
h = ['a', 'b', 'c'].inject({}) {|h, v| h[v] = 0; h }
By the way, from a performance standpoint, the one-liners run about 80% of the speed of:
h = {}
ary = ['a','b','c']
ary.each { |a| h[a]=0 }
Rails 6 added index_with on Enumerable module. This will help in creating a hash from an enumerator with default or fetched values.
ary = %w[a b c]
hash = ary.index_with(0) # => {"a"=>0, "b"=>0, "c"=>0}
Another option is to use the Enum#inject method which I'm a fan of for its cleanliness. I haven't benchmarked it compared to the other options though.
h = ary.inject({}) {|hash, key| hash[key] = 0; hash}
Alternate way of having a hash with the keys actually added
Hash[[:a, :b, :c].zip([])] # => {:a=>nil, :b=>nil, :c=>nil}
Hash[[:a, :b, :c].zip(Array.new(3, 0))] # => {:a=>0, :b=>0, :c=>0}

ruby hash setup

I found this in my some code I was working on and I was wondering what this is doing
h = Hash.new {|hash, key| hash[key] = 0}
=> {}
When a block is passed to Hash.new that block is called each time a non-existent key is accessed. Eg:
h = Hash.new { |hash, key| hash[key] = "Default" }
h[:defined_key] = "Example"
puts h[:defined_key] # => "Example"
puts h[:undefined_key] # => "Default"
See http://ruby-doc.org/core/classes/Hash.html#M000718 for more detail.
http://ruby-doc.org/core/classes/Hash.html#M000718
This block defines what the hash does when accessing a nonexistent key. So if there is no value for a key, then it sets the value to 0, and then returns 0 as the value.
It's not just good for defaults - you could have it throw an exception of there is no such key, for example. In fact, if you just want a default value, you can say:
Hash.new "defaultValue"
Its making the default values for any new keys equal to zero instead of nil, observe the test in and irb console session:
$ irb
>> normal_hash = Hash.new
=> {}
>> normal_hash[:new_key]
=> nil
>> h = Hash.new {|hash, key| hash[key] = 0}
=> {}
>> h[:new_key]
=> 0

There has got to be a cleaner way to do this

I have this code here and it works but there has to be a better way.....i need two arrays that look like this
[
{
"Vector Arena - Auckland Central, New Zealand" => {
"2010-10-10" => [
"Enter Sandman",
"Unforgiven",
"And justice for all"
]
}
},
{
"Brisbane Entertainment Centre - Brisbane Qld, Austr..." => {
"2010-10-11" => [
"Enter Sandman"
]
}
}
]
one for the past and one for the upcoming...the problem i have is i am repeating myself and though it works i want to clean it up ...here is my data
..
Try this:
h = Hash.new {|h1, k1| h1[k1] = Hash.new{|h2, k2| h2[k2] = []}}
result, today = [ h, h.dup], Date.today
Request.find_all_by_artist("Metallica",
:select => "DISTINCT venue, showdate, LOWER(song) AS song"
).each do |req|
idx = req.showdate < today ? 0 : 1
result[idx][req.venue][req.showdate] << req.song.titlecase
end
Note 1
In the first line I am initializing an hash of hashes. The outer hash creates the inner hash when a non existent key is accessed. An excerpt from Ruby Hash documentation:
If this hash is subsequently accessed by a key that doesn‘t correspond to a hash
entry, the block will be called with the hash object and the key, and should
return the default value. It is the block‘s responsibility to store the value in
the hash if required.
The inner hash creates and empty array when the non existent date is accessed.
E.g: Construct an hash containing of content as values and date as keys:
Without a default block:
h = {}
list.each do |data|
h[data.date] = [] unless h[data.date]
h[data.date] << data.content
end
With a default block
h = Hash.new{|h, k| h[k] = []}
list.each do |data|
h[data.date] << data.content
end
Second line simply creates an array with two items to hold the past and future data. Since both past and the present stores the data as Hash of Hash of Array, I simply duplicate the value.
Second line can also be written as
result = [ h, h.dup]
today = Date.today

Resources