Working with Hashes that have a default value - ruby

Am learning to code with ruby. I am learning about hashes and i dont understand this code: count = Hash.new(0). It says that the 0 is a default value, but when i run it on irb it gives me an empty hash {}. If 0 is a default value why can't i see something like count ={0=>0}. Or is the zero an accumulator but doesn't go to the keys or values? Thanks

0 will be the fallback if you try to access a key in the hash that doesn't exist
For example:
count = Hash.new -> count['key'] => nil
vs
count = Hash.new(0) -> count['key'] => 0

To expand on the answer from #jeremy-ramos and comment from #mu-is-too-short.
There are two common gotcha's with defaulting hash values in this way.
1. Accidentally shared references.
Ruby uses the exact same object in memory that you pass in as the default value for every missed key.
For an immutable object (like 0), there is no problem. However you might want to write code like:
hash = Hash.new([])
hash[key] << value
or
hash = Hash.new({})
hash[key][second_key] = value
This will not do what you'd expect. Instead of hash[unknown_key] returning a new, empty array or hash it will return the exact same array/hash object for every key.
so doing:
hash = Hash.new([])
hash[key1] << value1
hash[key2] << value2
results in a hash where key1 and key2 both point to the same array object containing [value1, value2]
See related question here
Solution
To solve this you can create a hash with a default block argument instead (which is called whenever a missing key is accessed and lets you assign a value to the missed key)
hash = Hash.new{|h, key| h[key] = [] }
2. Assignment of missed keys with default values
When you access a missing key that returns the default value, you might expect that the hash will now contain that key with the value returned. It does not. Ruby does not modify the hash, it simply returns the default value. So, for example:
hash = Hash.new(0) #$> {}
hash.keys.empty? #$> true
hash[:foo] #$> 0
hash[:foo] == 0 #$> true
hash #$> {}
hash.keys.empty? #$> true
Solution
This confusion is also addressed using the block approach, where they keys value can be explicitly set.

The Hash.new docs are not very clear on this. I hope that the example below clarifies the difference and one of the frequent uses of Hash.new(0).
The first chunk of code uses Hash.new(0). The hash has a default value of 0, and when new keys are encountered, their value is 0. This method can be used to count the characters in the array.
The second chunk of code fails, because the default value for the key (when not assigned) is nil. This value cannot be used in addition (when counting), and generates an error.
count = Hash.new(0)
puts "count=#{count}"
# count={}
%w[a b b c c c].each do |char|
count[char] += 1
end
puts "count=#{count}"
# count={"a"=>1, "b"=>2, "c"=>3}
count = Hash.new
puts "count=#{count}"
%w[a b b c c c].each do |char|
count[char] += 1
# Fails: in `block in <main>': undefined method `+' for nil:NilClass (NoMethodError)
end
puts "count=#{count}"
SEE ALSO:
What's the difference between "Hash.new(0)" and "{}"

TL;DR When you initialize hash using Hash.new you can setup default value or default proc (the value that would be returned if given key does not exist)
Regarding the question to understand this magic firstly you need to know that Ruby hashes have default values. To access default value you can use Hash#default method
This default value by default :) is nil
hash = {}
hash.default # => nil
hash[:key] # => nil
You can set default value with Hash#default=
hash = {}
hash.default = :some_value
hash[:key] # => :some_value
Very important note: it is dangerous to use mutable object as default because of side effect like this:
hash = {}
hash.default = []
hash[:key] # => []
hash[:other_key] << :some_item # will mutate default value
hash[:key] # => [:some_value]
hash.default # => [:some_value]
hash # => {}
To avoid this you can use Hash#default_proc and Hash#default_proc= methods
hash = {}
hash.default_proc # => nil
hash.default_proc = proc { [] }
hash[:key] # => []
hash[:other_key] << :some_item # will not mutate default value
hash[:other_key] # => [] # because there is no this key
hash[:other_key] = [:symbol]
hash[:other_key] << :some_item
hash[:other_key] # => [:symbol, :some_item]
hash[:key] # => [] # still empty array as default
Setting default cancels default_proc and vice versa
hash = {}
hash.default = :default
hash.default_proc = proc { :default_proc }
hash[:key] # => :default_proc
hash.default = :default
hash[:key] # => :default
hash.default_proc # => nil
Going back to Hash.new
When you pass argument to this method, you initialize default value
hash = Hash.new(0)
hash.default # => 0
hash.default_proc # => nil
When you pass block to this method, you initialize default proc
hash = Hash.new { 0 }
hash.default # => nil
hash[:key] # => 0

