Reference inside hash - ruby

I have a hash:
test = {
:key1 => "Test",
:key2 => "Test2",
:key3 => REF TO KEY1
}
Is it possible to let key3 refer to the key1 value?

Yes, this is easily possible. The expression for the value can be any arbitrary Ruby expression, including of course accessing a value from a Hash:
test = {
:key1 => "Test",
:key2 => "Test2",
}
test[:key3] = test[:key1]

This is really not recommended and my guess is that there is a better way to solve whatever larger problem you are attempting to solve with this technique.
But one way you could do this is by creating a Hash whose default_proc returns the value of :key1 if it is passed :key3.
> test = Hash.new { |h,k| k == :key3 ? h[:key1] : nil }
> test[:key1] = "Test"
> puts test[:key3]
Test
And this acts as a reference as can be seen if we modify the value of :key1
> test[:key1] = "Test2"
> puts test[:key3]
Test2

Related

visiting hash with keys from array

I have a big hash with lots of nested key value pairs.
Eg.
h = {"foo" => {"bar" => {"hello" => {"world" => "result" } } } }
Now I want to access result and I have keys for that in array in proper sequence.
keys_arr = ["foo", "bar", "hello", "world"]
The motive is clear, I want to do following:
h["foo"]["bar"]["hello"]["world"]
# => "result"
But I don't know how to do this. I am currently doing:
key = '["' + keys_arr.join('"]["') + '"]'
eval("h"+key)
# => "result"
Which looks like a hack. Also it greatly reduces my ability to work with hash in real environment.
Please suggest alternate and better ways.
Using Enumerable#inject (or Enumerable#reduce):
h = {"foo" => {"bar" => {"hello" => {"world" => "result" } } } }
keys_arr = ["foo", "bar", "hello", "world"]
keys_arr.inject(h) { |x, k| x[k] }
# => "result"
UPDATE
If you want to do something like: h["foo"]["bar"]["hello"]["world"] = "ruby"
innermost = keys_arr[0...-1].inject(h) { |x, k| x[k] } # the innermost hash
innermost[keys_arr[-1]] = "ruby"
keys_arr.inject(h, :[])
will do
Another way:
h = {"foo" => {"bar" => {"hello" => {"world" => 10 } } } }
keys = ["foo", "bar", "hello", "world"]
result = h
keys.each do |key|
result = result[key]
end
puts result #=>10
If the key may not exist, see here:
Dealing with many [...] in Ruby

Extract value nested with an array of hashes in Ruby

