loop through hashes and produce new hash/array in ruby on rails - ruby-on-rails

Hi guys I am having trouble to find out a solution to a problem.So I have an array like bellow.
{
"9": [
{
"id": "9",
"day": "2017-08-02",
"voltage": "3397.1"
},
{
"id": "9",
"day": "2017-08-01",
"voltage": "11518.67"
},
{
"id": "9",
"day": "2017-07-31",
"voltage": "12835.5900000002"
}
],
"11": [
{
"id": "11",
"day": "2017-08-02",
"voltage": "910.21"
},
{
"id": "11",
"day": "2017-08-01",
"voltage": "3616.43"
},
{
"id": "11",
"day": "2017-07-31",
"voltage": "2085.37"
}
],
"12": [
{
"id": "12",
"day": "2017-08-02",
"voltage": "4793.96"
},
{
"id": "12",
"day": "2017-08-01",
"voltage": "17762.2999999998"
},
{
"id": "12",
"day": "2017-07-31",
"voltage": "18334.4000000001"
}
]
}
Now what I want to do is to produce an array like below
{
"9": [
"day": ["2017-08-02", "2017-08-01", "2017-07-31"],
"voltage": ["3397.1", "11518.67", "12835.5900000002"]
],
"11": [
"day": ["2017-08-02","2017-08-01", "2017-07-31"]
"voltage": ["910.21", "3616.43", "2085.37"]
]
...
}
and so on.
I have tried to iterate over the array using .each and .map method but it didn't work.I search trough existing solutions in stackoverflow but nothing helps me out.
Can some one help me with this.
Thanks so much in advance.

If you define hash as the original object you can do it with reduce like so:
result = hash.reduce({}) do |memo, (key, vals)|
memo[key] = {
"day" => vals.map { |val| val[:day] },
"voltage" => vals.map { |val| val[:voltage] }
}
memo
end
You can also do it with just each:
result = {}
hash.each do |key, vals|
result[key] = {
"day" => vals.map { |val| val[:day] },
"voltage" => vals.map { |val| val[:voltage] }
}
end

I am unsure if you are writing your hash as JSON or Ruby, but I didn't have time to rewrite it to Ruby, to I just copy pasted it into a script, which made all the keys be symbols.
I believe this should be working:
new_hash = {}
current_hash.each do |key, value_array|
new_hash[key] ||= {}
value_array.each do |values|
new_values = values.dup
new_values.delete(:id)
new_values.each do |attribute_name, attribute_value|
new_hash[key][attribute_name] ||= []
new_hash[key][attribute_name] << attribute_value
end
end
end
Which gives me this result:
{:"9"=>{:day=>["2017-08-02", "2017-08-01", "2017-07-31"], :voltage=>["3397.1", "11518.67", "12835.5900000002"]}, :"11"=>{:day=>["2017-08-02", "2017-08-01", "2017-07-31"], :voltage=>["910.21", "3616.43", "2085.37"]}, :"12"=>{:day=>["2017-08-02", "2017-08-01", "2017-07-31"], :voltage=>["4793.96", "17762.2999999998", "18334.4000000001"]}}

One more solution from me :)
For Ruby 2.4.0:
hash.transform_values do |values|
values.each_with_object({}) do |value, memo|
memo[:day] ? memo[:day] << value[:day] : memo[:day] = [value[:day]]
memo[:voltage] ? memo[:voltage] << value[:voltage] : memo[:voltage] = [value[:voltage]]
end
end
And for earlier:
hash.map do |key, values|
[
key,
values.each_with_object({}) do |value, memo|
memo[:day] ? memo[:day] << value[:day] : memo[:day] = [value[:day]]
memo[:voltage] ? memo[:voltage] << value[:voltage] : memo[:voltage] = [value[:voltage]]
end
]
end.to_h

Related

how to merge Hashes in Ruby/Rails

I have a Hash like this, which should be "merged" to its uniq nested values
[
{
"slug": "color",
"values": [{ "slug": "amethyst" },
{ "slug": "coral" }],
},
{
"slug": "color",
"values": [{ "slug": "amethyst" }],
},
{
"slug": "power-source",
"values": [{ "slug": "110V"}],
}
]
at the same time it should count the duplicate values but made uniq in an items array:
{ "slug": "color",
"items": [
{
"slug": "amethyst",
"count": 2
},
{
"slug": "coral",
"count": 1
}]
},
{
"slug": "power-source",
"items": [
{
"slug": "110V",
"count": 1
}]
}
]
is there a "Rails method" to achieve this?
Thank you
I think there's nothing built-in in Rails that allows you to get such a custom requirement, but you can achieve it by playing around with different methods and their return values:
data
.group_by { |hash| hash[:slug] }
.transform_values do |values|
values
.flat_map { |vals| vals[:values] }
.group_by { |value| value[:slug] }
.transform_values(&:count)
end.map do |slug, items|
[slug, items.map { |item, count| {slug: item, count: count} }]
end.map { |slug, items| {slug: slug, items: items} }
# [{:slug=>"color",
# :items=>[{:slug=>"amethyst", :count=>2}, {:slug=>"coral", :count=>1}]},
# {:slug=>"power-source", :items=>[{:slug=>"110V", :count=>1}]}]
As you see, you can first group every hash in the array by their slug value, then transform the values that hash contains, mapping and flattening every array by their values key and then grouping to get their total.
After that you can just create the hash with its keys/values you need.
It might simplify the things a bit if you end up with a single hash, whose keys are the "slugs" and contains the items as its values.