Related

How do I add an object to an array, where the array is a value to a key in a hash?

So basically my code is as follows
anagrams = Hash.new([])
self.downcase.scan(/\b[a-z]+/i).each do |key|
anagrams[key.downcase.chars.sort] = #push key into array
end
so basically the hash would look like this
anagrams = { "abcdef" => ["fdebca", "edfcba"], "jklm" => ["jkl"]}
Basically what I don't understand is how to push "key" (which is obviously a string) as the value to "eyk"
I've been searching for awhile including documentation and other stackflow questions and this was my best guess
anagrams[key.downcase.chars.sort].push(key)
Your guess:
anagrams[key.downcase.chars.sort].push(key)
is right. The problem is your hash's default value:
anagrams = Hash.new([])
A default value doesn't automatically create an entry in the hash when you reference it, it just returns the value. That means that you can do this:
h = Hash.new([])
h[:k].push(6)
without changing h at all. The h[:k] gives you the default value ([]) but it doesn't add :k as a key. Also note that the same default value is used every time you try to access a key that isn't in the hash so this:
h = Hash.new([])
a = h[:k].push(6)
b = h[:x].push(11)
will leave you with [6,11] in both a and b but nothing in h.
If you want to automatically add defaults when you access them, you'll need to use a default_proc, not a simple default:
anagrams = Hash.new { |h, k] h[k] = [ ] }
That will create the entries when you access a non-existent key and give each one a different empty array.
It's not entirely clear what your method is supposed to do, but I think the problem is that you don't have an array to push a value onto.
In Ruby you can pass a block to Hash.new that tells it what to do when you try to access a key that doesn't exist. This is a handy way to automatically initialize values as empty arrays. For example:
hsh = Hash.new {|hsh, key| hsh[key] = [] }
hsh[:foo] << "bar"
p hsh # => { :foo => [ "bar" ] }
In your method (which I assume you're adding to the String class), you would use it like this:
class String
def my_method
anagrams = Hash.new {|hsh, key| hsh[key] = [] }
downcase.scan(/\b[a-z]+/i).each_with_object(anagrams) do |key|
anagrams[key.downcase.chars.sort.join] << key
end
end
end

Difference between Ruby's .push and << [duplicate]

This question already has answers here:
Ruby - Difference between Array#<< and Array#push
(5 answers)
Closed 8 years ago.
Here's an example with push:
#connections = Hash.new []
#connections[1] = #connections[1].push(2)
puts #connections # => {1=>[2]}
Here's an example with <<
#connections = Hash.new []
#connections[1] << 2
puts #connections # => {}
For some reason the output (#connections) is different, but why? I'm guessing it has something to do with Ruby object model?
Perhaps the new hash object [] is being create each time, but not saved? But why?
The difference in your code isn't about << vs. push, it's about the fact that you re-assign in one case and don't in the other. The following two pieces of code are equivalent:
#connections = Hash.new []
#connections[1] = #connections[1].push(2)
puts #connections # => {1=>[2]}
#connections = Hash.new []
#connections[1] = (#connections[1] << 2)
puts #connections # => {1=>[2]}
As are these two:
#connections = Hash.new []
#connections[1].push(2)
puts #connections # => {}
#connections = Hash.new []
#connections[1] << 2
puts #connections # => {}
The reason that re-assignment makes a difference here is that accessing a default value, does not automatically add an entry for it to the hash. That is if you have h = Hash.new(0) and then you do p h[0], you'll print 0, but the value of h will still be {} (not {0 => 0}) because the 0 is not added to the hash. If you do h[0] += 1, this will call the []= method on the hash and actually add an entry for 0 to it, so h becomes {0 => 1}.
So when you do #connections[1] << 2 in your code, you get the default array and perform << on it, but you don't store anything in #connections, so it stays {}. When you do #connections[i] = #connections[i].push(2) or #connections[i] = (#connections[i] << 2), you're calling []=, so the entry gets added to the hash.
However you should note that the hash will return a reference to the same array each time, so even if you do add the entry to the hash, it will likely still not behave as you expect once you add more than one entry (since all entries refer to the same array):
#connections = Hash.new []
#connections[1] = #connections[1].push(2)
#connections[2] = #connections[2].push(42)
puts #connections # => {1 => [2, 42], 2 => [2, 42]}
What you really want is a hash that returns a reference to a new array each time that a new key is accessed and that automatically adds an entry for the new array when that happens. To do that you can use the block form of Hash.new like this:
#connections = Hash.new do |h, k|
h[k] = []
end
#connections[1].push(2)
#connections[2].push(42)
puts #connections # => {1 => [2], 2 => [42]}
Note that when you write
h = Hash.new |this_hash, non_existent_key| { this_hash[non_existent_key] = [] }
...Ruby will execute the block whenever you try to lookup a key that doesn't exist, and then return the block's return value. A block is like a def in that all variables inside it(including the parameter variables) are created anew every time the block is called. In addition, note that [] is an Array constructor, and each time it is called, it creates a new array.
A block returns the result of the last statement that was executed in the block, which is the assignment statement:
this_hash[non_existent_key] = []
And an assignment statement returns the right hand side, which will be a reference to the same Array that was assigned to the key in the hash, so any changes to the returned Array will change the Array in the hash.
On the other hand, when you write:
Hash.new([])
The [] constructor creates a new, empty Array; and that Array becomes the argument for Hash.new(). There is no block for ruby to call every time you look up a non existent key, so ruby just returns that one Array as the value for ALL non-existent keys--and very importantly nothing is done to the hash.

Ruby Hash Interaction With Pushing Onto Array

So let's say I do the following:
lph = Hash.new([]) #=> {}
lph["passed"] << "LCEOT" #=> ["LCEOT"]
lph #=> {} <-- Expected that to have been {"passed" => ["LCEOT"]}
lph["passed"] #=> ["LCEOT"]
lph["passed"] = lph["passed"] << "HJKL"
lph #=> {"passed"=>["LCEOT", "HJKL"]}
I'm surprised by this. A couple questions:
Why does it not get set until I push the second string on to the array? What is happening in the background?
What is the more idiomatic ruby way to essentially say. I have a hash, a key, and a value I want to to end up in the array associated with the key. How do I push the value in an array associated with a key into a hash the first time. In all future uses of the key, I just want to addd to the array.
Read the Ruby Hash.new documentation carefully - "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".
new(obj) → new_hash
...If obj is specified, this single object will be used for all default values.
In your example you attempt to push something onto the value associated with a key which does not exist, so you end up mutating the same anonymous array you used to construct the hash initially.
the_array = []
h = Hash.new(the_array)
h['foo'] << 1 # => [1]
# Since the key 'foo' was not found
# ... the default value (the_array) is returned
# ... and 1 is pushed onto it (hence [1]).
the_array # => [1]
h # {} since the key 'foo' still has no value.
You probably want to use the block form:
new { |hash, key| block } → new_hash
...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.
For example:
h = Hash.new { |hash, key| hash[key] = [] } # Assign a new array as default for missing keys.
h['foo'] << 1 # => [1]
h['foo'] << 2 # => [1, 2]
h['bar'] << 3 # => [3]
h # => { 'foo' => [1, 2], 'bar' => [3] }
Why does it not get set until I push the second string on to the array?
In short; because you don't set anything in the hash until the point, where you also add the second string to the array.
What is happening in the background?
To see what's happening in the background, let's take this one line at a time:
lph = Hash.new([]) #=> {}
This creates an empty hash, configured to return the [] object whenever a non-existing key is accessed.
lph["passed"] << "LCEOT" #=> ["LCEOT"]
This can be written as
value = lph["passed"] #=> []
value << "LCEOT" #=> ["LCEOT"]
We see that lph["passed"] returns [] as expected, and we then proceed to append "LCEOT" to [].
lph #=> {}
lph is still an empty Hash. At no point have we added anything to the Hash. We have added something to its default value, but that doesn't change lph itself.
lph["passed"] #=> ["LCEOT"]
This is where it gets interesting. Remember above when we did value << ["LCEOT"]. That actually changed the default value that lph returns when a key isn't found. The default value is no longer [], but has become ["LCEOT"]. That new default value is returned here.
lph["passed"] = lph["passed"] << "HJKL"
This is our first change to lph. And what we actually assign to lph["passed"] is the default value (because "passed" is still a non-existing key in lph) with "HJKL" appended. Before this, the default value was ["LCEOT"], after this it is ["LCEOT", "HJKL"].
In other words lph["passed"] << "HJKL" returns ["LCEOT", "HJKL"] which is then assigned to lph["passed"].
What is the more idiomatic Ruby way
Using <<=:
>> lph = Hash.new { [] }
=> {}
>> lph["passed"] <<= "LCEOT"
=> ["LCEOT"]
>> lph
=> {"passed"=>["LCEOT"]}
Also note the change in how the Hash is initialized, using a block instead of a verbatim array. This ensures a new, blank array is created and returned whenever a new key is accessed, as opposed to the same array being used every time.

Ruby hash default value behavior

I'm going through Ruby Koans, and I hit #41 which I believe is this:
def test_default_value_is_the_same_object
hash = Hash.new([])
hash[:one] << "uno"
hash[:two] << "dos"
assert_equal ["uno","dos"], hash[:one]
assert_equal ["uno","dos"], hash[:two]
assert_equal ["uno","dos"], hash[:three]
assert_equal true, hash[:one].object_id == hash[:two].object_id
end
It could not understand the behavior so I Googled it and found Strange ruby behavior when using Hash default value, e.g. Hash.new([]) that answered the question nicely.
So I understand how that works, my question is, why does a default value such as an integer that gets incremented not get changed during use? For example:
puts "Text please: "
text = gets.chomp
words = text.split(" ")
frequencies = Hash.new(0)
words.each { |word| frequencies[word] += 1 }
This will take user input and count the number of times each word is used, it works because the default value of 0 is always used.
I have a feeling it has to do with the << operator but I'd love an explanation.
The other answers seem to indicate that the difference in behavior is due to Integers being immutable and Arrays being mutable. But that is misleading. The difference is not that the creator of Ruby decided to make one immutable and the other mutable. The difference is that you, the programmer decided to mutate one but not the other.
The question is not whether Arrays are mutable, the question is whether you mutate it.
You can get both the behaviors you see above, just by using Arrays. Observe:
One default Array with mutation
hsh = Hash.new([])
hsh[:one] << 'one'
hsh[:two] << 'two'
hsh[:nonexistent]
# => ['one', 'two']
# Because we mutated the default value, nonexistent keys return the changed value
hsh
# => {}
# But we never mutated the hash itself, therefore it is still empty!
One default Array without mutation
hsh = Hash.new([])
hsh[:one] += ['one']
hsh[:two] += ['two']
# This is syntactic sugar for hsh[:two] = hsh[:two] + ['two']
hsh[:nonexistant]
# => []
# We didn't mutate the default value, it is still an empty array
hsh
# => { :one => ['one'], :two => ['two'] }
# This time, we *did* mutate the hash.
A new, different Array every time with mutation
hsh = Hash.new { [] }
# This time, instead of a default *value*, we use a default *block*
hsh[:one] << 'one'
hsh[:two] << 'two'
hsh[:nonexistent]
# => []
# We *did* mutate the default value, but it was a fresh one every time.
hsh
# => {}
# But we never mutated the hash itself, therefore it is still empty!
hsh = Hash.new {|hsh, key| hsh[key] = [] }
# This time, instead of a default *value*, we use a default *block*
# And the block not only *returns* the default value, it also *assigns* it
hsh[:one] << 'one'
hsh[:two] << 'two'
hsh[:nonexistent]
# => []
# We *did* mutate the default value, but it was a fresh one every time.
hsh
# => { :one => ['one'], :two => ['two'], :nonexistent => [] }
It is because Array in Ruby is mutable object, so you can change it internal state, but Fixnum isn't mutable. So when you increment value using += internally it get that (assume that i is our reference to Fixnum object):
get object referenced by i
get it internal value (lets name it raw_tmp)
create new object that internal value is raw_tmp + 1
assign reference to created object to i
So as you can see, we created new object, and i reference now to something different than at the beginning.
In the other hand, when we use Array#<< it works that way:
get object referenced by arr
to it's internal state append given element
So as you can see it is much simpler, but it can cause some bugs. One of them you have in your question, another one is thread race when booth are trying simultaneously append 2 or more elements. Sometimes you can end with only some of them and with thrashes in memory, when you use += on arrays too, you will get rid of both of these problems (or at least minimise impact).
From the doc, setting a default value has the following behaviour:
Returns the default value, the value that would be returned by hsh if key did not exist in hsh. See also Hash::new and Hash#default=.
Therefore, every time frequencies[word] is not set, the value for that individual key is set to 0.
The reason for the discrepancy between the two code blocks is that arrays are mutable in Ruby, while integers are not.

Accessing elements of nested hashes in ruby [duplicate]

This question already has answers here:
Ruby Style: How to check whether a nested hash element exists
(16 answers)
How to avoid NoMethodError for nil elements when accessing nested hashes? [duplicate]
(4 answers)
Closed 7 years ago.
I'm working a little utility written in ruby that makes extensive use of nested hashes. Currently, I'm checking access to nested hash elements as follows:
structure = { :a => { :b => 'foo' }}
# I want structure[:a][:b]
value = nil
if structure.has_key?(:a) && structure[:a].has_key?(:b) then
value = structure[:a][:b]
end
Is there a better way to do this? I'd like to be able to say:
value = structure[:a][:b]
And get nil if :a is not a key in structure, etc.
Traditionally, you really had to do something like this:
structure[:a] && structure[:a][:b]
However, Ruby 2.3 added a method Hash#dig that makes this way more graceful:
structure.dig :a, :b # nil if it misses anywhere along the way
There is a gem called ruby_dig that will back-patch this for you.
Hash and Array have a method called dig.
value = structure.dig(:a, :b)
It returns nil if the key is missing at any level.
If you are using a version of Ruby older than 2.3, you can install a gem such as ruby_dig or hash_dig_and_collect, or implement this functionality yourself:
module RubyDig
def dig(key, *rest)
if value = (self[key] rescue nil)
if rest.empty?
value
elsif value.respond_to?(:dig)
value.dig(*rest)
end
end
end
end
if RUBY_VERSION < '2.3'
Array.send(:include, RubyDig)
Hash.send(:include, RubyDig)
end
The way I usually do this these days is:
h = Hash.new { |h,k| h[k] = {} }
This will give you a hash that creates a new hash as the entry for a missing key, but returns nil for the second level of key:
h['foo'] -> {}
h['foo']['bar'] -> nil
You can nest this to add multiple layers that can be addressed this way:
h = Hash.new { |h, k| h[k] = Hash.new { |hh, kk| hh[kk] = {} } }
h['bar'] -> {}
h['tar']['zar'] -> {}
h['scar']['far']['mar'] -> nil
You can also chain indefinitely by using the default_proc method:
h = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }
h['bar'] -> {}
h['tar']['star']['par'] -> {}
The above code creates a hash whose default proc creates a new Hash with the same default proc. So, a hash created as a default value when a lookup for an unseen key occurs will have the same default behavior.
EDIT: More details
Ruby hashes allow you to control how default values are created when a lookup occurs for a new key. When specified, this behavior is encapsulated as a Proc object and is reachable via the default_proc and default_proc= methods. The default proc can also be specified by passing a block to Hash.new.
Let's break this code down a little. This is not idiomatic ruby, but it's easier to break it out into multiple lines:
1. recursive_hash = Hash.new do |h, k|
2. h[k] = Hash.new(&h.default_proc)
3. end
Line 1 declares a variable recursive_hash to be a new Hash and begins a block to be recursive_hash's default_proc. The block is passed two objects: h, which is the Hash instance the key lookup is being performed on, and k, the key being looked up.
Line 2 sets the default value in the hash to a new Hash instance. The default behavior for this hash is supplied by passing a Proc created from the default_proc of the hash the lookup is occurring in; ie, the default proc the block itself is defining.
Here's an example from an IRB session:
irb(main):011:0> recursive_hash = Hash.new do |h,k|
irb(main):012:1* h[k] = Hash.new(&h.default_proc)
irb(main):013:1> end
=> {}
irb(main):014:0> recursive_hash[:foo]
=> {}
irb(main):015:0> recursive_hash
=> {:foo=>{}}
When the hash at recursive_hash[:foo] was created, its default_proc was supplied by recursive_hash's default_proc. This has two effects:
The default behavior for recursive_hash[:foo] is the same as recursive_hash.
The default behavior for hashes created by recursive_hash[:foo]'s default_proc will be the same as recursive_hash.
So, continuing in IRB, we get the following:
irb(main):016:0> recursive_hash[:foo][:bar]
=> {}
irb(main):017:0> recursive_hash
=> {:foo=>{:bar=>{}}}
irb(main):018:0> recursive_hash[:foo][:bar][:zap]
=> {}
irb(main):019:0> recursive_hash
=> {:foo=>{:bar=>{:zap=>{}}}}
I made rubygem for this. Try vine.
Install:
gem install vine
Usage:
hash.access("a.b.c")
I think one of the most readable solutions is using Hashie:
require 'hashie'
myhash = Hashie::Mash.new({foo: {bar: "blah" }})
myhash.foo.bar
=> "blah"
myhash.foo?
=> true
# use "underscore dot" for multi-level testing
myhash.foo_.bar?
=> true
myhash.foo_.huh_.what?
=> false
value = structure[:a][:b] rescue nil
Solution 1
I suggested this in my question before:
class NilClass; def to_hash; {} end end
Hash#to_hash is already defined, and returns self. Then you can do:
value = structure[:a].to_hash[:b]
The to_hash ensures that you get an empty hash when the previous key search fails.
Solution2
This solution is similar in spirit to mu is too short's answer in that it uses a subclass, but still somewhat different. In case there is no value for a certain key, it does not use a default value, but rather creates a value of empty hash, so that it does not have the problem of confusion in assigment that DigitalRoss's answer has, as was pointed out by mu is too short.
class NilFreeHash < Hash
def [] key; key?(key) ? super(key) : self[key] = NilFreeHash.new end
end
structure = NilFreeHash.new
structure[:a][:b] = 3
p strucrture[:a][:b] # => 3
It departs from the specification given in the question, though. When an undefined key is given, it will return an empty hash instread of nil.
p structure[:c] # => {}
If you build an instance of this NilFreeHash from the beginning and assign the key-values, it will work, but if you want to convert a hash into an instance of this class, that may be a problem.
You could just build a Hash subclass with an extra variadic method for digging all the way down with appropriate checks along the way. Something like this (with a better name of course):
class Thing < Hash
def find(*path)
path.inject(self) { |h, x| return nil if(!h.is_a?(Thing) || h[x].nil?); h[x] }
end
end
Then just use Things instead of hashes:
>> x = Thing.new
=> {}
>> x[:a] = Thing.new
=> {}
>> x[:a][:b] = 'k'
=> "k"
>> x.find(:a)
=> {:b=>"k"}
>> x.find(:a, :b)
=> "k"
>> x.find(:a, :b, :c)
=> nil
>> x.find(:a, :c, :d)
=> nil
This monkey patch function for Hash should be easiest (at least for me). It also doesn't alter structure i.e. changing nil's to {}. It would still also apply even if you're reading a tree from a raw source e.g. JSON. It also doesn't need to produce empty hash objects as it goes or parse a string. rescue nil was actually a good easy solution for me as I'm brave enough for such a low risk but I find it to essentially have a drawback with performance.
class ::Hash
def recurse(*keys)
v = self[keys.shift]
while keys.length > 0
return nil if not v.is_a? Hash
v = v[keys.shift]
end
v
end
end
Example:
> structure = { :a => { :b => 'foo' }}
=> {:a=>{:b=>"foo"}}
> structure.recurse(:a, :b)
=> "foo"
> structure.recurse(:a, :x)
=> nil
What's also good is that you can play around saved arrays with it:
> keys = [:a, :b]
=> [:a, :b]
> structure.recurse(*keys)
=> "foo"
> structure.recurse(*keys, :x1, :x2)
=> nil
The XKeys gem will read and auto-vivify-on-write nested hashes (::Hash) or hashes and arrays (::Auto, based on the key/index type) with a simple, clear, readable, and compact syntax by enhancing #[] and #[]=. The sentinel symbol :[] will push onto the end of an array.
require 'xkeys'
structure = {}.extend XKeys::Hash
structure[:a, :b] # nil
structure[:a, :b, :else => 0] # 0 (contextual default)
structure[:a] # nil, even after above
structure[:a, :b] = 'foo'
structure[:a, :b] # foo
You can use the andand gem, but I'm becoming more and more wary of it:
>> structure = { :a => { :b => 'foo' }} #=> {:a=>{:b=>"foo"}}
>> require 'andand' #=> true
>> structure[:a].andand[:b] #=> "foo"
>> structure[:c].andand[:b] #=> nil
There is the cute but wrong way to do this. Which is to monkey-patch NilClass to add a [] method that returns nil. I say it is the wrong approach because you have no idea what other software may have made a different version, or what behavior change in a future version of Ruby can be broken by this.
A better approach is to create a new object that works a lot like nil but supports this behavior. Make this new object the default return of your hashes. And then it will just work.
Alternately you can create a simple "nested lookup" function that you pass the hash and the keys to, which traverses the hashes in order, breaking out when it can.
I would personally prefer one of the latter two approaches. Though I think it would be cute if the first was integrated into the Ruby language. (But monkey-patching is a bad idea. Don't do that. Particularly not to demonstrate what a cool hacker you are.)
Not that I would do it, but you can Monkeypatch in NilClass#[]:
> structure = { :a => { :b => 'foo' }}
#=> {:a=>{:b=>"foo"}}
> structure[:x][:y]
NoMethodError: undefined method `[]' for nil:NilClass
from (irb):2
from C:/Ruby/bin/irb:12:in `<main>'
> class NilClass; def [](*a); end; end
#=> nil
> structure[:x][:y]
#=> nil
> structure[:a][:y]
#=> nil
> structure[:a][:b]
#=> "foo"
Go with #DigitalRoss's answer. Yes, it's more typing, but that's because it's safer.
In my case, I needed a two-dimensional matrix where each cell is a list of items.
I found this technique which seems to work. It might work for the OP:
$all = Hash.new()
def $all.[](k)
v = fetch(k, nil)
return v if v
h = Hash.new()
def h.[](k2)
v = fetch(k2, nil)
return v if v
list = Array.new()
store(k2, list)
return list
end
store(k, h)
return h
end
$all['g1-a']['g2-a'] << '1'
$all['g1-a']['g2-a'] << '2'
$all['g1-a']['g2-a'] << '3'
$all['g1-a']['g2-b'] << '4'
$all['g1-b']['g2-a'] << '5'
$all['g1-b']['g2-c'] << '6'
$all.keys.each do |group1|
$all[group1].keys.each do |group2|
$all[group1][group2].each do |item|
puts "#{group1} #{group2} #{item}"
end
end
end
The output is:
$ ruby -v && ruby t.rb
ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-linux]
g1-a g2-a 1
g1-a g2-a 2
g1-a g2-a 3
g1-a g2-b 4
g1-b g2-a 5
g1-b g2-c 6
I am currently trying out this:
# --------------------------------------------------------------------
# System so that we chain methods together without worrying about nil
# values (a la Objective-c).
# Example:
# params[:foo].try?[:bar]
#
class Object
# Returns self, unless NilClass (see below)
def try?
self
end
end
class NilClass
class MethodMissingSink
include Singleton
def method_missing(meth, *args, &block)
end
end
def try?
MethodMissingSink.instance
end
end
I know the arguments against try, but it is useful when looking into things, like say, params.

Resources