Get the month numbers for the current quarter - ruby-on-rails

I need an array with the numbers for the months in the current quarter. I want to supply Date.today and then get eg. [1,2,3].
How do I do that in the easiest way? (Not by using switch/case).

def quarter(date)
1 + ((date.month-1)/3).to_i
end

def quarter_month_numbers(date)
quarters = [[1,2,3], [4,5,6], [7,8,9], [10,11,12]]
quarters[(date.month - 1) / 3]
end

I would suggest building a hash indexed by month like so:
#quarters_by_month = Hash[(1..12).map {|v| i=((v-1)/3)*3; [v,[i+1, i+2, i+3]]}]
then any future lookup is just
#quarters_by_month[month]
Since #x3ro mentioned CPU time I thought it would be fun to benchmark all of the proposed solutions including the case statement which the OP wanted to exclude. Here are the results:
> ruby jeebus.rb
user system total real
case_statement: 0.470000 0.000000 0.470000 ( 0.469372)
quarter_month: 0.420000 0.000000 0.420000 ( 0.420217)
solution1: 0.740000 0.000000 0.740000 ( 0.733669)
solution2: 1.630000 0.010000 1.640000 ( 1.634004)
defined_hash: 0.470000 0.000000 0.470000 ( 0.469814)
Here is the code:
def case_statement(month)
case month
when 1,2,3
[1,2,3]
when 4,5,6
[4,5,6]
when 7,8,9
[7,8,9]
when 10,11,12
[10,11,12]
else
raise ArgumentError
end
end
def defined_hash(month)
#quarters_by_month[month]
end
def solution1(month)
(((month - 1) / 3) * 3).instance_eval{|i| [i+1, i+2, i+3]}
end
def solution2(month)
[*1..12][((month - 1) / 3) * 3, 3]
end
def quarter_month_numbers(month)
#quarters[(month - 1) / 3]
end
require 'benchmark'
n = 1e6
Benchmark.bm(15) do |x|
x.report('case_statement:') do
for i in 1..n do
case_statement(rand(11) + 1)
end
end
x.report('quarter_month:') do
#quarters = [[1,2,3], [4,5,6], [7,8,9], [10,11,12]]
for i in 1..n do
quarter_month_numbers(rand(11) + 1)
end
end
x.report('solution1:') do
for i in 1..n do
solution1(rand(11) + 1)
end
end
x.report('solution2:') do
for i in 1..n do
solution2(rand(11) + 1)
end
end
x.report('defined_hash:') do
#quarters_by_month = Hash[(1..12).map {|v| i=((v-1)/3)*3; [v,[i+1, i+2, i+3]]}]
for i in 1..n do
defined_hash(rand(11) + 1)
end
end
end

Solution 1
(((Date.today.month - 1) / 3) * 3).instance_eval{|i| [i+1, i+2, i+3]}
Solution 2
[*1..12][((Date.today.month - 1) / 3) * 3, 3]

You can do the following:
m = date.beginning_of_quarter.month
[m, m+1, m+2]
Demonstrated below in irb:
>> date=Date.parse "27-02-2011"
=> Sun, 27 Feb 2011
>> m = date.beginning_of_quarter.month
=> 1
>> [m, m+1, m+2]
=> [1, 2, 3]
I don't know how fast this is compared to the other methods, perhaps #Wes can kindly benchmark this way as well.
One advantage of this approach I think is the clarity of the code. It's not convoluted.

Have a look at this little snippet:
months = (1..12).to_a
result = months.map do |m|
quarter = (m.to_f / 3).ceil
((quarter-1)*3+1..quarter*3).to_a
end
puts result.inspect

For Array
month = Date.today.month # 6
quarters = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]
quarters.select { |quarter| quarter.include?(month) }
=> [[4, 5, 6]]
For Hash
month = Date.today.month # 6
quarters = {
[1, 2, 3] => 'First quarter',
[4, 5, 6] => 'Second quarter',
[7, 8, 9] => 'Third quarter',
[10, 11, 12] => 'Fourth quarter',
}
quarters.select { |quarter| quarter.include?(month) }
=> {[4, 5, 6]=>"Second quarter"}
Wish it helped ;)

