Group Hash by values in ruby - ruby-on-rails

I have a hash in ruby which looks something like this:
{
"admin_milestones"=>"1",
"users_milestones"=>"0",
"admin_goals"=>"1",
"users_goals"=>"0",
"admin_tasks"=>"1",
"users_tasks"=>"0",
"admin_messages"=>"1",
"users_messages"=>"0",
"admin_meetings"=>"1",
"users_meetings"=>"0"
}
I am trying to lookout for a solutions which can cut this hash in to two parts, one with value as 1 and other hash with value as 0.

You can group hash by its value:
h1 = {
"admin_milestones"=>"1",
"users_milestones"=>"0",
"admin_goals"=>"1",
"users_goals"=>"0",
"admin_tasks"=>"1",
"users_tasks"=>"0",
"admin_messages"=>"1",
"users_messages"=>"0",
"admin_meetings"=>"1",
"users_meetings"=>"0"
}
h2 = h1.group_by{|k,v| v}
It will produce a hash grouped by its values like this:
h2 = {"1"=>[["admin_milestones", "1"], ["admin_goals", "1"], ["admin_tasks", "1"], ["admin_messages", "1"], ["admin_meetings", "1"]],
"0"=>[["users_milestones", "0"], ["users_goals", "0"], ["users_tasks", "0"], ["users_messages", "0"], ["users_meetings", "0"]]}

If you want an array as answer the cleanest solution is the partition method.
zeros, ones = my_hash.partition{|key, val| val == '0'}

You should use group_by on the keys arrays and use the value as the grouping element:
h1 = {
"admin_milestones"=>"1",
"users_milestones"=>"0",
"admin_goals"=>"1",
"users_goals"=>"0",
"admin_tasks"=>"1",
"users_tasks"=>"0",
"admin_messages"=>"1",
"users_messages"=>"0",
"admin_meetings"=>"1",
"users_meetings"=>"0"
}
# group_by on the keys, then use the value from the hash as bucket
h2 = h1.keys.group_by { |k| h1[k] }
puts h2.inspect
Returns a hash from value to array of keys:
{
"1" => [
[0] "admin_milestones",
[1] "admin_goals",
[2] "admin_tasks",
[3] "admin_messages",
[4] "admin_meetings"
],
"0" => [
[0] "users_milestones",
[1] "users_goals",
[2] "users_tasks",
[3] "users_messages",
[4] "users_meetings"
]
}

Just Hash.select:
h1.select { |key, value| value == '0' } #=> {"users_milestones"=>"0", "users_goals"=>"0", ...}
h1.select { |key, value| value == '1' } #=> {"admin_milestones"=>"1", "admin_goals"=>"1", ...}
The return value depends on your Ruby version. Ruby 1.8 returns a array of arrays, whereas Ruby 1.9 returns a hash like in the example above.

Similar with https://stackoverflow.com/a/56164608/14718545 you can use group_by but with then, in this case, you will avoid instantiating an extra variable.
{
"admin_milestones" => "1",
"users_milestones" => "0",
"admin_goals" => "1",
"users_goals" => "0",
"admin_tasks" => "1",
"users_tasks" => "0",
"admin_messages" => "1",
"users_messages" => "0",
"admin_meetings" => "1",
"users_meetings" => "0"
}.then { |h| h.keys.group_by { |k| h[k] } }
{"1"=>["admin_milestones", "admin_goals", "admin_tasks", "admin_messages", "admin_meetings"],
"0"=>["users_milestones", "users_goals", "users_tasks", "users_messages", "users_meetings"]}

Related

Sort items in a nested hash by their key

