Hash into grouped array - ruby

I'm not very experienced in ruby, so I'm struggling to format a piece of data.
I have this hash, which contains some keys that have the same value, ex:
{"key" => "value1", "key2" => "value2", "key3" => "value3", "key4" => "value1", "key5" => "value2" ..}
I'm trying to turn this into, an array containing the keys grouped by the values
[["key","key4"],["key2","key5"],["key3"]]

new_hash = {}
hash.each do |key, value|
new_hash[value] ||= []
new_hash[value] << key
end
array = new_hash.values # => [["key", "key4"], ["key2", "key5"], ["key3"]]

hash = {
"key" => "value1",
"key2" => "value2",
"key3" => "value3",
"key4" => "value1",
"key5" => "value2"
}
hash.group_by { |key, value| value }.values.map { |pairs| pairs.map &:first }
# => [["key", "key4"], ["key2", "key5"], ["key3"]]

hash.group_by{|k,v| v}.map{|k,v| v.reduce([]){|res,n| res << n.first}}

Related

Setting hash values from array

I have a hash:
row = {
'name' => '',
'description' => '',
'auth' => '',
'https' => '',
'cors' => '',
'url' => ''
}
and I also have an array:
["Cat Facts", "Daily cat facts", "No", "Yes", "No", "https://example.com/"]
How can I grab the array elements and set them as values for each key in the hash?
Let's say row is your hash and values is your array
row.keys.zip(values).to_h
=> {"name"=>"Cat Facts", "description"=>"Daily cat facts", "auth"=>"No", "https"=>"Yes", "cors"=>"No", "url"=>"https://example.com/"}
It works if they are in the right order, of course
h = { 'name'=>'',
'description'=>'',
'auth'=>'',
'https'=>'',
'cors'=>'',
'url'=>'' }
arr = ["Cat Facts", "Daily cat facts", "No", "Yes", "No",
"https://example.com/"]
enum = arr.to_enum
#=> #<Enumerator: ["Cat Facts", "Daily cat facts", "No",
# "Yes", "No", "https://example.com/"]:each>
h.transform_values { enum.next }
#=> { "name"=>"Cat Facts",
# "description"=>"Daily cat facts",
# "auth"=>"No",
# "https"=>"Yes",
# "cors"=>"No",
# "url"=>"https://example.com/" }
See Hash#transform_values. Array#each can be used in place of Kernel#to_enum.
If arr can be mutated enum.next can be replaced with arr.shift.
Given the hash and the array:
row = { 'name' => '', 'description' => '', 'auth' => '', 'https' => '', 'cors' => '', 'url' => '' }
val = ["Cat Facts", "Daily cat facts", "No", "Yes", "No", "https://example.com/"]
One option is to use Enumerable#each_with_index while transforming the values of the hash:
row.transform_values!.with_index { |_, i| val[i] }
row
#=> {"name"=>"Cat Facts", "description"=>"Daily cat facts", "auth"=>"No", "https"=>"Yes", "cors"=>"No", "url"=>"https://example.com/"}
The bang ! changes the original Hash.

In Ruby, what's the advantage of #each_pair over #each when iterating through a hash?

Let's say I want to access the values of a hash like this:
munsters = {
"Herman" => { "age" => 32, "gender" => "male" },
"Lily" => { "age" => 30, "gender" => "female" },
"Grandpa" => { "age" => 402, "gender" => "male" },
"Eddie" => { "age" => 10, "gender" => "male" },
"Marilyn" => { "age" => 23, "gender" => "female"}
}
I could use #each with two parameters:
munsters.each do |key, value|
puts "#{name} is a #{value["age"]}-year-old #{value["gender"]}."
end
Or I could use #each_pair with two parameters:
munsters.each_pair do |key, value|
puts "#{name} is a #{value["age"]}-year-old #{value["gender"]}."
end
Perhaps the difference between the two is not borne out in this simple example, but can someone help me to understand the advantage of using #each_pair over #each ?
Because Hash is an Enumerable, it has to have an each method. each_pair may be a clearer name, since it strongly suggests that two-element arrays containing key-value pairs are passed to the block.
They are aliases for each other: they share the same source code.

Group Array of Hashes by Key followed by values

