How do I sum and group values in Array of Hashes? - ruby-on-rails

I have the following data structure:
a = [
{ "payer": "UNILEVER", "points": 200, "timestamp": "2020-10-31T11:00:00Z" },
{ "payer": "DANNON", "points": -200, "timestamp": "2020-10-31T15:00:00Z" },
{ "payer": "MILLER COORS", "points": 10000, "timestamp": "2020-11-01T14:00:00Z" },
{ "payer": "DANNON", "points": 300, "timestamp": "2020-10-31T10:00:00Z" }
]
I want to sum up all point by payer. How can I do so?

a.group_by { |element| element[:payer] }.each_with_object({}) { |(payer, records), result| result[payer] = records.inject(0) { |sum, record| sum + record[:points] } }
=> {"UNILEVER"=>200, "DANNON"=>100, "MILLER COORS"=>10000}

a.group_by {|h| h[:payer] }
.transform_values {|a| a.sum {|h| h[:points] } }
# => {"UNILEVER"=>200, "DANNON"=>100, "MILLER COORS"=>10000}

Input
a = [{ "payer": "UNILEVER", "points": 200, "timestamp": "2020-10-31T11:00:00Z" },
{ "payer": "DANNON", "points": -200, "timestamp": "2020-10-31T15:00:00Z" },
{ "payer": "MILLER COORS", "points": 10000, "timestamp": "2020-11-01T14:00:00Z" },
{ "payer": "DANNON", "points": 300, "timestamp": "2020-10-31T10:00:00Z" }]
Code
result=a.group_by { |h| h[:payer] }.values.map do |arr|
arr.reduce do |h1, h2|
h1.merge(h2) do |k, f, s|
k.eql?(:points) ? f + s : f
end
end
end
p result
Output
#=>[{:payer=>"UNILEVER", :points=>200, :timestamp=>"2020-10-31T11:00:00Z"}, {:payer=>"DANNON", :points=>100, :timestamp=>"2020-10-31T15:00:00Z"}, {:payer=>"MILLER COORS", :points=>10000, :timestamp=>"2020-11-01T14:00:00Z"}]

You can get result with single loop:
a.each_with_object(Hash.new { |h, k| h[k] = 0 }) { |o, r| r[o[:payer]] += o[:points] }
# => {"UNILEVER"=>200, "DANNON"=>100, "MILLER COORS"=>10000}

Related

OData V4: How to sum individual $count values

