Ruby hash with multiple keys pointing to the same value - ruby

I am looking for a way to have, I would say synonym keys in the hash.
I want multiple keys to point to the same value, so I can read/write a value through any of these keys.
As example, it should work like that (let say :foo and :bar are synonyms)
hash[:foo] = "foo"
hash[:bar] = "bar"
puts hash[:foo] # => "bar"
Update 1
Let me add couple of details. The main reason why I need these synonyms, because I receive keys from external source, which I can't control, but multiple keys could actually be associated with the same value.

Rethink Your Data Structure
Depending on how you want to access your data, you can make either the keys or the values synonyms by making them an array. Either way, you'll need to do more work to parse the synonyms than the definitional word they share.
Keys as Definitions
For example, you could use the keys as the definition for your synonyms.
# Create your synonyms.
hash = {}
hash['foo'] = %w[foo bar]
hash
# => {"foo"=>["foo", "bar"]}
# Update the "definition" of your synonyms.
hash['baz'] = hash.delete('foo')
hash
# => {"baz"=>["foo", "bar"]}
Values as Definitions
You could also invert this structure and make your keys arrays of synonyms instead. For example:
hash = {["foo", "bar"]=>"foo"}
hash[hash.rassoc('foo').first] = 'baz'
=> {["foo", "bar"]=>"baz"}

You could subclass hash and override [] and []=.
class AliasedHash < Hash
def initialize(*args)
super
#aliases = {}
end
def alias(from,to)
#aliases[from] = to
self
end
def [](key)
super(alias_of(key))
end
def []=(key,value)
super(alias_of(key), value)
end
private
def alias_of(key)
#aliases.fetch(key,key)
end
end
ah = AliasedHash.new.alias(:bar,:foo)
ah[:foo] = 123
ah[:bar] # => 123
ah[:bar] = 456
ah[:foo] # => 456

What you can do is completely possible as long as you assign the same object to both keys.
variable_a = 'a'
hash = {foo: variable_a, bar: variable_a}
puts hash[:foo] #=> 'a'
hash[:bar].succ!
puts hash[:foo] #=> 'b'
This works because hash[:foo] and hash[:bar] both refer to the same instance of the letter a via variable_a. This however wouldn't work if you used the assignment hash = {foo: 'a', bar: 'a'} because in that case :foo and :bar refer to different instance variables.

The answer to your original post is:
hash[:foo] = hash[:bar]
and
hash[:foo].__id__ == hash[:bar].__id__it
will hold true as long as the value is a reference value (String, Array ...) .
The answer to your Update 1 could be:
input.reduce({ :k => {}, :v => {} }) { |t, (k, v)|
t[:k][t[:v][v] || k] = v;
t[:v][v] = k;
t
}[:k]
where «input» is an abstract enumerator (or array) of your input data as it comes [key, value]+, «:k» your result, and «:v» an inverted hash that serves the purpose of finding a key if its value is already present.

Related

How do I add a key/value pair to the beginning of a hash?