Assuming I have the following dataset
[
{
:name => "sam",
:animal => "dog",
:gender => "male"
}, {
:name => "max",
:animal => "cat",
:gender => "female"
}, {
:name => "joe",
:animal => "snake",
:gender => "male"
}
]
How would you group the array of hashes to:
{
:name => ["sam", "max", "joe"]
:animal => ["dog", "cat", "snake"]
:gender => ["male", "female", "male"]
}
I've read similar articles such as this and Group array of hashes by key
However, most examples return the values as increment counts where I'm looking for actual separate values.
My attempt
keys = []
values = []
arr.each do |a|
a.each do |k, v|
keys << k
#this is where it goes wrong and where I'm stuck at
values << v
end
end
keys = keys.uniq
I understand where I went wrong is how I'm trying to segment the values. Any direction would be appreciated!
input.reduce { |e, acc| acc.merge(e) { |_, e1, e2| [*e2, *e1] } }
#⇒ {:name=>["sam", "max", "joe"],
# :animal=>["dog", "cat", "snake"],
# :gender=>["male", "female", "male"]}
few more approaches
data.each_with_object({}){ |i,r| i.each{ |k,v| (r[k] ||= []) << v } }
data.flat_map(&:to_a).each_with_object({}){ |(k,v), r| (r[k] ||= []) << v }
data.flat_map(&:to_a).group_by(&:first).inject({}){ |r, (k,v)| r[k] = v.map(&:last); r }

ruby sort hash based on another hash

I have the following two hashes:
db = {"1" => "first_name", "2" => "last_name", "5" => "status", "10" => "city" }
csv = {"1" => "first_name", "2" => "last_name", "5" => "status", "7" => "address", "10" => "city" }
I want to order csv based on db, and if there are any keys in csv not in db, then I want to move them to the end of csv, so in the above example the result would look like this:
{"1" => "first_name", "2" => "last_name", "5" => "status", "10" => "city", "7" => "address" }
Since the key "7" wasn't in db hash, we just moved it to the end of the csv hash.
This is what I tried:
db = {"1" => "first_name", "2" => "last_name", "5" => "status", "10" => "city" }
csv = {"1" => "first_name", "2" => "last_name", "5" => "status", "7" => "address", "10" => "city" }
rejects = csv.reject {|k| db.include? k }
result = csv.keep_if {|k,_| db.include? k }
result.merge!(rejects)
result
=> {"1"=>"first_name", "2"=>"last_name", "5"=>"status", "10"=>"city", "7"=>"address"}
It seems to work. But is it guaranteed to work? Will the merger always put the second hash at the end, or is there a possibility that the merger could mix the hashes together without consideration of order?
You could do the following:
db_keys = db.keys
#=> ["1", "2", "5", "10"]
keys = db_keys + (csv.keys-db_keys)
#=> ["1", "2", "5", "10", "7"]
Hash[keys.zip(csv.values_at(*keys))]
#=> { "1"=>"first_name", "2"=>"last_name", "5"=>"status",
# "10"=>"city", "7"=>"address"}

Convert array of key value object to object of the key values (ruby)

I have a list of objects that have key attribute and value attribute.
I would like to convert it to an object that contains attributes named as keys with the values.
Example will make it clearer...
This
[{
:key => "key1",
:value => "value1"
}, {
:key => "key2",
:value => "value2"
}]
Should become like this:
{
:key1 => "value1"
:key2 => "value2"
}
I'm sure there is one line to make it happen
Thanks
Using Hash::[], Array#map:
a = [{
:key => "key1",
:value => "value1"
}, {
:key => "key2",
:value => "value2"
}]
Hash[a.map { |h| [h[:key], h[:value]] }]
# => {"key1"=>"value1", "key2"=>"value2"}
Hash[a.map { |h| h.values_at(:key, :value) }]
# => {"key1"=>"value1", "key2"=>"value2"}
Hash[a.map { |h| [h[:key].to_sym, h[:value]] }]
# => {:key1=>"value1", :key2=>"value2"}
a.each_with_object({}) {|h,g| g.update({h[:key].to_sym => h[:value]}) }
# => {:key1=>"value1", :key2=>"value2"}
Hash[array.map(&:values)]
#=> {"key1"=>"value1", "key2"=>"value2"}
Just to promote the to_h a bit:
[{
:key => "key1",
:value => "value1"
}, {
:key => "key2",
:value => "value2"
}].map(&:values).map{|k,v| [k.to_sym,v]}.to_h
# => {:key1=>"value1", :key2=>"value2"}

Resources