Convert hash of hash in array of hash - ruby-on-rails

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

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.

Merging Two Hashses

I am using the Google Civic Information API. It is giving me two arrays of hashes. I want to print information from both on the screen. Hash 1 has some integers as a k-v pair (officialIndices). These represent the index number for the corresponding object in the second hash. How can merge these two? I want to display the information from both hashes together. Maybe it would be better to replace the values of officialIndices with the indexed hash in the second array. Thanks for any advice!
Hash 1:
{
"name" => "President of the United States",
"divisionId" => "ocd-division/country:us",
"levels" => ["country"],
"roles" => ["headOfState", "headOfGovernment"],
"officialIndices" => [0]
}
Hash 2:
{
"name" => "Barack Obama",
"address" => [{
"line1" => "The White House",
"line2" => "1600 pennsylvania avenue nw",
"city" => "washington",
"state" => "DC",
"zip" => "20500"
}],
"party" => "Democratic",
"phones" => ["(202) 456-1111"],
"urls" => ["http://www.whitehouse.gov/"],
"photoUrl" => "http://www.whitehouse.gov/sites/default/files/imagecache/admin_official_lowres/administration-official/ao_image/president_official_portrait_hires.jpg",
"channels" => [
{ "type" => "GooglePlus", "id" => "+whitehouse" },
{ "type" => "Facebook", "id" => "whitehouse" },
{ "type" => "Twitter", "id" => "whitehouse" },
{ "type" => "YouTube", "id" => "barackobama" }
]
}
EDIT** To clarify, Hash 1 is the first hash in an array of hashes. Hash 2 is the first hash in an array of hashes. I would like to replace the number in officialIndice in Hash 1 with Hash 2. It's confusing me because some officialIndices have more than one number. Hope that makes sense.
Merge won't work; what will you do if officialIndices has multiple elements?
array1.each do |el1|
el1["officials"] = el1["officialIndices"].map { |idx|
array2[idx]
}
el1.delete("officialIndices")
end
(Note: this is destructive, i.e. it will change array1. If you want array1 unchanged, I'll rewrite.)
You can use Hash#merge with a block:
foo = { "name" => "President of the United States" }
bar = { "name" => "Barack Obama" }
foo.merge(bar) { |key, old_val, new_val| {description: old_val, value: new_val} }
=> {"name"=>{:description=>"President of the United States",
:value=>"Barack Obama"}}
So, you can specify your merge logic by this way. This solution effective if you have more than 1 overlapping key with similar logic.
You can use Hash#merge to merge information from two hashes. However, you have an overlapping key (name) in both, so you'll want to rename it either hash before merging:
# Rename "name" to "position_name" before merging to prevent collision
hash1["position_name"] = hash1.delete("name")
merged_hash = hash1.merge(hash2)

Group Hash by values in ruby

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

Accepting either a hash or an array of hashes as arguments to a Ruby method

I have the method:
def self.store(params)
params.each { }
end
It works perfectly, if I pass an Array of Hashes:
params = [ { key: 'value' }, { key: 'value' } ]
However, I might want to pass only a single Hash, instead of an Array of Hashes:
params = { key: 'value' }
What would be the cleanest Ruby way to convert a Hash into an Array of Hashes?
The Array() method a kind of ensures, that an array is always returned, but when the Hash is passed, it is converted into an Array itself.
Array({ key: 'value' }) => [[:key, 'value']]
What I need:
{ key: 'value' } => [ { key: 'value' } ]
Is there any nice way to implement this, or do I have to do a manual type checking with is_a?(Array) ?
For me, the best solution is to change the method to:
def self.store(*hashes)
params = hashes.flatten
puts params.inspect
end
If you pass a single hash, it will be an array
If you pass an array of hashes, it remains the same
If you pases N hashes, it compacts all parameters into a one dimensional array.
You can pass whatever you want.
self.store({:key => 'value'}) # => [{:key => 'value'}]
self.store({:key => 'value'}, {:foo => 'bar'}) # => [{:key => 'value'}, {:foo => 'bar'}]
self.store([{:key => 'value'}, {:foo => 'bar'}]) # => [{:key => 'value'}, {:foo => 'bar'}]
Try this
def self.store(params)
params = [params].flatten
...
end
I would do it like this:
def self.store(params)
(params.is_a?(Array) ? params : [params]).each {|single_hash| }
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