My code is:
hash = { two: 2, three: 3 }
def hash_add(hash, new_key, new_value)
temp_hash = {}
temp_hash[new_key.to_sym] = new_value
temp_hash.merge!(hash)
hash = temp_hash
puts hash
end
hash_add(hash, 'one', 1)
Within the method, puts hash returns { :one => 1, :two => 2, :three => 3 }, but when hash1 is put to the method, it remains unchanged afterward. It's like the assignment isn't carrying itself outside of the function.
I guess I could return the updated hash and set the hash I want to change to it outside the method:
hash = hash_add(hash, 'one', 1)
But I just don't see why the assignment I give to the hash does not stick outside of the method.
I have this, which works:
def hash_add(hash, new_key, new_value)
temp_hash = {}
temp_hash[new_key.to_sym] = new_value
temp_hash.merge!(hash)
hash.clear
temp_hash.each do |key, value|
hash[key] = value
end
end
Which gives me what I'm wanting when this method is called, but it just seems a little excessive to have to rebuild the hash like that.
How about this?
hash1 = { two: 2, three: 3 }
#add a new key,value
hash1 = Hash[:one,1].merge!(hash1) #=> {:one=>1, :two=>2, :three=>3}
Example #2:
h = { two: 2, three: 3 }
def hash_add(h,k,v)
Hash[k.to_sym,v].merge!(h)
end
h = hash_add(h, 'one', 1) #=> {:one=>1, :two=>2, :three=>3}
Ruby passes objects to methods by value, but the value is the reference to the object, so when you set hash=temp_hash within the add_hash method, that change only applies inside the method. The value of hash outside the method is unchanged.
def hash_add(hash, new_key, new_value)
temp_hash = {}
temp_hash[new_key.to_sym] = new_value
temp_hash.merge!(hash)
hash = temp_hash
hash
end
h2 = hash_add(hash, 'one', 1)
hash
=> {:two=>2, :three=>3}
h2
=>{:one=>1, :two=>2, :three=>3}
If you want hash to be updated, you need to replace the contents of hash rather than re-point hash at a new object as you did with the clear and re-adding the values. You can also do it with the replace method.
def hash_add(hash, new_key, new_value)
temp_hash = {}
temp_hash[new_key.to_sym] = new_value
temp_hash.merge!(hash)
hash.replace temp_hash
end
There are some good diagrams about pass by value in "Is Ruby pass by reference or by value?"
NOTE: this answer is old from times when Ruby 1.8 was still around.
In general, the class Hash in Ruby does not provide ordering. Behavior might differ between Ruby versions / implementations.
See also: Hash ordering preserved between iterations if not modified?
If you want ordering, you need to use the class OrderedHash which is provided through ActiveSupport
See: http://apidock.com/rails/ActiveSupport/OrderedHash
At the end of the function you are just putsing the hash, not returning it. Perhaps if you changed puts hash to return hash it would work (I haven't tried it myself).
temp_hash is a local variable, which gets deleted once the function returns.

Is there a built-in lazy Hash in Ruby?

I need to populate a Hash with various values. Some of values are accessed often enough and another ones really seldom.
The issue is, I'm using some computation to get values and populating the Hash becomes really slow with multiple keys.
Using some sort of cache is not a option in my case.
I wonder how to make the Hash compute the value only when the key is firstly accessed and not when it is added?
This way, seldom used values wont slow down the filling process.
I'm looking for something that is "kinda async" or lazy access.
There are many different ways to approach this. I recommend using an instance of a class that you define instead of a Hash. For example, instead of...
# Example of slow code using regular Hash.
h = Hash.new
h[:foo] = some_long_computation
h[:bar] = another_long_computation
# Access value.
puts h[:foo]
... make your own class and define methods, like this...
class Config
def foo
some_long_computation
end
def bar
another_long_computation
end
end
config = Config.new
puts config.foo
If you want a simple way to cache the long computations or it absolutely must be a Hash, not your own class, you can now wrap the Config instance with a Hash.
config = Config.new
h = Hash.new {|h,k| h[k] = config.send(k) }
# Access foo.
puts h[:foo]
puts h[:foo] # Not computed again. Cached from previous access.
One issue with the above example is that h.keys will not include :bar because you haven't accessed it yet. So you couldn't, for example, iterate over all the keys or entries in h because they don't exist until they're actually accessed. Another potential issue is that your keys need to be valid Ruby identifiers, so arbitrary String keys with spaces won't work when defining them on Config.
If this matters to you, there are different ways to handle it. One way you can do it is to populate your hash with thunks and force the thunks when accessed.
class HashWithThunkValues < Hash
def [](key)
val = super
if val.respond_to?(:call)
# Force the thunk to get actual value.
val = val.call
# Cache the actual value so we never run long computation again.
self[key] = val
end
val
end
end
h = HashWithThunkValues.new
# Populate hash.
h[:foo] = ->{ some_long_computation }
h[:bar] = ->{ another_long_computation }
h["invalid Ruby name"] = ->{ a_third_computation } # Some key that's an invalid ruby identifier.
# Access hash.
puts h[:foo]
puts h[:foo] # Not computed again. Cached from previous access.
puts h.keys #=> [:foo, :bar, "invalid Ruby name"]
One caveat with this last example is that it won't work if your values are callable because it can't tell the difference between a thunk that needs to be forced and a value.
Again, there are ways to handle this. One way to do it would be to store a flag that marks whether a value has been evaluated. But this would require extra memory for every entry. A better way would be to define a new class to mark that a Hash value is an unevaluated thunk.
class Unevaluated < Proc
end
class HashWithThunkValues < Hash
def [](key)
val = super
# Only call if it's unevaluated.
if val.is_a?(Unevaluated)
# Force the thunk to get actual value.
val = val.call
# Cache the actual value so we never run long computation again.
self[key] = val
end
val
end
end
# Now you must populate like so.
h = HashWithThunkValues.new
h[:foo] = Unevaluated.new { some_long_computation }
h[:bar] = Unevaluated.new { another_long_computation }
h["invalid Ruby name"] = Unevaluated.new { a_third_computation } # Some key that's an invalid ruby identifier.
h[:some_proc] = Unevaluated.new { Proc.new {|x| x + 2 } }
The downside of this is that now you have to remember to use Unevaluted.new when populating your Hash. If you want all values to be lazy, you could override []= also. I don't think it would actually save much typing because you'd still need to use Proc.new, proc, lambda, or ->{} to create the block in the first place. But it might be worthwhile. If you did, it might look something like this.
class HashWithThunkValues < Hash
def []=(key, val)
super(key, val.respond_to?(:call) ? Unevaluated.new(&val) : val)
end
end
So here is the full code.
class HashWithThunkValues < Hash
# This can be scoped inside now since it's not used publicly.
class Unevaluated < Proc
end
def [](key)
val = super
# Only call if it's unevaluated.
if val.is_a?(Unevaluated)
# Force the thunk to get actual value.
val = val.call
# Cache the actual value so we never run long computation again.
self[key] = val
end
val
end
def []=(key, val)
super(key, val.respond_to?(:call) ? Unevaluated.new(&val) : val)
end
end
h = HashWithThunkValues.new
# Populate.
h[:foo] = ->{ some_long_computation }
h[:bar] = ->{ another_long_computation }
h["invalid Ruby name"] = ->{ a_third_computation } # Some key that's an invalid ruby identifier.
h[:some_proc] = ->{ Proc.new {|x| x + 2 } }
You can define your own indexer with something like this:
class MyHash
def initialize
#cache = {}
end
def [](key)
#cache[key] || (#cache[key] = compute(key))
end
def []=(key, value)
#cache[key] = value
end
def compute(key)
#cache[key] = 1
end
end
and use it as follows:
1.9.3p286 :014 > hash = MyHash.new
=> #<MyHash:0x007fa0dd03a158 #cache={}>
1.9.3p286 :019 > hash["test"]
=> 1
1.9.3p286 :020 > hash
=> #<MyHash:0x007fa0dd03a158 #cache={"test"=>1}>
you can use this:
class LazyHash < Hash
def [] key
(_ = (#self||{})[key]) ?
((self[key] = _.is_a?(Proc) ? _.call : _); #self.delete(key)) :
super
end
def lazy_update key, &proc
(#self ||= {})[key] = proc
self[key] = proc
end
end
Your lazy hash will behave exactly as a normal Hash, cause it is actually a real Hash.
See live demo here
*** UPDATE - answering to nested procs question ***
Yes, it would work, but it is cumbersome.
See updated answer.
Use lazy_update instead of []= to add "lazy" values to your hash.
This isn't strictly an answer to the body of your question, but Enumerable::Lazy will definitely be a part of Ruby 2.0. This will let you do lazy evaluation on iterator compositions:
lazy = [1, 2, 3].lazy.select(&:odd?)
# => #<Enumerable::Lazy: #<Enumerator::Generator:0x007fdf0b864c40>:each>
lazy.to_a
# => [40, 50]

Creating an md5 hash of a number, string, array, or hash in Ruby

I need to create a signature string for a variable in Ruby, where the variable can be a number, a string, a hash, or an array. The hash values and array elements can also be any of these types.
This string will be used to compare the values in a database (Mongo, in this case).
My first thought was to create an MD5 hash of a JSON encoded value, like so: (body is the variable referred to above)
def createsig(body)
Digest::MD5.hexdigest(JSON.generate(body))
end
This nearly works, but JSON.generate does not encode the keys of a hash in the same order each time, so createsig({:a=>'a',:b=>'b'}) does not always equal createsig({:b=>'b',:a=>'a'}).
What is the best way to create a signature string to fit this need?
Note: For the detail oriented among us, I know that you can't JSON.generate() a number or a string. In these cases, I would just call MD5.hexdigest() directly.
I coding up the following pretty quickly and don't have time to really test it here at work, but it ought to do the job. Let me know if you find any issues with it and I'll take a look.
This should properly flatten out and sort the arrays and hashes, and you'd need to have to some pretty strange looking strings for there to be any collisions.
def createsig(body)
Digest::MD5.hexdigest( sigflat body )
end
def sigflat(body)
if body.class == Hash
arr = []
body.each do |key, value|
arr << "#{sigflat key}=>#{sigflat value}"
end
body = arr
end
if body.class == Array
str = ''
body.map! do |value|
sigflat value
end.sort!.each do |value|
str << value
end
end
if body.class != String
body = body.to_s << body.class.to_s
end
body
end
> sigflat({:a => {:b => 'b', :c => 'c'}, :d => 'd'}) == sigflat({:d => 'd', :a => {:c => 'c', :b => 'b'}})
=> true
If you could only get a string representation of body and not have the Ruby 1.8 hash come back with different orders from one time to the other, you could reliably hash that string representation. Let's get our hands dirty with some monkey patches:
require 'digest/md5'
class Object
def md5key
to_s
end
end
class Array
def md5key
map(&:md5key).join
end
end
class Hash
def md5key
sort.map(&:md5key).join
end
end
Now any object (of the types mentioned in the question) respond to md5key by returning a reliable key to use for creating a checksum, so:
def createsig(o)
Digest::MD5.hexdigest(o.md5key)
end
Example:
body = [
{
'bar' => [
345,
"baz",
],
'qux' => 7,
},
"foo",
123,
]
p body.md5key # => "bar345bazqux7foo123"
p createsig(body) # => "3a92036374de88118faf19483fe2572e"
Note: This hash representation does not encode the structure, only the concatenation of the values. Therefore ["a", "b", "c"] will hash the same as ["abc"].
Here's my solution. I walk the data structure and build up a list of pieces that get joined into a single string. In order to ensure that the class types seen affect the hash, I inject a single unicode character that encodes basic type information along the way. (For example, we want ["1", "2", "3"].objsum != [1,2,3].objsum)
I did this as a refinement on Object, it's easily ported to a monkey patch. To use it just require the file and run "using ObjSum".
module ObjSum
refine Object do
def objsum
parts = []
queue = [self]
while queue.size > 0
item = queue.shift
if item.kind_of?(Hash)
parts << "\\000"
item.keys.sort.each do |k|
queue << k
queue << item[k]
end
elsif item.kind_of?(Set)
parts << "\\001"
item.to_a.sort.each { |i| queue << i }
elsif item.kind_of?(Enumerable)
parts << "\\002"
item.each { |i| queue << i }
elsif item.kind_of?(Fixnum)
parts << "\\003"
parts << item.to_s
elsif item.kind_of?(Float)
parts << "\\004"
parts << item.to_s
else
parts << item.to_s
end
end
Digest::MD5.hexdigest(parts.join)
end
end
end
Just my 2 cents:
module Ext
module Hash
module InstanceMethods
# Return a string suitable for generating content signature.
# Signature image does not depend on order of keys.
#
# {:a => 1, :b => 2}.signature_image == {:b => 2, :a => 1}.signature_image # => true
# {{:a => 1, :b => 2} => 3}.signature_image == {{:b => 2, :a => 1} => 3}.signature_image # => true
# etc.
#
# NOTE: Signature images of identical content generated under different versions of Ruby are NOT GUARANTEED to be identical.
def signature_image
# Store normalized key-value pairs here.
ar = []
each do |k, v|
ar << [
k.is_a?(::Hash) ? k.signature_image : [k.class.to_s, k.inspect].join(":"),
v.is_a?(::Hash) ? v.signature_image : [v.class.to_s, v.inspect].join(":"),
]
end
ar.sort.inspect
end
end
end
end
class Hash #:nodoc:
include Ext::Hash::InstanceMethods
end
These days there is a formally defined method for canonicalizing JSON, for exactly this reason: https://datatracker.ietf.org/doc/html/draft-rundgren-json-canonicalization-scheme-16
There is a ruby implementation here: https://github.com/dryruby/json-canonicalization
Depending on your needs, you could call ary.inspect or ary.to_yaml, even.

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.

Merge Ruby arrays

I have a few arrays of Ruby objects of class UserInfo:
class UserInfo
attr_accessor :name, :title, :age
end
How can I merge these arrays into one array? A user is identified by its name, so I want no duplicate names. If name, title, age, etc. are equal I'd like to have 1 entry in the new array. If names are the same, but any of the other details differ I probably want those 2 users in a different array to manually fix the errors.
Thanks in advance
Redefine equality comparison on your object, and you can get rid of actual duplicates quickly with Array#uniq
class UserInfo
attr_accessor :name, :title, :age
def == other
name==other.name and title==other.title and age==other.age
end
end
# assuming a and b are arrays of UserInfo objects
c = a | b
# c will only contain one of each UserInfo
Then you can sort by name and look for name-only duplicates
d = c.sort{ |p,q| p.name <=> q.name } #sort by name
name = ""
e = []
d.each do |item|
if item.name == name
e[-1] = [e[-1],item].flatten
else
e << item
end
end
A year ago I monkey patched a kind of cryptic instance_variables_compare on Object. I guess you could use that.
class Object
def instance_variables_compare(o)
Hash[*self.instance_variables.map {|v|
self.instance_variable_get(v)!=o.instance_variable_get(v) ?
[v,o.instance_variable_get(v)] : []}.flatten]
end
end
A cheesy example
require 'Date'
class Cheese
attr_accessor :name, :weight, :expire_date
def initialize(name, weight, expire_date)
#name, #weight, #expire_date = name, weight, expire_date
end
end
stilton=Cheese.new('Stilton', 250, Date.parse("2010-12-02"))
gorgonzola=Cheese.new('Gorgonzola', 250, Date.parse("2010-12-17"))
irb is my weapon of choice
>> stilton.instance_variables_compare(gorgonzola)
=> {"#name"=>"Gorgonzola", "#expire_date"=>#<Date: 4910305/2,0,2299161>}
>> gorgonzola.instance_variables_compare(stilton)
=> {"#name"=>"Stilton", "#expire_date"=>#<Date: 4910275/2,0,2299161>}
>> stilton.expire_date=gorgonzola.expire_date
=> #<Date: 4910305/2,0,2299161>
>> stilton.instance_variables_compare(gorgonzola)
=> {"#name"=>"Gorgonzola"}
>> stilton.instance_variables_compare(stilton)
=> {}
As you can see the instance_variables_compare returns an empty Hash if the two objects has the same content.
An array of cheese
stilton2=Cheese.new('Stilton', 210, Date.parse("2010-12-02"))
gorgonzola2=Cheese.new('Gorgonzola', 250, Date.parse("2010-12-17"))
arr=[]<<stilton<<stilton2<<gorgonzola<<gorgonzola2
One hash without problems and one with
h={}
problems=Hash.new([])
arr.each {|c|
if h.has_key?(c.name)
if problems.has_key?(c.name)
problems[c.name]=problems[c.name]<<c
elsif h[c.name].instance_variables_compare(c) != {}
problems[c.name]=problems[c.name]<<c<<h[c.name]
h.delete(c.name)
end
else
h[c.name]=c
end
}
Now the Hash h contains the objects without merging problems and the problems hash contains those that has instance variables that differs.
>> h
=> {"Gorgonzola"=>#<Cheese:0xb375e8 #name="Gorgonzola", #weight=250, #expire_date=#<Date: 2010-12-17 (4911095/2,0,2299161)>>}
>> problems
=> {"Stilton"=>[#<Cheese:0xf54c30 #name="Stilton", #weight=210, #expire_date=#<Date: 2010-12-02 (4911065/2,0,2299161)>>, #<Cheese:0xfdeca8 #name="Stilton", #weight=250,#expire_date=#<Date: 2010-12-02 (4911065/2,0,2299161)>>]}
As far as I can see you will not have to modify this code at all to support an array of UserInfo objects.
It would most probably be much faster to compare the properties directly or with a override of ==. This is how you override ==
def ==(other)
return self.weight == other.weight && self.expire_date == other.expire_date
end
and the loop changes into this
arr.each {|c|
if h.has_key?(c.name)
if problems.has_key?(c.name)
problems[c.name]=problems[c.name]<<c
elsif h[c.name] != c
problems[c.name]=problems[c.name]<<c<<h[c.name]
h.delete(c.name)
end
else
h[c.name]=c
end
}
Finally you might want to convert the Hash back to an Array
result = h.values
Here's another potential way. If you have a way of identifying each UserInfo, say a to_str method that prints out the values:
def to_str()
return "#{#name}:#{#title}:#{#age}"
end
You can use inject and a hash
all_users = a + b # collection of users to "merge"
res = all_users.inject({})do |h,v|
h[v.to_str] = v #save the value indexed on the string output
h # return h for the next iteration
end
merged = res.values #the unique users

Resources