Related

ruby arrays count of most frequent element [duplicate]

This question already has answers here:
How to find an item in array which has the most occurrences [duplicate]
(11 answers)
Closed 6 years ago.
I'm trying to figure out how to find a count of the most frequent element in an array of integers. I can think of a few methods that might be helpful but when I get to writing an expression inside the block I get complete lost on how to compare an element with the next and previous element. Any ideas? All help is really really appreciated!!!
An easy was is to determine all the unique values, convert each to its count in the array, then determine the largest count.
def max_count(arr)
arr.uniq.map { |n| arr.count(n) }.max
end
For example:
arr = [1,2,4,3,2,6,3,4,2]
max_count(arr)
#=> 3
There are three steps:
a = arr.uniq
#=> [1, 2, 4, 3, 6]
b = a.map { |n| arr.count(n) }
#=> [1, 3, 2, 2, 1]
b.max
#=> 3
A somewhat more efficient way (because the elements of arr are enumerated only once) is to use a counting hash:
def max_count(arr)
arr.each_with_object(Hash.new(0)) { |n,h| h[n] += 1 }.values.max
end
max_count(arr)
#=> 3
We have:
a = arr.each_with_object(Hash.new(0)) { |n,h| h[n] += 1 }
#=> {1=>1, 2=>3, 4=>2, 3=>2, 6=>1}
b = a.values
#=> [1, 3, 2, 2, 1]
b.max
#=> 3
See Hash::new for an explanation of Hash.new(0). Briefly, if h = Hash.new(0) and h does not have a key k, h[k] will return the default value, which here is zero. h[k] += 1 expands to h[k] = h[k] + 1, so if h does not have a key k, this becomes h[k] = 0 + 1. On the other hand, if, say, h[k] => 2, then h[k] = h[k] + 1 #=> h[k] = 3 + 1.

Calculate NPS in Ruby

The net promoter score can have the values 0-10. It is divided in three groups:
Promoters = respondents giving a 9 or 10 score
Passives = respondents giving a 7 or 8 score
Detractors = respondents giving a 0 to 6 score
The score is calculated as the difference between the percentage of Promoters and Detractors.
Let's say we have the scores [10, 9, 10, 6, 2, 5, 10].
This would give the score +14 (57% - 43%).
I wish I could count occurrences of a range in an array, if that would be possible I would do
total_count = array.size
promoters = array.count(9..10)
passives = array.count(7..8)
detractors = array.count(0..6)
promoters_perc = promoters.to_f / total_count * 100
detractors_perc = detractors.to_f / total_count * 100
score = promoters_perc - detractors_perc
How can I do this calculation?
You can count all your metrics in hash:
arr = [10, 9, 10, 6, 2, 5, 10]
count = arr.each_with_object(Hash.new(0)) do |e, memo|
case e
when 0..6 then memo[:detractors] += 1
when 7..8 then memo[:passives] += 1
when 9..10 then memo[:promoters] += 1
end
end
score = (count[:promoters] - count[:detractors]).to_f / arr.size * 100
=> 14.285714285714285
Shorter solution:
metrics = { promoters: 9..10, passives: 7..8, detractors: 0..6 }
count = metrics.each {|k, v| metrics[k] = arr.count {|e| v === e}}
score = (count[:promoters] - count[:detractors]).to_f / arr.size * 100
=> 14.285714285714285
There are a some other ways of doing this as well, but for simplicity this should work.
array = [10, 9, 10, 6, 2, 5, 10]
total_count = array.size
promoters = array.count {|x| x > 8}
passives = array.count {|x| x > 6 && x <9}
detractors = array.count {|x| x < 7}
promoters_perc = promoters.to_f / total_count * 100
detractors_perc = detractors.to_f / total_count * 100
score = promoters_perc - detractors_perc
Here is one more way to do this, code is self explanatory.
data = [10, 9, 10, 6, 2, 5, 10]
score_range = [promoters = (9..10), passives = (7..8), detractors = (0..6)]
#=> [9..10, 7..8, 0..6]
grouped = data.group_by {|i| score_range.select {|sr| sr.cover?(i)} }.to_h
#=> {[9..10]=>[10, 9, 10, 10], [0..6]=>[6, 2, 5]}
percentage = grouped.map {|(k),v| [k, (v.size * 100.0/ data.size).round]}.to_h
#=> {9..10=>57, 0..6=>43}
nps = percentage[promoters] - percentage[detractors]
#=> 14

