Timer RSpec Test Ruby - ruby

I am trying to solve the timer problem from TestFirst Ruby.
I got the first two criteria correctly but the third one when tested for time = 12 seconds does not work. It does not look like Ruby is reading the time = 12 secs.
The codes are pretty lazy and not optimized, apparently. Also I did tried out the padded method but the test never worked. I had my padded method defined as
def padded(num)
if num<=9
return "0"<<num.to_s
else
return num.to_s
end
end
It would be great if someone can show me how to set that up correctly since that might have been the problems.
Here are my complete codes:
class Timer
#initialization of seconds
def seconds
return 0
end
#seconds= method
def seconds=(time)
#seconds = time_string(time)
end
#time_string method
def time_string(time=0)
#format of hour:minute:second
#minute must be less than 59 (or 59*60 seconds), otherwise it will convert to hour
minute = time/60 #note that this is integer math, so it will take the minute and not the remainder
hour = minute/60
remainder_seconds = time%60
if time<=9
return "00:00:0" << time.to_s
elsif time>9 && time<=60
return "00:00:" << time.to_s
elsif time>60 && time<=9*60 #9 minutes and greater than 1 min
#ensuring double XX seconds or 0X seconds (this would be easier to use the padded method)
if remainder_seconds >9
remainder_seconds_sd = remainder_seconds.to_s
else
remainder_seconds_sd = "0" << remainder_seconds.to_s
end
return "00:0" << minute.to_s << ":" << remainder_seconds_sd
end
end
end
RSpec below:
require '09_timer'
describe "Timer" do
before(:each) do
#timer = Timer.new
end
it "should initialize to 0 seconds" do
#timer.seconds.should == 0
end
describe 'time_string' do
it "should display 0 seconds as 00:00:00" do
#timer.seconds = 0
#timer.time_string.should == "00:00:00"
end
it "should display 12 seconds as 00:00:12" do
#timer.seconds = 12
#timer.time_string.should == "00:00:12"
end
it "should display 66 seconds as 00:01:06" do
#timer.seconds = 66
#timer.time_string.should == "00:01:06"
end
it "should display 4000 seconds as 01:06:40" do
#timer.seconds = 4000
#timer.time_string.should == "01:06:40"
end
end
# One way to implement the Timer is with a helper method.
# Uncomment these specs if you want to test-drive that
# method, then call that method from inside of time_string.
#
=begin
describe 'padded' do
it 'pads zero' do
#timer.padded(0).should == '00'
end
it 'pads one' do
#timer.padded(1).should == '01'
end
it "doesn't pad a two-digit number" do
#timer.padded(12).should == '12'
end
end
=end
end

The problem with your tests and Timer is that, in your tests you are setting the value of #timer.seconds, but the Timer#time_string does not rely on the #seconds variable set. Your time_string method is implemented the way it accepts the amount of seconds as an argument, not an attribute of Timer.
Try changing your tests as follows:
describe "Timer" do
# rest of your code
describe 'time_string' do
it "should display 0 seconds as 00:00:00" do
#timer.time_string(0).should == "00:00:00"
end
it "should display 12 seconds as 00:00:12" do
#timer.time_string(12).should == "00:00:12"
end
it "should display 66 seconds as 00:01:06" do
#timer.time_string(66).should == "00:01:06"
end
it "should display 4000 seconds as 01:06:40" do
#timer.time_string(4000).should == "01:06:40"
end
end
end
You might be wondering okay, but why the first test - 00:00:00 - did work in first place?. Well, this is, because your time_string method argument defaults to 0:
def time_string(time=0)
# Rest of the code
end
and because you were not passing any other value, the 0 has been used.
If you have any questions - I'm happy to help!
Good luck!
Edit
If you want to make it the other way around - make the class to work for your tests, change your Timer class:
class Timer
def initialize
#seconds = 0
end
def seconds
#seconds
end
def seconds=(time)
#seconds = time
end
def time_string
#format of hour:minute:second
#minute must be less than 59 (or 59*60 seconds), otherwise it will convert to hour
minute = #seconds/60 #note that this is integer math, so it will take the minute and not the remainder
hour = minute/60
remainder_seconds = #seconds%60
if #seconds<=9
return "00:00:0" << #seconds.to_s
elsif #seconds>9 && #seconds<=60
return "00:00:" << #seconds.to_s
elsif #seconds>60 && #seconds<=9*60 #9 minutes and greater than 1 min
#ensuring double XX seconds or 0X seconds (this would be easier to use the padded method)
if remainder_seconds >9
remainder_seconds_sd = remainder_seconds.to_s
else
remainder_seconds_sd = "0" << remainder_seconds.to_s
end
return "00:0" << minute.to_s << ":" << remainder_seconds_sd
end
end
end
We have added initialize method, we have changed def seconds=(time) method, and we have changed all occurrences of time in your time_string method.
If that works for you, consider posting the code to https://codereview.stackexchange.com/. There is a lot in the code to improve, and codereview is a great place to ask for help!

