Ruby hash use key value in default value - ruby

I have the following code to create an array to object hash:
tp = TupleProfile.new(98, 99)
keyDict = Hash[Array[98,99] => tp]
keyDict[[98,99]].addLatency(0.45)
puts keyDict[[98,99]].getAvg()
This works, but I'd like to be able to call addLatency without checking for an existing hash value:
keyDict[[100,98]].addLatency(0.45) #throws error right now
So I want to create a default value that varies based on the key, something like:
keyDict = Hash.new(TupleProfile.new(theKey[0], theKey[1]))
Where theKey is some sort of special directive. Is there any reasonably clean way to do this, or am I better off checking each time or making a wrapper class for the hash?

Try the Hash.new block notation:
keyDict = Hash.new {|hash,key| hash[key] = TupleProfile.new(*key) }
Using the standard parameter notation (Hash.new(xyz)) will really only instantiate a single TupleProfile object for the hash; this way there will be one for each individual key.

If I understand your question, I think you might be able to use a default procedure. The code in the default procedure will get run if you ask for a key that doesn't exist. Here is an example using a tuple key:
class Test
def initialize(a,b); #a = a; #b = b; end
attr_accessor :a, :b
end
keyDict = {}
keyDict.default_proc = proc do |hash, (key1, key2)|
hash[[key1, key2]] = Test.new(key1, key2)
end
keyDict[[99,200]]
=> #<Test:0x007f9681ad2720 #a=99, #b=200>
keyDict[[99,200]].a
=> 99

Related

Creating a ruby nested hash with array as inner value