I have a nested hash with unsorted keys:
given = {
"lorem" => {
:AA => "foo",
:GR => "foo",
:BB => "foo"
},
"ipsum" => {
:ZZ => "foo",
:GR => "foo",
}
}
What I'm trying to accomplish is a hash with sorted keys:
goal = {
"ipsum" => {
:GR => "foo",
:ZZ => "foo"
},
"lorem" => {
:AA => "foo",
:BB => "foo",
:GR => "foo"
}
}
I have experimented with .each method and sort_by
given.each { |topic| topic[:key].sort_by { |k, v| k } }
But I'm getting an error message: TypeError: no implicit conversion of Symbol into Integer
Any help is greatly appreciated!
PS: I noticed with gem pry the output is already sorted. But in IRB it's not.
You can use group_by, and transform_values to transform the values inside each hash, also using sort_by plus to_h:
given.transform_values { |value| value.sort.to_h }.sort.to_h
# {"ipsum"=>{:GR=>"foo", :ZZ=>"foo"}, "lorem"=>{:AA=>"foo", :BB=>"foo", :GR=>"foo"}}
You're getting an error because when iterating over a hash, you have to local variables within the block scope to use, the key and its value, you're assigning only one (topic) and trying to get its key, which would be trying to access a key in:
["lorem", {:AA=>"foo", :GR=>"foo", :BB=>"foo"}]
Which isn't possible as is an array. You can update your code to:
given.each do |topic, value|
...
end
But anyway you'll need a way to store the changes or updated and sorted version of that topic values.
given_hash = {"lorem"=>{:AA=>"foo", :GR=>"foo", :BB=>"foo"}, "ipsum"=>{:ZZ=>"foo", :GR=>"foo"}}
Get keys
given_hash.keys
=> ["lorem", "ipsum"]
New sorted hash
new_hash = {}
given_hash.keys.sort.each do |sorted_key|
new_hash[sorted_key] = given[sorted_key]
end
=> {"ipsum"=>{:ZZ=>"foo", :GR=>"foo"}, "lorem"=>{:AA=>"foo", :GR=>"foo", :BB=>"foo"}}
There can be a better way to do this.

Convert hash of hash in array of hash

I need to modify a hash of hash and convert it in a hash of array.
I also need to add a new key value.
This is my current hash:
{ "132552" => {
"name" => "Paul",
"id" => 53
},
"22478" => {
"name" => "Peter",
"id" => 55
}
}
I expect the output to be like this:
[
{
"person_id": "132552",
"name" => "Paul",
"id" => 53
},
{
"person_id": "22478",
"name" => "Peter",
"id" => 55
}
]
You could map with Enumerable#map to hash values merging (Hash#merge) the new pairs:
original_hash.map { |k,v| v.merge({ "person_id" => k }) }
#=> [{"name"=>"Paul", "id"=>53, "person_id"=>"132552"}, {"name"=>"Peter", "id"=>55, "person_id"=>"22478"}]
Probably not the best solution but the following should work (considering h is your hash):
#h.each do |key,value|
value["person_id"] = key
end
#array = []
#h.each do |key, value|
#array << value
end
This is a perfect fit for Enumerable#each_with_object.
output = input.each_with_object([]) do |(person_id, values), array|
array << values.merge("person_id" => person_id)
end
This method takes an arbitrary object for our initial state (here an array), iterate the collection (our hash) with a block. The initial object is yield as second argument of the block. At each iteration we're populating the array as we want. At the end of the block this object is returned, in output variable in my example.
Note that in my example, I destructure the hash into (person_id, values) : each entry of an hash can be destructed as (key, values) into block/method arguments.
Enumerable#each_with_object

Iterate a hash of nested arrays/hashes/strings and perform some logic on them