Group JSON as new objects based on data

I have some JSON data that stores a date time string and total. e.g.
[{
"Date": "2012-04-01 12:00:00",
"Total": "14"
}, {
"Date": "2012-04-02 06:00:00",
"Total": "3"
}, {
"Date": "2012-04-02 14:00:00",
"Total": "12"
}, {
"Date": "2012-04-02 16:00:00",
"Total": "5"
}, {
"Date": "2012-04-02 17:00:00",
"Total": "7"
}, {
"Date": "2012-04-03 06:00:00",
"Total": "9"
}, {
"Date": "2012-04-03 14:00:00",
"Total": "2"
}, {
"Date": "2012-04-04 06:00:00",
"Total": "1"
}, {
"Date": "2012-04-04 14:00:00",
"Total": "10"
}, {
"Date": "2012-04-04 19:00:00",
"Total": "8"
}, {
"Date": "2012-04-04 21:00:00",
"Total": "4"
}]
What I'd like to do is create new JSON objects for data that is the same year, month, and day.
So for example a full set of data could be formatted into:
DAY:
[{
"Date": "2012-04-01"
"Total": "3"
},{
"Date": "2012-04-02"
"Total": "4"
},{
"Date": "2012-04-03"
"Total": "6"
}]
MONTH:
[{
"Date": "2012-04"
"Total": "36"
},{
"Date": "2012-05"
"Total": "11"
},{
"Date": "2012-06"
"Total": "23"
},{
"Date": "2012-07"
"Total": "17"
}]
YEAR:
[{
"Date": "2012"
"Total": "91"
},{
"Date": "2013"
"Total": "102"
},{
"Date": "2014"
"Total": "78"
}]
How could I do this is Rails? If I could get an example of how I could do one of them, then I could then use that as a base to do the others!
So in the controller I have:
def get_count_day
render json: data.to_json
end
def get_count_month
render json: data.to_json
end
def get_count_year
render json: data.to_json
end
count_by_day =
json.group_by{ |record| record[:Date].to_date.strftime }
.map { |k, v| { Date: k, Total: v.map { |y| y[:Total].to_i }.sum.to_s } }
#=> [{:Date=>"2012-04-01", :Total=>"14"}, {:Date=>"2012-04-02", :Total=>"27"}, {:Date=>"2012-04-03", :Total=>"11"}, {:Date=>"2012-04-04", :Total=>"23"}]
count_by_month =
json.group_by{ |record| record[:Date].to_date.strftime('%Y-%m') }
.map { |k, v| { Date: k, Total: v.map { |y| y[:Total].to_i }.sum.to_s } }
#=> [{:Date=>"2012-04", :Total=>"75"}]
count_by_year =
json.group_by{ |record| record[:Date].to_date.strftime('%Y') }
.map { |k, v| { Date: k, Total: v.map { |y| y[:Total].to_i }.sum.to_s } }
#=> [{:Date=>"2012", :Total=>"75"}]
Hey you can try this way for Day
given_json.group_by{|b| b["Date"].to_date.strftime("%Y-%d-%m")}.collect{|key,value| {"Date" =>key , "Total" => value.sum{|d| d["Total"].to_i}}}
For Month
given_json.group_by{|b| b["Date"].to_date.strftime("%Y-%m")}.collect{|key,value| {"Date" =>key , "Total" => value.sum{|d| d["Total"].to_i}}}
For Year
given_json.group_by{|b| b["Date"].to_date.strftime("%Y")}.collect{|key,value| {"Date" =>key , "Total" => value.sum{|d| d["Total"].to_i}}}
Here is an example to retrieve them grouped by day:
grouped_json = Hash.new(0)
your_json_array.each do |array_element|
date = Date.parse(array_element['Date']).strftime('%Y-%m-%d')
grouped_json[date] += array_element['Total']
end
grouped_json.map {|k,v| {'Date' => k, 'Total' => v} }
by month?
grouped_json = Hash.new(0)
your_json_array.each do |array_element|
date = Date.parse(array_element['Date']).strftime('%Y-%m')
grouped_json[date] += array_element['Total']
end
grouped_json.map {|k,v| {'Date' => k, 'Total' => v} }
by year?
grouped_json = Hash.new(0)
your_json_array.each do |array_element|
date = Date.parse(array_element['Date']).strftime('%Y')
grouped_json[date] += array_element['Total']
end
grouped_json.map {|k,v| {'Date' => k, 'Total' => v} }

Reverse a nested array in Ruby