I have the following OData V4 query:
.../odata/Locations/?$select=Id&$filter=(Id eq 9bb29421-5160-4546-b87f-a78c0074f5c5 or Id eq 8b2a9727-a642-446e-b992-76c6d1584989)&$expand=Assets($select=Id;$filter=ParentAssetId eq null;$expand=Jobs($select=Id;$count=true;$top=0))
with the following, as expected, result:
{
"#odata.context": ".../odata/$metadata#Locations(Id,Assets(Id,Jobs(Id)))",
"value": [
{
"Id": "8b2a9727-a642-446e-b992-76c6d1584989",
"Assets": [
{
"Id": "540d0855-aa1c-4e94-9d0b-332c99ec00b6",
"Jobs#odata.count": 2,
"Jobs": []
},
{
"Id": "6dcaa0e8-fc31-4d86-9f1a-a64300f8815c",
"Jobs#odata.count": 1,
"Jobs": []
},
{
"Id": "db4cf86b-9f42-4a99-b7b4-a64300f8c740",
"Jobs#odata.count": 1,
"Jobs": []
}
]
},
{
"Id": "9bb29421-5160-4546-b87f-a78c0074f5c5",
"Assets": [
{
"Id": "08a2a046-86c1-41a9-b2b1-a7ac007bed64",
"Jobs#odata.count": 1,
"Jobs": []
},
{
"Id": "2b76dad1-4058-4261-8a40-a7af00cb8bd5",
"Jobs#odata.count": 1,
"Jobs": []
},
{
"Id": "3a5472a1-68c4-4dc4-b2cd-a797007b0068",
"Jobs#odata.count": 2,
"Jobs": []
}
]
}
]
}
How can I sum the Jobs#odata.count values and return a single value: 8?
I have tried some &$apply=aggregate(Assets/Jobs#odata.count with sum as Total) but it is an incorrect syntax :(
I fixed this by changing the query perspective and count the Jobs from the beginning:
.../odata/Jobs/$count/?$expand=Asset($select=LocationId)&$filter=Asset/LocationId in (9bb29421-5160-4546-b87f-a78c0074f5c5, 8b2a9727-a642-446e-b992-76c6d1584989)

Removing empty hashes {} from array in Active Model Serialized object

I have a few conditions where don't want to serialize the current object and want to skip it. But i haven't found a way to do that so I am ignoring attributes on attribute :foo, if: :condition. And this is generating empty {} in my serialized object inside arrays. How do I fix this?
[
{
"id": 392027,
"name": "ISC Board",
"grades":[
{
"id": 333938,
"name": "1",
"subjects": [
{
"id": 571671,
"subject": "Math"
},
{
"id": 742980,
"subject": "Science"
},
{
"id": 186926,
"subject": "English"
},
{
"id": 658224,
"subject": "Social_Studies"
},
{},
{},
{}
]
},
{
"id": 333943,
"name": "2",
"subjects": [
{
"id": 571671,
"subject": "Math"
},
{
"id": 742980,
"subject": "Science"
},
{
"id": 186926,
"subject": "English"
},
{
"id": 658224,
"subject": "Social_Studies"
},
{},
{},
{}
]
},
]
},
{
"id": 666627,
"name": "NY Board",
"grades":[
{
"id": 333938,
"name": "1",
"subjects": [
{
"id": 571671,
"subject": "Math"
},
{
"id": 742980,
"subject": "Science"
},
{
"id": 186926,
"subject": "English"
},
{
"id": 658224,
"subject": "Social_Studies"
},
{},
{},
{}
]
},
{
"id": 432943,
"name": "2",
"subjects": [
{
"id": 571671,
"subject": "Math"
},
{
"id": 742980,
"subject": "Science"
},
{
"id": 186926,
"subject": "English"
},
{
"id": 658224,
"subject": "Social_Studies"
},
{},
{},
{}
]
},
]
}
]
serializer looks something like this-
class BoardSerializer < ActiveModel::Serializer
#some code
class GradeSerializer < ActiveModel::Serializer
has_many :subjects
#some code
class SubjectSerializer < ActiveModel::Serializer
attribute :id, if: :condition
attribute :name, key: :subject, if: :condition
def condition
#some code
#returns true or false
#will not return both :id and :subject if false- I want to
#skip this current object if condition fails. (returns {})
end
end
end
end
How do I simply skip the current object in the serializer or remove empty hashes? Thanks
Please, check if this is the expected result:
input.transform_values { |v| v.map {|e| e.transform_values { |vv| vv.class == Array ? vv.select { |ee| ee unless ee.empty? } : vv } } }
# => {:grades=>[{:id=>333938, :name=>"1", :subjects=>[{:id=>571671, :subject=>"Math"}, {:id=>742980, :subject=>"Science"}, {:id=>186926, :subject=>"English"}, {:id=>658224, :subject=>"Social_Studies"}]}]}
EDIT: to meet changes in OP question.
input.map { |e| e.transform_values { |v| v.is_a?(Array) ? v.map {|ee| ee.transform_values { |vv| vv.is_a?(Array) ? vv.select { |eee| eee unless eee.empty? } : vv } } : v } }
# => [{:id=>392027, :name=>"ISC Board", :grades=>[{:id=>333938, :name=>"1", :subjects=>[{:id=>571671, :subject=>"Math"}, {:id=>742980, :subject=>"Science"}, {:id=>186926, :subject=>"English"}, {:id=>658224, :subject=>"Social_Studies"}]}, {:id=>333943, :name=>"2", :subjects=>[{:id=>571671, :subject=>"Math"}, {:id=>742980, :subject=>"Science"}, {:id=>186926, :subject=>"English"}, {:id=>658224, :subject=>"Social_Studies"}]}]}, {:id=>666627, :name=>"NY Board", :grades=>[{:id=>333938, :name=>"1", :subjects=>[{:id=>571671, :subject=>"Math"}, {:id=>742980, :subject=>"Science"}, {:id=>186926, :subject=>"English"}, {:id=>658224, :subject=>"Social_Studies"}]}, {:id=>432943, :name=>"2", :subjects=>[{:id=>571671, :subject=>"Math"}, {:id=>742980, :subject=>"Science"}, {:id=>186926, :subject=>"English"}, {:id=>658224, :subject=>"Social_Studies"}]}]}]
You can use #select! for this matter:
input = {
"grades":
[
{
"id": 333938,
"name": "1",
"subjects":
[
{
"id": 571671,
"subject": "Math"
},
{
"id": 742980,
"subject": "Science"
},
{
"id": 186926,
"subject": "English"
},
{
"id": 658224,
"subject": "Social_Studies"
},
{},
{},
{}
]
}
]
}
input[:grades].first[:subjects].select! { |i| !i.empty? }

loop through hashes and produce new hash/array in 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

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

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