I want to loop through a hash that contains a mix of nested object types. E.g.
{
"a" => "1",
"b" => ["1", "2"],
"c" => {"d" => "1", "e" => {"f" => ["1", "2", {"g" => ["x", "y", "1"]}]}}
}
and transform all string values to integers, given that they consist of numbers only. So they should match /^\d+$/.
But the important part is the iteration part.
How do I find all the values of key/value pairs as well as all values inside arrays, so I can manipulate them?
It's a bit similar to the deep_symbolize_keys method, only here I'm interested in everything else that hash keys.
My own implementation goes like this, but it's long and I may not have catched all edge cases:
def all_to_i(x)
if x.is_a?(Hash)
x.each do |k, v|
if v.is_a?(Hash)
all_to_i(v)
elsif v.is_a?(Array)
x[k] = v.map { |v2| all_to_i(v2) }
elsif v.is_a?(String)
if v.scan(/^\d+$/) != []
x[k] = v.to_i
else
x[k] = v
end
end
end
elsif x.is_a?(Array)
x.map { |x2| all_to_i(x2) }
elsif x.is_a?(String)
if x.scan(/^\d+$/) != []
x.to_i
else
x
end
end
end
# => {"a"=>1, "b"=>[1, 2], "c"=>{"d"=>1, "e"=>{"f"=>[1, 2, {"g"=>["x", "y", 1]}]}}}

Rails iterate multiple hash

I have the following hash :
{
"2017-01-01" => {
"2"=> [
{:a=>"2017-01-01", :b=>"2", :c=>"1"},
{:a=>"2017-01-01", :b=>"2", :c=>"2"}
]
},
"2017-01-02" => {
"5"=> [
{:a=>"2017-01-02", :b=>"5", :c=>"1"}
]
}
}
I would iterate separately
1)first iteration
{
{:a=>"2017-01-01", :b=>"2", :c=>"1"},
{:a=>"2017-01-01", :b=>"2", :c=>"2"}
}
2) second iteration
{
{:a=>"2017-01-02", :b=>"5", :c=>"1"}
}
How can I do? Thanks in advance.
answer for your question is in How to iterate over a hash in Ruby?
check it.
hash.each do |key, array|
puts array
end
if 'array' again is a hash, then you need to loop it as follows
hash.each do |key, hash2|
hash2.each do |key2,array|
puts array
end
end

how can I simply merge a hash into a new one?

I have a simple hash like so { "1234" => "5", "2345" => "6" }
How can I create a new hash with both the keys and values in side it? Like so:
{ key_id = "1234", value_id = "5" }, { key_id = "2345", value_id = "6" }
What are you actually trying to achieve with this? If you're looking to iterate over all of the keys, you can use .keys:
h = { "1234" => "5", "2345" => "6" }
h.keys
=> ["1234", "2345"]
If you want to just create an array of hashes, you should be able to iterate over the keys:
h = { "1234" => "5", "2345" => "6" }
a = []
h.each {|k, v| a << {:key_id => k, :value_id => v}
By "merging" two hashes, I think you mean to put all the contents of two different hashes into one new hash. Because the keys of a hash must be unique, if the same key exists in the both source hashes, only one value can survive.
In this example, I merge the contents of hash x and hash y into hash z. The values in y will overwrite the values in z if there are any duplicate keys.
x = { "a" => "1","b" => "2","c" => "3" }
y = { "c" => "999","d" => "4","e" => "5" }
z = {}
x.each do |key,value|
z[key] = value
end
y.each do |key,value|
z[key] = value
end
The source hashes had a total of 6 keys. Because the key "c" was in both hases, the merged hash only has 5 keys.
=> {"a"=>"1", "b"=>"2", "c"=>"999", "d"=>"4", "e"=>"5"}
You can loop through each pair of the original hash and build up an array of hashes:
hashes = []
{ "1234" => "5", "2345" => "6" }.each_pair {|key, value| hashes << { :key_id => key, :value_id => value } }
Will yield:
[{:key_id=>"2345", :value_id=>"6"}, {:key_id=>"1234", :value_id=>"5"}]
What should the keys be for the derived hash, the same at the original? In that case use this snippet:
x = { "1234" => "5", "2345" => "6" }
y = {}
x.each do |key, value|
y[key] = { "key_id" => key, "value_id" => value }
end

Resources