I am trying to create a nested hash where the inner values are arrays. For example
{"monday"=>{"morning"=>["John", "Katie", "Dave"],"afternoon"=>["Anne", "Charlie"]},
"tuesday"=>{"morning"=>["Joe"],"afternoon"=>["Chris","Tim","Melissa"]}}
I tried
h = Hash.new( |hash, key| hash[key] = Hash.new([]) }
When I try
h["monday"]["morning"].append("Ben")
and look at h, I get
{"monday" => {}}
rather than
{"monday" => {"morning"=>["Ben"]}}
I'm pretty new to Ruby, any suggestions for getting the functionality I want?
Close, you'll have to initialise a new hash as the value of the initial key, and set an Array as the value of the nested hash:
h = Hash.new { |hash, key| hash[key] = Hash.new { |k, v| k[v] = Array.new } }
h["monday"]["morning"] << "Ben"
{"monday"=>{"morning"=>["Ben"]}}
This way you will not have to initialise an array every time you want to push a value. The key will be as you set in the initial parameter, the second parameter will create a nested hash where the value will be an array you can push to with '<<'. Is this a solution to use in live code? No, it’s not very readable but explains a way of constructing data objects to fit your needs.
Refactored for Explicitness
While it's possible to create a nested initializer using the Hash#new block syntax, it's not really very readable and (as you've seen) it can be hard to debug. It may therefore be more useful to construct your nested hash in steps that you can inspect and debug as you go.
In addition, you already know ahead of time what your keys will be: the days of the week, and morning/afternoon shifts. For this use case, you might as well construct those upfront rather than relying on default values.
Consider the following:
require 'date'
# initialize your hash with a literal
schedule = {}
# use constant from Date module to initialize your
# lowercase keys
Date::DAYNAMES.each do |day|
# create keys with empty arrays for each shift
schedule[day.downcase] = {
"morning" => [],
"afternoon" => [],
}
end
This seems more explicit and readable to me, but that's admittedly subjective. Meanwhile, calling pp schedule will show you the new data structure:
{"sunday"=>{"morning"=>[], "afternoon"=>[]},
"monday"=>{"morning"=>[], "afternoon"=>[]},
"tuesday"=>{"morning"=>[], "afternoon"=>[]},
"wednesday"=>{"morning"=>[], "afternoon"=>[]},
"thursday"=>{"morning"=>[], "afternoon"=>[]},
"friday"=>{"morning"=>[], "afternoon"=>[]},
"saturday"=>{"morning"=>[], "afternoon"=>[]}}
The new data structure can then have its nested array values assigned as you currently expect:
schedule["monday"]["morning"].append("Ben")
#=> ["Ben"]
As a further refinement, you could append to your nested arrays in a way that ensures you don't duplicate names within a scheduled shift. For example:
schedule["monday"]["morning"].<<("Ben").uniq!
schedule["monday"]
#=> {"morning"=>["Ben"], "afternoon"=>[]}
There are many ways to create the hash. One simple way is as follows.
days = [:monday, :tuesday]
day_parts = [:morning, :afternoon]
h = days.each_with_object({}) do |d,h|
h[d] = day_parts.each_with_object({}) { |dp,g| g[dp] = [] }
end
#=> {:monday=>{:morning=>[], :afternoon=>[]},
# :tuesday=>{:morning=>[], :afternoon=>[]}}
Populating the hash will of course depend on the format of the data. For example, if the data were as follows:
people = { "John" =>[:monday, :morning],
"Katie" =>[:monday, :morning],
"Dave" =>[:monday, :morning],
"Anne" =>[:monday, :afternoon],
"Charlie"=>[:monday, :afternoon],
"Joe" =>[:tuesday, :morning],
"Chris" =>[:tuesday, :afternoon],
"Tim" =>[:tuesday, :afternoon],
"Melissa"=>[:tuesday, :afternoon]}
we could build the hash as follows.
people.each { |name,(day,day_part)| h[day][day_part] << name }
#=> {
# :monday=>{
# :morning=>["John", "Katie", "Dave"],
# :afternoon=>["Anne", "Charlie"]
# },
# :tuesday=>{
# :morning=>["Joe"],
# :afternoon=>["Chris", "Tim", "Melissa"]
# }
# }
As per your above-asked question
h = Hash.new{ |hash, key| hash[key] = Hash.new([]) }
you tried
h["monday"]["morning"].append("Ben")
instead you should first initialize that with an array & then you can use array functions like append
h["monday"]["morning"] = []
h["monday"]["morning"].append("Ben")
This would work fine & you will get the desired results.

Ruby: ignoring nil?

I am trying to parse a bunch of json attributes and assign them to an hash. The json has a ton of levels to it:
player[:position]=player["positions"]["primary_position"]["position"]["name"]
The problem I'm encountering is that any one of these levels may be nil, which means that I need to write four separate nil? checks just to extract the value.
I don't care about the nil values -- it would be fine if the array just recorded them as " " or something similar.
Is there any way I can turn off nil errors for this particular type of thing? Or should I run the whole thing thru a rescue method that returns the value I want in the event of nil?
You could use the andand gem, which is similar to the Maybe monad.
player[:position] = player.andand["positions"].andand["primary_position"].andand["position"].andand["name"]
This is a guarded method invocation. It only continues to send the method to the object if the result is not nil otherwise it returns nil. This way the chained invocation is stopped the moment one of the them returns nil.
player[:position]=player["positions"]["primary_position"]["position"]["name"] rescue " "
This would return the desired value, or " " if something is nil.
The usual approach is to use inject thusly:
path = %w[positions primary_position position name]
value = path.inject(player) { |h, k| h && h[k] }
You'd adjust the h && h[k] test to suite your specific needs, that one assumes that you won't have to worry about non-Hash values inside h. You make it as elaborate as you need.
You could even patch that into Hash if you were doing this sort of thing all the time:
class Hash
def follow_path(*path)
# You could also path.flatten if you're not expecting Array keys
path.inject(self) { |h, k| h && h[k] }
end
end
value = person.follow_path(*%w[positions primary_position position name])
or toss it into a private utility method:
private
def follow_path(h, *path)
# Assuming you're not using Array keys...
path.flatten.inject(h) { |h, k| h && h[k] }
end
and then:
value = follow_path(person, %w[positions primary_position position name])

How do I access the elements in a hash which is itself a value in a hash?

I have this hash $chicken_parts, which consists of symbol/hash pairs (many more than shown here):
$chicken_parts = { :beak = > {"name"=>"Beak", "color"=>"Yellowish orange", "function"=>"Pecking"}, :claws => {"name"=>"Claws", "color"=>"Dirty", function"=>"Scratching"} }
Then I have a class Embryo which has two class-specific hashes:
class Embryo
#parts_grown = Hash.new
#currently_developing = Hash.new
Over time, new pairs from $chicken_parts will be .merge!ed into #parts_grown. At various times, #currently developing will be declared equal to one of the symbol/hash pairs from #parts_grown.
I'm creating Embryo class functions and I want to be able to access the "name", "color", and "function" values in #currently_developing, but I don't seem to be able to do it.
def grow_part(part)
#parts_grown.merge!($chicken_parts[part])
end
def develop_part(part)
#currently_developing = #parts_grown[part]
seems to populate the hashes as expected, but
puts #currently_developing["name"]
does not work. Is this whole scheme a bad idea? Should I just make the Embryo hashes into arrays of symbols from $chicken_parts, and refer to it whenever needed? That seemed like cheating to me for some reason...
There's a little bit of confusion here. When you merge! in grow_part, you aren't adding a :beak => {etc...} pair to #parts_grown. Rather, you are merging the hash that is pointed too by the part name, and adding all of the fields of that hash directly to #parts_grown. So after one grow_part, #parts_grown might look like this:
{"name"=>"Beak", "color"=>"Yellowish orange", "function"=>"Pecking"}
I don't think that's what you want. Instead, try this for grow_part:
def grow_part(part)
#parts_grown[part] = $chicken_parts[part]
end
class Embryo
#parts_grown = {a: 1, b: 2}
def show
p #parts_grown
end
def self.show
p #parts_grown
end
end
embryo = Embryo.new
embryo.show
Embryo.show
--output:--
nil
{:a=>1, :b=>2}

common way to store single keys in ruby

Currently I have a collection of keys in my ruby code, which stored inside hash object. And when I add new element, I just check if that key already exist, if not then add new key to my collection with some default value, like this:
unless #issues.has_key?(issue_id)
#issues[issue_id] = '';
end
But I don't like this method. Is it possible to make that better, without using unnecessary value.
Use a Set.
Sets are a collection of unique objects (no repeats).
# #issues = Set.new
unless #issues.include?(issue_id)
#issues << issue_id
end
They keys of a hash are, in fact, a set (although not necessarily implemented via the Set class).
[Edit] Note that if you are storing complex objects (e.g. not builtins such as numbers, strings, symbols, etc.) you'll need to override both the hash method and the eql? method so that they can be hashed properly. The same goes if you are using complex objects as keys for hashing.
class Foo
attr_read :name, :hash
def initialize(name)
#name = name
#hash = name.hash
end
def eql?(o)
o.is_a?(Foo) && (o.name == self.name)
end
end
s = Set.new
s << Foo.new("Foo!")
s << Foo.new("Foo!")
s.to_a # => [ #<Foo:0x0123 #name="Foo!"> ]
You can use default value for hash
h = Hash.new("")
h[issue_id] => ""

how to name an object reference (handle) dynamically in ruby

So I have a class like this:
def Word
end
and im looping thru an array like this
array.each do |value|
end
And inside that loop I want to instantiate an object, with a handle of the var
value = Word.new
Im sure there is an easy way to do this - I just dont know what it is!
Thanks!
To assign things to a dynamic variable name, you need to use something like eval:
array.each do |value|
eval "#{value} = Word.new"
end
but check this is what you want - you should avoid using eval to solve things that really require different data structures, since it's hard to debug errors created with eval, and can easily cause undesired behaviour. For example, what you might really want is a hash of words and associated objects, for example
words = {}
array.each do |value|
words[value] = Word.new
end
which won't pollute your namespace with tons of Word objects.
Depending on the data structure you want to work with, you could also do this:
# will give you an array:
words = array.map { |value| Word.new(value) }
# will give you a hash (as in Peter's example)
words = array.inject({}) { |hash, value| hash.merge value => Word.new }
# same as above, but more efficient, using monkey-lib (gem install monkey-lib)
words = array.construct_hash { |value| [value, Word.new ] }

Resources