How to limit loop to a certain number of repetitions? - ruby-on-rails

In my Rails application I have this loop:
dates = []
loop do
date = start_date += increment
break if date > Date.today
dates << date
end
How can I limit this loop to a certain number of repetitions?
The start_date is dynamic in my application and a user could easily bring my app to its knees by inserting e.g. 0000-00-00 there which will cause millions of repetitions.
Thanks a lot for any help.

You could keep a counter of the repetitions and stop the loop as soon as a threshold is reached.
For example to limit the loop to 100 iterations:
dates = []
counter = 0
while counter < 100 # threshold
date = start_date += increment
break if date > Date.today
dates << date
counter += 1
end
Otherwise, you could only enter the loop if start_date is inside a certain timeframe:
dates = []
loop do
date = start_date += increment
break if date > Date.today
dates << date
end if start_date >= Date.today - 3.months.ago

Related

Find Gaps in range of dates

Hi Folks I used google calendars to pull free/busy schedule for users. I want to able to take those range of dates and then output all the gaps into a new array of some sort?
Any direction pointing would be greatly appreciated.
Step 1: Get all your dates (as integers) into a sorted (by time) two-dimensional array, noting whether each date is the beginning or end of the timeframe. For example:
array = [[1484715564, 'start'], [1484715565, 'start'], [1484715569, 'end'], [1484715587, 'end'], ...]
Then, all you need to do is keep track of whether you've gone through as many ends as starts, and if you have, make note of it!
num_starts = 0
gap_start = 0
gaps = []
array.each do |date, which_end|
if which_end == 'start'
num_starts += 1
if num_starts == 1
gaps << [gap_start, date]
end
else
num_starts -= 1
if num_starts == 0
gap_start = date
end
end
end

For a given period, getting the smallest list of dates, using jokers