I have an XML response from a URL that is being converted into an array of hashes which looks like:
{
"EmployeeList"=>{
"EmployeeProfile"=>{
"BuildLoc"=>{"$"=>"1 Happy Place"},
"Status"=>{"$"=>"A"},
"SecrTitle"=>[{}, {}],
"ID"=>{},
"bct"=>{},
"NUM"=>{"$"=>"1234567"},
"BuildCity"=>{"$"=>"Dayton"},
"BuildFloor"=>{"$"=>"6"},
"Expense"=>{"$"=>"1345"},
"LastName"=>{"$"=>"Smith"},
"Middle"=>{},
"SecrName"=>[{}, {}],
"InternalSMTPAddress"=>{"$"=>"Joe.Smith#happy.com"},
"IAddress"=>{"$"=>"Joe.Smith#happy.com"},
"PreferredLastName"=>{},
"DisplayName"=>{"$"=>"Joe Smith"},
"CellPhoneNo"=>{},
"Title"=>{"$"=>"Dr."},
"BuildStreetAddress"=>{"$"=>"123 Happy town"},
"BuildState"=>{"$"=>"IL"},
"FirstName"=>{"$"=>"Joe"},
"AltContactTitle1"=>{},
"Dept-CostCtrNo"=>{"$"=>"129923"},
"PreferredFirstName"=>{"$"=>"Joe"},
"AltContactName2"=>{},
"AltContactPhone2"=>{},
"GDP"=>{},
"BuildZip"=>{"$"=>"112345"},
"RegionID"=>{"$"=>"NAMR"},
"EmploymentType"=>{"$"=>"E"},
"TempPhone"=>{},
"BuildID"=>{"$"=>"01114"},
"CountryAbbr"=>{"$"=>"USA"},
"FaxDisp1"=>{},
"BuildCountry"=>{"$"=>"United States"}
}
},
nil=>nil
}
What's the easiest way to extract the value of "DisplayName" and "InternalSMTPAddress"?
If you assign the returned hash to a variable named "hash" you can access the two desired values for those keys like:
hash['EmployeeList']['EmployeeProfile']['DisplayName']
=> {"$"=>"Joe Smith"}
and
hash['EmployeeList']['EmployeeProfile']['InternalSMTPAddress']
=> {"$"=>"Joe.Smith#happy.com"}
If you want the actual data in them add a trailing ['$']:
hash['EmployeeList']['EmployeeProfile']['DisplayName']['$']
=> "Joe Smith"
hash['EmployeeList']['EmployeeProfile']['InternalSMTPAddress']['$']
=> "Joe.Smith#happy.com"
If you need to find some key in nested hashes use this method:
def find_key(hash,key)
hash.each {|k, v|
return v if k==key
tmp=find_key(v,key) if v.is_a?(Hash)
return tmp unless tmp.nil?
}
return nil
end
Usage:
hash = Hash.new
hash["key1"] = "value1"
hash["key2"] = "value2"
hash["key3"] = Hash.new
hash["key3"]["key4"] = "value4"
hash["key3"]["key5"] = "value5"
hash["key6"] = Hash.new
hash["key6"]["key7"] = "value7"
hash["key6"]["key8"] = Hash.new
hash["key6"]["key8"]["key9"] = "value9"
find_key(hash,"key9") => "value9"
find_key(hash,"key8") => {"key9"=>"value9"}
find_key(hash,"dsfsdfsd") => nil
I would recommend you to use ruby gem nested_lookup.
Install the gem using command gem install nested_lookup
In your case you need the value of 'DisplayName' and 'InternalSMTPAddress'. This is what you need to do.
Rameshs-MacBook-Pro:~ rameshrv$ irb
irb(main):001:0> require 'nested_lookup'
=> true
irb(main):051:0> include NestedLookup
=> Object
# Here the test_data is the data you gave in the question
irb(main):052:0> test_data.nested_get('DisplayName')
=> {"$"=>"Joe Smith"}
irb(main):053:0> test_data.nested_get('InternalSMTPAddress')
=> {"$"=>"Joe.Smith#happy.com"}
irb(main):054:0>
For more information please read https://github.com/rameshrvr/nested_lookup

Ruby - getting value of hash

I have a hash like
{:key1 => "value1", :key2 => "value2"}
And I have a variable k which will have the value as 'key1' or 'key2'.
I want to get the value of k into a variable v.
Is there any way to achieve this with out using if or case? A single line solution is preferred. Please help.
Convert the key from a string to a symbol, and do a lookup in the hash.
hash = {:key1 => "value1", :key2 => "value2"}
k = 'key1'
hash[k.to_sym] # or iow, hash[:key1], which will return "value1"
Rails uses this class called HashWithIndifferentAccess that proves to be very useful in such cases. I know that you've only tagged your question with Ruby, but you could steal the implementation of this class from Rails' source to avoid string to symbol and symbol to string conversions throughout your codebase. It makes the value accessible by using a symbol or a string as a key.
hash = HashWithIndifferentAccess.new({:key1 => "value1", :key2 => "value2"})
hash[:key1] # "value1"
hash['key1'] # "value1"

Ruby: how to check if variable exists within a hash definition