I'm attempting to take a JSON API response, with nested associated resources, and reverse the associations in a Rails app.
So, imagine I get a response like this:
{
"spenders": [
{
"id": 1,
"name": "John Doe",
"accounts": [
{
"id": 1,
"name": "Account One"
},
{
"id": 2,
"name": "Account Two"
}
]
},
{
"id": 2,
"name": "Jane Doe",
"accounts": [
{
"id": 1,
"name": "Account One"
},
{
"id": 3,
"name": "Account Three"
}
]
}
]
}
My goal is to convert this into structure like this:
{
"accounts": [
{
"id": 1,
"name": "Account One",
"spenders": [
{
"id": 1,
"name": "Stephen Margheim"
},
{
"id": 2,
"name": "Greg Barendt"
}
]
},
{
"id": 2,
"name": "Account Two",
"spenders": [
{
"id": 1,
"name": "Stephen Margheim"
}
]
},
{
"id": 3,
"name": "Account Three",
"spenders": [
{
"id": 2,
"name": "Greg Barendt"
}
]
}
]
}
Now, I can do this fairly well with iteration over the hash and building a new hash:
spenders_hash = {}
accounts.each do |account|
account.spenders.each do |spender|
if spenders_hash.key? spender.id
spenders_hash[spender.id][:accounts] << account
else
spenders_hash[spender.id] = hash_from_spender_and_account(spender, account)
end
end
end
spenders_hash
def hash_from_spender_and_account(spender, account)
{
id: spender.id,
name: spender.name,
accounts: [account],
}
end
I'm hoping to find [1] a more flexible solution that isn't reliant on knowing the key names in advance and [2] hopefully more efficient.
Thoughts?

ruby return an array of sub elements using map

I have the following structure:
"countries": [
{
"states" :[
{
"name" :"Texas",
"id": "a1"
},
{
"name" :"Nebraska",
"id": "a1"
}
]
},
{
"states" :[
{
"name" :"New York",
"id": "a1",
},
{
"name" :"Florida",
"id": "a1"
}
]
}
]
I want to return an array of all the states from above.
Here is what I tried:
countries.map { |country| country.states.map { |state| state.name } }
But it returns only the first 2 statest 'Texas' and Nebraska.
Can someone tell me what I am doing wrong here?
you structure wasn't right, so corrected:
countries = [
{
"states" => [
{
"name" => "Texas",
"id"=> "a1"
},
{
"name"=> "Nebraska",
"id"=> "a1"
}
]
},
{
"states" => [
{
"name"=> "New York",
"id"=> "a1",
},
{
"name" =>"Florida",
"id"=> "a1"
}
]
}
]
Ruby wasn't accepting ":" for strings for some weird reasons. Like this(which isn't working):
countries = [
{
"states": [
{
"name": "Texas",
"id": "a1"
},
{
"name": "Nebraska",
"id": "a1"
}
]
},
{
"states": [
{
"name": "New York",
"id": "a1",
},
{
"name" :"Florida",
"id": "a1"
}
]
}
]
For this, you can do:
countries.map{ |c| c["states"].map{|s| s["name"]}}.flatten
#=> ["Texas", "Nebraska", "New York", "Florida"]
Or if you get the repetitive values then:
countries.map{ |c| c["states"].map{|s| s["name"]}}.flatten.uniq
#=> ["Texas", "Nebraska", "New York", "Florida"]
I hope this helps.
Go for Surya's answer, it is the same solution. Just wanna show how I write it:
countries.map{|x|x['states']}
.flatten
.map{|x|x['name']}

Rails - better flow control - loop

I am grabbing value data: name, uid, highschool_name, graduateschool_name like this:
def add_friends
facebook.get_connections("me", "friends", :fields => "name, id, education").each do |hash|
self.friends.where(:name => hash['name'],
:uid => hash['id'],
:highschool_name => hash['education']['school']['name'] unless hash["education"].blank?,
:graduateschool_name => hash['education']['school']['name'] unless hash["education"].blank?).
first_or_create
end
end
From an array of hash:
"education": [
{
"school": {
"id": "110703012290674",
"name": "Kunskapsgymnasiet Malmö"
},
"year": {
"id": "136328419721520",
"name": "2009"
},
"type": "High School"
},
{
"school": {
"id": "112812485399398",
"name": "Malmö University"
},
"year": {
"id": "118118634930920",
"name": "2012"
},
"concentration": [
{
"id": "104076956295773",
"name": "Computer Science"
}
],
"type": "Graduate School",
"classes": [
{
"id": "165093923542525",
"name": "Programmering",
"description": "Kursen fokuserar på metoder och tekniker vid utveckling av webbapplikationer med hjälp av HTML5."
}
]
}
],
EDIT:
This code dosent work. I would like to pick every hichschool and Graduate School from this array of hash and save it.
high_schools = response['education'].collect{|ed| ed['school']['name'] if ed['type'] == "High School" }
grad_schools = response['education'].collect{|ed| ed['school']['name'] if ed['type'] == "Graduate School" }

Resources