What's a good way to create a string array in Ruby based on integer variables?

The integer variables are:
toonie = 2, loonie = 1, quarter = 1, dime = 0, nickel = 1, penny = 3
I want the final output to be
"2 toonies, 1 loonie, 1 quarter, 1 nickel, 3 pennies"
Is there a way to interpolate this all from Ruby code inside [] array brackets and then add .join(", ")?
Or will I have to declare an empty array first, and then write some Ruby code to add to the array if the integer variable is greater than 0?
I would do something like this:
coins = { toonie: 2, loonie: 1, quarter: 1, dime: 0, nickel: 1, penny: 3 }
coins.map { |k, v| pluralize(v, k) if v > 0 }.compact.join(', ')
#=> "2 toonie, 1 loonie, 1 quarter, 1 nickel, 3 penny"
Note that pluralize is a ActionView::Helpers::TextHelper method. Therefore it is only available in views and helpers.
When you want to use your example outside of views, you might want to use pluralize from ActiveSupport instead - what makes the solution slightly longer:
coins.map { |k, v| "#{v} #{v == 1 ? k : k.pluralize}" if v > 0 }.compact.join(', ')
#=> "2 toonie, 1 loonie, 1 quarter, 1 nickel, 3 penny"
Can be done in rails:
hash = {
"toonie" => 2,
"loonie" => 1,
"quarter" => 1,
"dime" => 0,
"nickel" => 1,
"penny" => 3
}
hash.to_a.map { |ele| "#{ele.last} #{ele.last> 1 ? ele.first.pluralize : ele.first}" }.join(", ")
Basically what you do is convert the hash to an array, which will look like this:
[["toonie", 2], ["loonie", 1], ["quarter", 1], ["dime", 0], ["nickel", 1], ["penny", 3]]
Then you map each element to the function provided, which takes the inner array, takes the numeric value in the last entry, places it in a string and then adds the plural or singular value based on the numeric value you just checked. And finally merge it all together
=> "2 toonies, 1 loonie, 1 quarter, 1 nickel, 3 pennies"
I'm not sure what exactly you're looking for, but I would start with a hash like:
coins = {"toonie" => 2, "loonie" => 1, "quarter" => 1, "dime" => 0, "nickel" => 1, "penny" => 3}
then you can use this to print the counts
def coin_counts(coins)
(coins.keys.select { |coin| coins[coin] > 0}.map {|coin| coins[coin].to_s + " " + coin}).join(", ")
end
If you would like appropriate pluralizing, you can do the following:
include ActionView::Helpers::TextHelper
def coin_counts(coins)
(coins.keys.select { |coin| coins[coin] > 0}.map {|coin| pluralize(coins[coin], coin)}).join(", ")
end
This is just for fun and should not be used in production but you can achieve it like
def run
toonie = 2
loonie = 1
quarter = 1
dime = 0
nickel = 1
penny = 3
Kernel::local_variables.each_with_object([]) { |var, array|
next if eval(var.to_s).to_i.zero?
array << "#{eval(var.to_s)} #{var}"
}.join(', ')
end
run # returns "2 toonie, 1 loonie, 1 quarter, 1 nickel, 3 penny"
The above does not implement the pluralization requirement because it really depends if you will have irregular plural nouns or whatever.
I would go with a hash solution as described in the other answers

Separate array of hashes by nil

