merge data according to month wise same month revenu added in own month and month name no replicated
[{"revenu":0,"month":"January"},
{"revenu":0,"month":"February"},
{"revenu":0,"month":"March"},
{"revenu":0,"month":"April"},
{"revenu":1832.4430203602753,"month":"May"},
{"revenu":4502.1,"month":"May"},
{"revenu":54.673303657726436,"month":"May"},
{"revenu":0,"month":"June"},
{"revenu":0,"month":"July"},
{"revenu":0,"month":"August"},
{"revenu":0,"month":"September"},
{"revenu":0,"month":"October"},
{"revenu":0,"month":"November"},
{"revenu":0,"month":"December"}]
sample = []
array.each do |data|
sample_hash = {}
check_exi = sample.select {|h| h[:month] == data[:month]}
if check_exi.empty?
sample_hash[:revenu] = data[:revenu]
sample_hash[:month] = data[:month]
sample.push(sample_hash)
else
check_exi[0][:revenu] = check_exi[0][:revenu] + data[:revenu]
end
end
grouped = array.group_by { |d| d[:month] }
new_array = []
grouped.each do |data|
list = {}
sum = 0
month = ""
data[1].each do |e|
sum = sum + e[:revenu]
month = e[:month]
end
list[:revenu] = sum
list[:month] = month
new_array.push(list)
end
new_arr = given_array.group_by{ |data| data[:month]}
new_arr.map{|key, val| {"month": key, "revenu": val.inject(0){|sum, hash| sum + hash[:revenu]}}}
You can make use of group_by to group the data by :month and then use sum to add the revenue.
array
.group_by{ |data| data[:month] }
.map{ |k, v| {month: k, revenu: v.sum { |m| m[:revenu] || 0}} }
#=> [{:month=>"January", :revenu=>0},
#=> {:month=>"February", :revenu=>0},
#=> {:month=>"March", :revenu=>0},
#=> {:month=>"April", :revenu=>0},
#=> {:month=>"May", :revenu=>6389.216324018002},
#=> {:month=>"June", :revenu=>0},
#=> {:month=>"July", :revenu=>0},
#=> {:month=>"August", :revenu=>0},
#=> {:month=>"September", :revenu=>0},
#=> {:month=>"October", :revenu=>0},
#=> {:month=>"November", :revenu=>0},
#=> {:month=>"December", :revenu=>0}]
Related
I want to dynamically create a Hash without overwriting keys from an array of arrays. Each array has a string that contains the nested key that should be created. However, I am running into the issue where I am overwriting keys and thus only the last key is there
data = {}
values = [
["income:concessions", 0, "noi", "722300", "purpose", "refinancing"],
["fees:fee-one", "0" ,"income:gross-income", "900000", "expenses:admin", "7500"],
["fees:fee-two", "0", "address:zip", "10019", "expenses:other", "0"]
]
What it should look like:
{
"income" => {
"concessions" => 0,
"gross-income" => "900000"
},
"expenses" => {
"admin" => "7500",
"other" => "0"
}
"noi" => "722300",
"purpose" => "refinancing",
"fees" => {
"fee-one" => 0,
"fee-two" => 0
},
"address" => {
"zip" => "10019"
}
}
This is the code that I currently, have how can I avoid overwriting keys when I merge?
values.each do |row|
Hash[*row].each do |key, value|
keys = key.split(':')
if !data.dig(*keys)
hh = keys.reverse.inject(value) { |a, n| { n => a } }
a = data.merge!(hh)
end
end
end
The code you've provided can be modified to merge hashes on conflict instead of overwriting:
values.each do |row|
Hash[*row].each do |key, value|
keys = key.split(':')
if !data.dig(*keys)
hh = keys.reverse.inject(value) { |a, n| { n => a } }
data.merge!(hh) { |_, old, new| old.merge(new) }
end
end
end
But this code only works for the two levels of nesting.
By the way, I noted ruby-on-rails tag on the question. There's deep_merge method that can fix the problem:
values.each do |row|
Hash[*row].each do |key, value|
keys = key.split(':')
if !data.dig(*keys)
hh = keys.reverse.inject(value) { |a, n| { n => a } }
data.deep_merge!(hh)
end
end
end
values.flatten.each_slice(2).with_object({}) do |(f,v),h|
k,e = f.is_a?(String) ? f.split(':') : [f,nil]
h[k] = e.nil? ? v : (h[k] || {}).merge(e=>v)
end
#=> {"income"=>{"concessions"=>0, "gross-income"=>"900000"},
# "noi"=>"722300",
# "purpose"=>"refinancing",
# "fees"=>{"fee-one"=>"0", "fee-two"=>"0"},
# "expenses"=>{"admin"=>"7500", "other"=>"0"},
# "address"=>{"zip"=>"10019"}}
The steps are as follows.
values = [
["income:concessions", 0, "noi", "722300", "purpose", "refinancing"],
["fees:fee-one", "0" ,"income:gross-income", "900000", "expenses:admin", "7500"],
["fees:fee-two", "0", "address:zip", "10019", "expenses:other", "0"]
]
a = values.flatten
#=> ["income:concessions", 0, "noi", "722300", "purpose", "refinancing",
# "fees:fee-one", "0", "income:gross-income", "900000", "expenses:admin", "7500",
# "fees:fee-two", "0", "address:zip", "10019", "expenses:other", "0"]
enum1 = a.each_slice(2)
#=> #<Enumerator: ["income:concessions", 0, "noi", "722300",
# "purpose", "refinancing", "fees:fee-one", "0", "income:gross-income", "900000",
# "expenses:admin", "7500", "fees:fee-two", "0", "address:zip", "10019",
# "expenses:other","0"]:each_slice(2)>
We can see what values this enumerator will generate by converting it to an array.
enum1.to_a
#=> [["income:concessions", 0], ["noi", "722300"], ["purpose", "refinancing"],
# ["fees:fee-one", "0"], ["income:gross-income", "900000"],
# ["expenses:admin", "7500"], ["fees:fee-two", "0"],
# ["address:zip", "10019"], ["expenses:other", "0"]]
Continuing,
enum2 = enum1.with_object({})
#=> #<Enumerator: #<Enumerator:
# ["income:concessions", 0, "noi", "722300", "purpose", "refinancing",
# "fees:fee-one", "0", "income:gross-income", "900000", "expenses:admin", "7500",
# "fees:fee-two", "0", "address:zip", "10019", "expenses:other", "0"]
# :each_slice(2)>:with_object({})>
enum2.to_a
#=> [[["income:concessions", 0], {}], [["noi", "722300"], {}],
# [["purpose", "refinancing"], {}], [["fees:fee-one", "0"], {}],
# [["income:gross-income", "900000"], {}], [["expenses:admin", "7500"], {}],
# [["fees:fee-two", "0"], {}], [["address:zip", "10019"], {}],
# [["expenses:other", "0"], {}]]
enum2 can be thought of as a compound enumerator (though Ruby has no such concept). The hash being generated is initially empty, as shown, but will be filled in as additional elements are generated by enum2
The first value is generated by enum2 and passed to the block, and the block values are assigned values by a process called array decomposition.
(f,v),h = enum2.next
#=> [["income:concessions", 0], {}]
f #=> "income:concessions"
v #=> 0
h #=> {}
We now perform the block calculation.
f.is_a?(String)
#=> true
k,e = f.is_a?(String) ? f.split(':') : [f,nil]
#=> ["income", "concessions"]
e.nil?
#=> false
h[k] = e.nil? ? v : (h[k] || {}).merge(e=>v)
#=> {"concessions"=>0}
h[k] equals nil if h does not have a key k. In that case (h[k] || {}) #=> {}. If h does have a key k (and h[k] in not nil).(h[k] || {}) #=> h[k].
A second value is now generated by enum2 and passed to the block.
(f,v),h = enum2.next
#=> [["noi", "722300"], {"income"=>{"concessions"=>0}}]
f #=> "noi"
v #=> "722300"
h #=> {"income"=>{"concessions"=>0}}
Notice that the hash, h, has been updated. Recall it will be returned by the block after all elements of enum2 have been generated. We now perform the block calculation.
f.is_a?(String)
#=> true
k,e = f.is_a?(String) ? f.split(':') : [f,nil]
#=> ["noi"]
e #=> nil
e.nil?
#=> true
h[k] = e.nil? ? v : (h[k] || {}).merge(e=>v)
#=> "722300"
h #=> {"income"=>{"concessions"=>0}, "noi"=>"722300"}
The remaining calculations are similar.
merge overwrites a duplicate key by default.
{ "income"=> { "concessions" => 0 } }.merge({ "income"=> { "gross-income" => "900000" } } completely overwrites the original value of "income". What you want is a recursive merge, where instead of just merging the top level hash you're merging the nested values when there's duplication.
merge takes a block where you can specify what to do in the event of duplication. From the documentation:
merge!(other_hash){|key, oldval, newval| block} → hsh
Adds the contents of other_hash to hsh. If no block is specified, entries with duplicate keys are overwritten with the values from other_hash, otherwise the value of each duplicate key is determined by calling the block with the key, its value in hsh and its value in other_hash
Using this you can define a simple recursive_merge in one line
def recursive_merge!(hash, other)
hash.merge!(other) { |_key, old_val, new_val| recursive_merge!(old_val, new_val) }
end
values.each do |row|
Hash[*row].each do |key, value|
keys = key.split(':')
if !data.dig(*keys)
hh = keys.reverse.inject(value) { |a, n| { n => a } }
a = recursive_merge!(data, hh)
end
end
end
A few more lines will give you a more robust solution, that will overwrite duplicate keys that are not hashes and even take a block just like merge
def recursive_merge!(hash, other, &block)
hash.merge!(other) do |_key, old_val, new_val|
if [old_val, new_val].all? { |v| v.is_a?(Hash) }
recursive_merge!(old_val, new_val, &block)
elsif block_given?
block.call(_key, old_val, new_val)
else
new_val
end
end
end
h1 = { a: true, b: { c: [1, 2, 3] } }
h2 = { a: false, b: { x: [3, 4, 5] } }
recursive_merge!(h1, h2) { |_k, o, _n| o } # => { a: true, b: { c: [1, 2, 3], x: [3, 4, 5] } }
Note: This method reproduces the results you would get from ActiveSupport's Hash#deep_merge if you're using Rails.
This is how I would handle this:
def new_h
Hash.new{|h,k| h[k] = new_h}
end
values.flatten.each_slice(2).each_with_object(new_h) do |(k,v),obj|
keys = k.is_a?(String) ? k.split(':') : [k]
if keys.count > 1
set_key = keys.pop
obj.merge!(keys.inject(new_h) {|memo,k1| memo[k1] = new_h})
.dig(*keys)
.merge!({set_key => v})
else
obj[k] = v
end
end
#=> {"income"=>{
"concessions"=>0,
"gross-income"=>"900000"},
"noi"=>"722300",
"purpose"=>"refinancing",
"fees"=>{
"fee-one"=>"0",
"fee-two"=>"0"},
"expenses"=>{
"admin"=>"7500",
"other"=>"0"},
"address"=>{
"zip"=>"10019"}
}
Explanation:
Define a method (new_h) for setting up a new Hash with default new_h at any level (Hash.new{|h,k| h[k] = new_h})
First flatten the Array (values.flatten)
then group each 2 elements together as sudo key value pairs (.each_slice(2))
then iterate over the pairs using an accumulator where each new element added is defaulted to a Hash (.each_with_object(new_h.call) do |(k,v),obj|)
split the sudo key on a colon (keys = k.is_a?(String) ? k.split(':') : [k])
if there is a split then create the parent key(s) (obj.merge!(keys.inject(new_h.call) {|memo,k1| memo[k1] = new_h.call}))
merge the last child key equal to the value (obj.dig(*keys.merge!({set_key => v}))
other wise set the single key equal to the value (obj[k] = v)
This has infinite depth as long as the depth chain is not broken say [["income:concessions:other",12],["income:concessions", 0]] in this case the latter value will take precedence (Note: this applies to all the answers in one way or anther e.g. the accepted answer the former wins but a value is still lost dues to inaccurate data structure)
repl.it Example
I have this query:
dates = MyModel.pluck("distinct date(datetime_column)")
Hash[dates.group_by(&:year).map{|y, items| [y, items.group_by{|d| d.month}]}]
which find distinct dates and then gives me this array:
=> {2017=>{12=>[Tue, 12 Dec 2017], 1=>[Sun, 01 Jan 2017]},
2016=>{11=>[Sun, 20 Nov 2016], 12=>[Sat, 24 Dec 2016, Mon, 12 Dec 2016, Fri, 30 Dec 2016]}}
How do I add 3rd level to have hash where there are particular days under each month? Thank you for any help!
Update
If someone needs ordered result, try this for "dates" part:
dates = MyModel.order(:datetime_column).distinct.pluck(:datetime_column)
For better performance can try to use this:
dates = MyModel.order("date_trunc('day', datetime_column) DESC")
.distinct.pluck("date_trunc('day', datetime_column)")
Here is nice blog post on using datetrunc.
You can use each_with_object, but you need to initialize the object correctly. It's a Hash of Hashes of Hashes!
require 'date'
a = Date.today
b = Date.new(2017,1,3)
c = Date.new(2015,12,5)
d = Date.new(2015,11,7)
dates = [a,b,c,d]
hash = Hash.new { |h, y| h[y] = Hash.new { |h2, m| h2[m] = {} } }
dates_by_ymd = dates.each_with_object(hash) do |date, h|
h[date.year][date.month][date.day] = date
end
require 'pp'
pp dates_by_ymd
# {2017=>
# {1=>
# {7=>#<Date: 2017-01-07 ((2457761j,0s,0n),+0s,2299161j)>,
# 3=>#<Date: 2017-01-03 ((2457757j,0s,0n),+0s,2299161j)>}},
# 2015=>
# {12=>{5=>#<Date: 2015-12-05 ((2457362j,0s,0n),+0s,2299161j)>},
# 11=>{7=>#<Date: 2015-11-07 ((2457334j,0s,0n),+0s,2299161j)>}}}
In your case, you'd write :
dates = MyModel.pluck("distinct date(datetime_column)")
hash = Hash.new { |h, y| h[y] = Hash.new { |h2, m| h2[m] = {} } }
dates_by_ymd = dates.each_with_object(hash) do |date, h|
h[date.year][date.month][date.day] = date
end
Note that this code returns Date objects as leaves of the nested hash, as you mentioned in your question.
If you want a modified version of your code, you can use this Rails code :
dates.group_by(&:year).map do |y, items|
[y, items.group_by(&:month).map { |m, days| [m, days.index_by(&:day)] }.to_h]
end.to_h
Try
dates = MyModel.pluck("distinct date(datetime_column)")
dates.group_by(&:year).map do |y, items|
[y, items.group_by(&:month).map { |m, days| [m, days.map(&:day)] }.to_h]
end.to_h
I have a JSON object like this
[{"id":"ad-34","date":"28.07.2016","value":"cheese"},
{"id":"ad-34","date":"31.07.2016","value":"cheese updated"},
{"id":"ad-21","date":"31.07.2016","value":"sausage updated"},
{"id":"ad-34","date":"02.08.2016","value":"cheese updated v2"}
{"id":"ad-21","date":"28.07.2016","value":"sausage"}]
and i want this:
[{"id":"ad-21","date":"31.07.2016","value":"sausage updated"},
{"id":"ad-34","date":"02.08.2016","value":"cheese updated v2"}]
So i need the latest version of "id" only. How can I manage that in Rails?
If array is your array,
array.group_by { |h| h[:id] }.
values.
map { |arr| arr.max_by { |h| h[:date].split('.').reverse } }
#=> [{:id=>"ad-34", :date=>"02.08.2016", :value=>"cheese updated v2"},
# {:id=>"ad-21", :date=>"31.07.2016", :value=>"sausage updated"}]
The steps are as follows.
a = array.group_by { |h| h[:id] }
#=> {"ad-34"=>[{:id=>"ad-34", :date=>"28.07.2016", :value=>"cheese"},
# {:id=>"ad-34", :date=>"31.07.2016", :value=>"cheese updated"},
# {:id=>"ad-34", :date=>"02.08.2016", :value=>"cheese updated v2"}],
# "ad-21"=>[{:id=>"ad-21", :date=>"31.07.2016", :value=>"sausage updated"},
# {:id=>"ad-21", :date=>"28.07.2016", :value=>"sausage"}]}
b = a.values
#=> [[{:id=>"ad-34", :date=>"28.07.2016", :value=>"cheese"},
# {:id=>"ad-34", :date=>"31.07.2016", :value=>"cheese updated"},
# {:id=>"ad-34", :date=>"02.08.2016", :value=>"cheese updated v2"}],
# [{:id=>"ad-21", :date=>"31.07.2016", :value=>"sausage updated"},
# {:id=>"ad-21", :date=>"28.07.2016", :value=>"sausage"}]]
b.map { |arr| arr.max_by { |h| h[:date].split('.').reverse } }
#=> [{:id=>"ad-34", :date=>"02.08.2016", :value=>"cheese updated v2"},
# {:id=>"ad-21", :date=>"31.07.2016", :value=>"sausage updated"}]
In the last step the first value map passes to its block is
arr = b.first
#=> [{:id=>"ad-34", :date=>"28.07.2016", :value=>"cheese"},
# {:id=>"ad-34", :date=>"31.07.2016", :value=>"cheese updated"},
# {:id=>"ad-34", :date=>"02.08.2016", :value=>"cheese updated v2"}]
so the block calculation is
arr.max_by { |h| h[:date].split('.').reverse }
#=> {:id=>"ad-34", :date=>"02.08.2016", :value=>"cheese updated v2"}
To elaborate on the last calculation, the first value of c passed to the block is
h = arr.first
#=> {:id=>"ad-34", :date=>"28.07.2016", :value=>"cheese"}
so the inner block calculation is
d = h[:date]
#=> "28.07.2016"
e = d.split('.')
#=> ["28", "07", "2016"]
e.reverse
#=> ["2016", "07", "28"]
The two other hashes for id: "ad-34" are processed similarly. arr.max_by computes
[["2016", "07", "28"], ["2016", "07", "31"], ["2016", "02", "08"]].max
#=> ["2016", "07", "31"]
and therefore selects c[1]. The calculations for b[1] are similar.
In your controller use something like:
#variabel = Model.all.order("ID ASC") and the, call a method that compares the id.
For exmple:
def compare_id
if #variable.id == latest
do something
end
The method is just a basic idea, but you can implement something with real code in order to accomplish what you need
I think we just update entry by entry to a hash with structure |key, value| = |id, entry| then we just get the values of the hash result. The code will be like:
input = your_json_input
output = {}.tap do |my_hash|
json.each{ |entry| input[entry['id']] = entry }
end.values
So the latest entry of id will be in the my_hash
My desired outcome is something like this:
{date: 12/02/2014, minutes: 36}
I'm scraping with Nokogiri using:
dates = doc.css('td:nth-child(3)')
minutes = doc.css('td:nth-child(10)')
Then I do some filtering and pushing results into arrays:
dates.each do |x|
if x.text.length == 10
date_array << x.text
end
end
minutes.each do |x|
minutes_array << x.text
end
How can I zip these two arrays together to create my desired outcome?
i've tried something like this, but it's not quite right (gives me {"2013-10-29"=>"32:14"} )
result = Hash[date_array.zip(minutes_array)]
or even something like this:
result = Hash[date_array.zip(minutes_array).map {|d, m| {:date => d, :minutes => m}}
but i get this error: wrong element type Hash at 163
i've also tinkered with .flatten but to no avail. Can anybody help?
assuming you have 2 equal length arrays x and y
x = [:key1, :key2, :key3]
y = [:value1, :value2, :value3]
z = {}
x.each_with_index { |key,index| z[key] = y[index] }
puts z
=> {:key1=>:value1, :key2=>:value2, :key3=>:value3}
is that what you are looking for?
then maybe this:
x = [:key1, :key2, :key3]
y = [:value1, :value2, :value3]
z = []
x.each_with_index { |key,index| z << { date: key, minutes: y[index]} }
puts z
{:date=>:key1, :minutes=>:value1}
{:date=>:key2, :minutes=>:value2}
{:date=>:key3, :minutes=>:value3}
Stealing from nPn (I can't comment on his answer because I've got no reputation )
Assuming you have
x = [ "date1", "date2", "date3"]
y = [ "time1", "time2", "time3"]
Then you can do:
z = []
x.each_with_index { |k, i| z << { date: k, time: y[i] } }
puts z
=> [ { date: "date1", time: "time1" },
{ date: "date2", time: "time2" },
{ date: "date3", time: "time3" } ]
Is this what you are looking for ?
You are trying to have the same key (date, minutes) for multiple values. You can instead have an array of hash for all those date-minute combos though, with this -
date.zip(minutes).reduce([]) { |memo, combo| memo << Hash[*[:date, :minutes].zip(combo).flatten] }
Here is how it looks -
2.1.5 :035 > date=["10/10,2010","11/10/2010","12/10/2010","13/10/2010","14/10/2010"]
=> ["10/10,2010", "11/10/2010", "12/10/2010", "13/10/2010", "14/10/2010"]
2.1.5 :036 > minutes = [10,20,30,40,50]
=> [10, 20, 30, 40, 50]
2.1.5 :037 > date.zip(minutes).reduce([]) { |memo, combo| memo << Hash[*[:date, :minutes].zip(combo).flatten] }
=> [{:date=>"10/10,2010", :minutes=>10}, {:date=>"11/10/2010", :minutes=>20}, {:date=>"12/10/2010", :minutes=>30}, {:date=>"13/10/2010", :minutes=>40}, {:date=>"14/10/2010", :minutes=>50}]
2.1.5 :038 >
Word of caution - you should really use a Struct, and then create an array of that Struct instances, instead of working on arrays of hashes like this.
If
dates = ["12/02/14", "6/03/14"]
minutes = [12, 19]
then if I've not misunderstood the question, it's just:
dates.zip(minutes).map { |d,m| {date: d, minutes: m} }
#=> [{:date=>"12/02/14", :minutes=>12}, {:date=>"6/03/14", :minutes=>19}]
I have any array of structs. Each struct in the array has the following attributes:
user_id
num_hot_dogs_eaten
date_last_pigged_out
Here's what I want to do:
Find the structs with matching user_id's, and merge them into one struct record where num_hot_dogs_eaten is the sum of all matching records and date_last_pigged_out is the most-recent date the user pigged out.
Sort the array of structs by num_hot_dogs_eaten (first order of priority) and by date_last_pigged_out (second order of priority...most-recent first).
Return a new sorted array of structs.
Use this:
def f(users)
r = []
users.each do |u|
new_match = false
match = r.find {|x| x.user_id == u.user_id }
unless match
match = u.dup
r << match
new_match = true
end
match.num_hot_dogs_eaten += u.num_hot_dogs_eaten unless new_match
match.date_last_pigged_out =
[match, u].max_by(&:date_last_pigged_out).date_last_pigged_out
end
r.sort_by {|u| [u.num_hot_dogs_eaten, u.date_last_pigged_out] }.
reverse
end
A more functional programming approach:
User = Struct.new(:user_id, :num_hot_dogs_eaten, :date_last_pigged_out)
ONE_DAY = 60 * 60 * 24
class Object
def returning(object)
yield object
object
end
end
users = [
User.new(1, 3, Time.now),
User.new(1, 2, Time.now + ONE_DAY),
User.new(1, 1, Time.now - ONE_DAY),
User.new(2, 2, Time.now - ONE_DAY),
User.new(2, 3, Time.now),
User.new(3, 5, Time.now - ONE_DAY),
]
users.inject(Hash.new { |hash, key| hash[key] = Hash.new { |hash, key| hash[key] = [] } }) do |collection, user|
returning(collection) do
collection[user.user_id][:num_hot_dogs_eaten] << user.num_hot_dogs_eaten
collection[user.user_id][:date_last_pigged_out] << user.date_last_pigged_out
end
end.map do |user_id, stats|
User.new(user_id, stats[:num_hot_dogs_eaten].inject(&:+), stats[:date_last_pigged_out].max)
end.sort_by { |user| [user.num_hot_dogs_eaten, user.date_last_pigged_out] }.reverse
The actual implementation is (assuming you have returning defined):
users.inject(Hash.new { |hash, key| hash[key] = Hash.new { |hash, key| hash[key] = [] } }) do |collection, user|
returning(collection) do
collection[user.user_id][:num_hot_dogs_eaten] << user.num_hot_dogs_eaten
collection[user.user_id][:date_last_pigged_out] << user.date_last_pigged_out
end
end.map do |user_id, stats|
User.new(user_id, stats[:num_hot_dogs_eaten].inject(&:+), stats[:date_last_pigged_out].max)
end.sort_by { |user| [user.num_hot_dogs_eaten, user.date_last_pigged_out] }.reverse