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

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.

Related

Reference inside hash

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

Select hash from collection of hash based on subset of another hash

Given I have hash:
Grp1:
key1: value1
key2: value2
key3: value3
Grp2:
key1: value4
key2: value2
key3: value5
and I have another hash:
key1: value4
key2: value2
I want to search in first set of hash and choose Grp2' key1:value4 because Grp2 matches the condition. How should I do that?
Note: I tried using the select command and tried some logic offered by referring documents. My intentions are not to get sample code but just a hint. Sorry if my question sounds like I am asking for code.
What you refer to as your first hash is not hash. In fact, it's not a Ruby object of any kind. If it is to be a hash, you need to write it like this:
g = { :Grp1 => { key1: value1, key2: value2, key3: value3 },
:Grp2 => { key1: value4, key2: value2, key3: value5 } }
Since the keys are all symbols, you could instead use the syntax:
g = { Grp1: { key1: value1, key2: value2, key3: value3 },
Grp2: { key1: value4, key2: value2, key3: value5 } }
value1 (and value2 and so on) must be either a variable or a method, but you have not given us it's value (if it's a variable) or its return value is (if it's a method), so I will replace those variables or methods with literals:
g = { Grp1: { key1: 7, key2: 4, key3: 'cat' },
Grp2: { key1: 1, key2: 3, key3: 'dog' } }
Your second hash:
h = { :key1 => value4, :key2 => value2 }
has the same problem, so I'll replace it with:
h = { :key1 => 1, :key2 => 3 }
which alternatively could be expressed:
h = { key1: 1, key2: 3 }
Assuming what I have written is correct, we can write a method as follows, using the methods Hash#keys, Hash#key?, Enumerable#find and Enumerable#all?:
def doit(g, h)
hkeys = h.keys
puts "hkeys=#{hkeys}"
g.find do |k,v|
puts "k=#{k}"
puts "v=#{v}"
hkeys.all? do |j|
puts " v.key?(#{j})=#{v.key?(j)}"
puts " v[#{j}]==#{h[j]}: #{v[j]==h[j]}"
v.key?(j) && v[j] == h[j]
end
end
end
I've added some puts statements so you can see the results of the calculations. For g and h defined above (with literal values):
doit(g,h)
hkeys=[:key1, :key2]
k=Grp1
v={:key1=>7, :key2=>4, :key3=>"cat"}
v.key?(key1)=true
v[key1]==1: false
k=Grp2
v={:key1=>1, :key2=>3, :key3=>"dog"}
v.key?(key1)=true
v[key1]==1: true
v.key?(key2)=true
v[key2]==3: true
#=> [:Grp2, {:key1=>1, :key2=>3, :key3=>"dog"}]
After stripping out the puts statements and making one small change, I would write the method like this:
def doit(g, h)
hkeys = h.keys
g.find { |_,v| hkeys.all? { |j| v.key?(j) && v[j] == h[j] } }
end
The small change is that I've replaced the block variable k with the variable _ to draw attention to the fact that I'm not using it in the block.
There are many ways to to write this method. Here's another, using the method Hash#values_at:
def doit(g, h)
hkeys = h.keys
hvalues = h.values
g.find { |_,v| v.values_at(*hkeys) == hvalues }
end

Anonymous method inside hash assignment

Is there anything like this possible in Ruby:
hash = { :foo => 'bar', :bar => lambda{ condition ? return 'value1' : return 'value2'}}
That actual code doesn't work (clearly), and I know I could just do the logic before the hash assignment, but it would be nice to work inside the assignment like this. Is such a thing possible?
You don't need a lambda for that, just this should work:
hash = {
:foo => 'bar',
:bar => condition ? 'value1' : 'value2'
}
Or if you want to use a function result on hash,
hash= {
:foo=> 'foooooo',
:bar=> lambda {
if condition
value1
else
value2
end
}.call
}

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

Adding a key only to a hash based on an if statement

Typically, we define a hash as
h={:a=>val1, :b=>val2}
However, i want to add a condition to only add in the key :b if val2 is not a nil value. Something like
h={:a=>val1}
h[:b]=val2 if val2
But can it be encapsulated in a single line?
h = { :a => val1 }.merge(val2 ? { :b => val2 } : {})
But don't do this. Just keep it simple.
You don't have to worry about nil elements in hash, because you can simply clean up hash from them:
{:a => 1, :b => nil}.reject { |k, v| v.nil? } # {:a => 1}
h[:b] = val unless val.nil?
as of ruby 2.4, you can use Hash#compact
h = { a: 1, b: false, c: nil }
h.compact #=> { a: 1, b: false }
h #=> { a: 1, b: false, c: nil }
You could override the []= operator for just that one hash, or make a subclass of Hash and override it there.
hash = {}
class << hash
def []=(key, value)
case key
when :b
raise StandardError("Invalid :b value") if value.nil?
end
super(key,value)
end
end
hash[:a] = 10
hash[:b] = nil # Will raise exception

Resources