I'm new to Ruby. Is there a way to do the following?
hash = {
:key1 => defined? value1 ? value1 : nil,
:key2 => defined? value2 ? value2 : nil
}
puts hash[:key1] # outputs: ["expression"]
The above code stores the expression, instead of the value (if it is defined) or nil (if it is not defined).
d11wtg answer will do. Also, by adding parentheses, the values are stored as expected:
hash = {
:key1 => (defined? value1) ? value1 : nil,
:key2 => (defined? value2) ? value2 : nil
}
You're looking for lambda, or Proc.
hash = {
:key1 => lambda { defined?(value1) ? value1 : nil },
:key2 => lambda { defined?(value2) ? value1 : nil }
}
hash[:key1].call
http://www.ruby-doc.org/core-1.9.2/Kernel.html#method-i-lambda
What exactly do you want to do?
hash[:key].nil?
will return true or false, depending if the key exists. Not sure if that's what you are looking for.

Building a hash in a conditional way

I am using Ruby on Rails 3.0.10 and I would like to build an hash key\value pairs in a conditional way. That is, I would like to add a key and its related value if a condition is matched:
hash = {
:key1 => value1,
:key2 => value2, # This key2\value2 pair should be added only 'if condition' is 'true'
:key3 => value3,
...
}
How can I do that and keep a "good" readability for the code? Am I "forced" to use the merge method?
I prefer tap, as I think it provides a cleaner solution than the ones described here by not requiring any hacky deleting of elements and by clearly defining the scope in which the hash is being built.
It also means you don't need to declare an unnecessary local variable, which I always hate.
In case you haven't come across it before, tap is very simple - it's a method on Object that accepts a block and always returns the object it was called on. So to build up a hash conditionally you could do this:
Hash.new.tap do |my_hash|
my_hash[:x] = 1 if condition_1
my_hash[:y] = 2 if condition_2
...
end
There are many interesting uses for tap, this is just one.
A functional approach with Hash.compact:
hash = {
:key1 => 1,
:key2 => (2 if condition),
:key3 => 3,
}.compact
Probably best to keep it simple if you're concerned about readability:
hash = {}
hash[:key1] = value1
hash[:key2] = value2 if condition?
hash[:key3] = value3
...
Keep it simple:
hash = {
key1: value1,
key3: value3,
}
hash[:key2] = value2 if condition
This way you also visually separate your special case, which might get unnoticed if it is buried within hash literal assignment.
I use merge and the ternary operator for that situation,
hash = {
:key1 => value1,
:key3 => value3,
...
}.merge(condition ? {:key2 => value2} : {})
Simple as this:
hash = {
:key1 => value1,
**(condition ? {key2: value2} : {})
}
Hope it helps!
IF you build hash from some kind of Enumerable data, you can use inject, for example:
raw_data.inject({}){ |a,e| a[e.name] = e.value if expr; a }
In case you want to add few keys under single condition, you can use merge:
hash = {
:key1 => value1,
:key2 => value2,
:key3 => value3
}
if condition
hash.merge!(
:key5 => value4,
:key5 => value5,
:key6 => value6
)
end
hash
First build your hash thusly:
hash = {
:key1 => value1,
:key2 => condition ? value2 : :delete_me,
:key3 => value3
}
Then do this after building your hash:
hash.delete_if {|_, v| v == :delete_me}
Unless your hash is frozen or otherwise immutable, this would effectively only keep values that are present.
Using fetch can be useful if you're populating a hash from optional attributes somewhere else. Look at this example:
def create_watchable_data(attrs = {})
return WatchableData.new({
id: attrs.fetch(:id, '/catalog/titles/breaking_bad_2_737'),
titles: attrs.fetch(:titles, ['737']),
url: attrs.fetch(:url, 'http://www.netflix.com/shows/breaking_bad/3423432'),
year: attrs.fetch(:year, '1993'),
watchable_type: attrs.fetch(:watchable_type, 'Show'),
season_title: attrs.fetch(:season_title, 'Season 2'),
show_title: attrs.fetch(:id, 'Breaking Bad')
})
end
Same idea as Chris Jester-Young, with a slight readability trick
def cond(x)
condition ? x : :delete_me
end
hash = {
:key1 => value1,
:key2 => cond(value2),
:key3 => value3
}
and then postprocess to remove the :delete_me entries

Resources