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
Related
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.
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
Say I have the following hash.
my_hash = {
'array1' => %w[
value1
value2
],
'array2' => %w[
value3
value4
]
}
How do I make an array that looks like
my_array = %w[value1 value2 value3 valuu4]
my_array = my_hash.values.flatten
=> ["value1", "value2", "value3", "value4"]
Flatten Hash Values
Use Hash#values to collect the values from your Hash, and then use Array#flatten to turn the result into a single Array rather than one containing nested arrays. For example:
my_hash.values.flatten
#=> ["value1", "value2", "value3", "value4"]
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
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