Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 3 years ago.
Improve this question
It could be have similar names and dates. but, take sum of count of similar dates.
#<ActiveRecord::Relation [Model name: "a", date: "20190703", count: 20>
#<ActiveRecord::Relation [Model name: "b", date: "20190702", count: 10>
#<ActiveRecord::Relation [Model name: "a", date: "20190702", count: nil>
#<ActiveRecord::Relation [Model name: "c", date: "20190703", count: 20>
#<ActiveRecord::Relation [Model name: "c", date: "20190702", count: 3>
#<ActiveRecord::Relation [Model name: "c", date: "20190702", count: 5> ...etc
Expected result would be like
{
:name=>"a", :data=>[{:date=>20190702, :count=>10}, {:date=>20190703, :count=>20}.....]
:name=>"b", :data=>[{:date=>20190702, :count=>10}, ...etc]
:name=>"c", :data=>[{:date=>20190702, :count=>8}, {:date=>20190703, :count=>20}, ..etc]
}
So given the fact that this is ActiveRecord data to begin with why not something like
data = Model.group(:name,:date).order(name: :asc, date: :asc).sum(:count)
data.each_with_object(Hash.new {|h,k| h[k] = []}) do |((name,date),count),obj|
obj[name] << {date: date, count: count}
end
This will produce:
{
"a"=>[{:date=>20190702, :count=>0}, {:date=>20190703, :count=>20}],
"b"=>[{:date=>20190702, :count=>10}],
"c"=>[{:date=>20190702, :count=>8}, {:date=>20190703, :count=>20}]
}
and all of the summing and grouping is placed on the SQL side of things (which is very efficient at aggregation)
You can have,
hash = users.group_by { |x| x.name }
hash.map do |name, model_data|
{
name: name,
data: model_data.group_by { |x| x[:date] }.values.map { |z| {date: z[0][:date], count: (z.inject(0) { |m,a| m += a[:count].to_i })} }
}
end
Output will be,
[
{:name=>"a", :data=>[{:date=>20190702, :count=>10}, {:date=>20190703, :count=>20}.....]},
{:name=>"b", :data=>[{:date=>20190702, :count=>10}, ...etc]},
{:name=>"c", :data=>[{:date=>20190702, :count=>8}, {:date=>20190703, :count=>20}, ..etc]}
]
Related
I have an array and it has many columns and I want to change one value of my one column.
My array is:
[
{
id: 1,
Districts: "Lakhisarai",
Area: 15.87,
Production: 67.77,
Productivity: 4271,
Year: 2015,
Area_Colour: "Red",
Production_Colour: "Orange",
Productivity_Colour: "Dark_Green",
created_at: "2018-07-24T11:24:13.000Z",
updated_at: "2018-07-24T11:24:13.000Z"
},
{
id: 29,
Districts: "Begusarai",
Area: 18.53,
Production: 29.35,
Productivity: 1584,
Year: 2015,
Area_Colour: "Red",
Production_Colour: "Red",
Productivity_Colour: "Orange",
created_at: "2018-07-24T11:24:13.000Z",
updated_at: "2018-07-24T11:24:13.000Z"
},
...
]
This is my sample array and I want my Productivity to be divided by 100 for that I am using one empty array and pushing these hashes to my array like:
j = []
b.map do |k|
if k.Productivity
u = k.Productivity/100
j.push({id: k.id, Productivity: u })
else
j.push({id: k.id, Productivity: k.Productivity })
end
Is there any simple way where I can generate this kind of array and reflect my changes to to one column. Is there any way where I don't need to push name of column one by one in push method.
I want to generate exact same array with one modification in productivity
let's say your array is e, then:
e.each { |item| item[:Productivity] = item[:Productivity]/100}
Example:
e = [{p: 12, d: 13}, {p:14, d:70}]
e.each { |item| item[:p] = item[:p]/10}
output: [{:p=>1, :d=>13}, {:p=>1, :d=>70}]
You could take help of map method here to create a new array from your original array, but with the mentioned changes.
ary.map do |elem|
h = elem.slice(:id)
h[:productivity] = elem[:Productivity] / 100 if elem[:Productivity]
h
end
=> [{:id=>1, :productivity=>42}, {:id=>29, :productivity=>15}]
Note, Hash#slice returns a new hash with only the key-value pairs for the keys passed in argument e.g. here, it returns { id: 1 } for first element.
Also, we are assigning the calculated productivity to the output only when it is set on original hash. Hence, the if condition there.
I'm having this class method on my Post model for getting archives
def self.archives
Post.unscoped.select("YEAR(created_at) AS year, MONTHNAME(created_at) AS month, COUNT(id) AS total")
.group("year, month, MONTH(created_at)")
.order("year DESC, MONTH(created_at) DESC")
end
This is the test I have wrote for my method
context '.archives' do
first = FactoryGirl.create(:post, published_at: Time.zone.now)
second = FactoryGirl.create(:post, published_at: 1.month.ago)
it 'returns articles archived' do
archives = Post.archives()
expect(
[{
year: first.published_at.strftime("%Y"),
month: first.published_at.strftime("%B"),
published: 1
},
{
year: second.published_at.strftime("%Y"),
month: second.published_at.strftime("%B"),
published: 1
}]
).to match_array(archives)
end
end
However I get the following error
expected collection contained: [#<Post id: nil>, #<Post id: nil>]
actual collection contained: [{:year=>"2017", :month=>"October", :published=>1}, {:year=>"2017", :month=>"September", :total=>1}]
the missing elements were: [#<Post id: nil>, #<Post id: nil>]
the extra elements were: [{:year=>"2017", :month=>"October", :total=>1}, {:year=>"2017", :month=>"September", :total=>1}]
So although I have created 2 factories, the archives array is empty. What am I doing wrong?
Rspec standard is to use the let syntax for defining variables within a context or describe block. The test should look something like this:
describe '.archives' do
let!(:first) { FactoryGirl.create(:post, published_at: Time.zone.now) }
let!(:second) { FactoryGirl.create(:post, published_at: 1.month.ago) }
it 'returns year, month, and total for articles archived' do
actual_attributes = Post.archives.map { |post| [post.year, post.month, post.total] }
expected_total = 1 # I don't know why the query is returning 1 for total, but including this for completeness
expected_attributes = [first, second].map { |post| [post.created_at.year, post.created_at.strftime("%B"), expected_total] }
expect(actual_attributes).to match_array(expected_attributes)
end
end
The issue here is that you are comparing records pulled with only a few attributes (the result of your SQL query) with fully-formed records (created by your test). This test pulls the applicable attributes from both groups and compares them.
Actual array is not empty, it's an array of two Post instances with ids unset (because Select in .archives method doesn't contain id field).
You could compare expected hashes not with archives, but with smth like that:
actual = Post.archives().map do |post|
{ year: post["year"].to_s, month: post["month"], published: post["total"] }
end
expected = [{
year: first.published_at.strftime("%Y").to_s,
month: first.published_at.strftime("%B"),
published: 1
},
{
year: second.published_at.strftime("%Y").to_s,
month: second.published_at.strftime("%B"),
published: 1
}]
expect(actual).to match_array(expected)
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 6 years ago.
Improve this question
Array 1 = 7 elements
Array 2 = 7 elements
Hash = 7 elements
My requirement is to iterate through the each element in the hash with each element in array 1 and array 2 and come out with a new hash after applying the desired logic. The comparison is always going to be between the first element in hash against first element in array 1 and first element in array 2 and so on and so forth till I complete the list.
I am not even sure where and how to start so any help is appreciated to get me started
Is this what you are looking for?
arr1 = ['char', 'number', 'number', 'number', 'char', 'number', 'char']
arr2 = [6, '(7,0)','(15,0)','(5,0)',3,'(15,2)', 17]
h = { 'col1'=>'123456', 'col2'=>'0000111', 'col3'=>'000000002345',
'col4'=>'00023', 'col5'=>'abc', 'col6'=>'00000000000052367',
'col7'=>'0000000000321456' }
enum = arr1.zip(arr2).to_enum
#=> #<Enumerator: [["char", 6], ["number", "(7,0)"], ["number", "(15,0)"],
# ["number", "(5,0)"], ["char", 3], ["number", "(15,2)"], ["char", 17]]:each>
h.merge(h) { |*,v| [v].concat(enum.next) }
#=> {"col1"=>["123456", "char", 6], "col2"=>["0000111", "number", "(7,0)"],
# "col3"=>["000000002345", "number", "(15,0)"], "col4"=>["00023", "number", "(5,0)"],
# "col5"=>["abc", "char", 3], "col6"=>["00000000000052367", "number", "(15,2)"],
# "col7"=>["0000000000321456", "char", 17]}
This uses the form of Hash#merge that employs a block to determine the values of keys that are present in both hashes being merged, which here is all the keys of the hash.
You could use Array#zip to mix the hash and arrays :
hash = {
key1: :value1,
key2: :value2,
key3: :value3
}
array1 = %i(x1 x2 x3)
array2 = %i(y1 y2 y3)
new_hash = {}
hash.to_a.zip(array1, array2).each do |(key, value), element1, element2|
# logic with key, value, element1, and element2
# basic example :
new_hash[key] = [value, element1, element2]
end
p new_hash
# {:key1=>[:value1, :x1, :y1], :key2=>[:value2, :x2, :y2], :key3=>[:value3, :x3, :y3]}
This question follows on from a similar question I asked previously:
Splitting an array into sub arrays
I have been struggling with this code for a while now. I have an array of data "master" with each element having a lft & rgt value. I wish to break this "master" array in to an array of sub arrays "groups". The desired groupings of these sub arrays is illustrated below.
The trigger to create a new sub array is where the lft value is not between the lft & rgt values of the first elemenet in the array.
My thinking was to:
a) initialise the first array then loop through the remaining elements.
b) Check the element's lft value against the lft & rgt values of the first element in the last sub array.
c) if out side the range then create a new sub array
d) append the element onto the last sub array.
when I try this I recive an error for a unknown method "new"
def display_visiting
groups = []
master = []
master << { id: 1, name: "Fred", lft: 1, rgt: 4 }
master << { id: 4, name: "Sue", lft: 2, rgt: 3 }
master << { id: 2, name: "May", lft: 5, rgt: 12 }
master << { id: 5, name: "Helen", lft: 6, rgt: 7 }
master << { id: 6, name: "Peter", lft: 8, rgt: 9 }
master << { id: 7, name: "Grace", lft: 10, rgt: 11 }
master << { id: 3, name: "Brian", lft: 13, rgt: 18 }
master << { id: 8, name: "Michael", lft: 14, rgt: 15 }
master << { id: 9, name: "Paul", lft: 16, rgt: 17 }
groups[0] = master.shift(1)
master.each do |employee|
if (employee.lft < groups.last.first.lft) or (employee.lft > groups.last.first.rgt)
groups.new
end
groups.last << employee
end
return groups
else
return nil
end
new is a Class method used to create a new instance of a Class. E.g an object. groups is an instance of the Array class which is why it doesn't have a new method. If you're not too familiar with class methods vs instance methods, checkout this article.
So if you wanted to add a new Array to the end of the groups array, you would change this:
groups.new
To this:
groups << Array.new
However, you can use the Array initialization shorthand exactly like how you initialized the master array:
groups << []
I currently sort my events like;
#events_by_month = #events.group_by { |x| x.date.month }
this relied on the fact that all events occurred in the same year, however I now want to show events from next year too.
I'd like the output in the same format as what the above outputs but sorted by year i.e
this is what I have;
{3=>[#<Event id: 7032, date: "2014-03-02 00:00:00">, #<Event id: 7033, date: "2015-03-02 00:00:00">]}
and this is what I'd like;
{3=>[#<Event id: 7032, date: "2014-03-02 00:00:00">]},{3=> [#<Event id: 7033, date: "2015-03-02 00:00:00">]}
You want to group events by month and year as I understood.
Then use
#events_by_month = #events.group_by { |x| [x.date.month, x.date.year] }
you'll receive
{[3, 2014]=>[#<Event id: 7032, date: "2014-03-02 00:00:00">]},{[3,2015]=> [#<Event id: 7033, date: "2015-03-02 00:00:00">]}
It will be more correct then duplicated keys in your example.
I think this will work for you
results = []
#events.group_by { |x| x.date.year }.sort.
each { |_, e| results << e.group_by { |x| x.date.month } }
example
[
#<Event:0x007fd957d3dd98 #date=2014-03-02 21:37:18 +0200>,
#<Event:0x007fd957d6e9e8 #date=2014-01-01 00:00:00 +0200>,
#<Event:0x007fd957da4228 #date=2014-03-01 17:52:12 +0200>,
#<Event:0x007fd957db98f8 #date=33702-11-27 23:25:40 +0200>
]
will give
[
{
3=>[#<Event:0x007fd957d3dd98 #date=2014-03-02 21:37:18 +0200>,
#<Event:0x007fd957da4228 #date=2014-03-01 17:52:12 +0200>],
1=>[#<Event:0x007fd957d6e9e8 #date=2014-01-01 00:00:00 +0200>]
},
{
11=>[#<Event:0x007fd957db98f8 #date=33702-11-27 23:25:40 +0200>]
}
]
Assuming that events are sorted (sort_by(&:date)):
#events.group_by { |e| e.date.year } \
.values \
.map { |es|
es.group_by { |e| e.date.month }
}