I use Elasticsearch where I have one index per day, and I want my Ruby on Rails application to query documents in a given period by specifying the smallest and most precise list of indices.
I can't find the code to get that list of indices. Let me explain it:
Consider a date formatted in YYYY-MM-DD.
You can use the joker * at the end of the date string. E.g. 2016-07-2* describes all the dates from 2016-07-20 to 2016-07-29.
Now, consider a period represented by a start date and an end date.
The code must return the smallest possible array of dates representing the period.
Let's use an example. For the following period:
start date: 2014-11-29
end date: 2016-10-13
The code must return an array containing the following strings:
2014-11-29
2014-11-30
2014-12-*
2015-*
2016-0*
2016-10-0*
2016-10-10
2016-10-11
2016-10-12
2016-10-13
It's better (but I'll still take a unoptimized code rather than nothing) if:
The code returns the most precise list of dates (i.e. doesn't return dates with a joker that describes a period starting before the start date, or ending after the end date)
The code returns the smallest list possible (i.e. ["2016-09-*"] is better than ["2016-09-0*", "2016-09-1*", "2016-09-2*", "2016-09-30"]
Any idea?
Okay, after more thinking and the help of a coworker, I may have a solution. Probably not totally optimized, but still...
def get_indices_from_period(start_date_str, end_date_str)
dates = {}
dates_strings = []
start_date = Date.parse(start_date_str)
end_date = Date.parse(end_date_str)
# Create a hash with, for each year and each month of the period: {:YYYY => {:MMMM => [DD1, DD2, DD3...]}}
(start_date..end_date).collect do |date|
year, month, day = date.year, date.month, date.day
dates[year] ||= {}
dates[year][month] ||= []
dates[year][month] << day
end
dates.each do |year, days_in_year|
start_of_year = Date.new(year, 1, 1)
max_number_of_days_in_year = (start_of_year.end_of_year - start_of_year).to_i + 1
number_of_days_in_year = days_in_year.collect{|month, days_in_month| days_in_month}.flatten.size
if max_number_of_days_in_year == number_of_days_in_year
# Return index formatted as YYYY-* if full year
dates_strings << "#{year}-*"
else
days_in_year.each do |month, days_in_month|
formatted_month = format('%02d', month)
if Time.days_in_month(month, year) == days_in_month.size
# Return index formatted as YYYY-MM-* if full month
dates_strings << "#{year}-#{formatted_month}-*"
else
decades_in_month = {}
days_in_month.each do |day|
decade = day / 10
decades_in_month[decade] ||= []
decades_in_month[decade] << day
end
decades_in_month.each do |decade, days_in_decade|
if (decade == 0 && days_in_decade.size == 9) ||
((decade == 1 || decade == 2) && days_in_decade.size == 10)
# Return index formatted as YYYY-MM-D* if full decade
dates_strings << "#{year}-#{formatted_month}-#{decade}*"
else
# Return index formatted as YYYY-MM-DD
dates_strings += days_in_decade.collect{|day| "#{year}-#{formatted_month}-#{format('%02d', day)}"}
end
end
end
end
end
end
return dates_strings
end
Test call:
get_indices_from_period('2014-11-29', '2016-10-13')
=> ["2014-11-29", "2014-11-30", "2014-12-*", "2015-*", "2016-01-*", "2016-02-*", "2016-03-*", "2016-04-*", "2016-05-*", "2016-06-*", "2016-07-*", "2016-08-*", "2016-09-*", "2016-10-0*", "2016-10-10", "2016-10-11", "2016-10-12", "2016-10-13"]

Donations over past 24 months with keys and sums

Having pulled donations from the past two years, I'm trying to derive the sum of those donations per month, storing the keys (each month) and the values (the sum of donations for each month) in an array of hashes. I would like the keys to be numbers 1 to 24 (1 being two years ago and 24 being this month) and if there are no donations for a given month, the value would be zero for that month. How would I do this as an array of hashes in Ruby/Rails?
This is my variable with the donations already in it.
donations = Gift.where(:date => (Date.today - 2.years)..Date.today)
the following gives you a hash, with keys '2013/09" , etc...
monthly_donations = {}
date = Time.now
while date > 2.years.ago do
range = date.beginning_of_month..date.end_of_month
monthly_donations[ "{#date.year}/#{date.month}" ] = Giftl.sum(:column, :conditions => {created_at >= range})
date -= 30.days
end
To select the records in that time-span, this should be enough:
donations = Gift.where("date >= #{2.years.ago}")
you can also do this:
donations = Gift.where("date >= :start_date AND date <= :end_date",
{start_date: 2.years.ago, end_date: Time.now} )
See also: 2.2.1 "Placeholder Conditions"
http://guides.rubyonrails.org/active_record_querying.html
To sum-up a column in the database record, you can then do this:
sum = Gift.sum(:column , :conditions => {created_at >= 2.years.ago})
First, we need a function to find the difference in months from the current time.
def month_diff(date)
(Date.current.year * 12 + Date.current.month) - (date.year * 12 + date.month)
end
Then we iterate through #donation, assuming that :amount is used to store the value of each donation:
q = {}
#donations.each do |donation|
date = month_diff(donation.date)
if q[date].nil?
q[date] = donation.amount
else
q[date] += donation.amount
end
end
I found a good solution that covered all the bases--#user1185563's solution didn't bring in months without donations and #Tilo's called the database 24 times, but I very much appreciated the ideas! I'm sure this could be done more efficiently, but I created the hash with 24 elements (key: beginning of each month, value: 0) and then iterated through the donations and added their amounts to the hash in the appropriate position.
def monthly_hash
monthly_hash = {}
date = 2.years.ago
i = 0
while date < Time.now do
monthly_hash["#{date.beginning_of_month}"] = 0
date += 1.month
i += 1
end
return monthly_hash
end
#monthly_hash = monthly_hash
#donations.each do |donation|
#monthly_hash["#{donation.date.beginning_of_month}"] += donation.amount
end

Ruby get arrays of dates based on given period

I have question is there maybe a fine simple solution to this task:
I have first_date = "2011-02-02" , last_date = "2013-01-20" and period = 90 (days).
I need to get arrays with two elements for example:
[first_date, first_date + period] ... [some_date, last_date].
I will make it with some kind of a loop but maybe there is some nice fancy way to do this :D.
Date has a step method:
require 'date'
first_date = Date.parse("2011-02-02")
last_date = Date.parse("2013-02-20")
period = 90
p first_date.step(last_date-period, period).map{|d| [d, d+period]}
#or
p first_date.step(last_date, period).map.each_cons(2).to_a
require 'pp'
require 'date'
first_date=Date.parse "2011-02-02"
last_date=Date.parse "2013-01-20"
period = 90
periods = []
current = first_date
last = current + period
while(last < last_date ) do
periods << [current, last]
current = last
last = current + period
end
if periods[-1][1] != last_date
periods << [periods[-1][1], last_date]
end
p periods
I am assuming that the last period must end on last_date regardless of its length, as your question implies.

Efficiently querying transactions by time range with activerecord

Is there a way to make this method more efficient? I am querying a large number of transactions and it seems inefficient to perform a separate activerecord query for each period.
Should I make a single query for the whole day and sort through the results, grouping them by period? If so, what's the most efficient way to do this? Look forward to your thoughts.
def self.transactions(period)
today = Date.today
time1 = Time.utc(today.year, today.month, today.day, 9, 30, 0)
time2 = Time.utc(today.year, today.month, today.day, 16, 0, 0)
transactions_by_period = {}
while time1 < time2
transactions = self.where("created_at >= ? and created_at <= ?", time1, time2)
transactions_by_period[time1] = transactions
time1 += period
end
return transactions_by_period
end
#todays_transactions_by_hour = Stock.transactions(1.hour)
First off, you can use ranges for nicer queries. I'd also rename time1 and time2 to start and finish
transactions = self.where(:created_at => start..finish)
Then you can reduce this to get them by periods.
transactions.reduce(Hash.new{|h, k| h[k] = []) do |periods, transaction|
# Calculate how many periods have gone by for this transaction
offset = (transaction.created_at - start).to_i / period
# Find the actual period
transaction_period = start + offset * period
periods[transaction_period] << transaction
periods
end
Edit
I haven't tested the code, it is shown more for the logic

Resources