A cleaner version:
class Timer
attr_accessor :seconds
def initialize
#seconds = 0
end
def time_string
seconds = #seconds % 60
minutes = (#seconds / 60) % 60
hours = #seconds / (60**2)
"#{padded(hours)}:#{padded(minutes)}:#{padded(seconds)}"
end
def padded(num)
return '0' + num.to_s if num < 10
return num.to_s if num >= 10
end
end

Related

Adding together string-based minutes and seconds values

I have a a Track model that has a duration attribute. The attribute is string based, and reads in minutes:seconds format. I was wondering what the best way would be to take these string-based values and add them together. For example, if there are duration values like this:
Duration 1: "1:00"
Duration 2: "1:30"
how could I get it to output "2:30"?
Most of the questions I found related to this issue start with an integer based value. What's the best way to get this done?
My suggestion is to store/manipulate them as seconds.
It's definitely easier to store them as the integer number of seconds, and apply a function to parse/format the value into the proper string representation.
Storing them as integer will make it very easy to sum and subtract them.
Here is one way this can be done:
class Track
attr_accessor :duration
def initialize(duration)
#duration = duration
end
end
arr = [Track.new("1:00"), Track.new("1:30")]
total_seconds = arr.reduce(0) do |a, i|
min, sec = i.duration.split(":").map(&:to_i)
a + min * 60 + sec
end
p total_duration = '%d:%02d' % total_seconds.divmod(60)
#=> "2:30"
Edit: I missed #Wand's earlier answer, which is the same as mine. I'll leave mine just for the way I've organized the calculations.
arr = %w| 1:30 3:07 12:53 |
#=> ["1:30", "3:07", "12:53"]
"%d:%2d" % arr.reduce(0) do |tot,str|
m,s = str.split(':')
tot + 60*m.to_i + s.to_i
end.divmod(60)
#=> "17:30"
I just had to implement something like this in a recent project. Here is a simple start. If you are sure you will always have this format 'H:S', you will not need to convert your duration to time objects:
entries = ["01:00", "1:30", "1:45"]
hours = 0
minutes = 0
entries.each do |e|
entry_hours, entry_minutes = e.split(':', 2)
hours += entry_hours.to_i
minutes += entry_minutes.to_i
end
hours += (minutes/60)
minutes = (minutes%60)
p "#{hours}:#{minutes}"
#=> "4:15"
I agree with #SimoneCarletti: store them as an integer number of seconds. However, you could wrap them in a duration value class that can output itself as a nicely formatted string.
class Duration
attr_accessor :seconds
def initialize(string)
minutes, seconds = string.split(':', 2)
#seconds = minutes.to_i * 60 + seconds.to_i
end
def to_s
sprintf("%d:%02d", #seconds / 60, #seconds % 60)
end
def self.sum(*durations)
Duration.new(durations.reduce(0) { |t,d| t + d.seconds })
end
end
EDIT: Added a sum method similar to that suggested by #CarySwoveland below. You can use this as follows:
durations = ["1:30", "2:15"].map { |str| Duration.new(str) }
total = Duration.sum *durations

Time variable not being passed somewhere in these methods

There aren't any error messages showing up once the code is run, I've resolved all of those before posting here. This is just a simple greeting with a few methods, which takes the current time into account and asks for the user's name, but for some reason there's a hole where the time variable should be in the greeting. This is my Ruby code here:
def determine_current_hour
current_time = Time.new
current_hour = current_time.hour
end
def greeting(name)
current_hour = determine_current_hour
if(current_hour > 3 && current_hour < 12)
time = "morning"
elsif(current_hour > 12 && current_hour < 18)
time = "afternoon"
elsif(current_hour > 18 || current_hour < 2)
time = "evening"
end
puts "Good #{time}, #{name.capitalize}!"
end
puts "What is your name?"
name = gets
greeting(name)
And this is the resulting input and output:
What is your name?
Ruby
Good , Ruby
!
It seems airtight to me but I must be missing something. Any help would greatly be appreciated!
Your conditions do not account for the hour being 2, 3, 12 or 18 because they are all exclusive checks. In any of those hours you will see the output you describe.
As Greg Beech correctly points out, some hours are wrong because your conditions exclude them. For example, your first condition says current_hour < 12 and your second condition says current_hour > 12, but if current_hour is equal to 12 it meets neither of those conditions. To fix it, either replace the comparison operator in the first condition with <= or in the second with >=:
def greeting(name)
current_hour = determine_current_hour
if current_hour >= 3 && current_hour < 12
time = "morning"
elsif current_hour >= 12 && current_hour < 18
time = "afternoon"
elsif current_hour >= 18 || current_hour < 3
time = "evening"
end
puts "Good #{time}, #{name.capitalize}!"
end
This is pretty verbose, though. Might I suggest a more idiomatic approach using Ruby's versatile case construct?
def current_day_part
case Time.new.hour
when 3..11 then "morning"
when 12..17 then "afternoon"
else "evening"
end
end
def greeting(name)
puts "Good #{current_day_part}, #{name.capitalize}!"
end

How to define new loop function

I have a medhod like this.
def run
loop do
sleep 0.1
# do something
end
end
And I want to write it like this.
def run
every 100, :msec do
# do something
end
end
How can I write a method like this every?
def every(quantity, units = :sec)
# this could be improved but you get the idea
quantity = quantity / 1000.0 if units == :msec
loop do
sleep quantity
yield
end
end
every 100, :msec do
puts Time.now
end

Ruby Rspec Timer - refactoring solution

I solved TestFirst.org question 09_timer for Ruby Rspec testing. My code works but I do not like it. It is very long. Any comments and/or suggestions for improving it would be greatly appreciated. Please include an explanation to clarify any suggestions. The goal was to create the Timer with an #seconds instance variable initialized to 0, and then return all values as a string with hours, minutes, seconds format: 00:00:00. So 12 seconds => 00:00:12; 66 seconds => 00:01:06; and 4000 seconds => 01:06:40.
Thank you. Code below.
class Timer
attr_accessor :seconds
def initialize
#seconds = 0
end
def padded(n)
"0#{n}"
end
def time_string
hours = #seconds/3600
h_minutes = ((#seconds%3600)/60)
minutes = #seconds/60
m_seconds = #seconds%60
second = #seconds
seconds = ""
if #seconds < 60
if second < 10
second = padded(second)
end
seconds << "00:00:#{second}"
elsif #seconds > 3600
if hours < 10
hours = padded(hours)
end
if h_minutes < 10
h_minutes = padded(h_minutes)
end
if m_seconds < 10
m_seconds = padded(m_seconds)
end
seconds << "#{hours}:#{h_minutes}:#{m_seconds}"
else
if minutes < 10
minutes = padded(minutes)
end
if m_seconds < 10
m_seconds = padded(m_seconds)
end
seconds << "00:#{minutes}:#{m_seconds}"
end
#seconds = seconds
end
end
There are several little things you can do to simplify your class, and a few large organizational changes.
1) Use String#rjust to pad the numbers:
def padded(n)
"#{n}".rjust(2, '0')
end
This lets you apply it to every number, regardless or whether or not it already has two digits. As a consequence, you can get rid of all of the single-digit checks (if h_minutes < 10, etc).
2) Get rid of everything starting from the first if statement, as none of it is necessary. Just a few lines before, you have hours = #seconds / 3600, h_minutes = ((#seconds%3600)/60), and m_seconds = #seconds%60, which are the only three values you need. Apply a simple map (for padding), and join with ":" to arrive at your final string.
3) If you want an object-oriented approach, each of your hours/minutes/seconds variables could be a method, so you end up with something more like this:
class Timer
attr_accessor :seconds
def initialize
#seconds = 0
end
def time_string
[hours, minutes, m_seconds].map(&method(:padded)).join(":")
end
def hours
seconds / 3600
end
def minutes
(seconds % 3600)/60
end
def m_seconds
(seconds % 60)
end
def padded(n)
"#{n}".rjust(2, '0')
end
end

Break from block based on time

I would like to run a block for 60 seconds. What I have come up with thus far does not break out of the block as desired.
#start_time = Time.now
stream_some_data do |x|
# .. do something ...
break if Time.now == #start_time + 60
end
Ruby's stdlib already has a Timeout module for this:
begin
require "timeout"
Timeout::timeout(60) do
# all of the things...
end
rescue Timeout::Error => e
end
Since you're unlikely to get to that line at exactly 60 seconds past the start, try:
break if Time.now > #start_time + 60

Resources