ruby hash setup - ruby-on-rails

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

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"}}}

hash with array rails not chain value but delete

I've tried to insert some Values into this hash were every key is an array but when I print all result just the last value
def self.hash_builder(query)
statistic = Hash.new { |hash, key| hash[key] = [] }
if !query.empty?
query.each do |q|
statistic[:sell].push(q.total_sell.to_i)
statistic[:price].push(q.total_price.to_f)
end
else
statistic[:sell].push(0)
statistic[:price].push(0.0)
end
return statistic
end
I call this method after make a query, and I send to this the query with the new params, but every time i see inside this hash just the last query value
THIS IS THE RESULT
I'll answer here because the comment section doesn't allow enough room. You're wrong about <<. It ought to work fine.
$ irb
irb(main):001:0> s = Hash.new {|h, k| h[k] = [] }
=> {}
irb(main):002:0> s[:sell] << 1
=> [1]
irb(main):003:0> s[:sell] << 2
=> [1, 2]
irb(main):004:0> s[:sell]
=> [1, 2]
irb(main):005:0> s[:price]
=> []
But push should work, too.
irb(main):006:0> s[:sell].push(3)
=> [1, 2, 3]

Kind of ugly- default value for non-existent hash key?

I'm working with an API that returns a hash to represent a product:
prod = API.getProduct(id)
prod["name"] => "Widget"
The problem arrises because not all products contain identical attribute pages, so I find myself doing a lot of one-off error catching- some products will have a key for size or color, some won't.
What's the easiest way to get to prod["non-existent attribute"] => "NA"?
As Dave Newton said, you can add the default value to the hash constructor:
hash = Hash.new { |hash, key| hash[key] = "NA" }
hash[:anything] == "NA" # => true
Or use the #default method:
hash = Hash.new
hash.default = "NA"
hash[:anything] == "NA" # => true
EDIT The quick syntax for setting the default value when initializing the hash is:
hash = Hash.new("NA")
hash[:anything] == "NA" # => true
Take a look at this: http://www.ruby-doc.org/core-1.9.3/Hash.html#method-i-default
You can use prod.default = "NA".

Rails source code : initialize hash in a weird way?

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

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}

Resources