Ruby - how to add a conditional key to a hash? - ruby

I have this method creating a hash:
def build_values(data)
{
key1: data.k1,
key2: data.k2,
key3: (#var == true ? data.k3 : nil),
key4: data.k4,
...
}
end
If #var == true, then key3 has the value data.k2. That's good.
If it is not true, then key3 has not value (actually, it has nil). But ideally, I would not want to print the key3 at all.
How do I do that? Something like this doesn't work:
def build_values(data)
{
key1: data.k1,
key2: data.k2,
if #var == true
key3: data.k3
end,
key4: data.k4,
...
}
end

Not the prettiest solution, but ** could be used:
{
key1: data.k1,
key2: data.k2,
**(#var == true ? { key3: data.k3 } : {}),
key4: data.k4,
}
Note that == true can be omitted if you just care whether #var is truthy.

If you care for the order of keys, you can do:
def build_values(data)
data = {
key1: data.k1,
key2: data.k2
}
data[:key3] = data.k3 if #var == true
data.merge(
key4: data.k4,
...
}
end
Alternatively, you can use predefined key order (this could be hand when there are more optional keys):
KEY_ORDER = %i[key1 key2 key3 key4 ...]
def build_values(data)
data = {
key1: data.k1,
key2: data.k2,
key4: data.k4
}
data[:key3] = data.k3 if #var == true
data.sort_by { |k, _| KEY_ORDER.index(k) }.to_h
end
However, if you got here it might be worth considering a custom class - either Struct or even Hash subclass to deal with this data.

Maybe you can consider something like rejecting the key if not #var:
def build_values(data)
{
key1: data.k1,
key2: data.k2,
key3: data.k3,
key4: data.k4
}.reject{ |k, _| k == :key3 && !#var }
end

def build_values(data)
hash = {
key1: data.k1,
key2: data.k2,
key4: data.k4,
...
}
hash[:key3] = data.k3 if #var == true
end

Related

Group by specific array value in hash and include key

I am trying to find out if it is possible to sort a Hash by a specific value if there are multiple values saved to a key.
Example Code:
{
key1: ["Value1", "Value2", "Value3"],
key2: ["Value1", "Value2", "Value3"],
key3: ["Value1", "Value2", "Value3"]
}
I would like to be able to sort by the values in Value2 or by Value1 or any specific value.
If key1 and key2 have the same Value2 they will be returned as:
{Value2: [key1, key2]}
Example:
Value2 representes pets:
For all the Value2 that have dog, the key will be saved under a new key, dog.
For all the Value2 that have cat, it will gather all keys that have Value2 as cat and group it.
{cat: ["key1", "key2"], dog: ["key3"]}
I am working in Ruby and VScode. I do not want to use an array or nested array because this is not efficient.
Given
{
key1: ["Cop", "dog", "house"],
key2: ["doctor", "cat", "apartment"],
key3: ["Chef", "dog", "house"],
key4: ["Cop", "cat", "apartment"]
}
Expected Output if asked to sort by the values in value2
{
dog: [key1, key3],
cat: [key2, key4]
}
This doesn't appear to be about sorting, but rather grouping.
It's a matter of iterating through each pair and building a new hash of the matching keys and values.
# A Hash where the default value is a new empty array.
# See https://stackoverflow.com/questions/30367487/creating-a-hash-with-values-as-arrays-and-default-value-as-empty-array
grouped = Hash.new { |h, k| h[k] = [] }
# Iterate through the Hash
h.each { |key, things|
# Get the 2nd thing
thing = things[1]
# Add its key to the thing's group
grouped[thing] << key
}
p grouped
Using #each_with_object to build up a hash as we iterate over the keys in the original hash.
data = {
key1: ["Cop", "dog", "house"],
key2: ["doctor", "cat", "apartment"],
key3: ["Chef", "dog", "house"],
key4: ["Cop", "cat", "apartment"]
}
data.keys.each_with_object(Hash.new([])) { |k, h|
h[data[k][1]] += [k]
}
# => {"dog"=>[:key1, :key3], "cat"=>[:key2, :key4]}
Maybe not the optimal solution but it returns the expected result. The code is at least groups by value and includes the key, resulting in:
{"cat"=>[:key2, :key4], "dog"=>[:key1, :key3]}
hash = {
key1: ["Cop", "dog", "house"],
key2: ["doctor", "cat", "apartment"],
key3: ["Chef", "dog", "house"],
key4: ["Cop", "cat", "apartment"]
}
def group_by(animal, hash)
keys = hash.select{|key, value| value[1] == animal }.keys
{animal => keys}
end
a = group_by("cat", hash)
b = group_by("dog", hash)
a.merge(b)
I am sure there is a more "direct" solution.

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

Accessing nested hashes using variables

I have a nested hash like so:
someVar = { key1: { key2: 'value' } }
I can access the value by using it in this manner:
someVar[:key1][:key2]
How would I access it using a variable?
hashObj = { key1: { key2: 'value' } }
oneKey = "key1"
twoKey = "key2"
puts hashObj[:key1] # Works
puts hashObj[:key1][:key2] # Works
puts hashObj[oneKey] # Blank
puts hashObj[oneKey][twoKey] # Error
I'm sure there is a duplicate of this question somewhere, but I can't seem to locate one however.
Your keys are symbols, and you are trying to accessing them using strings. Turn them into symbols:
puts hashObj[oneKey.to_sym][twoKey.to_sym]
You might find it convenient to write a small method to extract the values you want:
def get_val(h, *keys)
keys.reduce(h) do |h,k|
v = h[k]
return v unless v.is_a? Hash
v
end
end
h = { key1: { key2: 'cat' }, key3: { key4: { key5: 'dog' } } }
get_val(h, :key1, :key2) #=> "cat"
get_val(h, :key3, :key4, :key5) #=> "dog"
Some error-checking would be needed, should, for example,
get_val(h, :key1, :key2, :key3)
is entered.
Edit: With Ruby 2.3+ you can improve this by using Hash#dig:
def get_val(h, *keys)
h.dig *keys
end
get_val(h, :key3, :key4, :key5)
#=> "dog"
get_val(h, :key3, :key4)
#=> {:key5=>"dog"}
get_val(h, :key3, :key4, :key5)
#=> "dog"
get_val(h, :key3, :key5, :key4)
#=> nil

Ruby pretty print strings

I have a map where key (a string) can have a very variable number of characters and I'd like to print it intelligently:
MAP = {
"key1" => "value1",
"key2" => "value2",
}
would print:
key1 -> value1
key2 -> value2
and
MAP = {
"key1" => "value1",
"key2" => "value2",
"key3_dam_it_you_are_a_big_one_indeed" => "value3",
}
would print:
key1 -> value1
key2 -> value2
key3_dam_it_you_are_a_big_one_indeed -> value3
he idea is that key1 and key2 would change their print-line according to the size of key3.
Thanks!
What about
class Hash
def nice_print
max_key_length = keys.map(&:length).max
each { |key, value| puts "#{key.ljust(max_key_length)} -> #{value}" }
end
end
and you call
MAP.nice_print

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.

Resources