I have a method that is basically a loop and it calls itself at the end each time. What is the best way for the method to not call itself when the date reaches a certain point? Each iteration through adds 1 day and basically processes stats for that day. It looks like the below:
def loop(start_day)
date = start_day
#do a bunch of stuff
date = date +1.day
if date > Time.now
puts "loop should be over"
end
loop(date)
end
Each iteration through adds 1 day
That's not true for the code you've posted. In your code you add 1 day to the start date once and then you keep processing the same date over and over again because you recurse on the old date (start_date), not the incremented date (date).
What is the best way for the method to not call itself when the date reaches a certain point?
Just put the recursive call inside an if or, in this case, inside of the else of the if that you already have.
Since you set date to start_date immediately, it seems there's no point in having both. Here's the more canonical form of doing recursion:
def loop(date)
return if date > Time.now
#do a bunch of stuff
loop(date + 1.day)
end
Update: If it's not obvious to you that recursion isn't necessary here, in real life, it would make more sense to do something like this:
def process_from(date)
while date <= Time.now
# Process date
date += 1.day
end
end
What about this?
def loop(start_day)
return "loop should be over" if start_day >= Time.now
#...
loop(modified_date)
end
or...
def loop(start_day)
date = start_day.dup
time = Time.now
date += 1.day while date <= time
'loop should be over'
end
It seems like you want to iterate over all days from starting date to today. Then maybe this is even more simple:
def map_date(date)
(date.to_date..Date.today).map do |d|
d.strftime("Today is %A")
end
end
You must have base case (stop case) for recursive function;
example:
def loop(date)
if date == certain_point
return [something_for_stop]
end
loop(date - 1)
end
Related
*Apologies if the question's wording is confusing. I didn't know exactly how to ask it.
How can I do something like this?
def track_time(function, input)
beg = Time.now
function(input)
end = Time.now
end - beg
end
And then pass it a function and a value for that function to use.
def double(value)
value + value
end
p track_time(double, 5)
The goal is to create something repeatable so I can track how long different functions take to complete.
First you can not use 'end' as a variable name.
As for your question, I agree with Mladen Jablanovićyou that for this use case a block is better, but since you specifically asked about passing a method as a parameter to another method, you can use the 'send' method:
def track_time method, value
begin_time = Time.now
send method, value
end_time = Time.now
end_time - begin_time
end
def double(value)
value + value
end
p trcak_time(:double, 5)
Unfortunately, methods in Ruby are not first-class objects, so they can't be directly passed as arguments. You can pass a name of the method (usually passed as symbol) instead, as other answers suggest.
But the idiomatic way to achieve what you are aiming for are blocks:
def track_time
start = Time.now
yield
finish = Time.now
finish - start
end
track_time do
double(5)
end
#=> 6.127e-06
Remember that end is a reserved word in Ruby (I suspect it was for illustration purposes anyhow).
You could pass in the string/symbol of the function name instead.
def track_time(function, input)
start = Time.now
method(function).call(input)
finish = Time.now
finish - start
end
def double(value)
value + value
end
track_time('double', 5)
=> 6.127e-06
I'm doing the following Ruby Tutorial https://rubymonk.com/learning/books/4-ruby-primer-ascent/chapters/50-debugging/lessons/124-benchmarking_ruby_code. One of the exercises asks me to:
use Ruby's super-awesome blocks to create a method which takes in a
block, executes it, and returns the time it took.
The exercise looks like this:
def benchmark
# your code here!
end
time_taken = benchmark do
sleep 0.1
end
puts "Time taken #{time_taken}"
there is a hint (Need a hint?) below the exercise:
Ruby Blocks - Introduction to Blocks in Ruby (Ruby Primer)
and i did so:
def benchmark(time)
begin_time = Time.now
end_time = Time.now
time.benchmark {|time| yield time}
end
time_taken = benchmark do
sleep 0.1
end
puts "Time taken #{time_taken}
but received an error.
i am interested in: why is local variable - 'time_taken', suggested without representing an element after 'do'? or is it not necessary? Can anyone tell me how to write code to get the positive result.
You should do it much easier:
def benchmark
begin_time = Time.now
yield
end_time = Time.now
end_time - begin_time
end
time_taken = benchmark do
sleep 0.1
end
puts "Time taken #{time_taken}"
First you collect the time and store in in variable begin_time, then yield - so run the block, then collect the end time. Return the difference. That's it.
That's pretty far off, and not really at all salvagible.
Your benchmark method should look like this pseudo code:
def benchmark
let begin_time -> current time
execute the block
let end_time -> current_time
return endtime - begintime
end
As far as executing the block being passed in, there is no time.benchmark method, I'm not sure where that came from, and you do not need to pass anything into the block. You want a single, simple yield.
If I've got a time object:
t = Time.now
and I want to know if that time is AM or PM, right now the only way I can figure to do this is:
t.strftime("%p") == "PM"
Now, that %p is getting interpolated from something, right? Is there another way to get to it?
I ask because I'm doing some time formatting where I want to display a time range like:
"9:00 AM - 5:00 PM"
"4:00 - 5:30 PM"
"10:15 - 11:45 AM"
Right now I have to do this checking the string value of strftime, but I'd prefer to write something like:
if start_time.am? && end_time.pm? || start_time.pm? && end_time.am?
...instead of the much more verbose strftime string comparisons I'm doing now.
Based on http://www.ruby-doc.org/core-1.9.3/Time.html, I do not believe there is any other way. You could monkey-patch Time to save you some tedious strftime, however:
class Time
def meridian
self.strftime('%p')
end
def am?
self.meridian == 'AM'
end
def pm?
self.meridian == 'PM'
end
end
There isn't anything as nice as time.am? but you can use time.hour < 12 instead.
class Time
def am?
self.hour.in? (0..12)
end
def pm?
self.hour.in? (13..24)
end
end
I want to know if a time belongs to an schedule or another.
In my case is for calculate if the time is in night schedule or normal schedule.
I have arrived to this solution:
NIGHT = ["21:00", "06:00"]
def night?( date )
date_str = date.strftime( "%H:%M" )
date_str > NIGHT[0] || date_str < NIGHT[1]
end
But I think is not very elegant and also only works for this concrete case and not every time range.
(I've found several similar question is SO but all of them make reference to Date ranges no Time ranges)
Updated
Solution has to work for random time ranges not only for this concrete one. Let's say:
"05:00"-"10:00"
"23:00"-"01:00"
"01:00"-"01:10"
This is actually more or less how I would do it, except maybe a bit more concise:
def night?( date )
!("06:00"..."21:00").include?(date.strftime("%H:%M"))
end
or, if your schedule boundaries can remain on the hour:
def night?(date)
!((6...21).include? date.hour)
end
Note the ... - that means, basically, "day time is hour 6 to hour 21 but not including hour 21".
edit: here is a generic (and sadly much less pithy) solution:
class TimeRange
private
def coerce(time)
time.is_a? String and return time
return time.strftime("%H:%M")
end
public
def initialize(start,finish)
#start = coerce(start)
#finish = coerce(finish)
end
def include?(time)
time = coerce(time)
#start < #finish and return (#start..#finish).include?(time)
return !(#finish..#start).include?(time)
end
end
You can use it almost like a normal Range:
irb(main):013:0> TimeRange.new("02:00","01:00").include?(Time.mktime(2010,04,01,02,30))
=> true
irb(main):014:0> TimeRange.new("02:00","01:00").include?(Time.mktime(2010,04,01,01,30))
=> false
irb(main):015:0> TimeRange.new("01:00","02:00").include?(Time.mktime(2010,04,01,01,30))
=> true
irb(main):016:0> TimeRange.new("01:00","02:00").include?(Time.mktime(2010,04,01,02,30))
=> false
Note, the above class is ignorant about time zones.
In Rails 3.2 it has added Time.all_day and similars as a way of generating date ranges. I think you must see how it works. It may be useful.
Is there a way to iterate over a Time range in Ruby, and set the delta?
Here is an idea of what I would like to do:
for hour in (start_time..end_time, hour)
hour #=> Time object set to hour
end
You can iterate over the Time objects, but it returns every second between the two. What I really need is a way to set the offset or delta (such as minute, hour, etc.)
Is this built in to Ruby, or is there a decent plugin available?
Prior to 1.9, you could use Range#step:
(start_time..end_time).step(3600) do |hour|
# ...
end
However, this strategy is quite slow since it would call Time#succ 3600 times. Instead,
as pointed out by dolzenko in his answer, a more efficient solution is to use a simple loop:
hour = start_time
while hour < end_time
# ...
hour += 3600
end
If you're using Rails you can replace 3600 with 1.hour, which is significantly more readable.
If your start_time and end_time are actually instances of Time class then the solution with using the Range#step would be extremely inefficient since it would iterate over every second in this range with Time#succ. If you convert your times to integers the simple addition will be used but this way you will end up with something like:
(start_time.to_i..end_time.to_i).step(3600) do |hour|
hour = Time.at(hour)
# ...
end
But this also can be done with simpler and more efficient (i.e. without all the type conversions) loop:
hour = start_time
begin
# ...
end while (hour += 3600) < end_time
Range#step method is very slow in this case. Use begin..end while, as dolzenko posted here.
You can define a new method:
def time_iterate(start_time, end_time, step, &block)
begin
yield(start_time)
end while (start_time += step) <= end_time
end
then,
start_time = Time.parse("2010/1/1")
end_time = Time.parse("2010/1/31")
time_iterate(start_time, end_time, 1.hour) do |t|
puts t
end
if in rails.