Group by specific array value in hash and include key - ruby

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.

Related

Retrieve value from Array of Hashes Ruby

I have an array of hashes in ruby like this
blah = [{"key1"=>"value1","key2"=>"value2","key3"=>"value3"....}]
Now let's say I want to get the value of key2.
What I am doing is puts "key 2 is #{blah["key2"]}", but then I get ERROR: "no implicit conversion of String into Integer (TypeError)"
blah is an array so you could have more than one hash inside it with a "key2" key. let's find them all
p blah.map { |h| h['key2'] }
or if you know there is just one hash in your array
p blah[0]['key2']
As an addendum to the answer from Ursus, if you wish to find the first value associated with "key2" in any hash in your array, you could use #find with a block.
Consider:
foo = [ {a: 5, b: 6},
{f: 67, key2: 42},
{d: 27.3},
{key2: 35} ]
h = foo.find { |h| h.has_key? :key2 }
# => {f: 67, key2: 42}
puts h[:key2] unless h.nil?

Insert in multiple arrays, in hash with key values in ruby

array1 = [[a,b,c],[1,2,3],[x,y,z]]
array2 = [[1,2,1],[2,2,2],[a,a,a]]
array3 = [[d,d,d], {a=>1,b=>2}]
#keys = key1,key2,key3
i need to to show the one single hash in below format
output = {"key1" => [[a,b,c],[1,2,3],[x,y,z]], "key2" => [[1,2,1],[2,2,2],[a,a,a]], "key3" => [[d,d,d], {a=>1,b=>2}] }
i am tried to in this code
Hash[#key.zip(array1)]
Multiple variables can't be manipulated as easily as elements in, say, an array. The easiest way to produce desired output in your case is to just write it out manually.
output = {
key1: array1,
key2: array2,
key3: array3,
}
If you want to be more dynamic, put your arrays into a usable data structure. Then you can use .zip or whatever.
keys = [:key1, :key2, :key3]
usable_arrays = [array1, array2, array3]
Hash[keys.zip(usable_arrays)]

How do I collect nested hash values into an array in ruby?

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"]

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

Resources