I have a Hash value lik this:
hs = {2012 => [7,nil,3], 2013 => [2, 6, nil, 8], 2014 => [9, 1, 2, 8]}
The keys are years. I want to collect values backwards until nil appears like this:
some_separate_method(hs)
{2013 => [8], 2014 => [9, 1, 2, 8]}
I thought this is not difficult to implement by using reverse_each, but I couldn't. How can I make a method like this?
Edit
With AmitA's code I could make it.
new_hs = []
hs.reverse_each{|k,v| new_hs << [k,v]; break if v.include?(nil)}
new_hs = Hash[new_hs.sort]
How about this:
res = {}
hs.to_a.reverse.each do |k, arr|
res[k] = arr.split(nil).last
break unless res[k].length == arr.length
end
A pure-Ruby solution:
hs.reverse_each.with_object({}) do |(k,v),h|
h[k] = v.dup
ndx = v.rindex(&:nil?)
if ndx
h[k] = h[k][ndx+1..-1]
break h
end
end
#=> {2014=>[9, 1, 2, 8], 2013=>[8]}
v.dup is to avoid mutatinghs.
This looks a bit long but I think it should help anyone who's new to ruby and has a similar problem.
def some_separate_method(hs)
new_hash = {}
hs.each do | key, value |
new_arr = []
value.reverse.each do | item |
break if item.nil?
new_arr.unshift(item)
end
new_hash[key] = new_arr
end
new_hash
end
#=> {2012=>[3], 2013=>[8], 2014=>[9, 1, 2, 8]}

Creating a range from one column

I have a column called "Marks" which contains values like
Marks = [100,200,150,157,....]
I need to assign Grades to those marks using the following key
<25=0, <75=1, <125=2, <250=3, <500=4, >500=5
If Marks < 25, then Grade = 0, if marks < 75 then grade = 1.
I can sort the results and find the first record that matches using Ruby's find function. Is it the best method ? Or is there a way by which I can prepare a range using the key by adding Lower Limit and Upper Limit columns to the table and by populating those ranges using the key? Marks can have decimals too Ex: 99.99
Without using Rails, you could do it like this:
marks = [100, 200, 150, 157, 692, 12]
marks_to_grade = { 25=>0, 75=>1, 125=>2, 250=>3, 500=>4, Float::INFINITY=>5 }
Hash[marks.map { |m| [m, marks_to_grade.find { |k,_| m <= k }.last] }]
#=> {100=>2, 200=>3, 150=>3, 157=>3, 692=>5, 12=>0}
With Ruby 2.1, you could write this:
marks.map { |m| [m, marks_to_grade.find { |k,_| m <= k }.last] }.to_h
Here's what's happening:
Enumerable#map (a.k.a collect) converts each mark m to an array [m, g], where g is the grade computed for that mark. For example, when map passes the first element of marks into its block, we have:
m = 100
a = marks_to_grade.find { |k,_| m <= k }
#=> marks_to_grade.find { |k,_| 100 <= k }
#=> [125, 2]
a.last
#=> 2
so the mark 100 is mapped to [100, 2]. (I've replaced the block variable for the value of the key-value pair with the placeholder _ to draw attention to the fact that the value is not being used in the calculation within the block. One could also use, say, _v as the placeholder.) The remaining marks are similarly mapped, resulting in:
b = marks.map { |m| [m, marks_to_grade.find { |k,_| m <= k }.last] }
#=> [[100, 2], [200, 3], [150, 3], [157, 3], [692, 5], [12, 0]]
Lastly
Hash[b]
#=> {100=>2, 200=>3, 150=>3, 157=>3, 692=>5, 12=>0}
or, for Ruby 2.1+
b.to_h
#=> {100=>2, 200=>3, 150=>3, 157=>3, 692=>5, 12=>0}
You can make use of update_all:
Student.where(:mark => 0...25).update_all(grade: 0)
Student.where(:mark => 25...75).update_all(grade: 1)
Student.where(:mark => 75...125).update_all(grade: 2)
Student.where(:mark => 125...250).update_all(grade: 3)
Student.where(:mark => 250...500).update_all(grade: 4)
Student.where("mark > ?", 500).update_all(grade: 5)

Resources