Determine if a given year is a leap year - ruby

UPDATE The problem I was trying to solve required a bit more than the Date implementation, as it required a method to take into account both the post-1752 Gregorian and the pre-1752 Julian calendars' differing definitions of a leap year.
The following is my solution, which passed all my RSpecs and is a considerable improvement on the nested conditional I started with:
def leap_year?(year)
gregorian_year = true if year >= 1752
julian_year = true if year < 1752
gregorian_test = true if year % 4 == 0 && year % 100 != 0 || year % 400 == 0
julian_test = true if year % 4 == 0
case
when gregorian_year && gregorian_test
true
when julian_year && julian_test
true
else
false
end
end
ORIGINAL QUESTION
I wrote a simple, ugly nested conditional to achieve the following:
To achieve the following:
Return "true" if the year meets the following conditions:
Is divisible by four
AND is not a century year (e.g.) 1900
UNLESS it is divisible by 400 (e.g. 400, 800, 2000)
I wrote an ugly nested conditional:
if year % 4 == 0
puts "Divisible by four"
if year % 100 == 0
puts "Century year"
if year % 400 == 0
puts "Quad-century year, leap year"
true
else
"Not a Quad-century year, not a leap year"
false
end
else
puts "Not a century year, leap year"
true
end
else
puts "Not divisible by four: not a leap year."
false
end
I tried to achieve the same with a case conditional, but it fails to detect the number 2016 as leap year:
case year
when (year % 4 == 0 && year % 100 != 0)
true
when year % 400
true
when year % 4 != 0
false
end
Two questions:
What am I doing wrong in my case conditional?
Is there a better way to achieve this?

If you would like to determine if a given year is a leap year, that has already been implemented for you in the Date class as Date#leap?:
Date.leap?(2000)
#=> true
# Or, alternatively:
Date.gregorian_leap?(1900)
#=> false
Date.julian_leap?(1900)
#=> true
More info in the Ruby documentation: Date#leap?
If you would like to build it yourself regardless, this should work:
def leap_year?(year)
return false unless year % 4 == 0
return true unless year % 100 == 0
year % 400 == 0
end
leap_year?(2016)
#=> true

Remove the year from case year if your when arguments are all boolean:
case
when (year % 4 == 0 && year % 100 != 0)
true
when year % 400 == 0
true
else
false
end
You can check it works :
def is_leap?(year)
case
when (year % 4 == 0 && year % 100 != 0)
true
when year % 400 == 0
true
else
false
end
end
require 'date'
p (0..2050).all?{|y| Date.leap?(y) == is_leap?(y)}
# true
This variant might be a bit more readable :
def is_leap?(year)
case
when year % 400 == 0
true
when year % 100 == 0
false
when year % 4 == 0
true
else
false
end
end
Finally, you could just write a single expression without any if or case :
year % 400 == 0 || (year % 4 == 0 && year % 100 != 0)

Related

Difference Between Dates Program in Ruby not working

def getNumDaysInMonth(month, year)
if (month ==1 || month ==3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12 )
return 31
elsif (month == 2)
if (isLeapYear?(year)== true)
return 29
elsif (isLeapYear?(year)== false)
return 28
end
elsif (month == 4 || month == 6 || month == 9 || month ==11 )
return 30
end
end
def isLeapYear?(year)
if (year % 400 ==0)
return true
elsif (year% 100 != 0) and (year%4==0)
return true
else
return false
end
end
def numDaysInYear(year)
days = 365;
if (isLeapYear?(year)== true)
return days +=1
else
return days
end
end
def differenceBetweenDates()
puts "Enter in a starting date: "
startDate = gets
puts "Enter in an ending date: "
endDate = gets
mdyArr = startDate.split("/", 3)
mdyArr2 = endDate.split("/",3)
startMonth = mdyArr[0].to_i
startDay = mdyArr[1].to_i
startYear = mdyArr[2].to_i
endMonth = mdyArr2[0].to_i
endDay = mdyArr2[1].to_i
endYear = mdyArr2[2].to_i
differenceVal = 0
if (startYear < endYear)
daysleftMonth = (getNumDaysInMonth(startMonth,startYear)) - startDay + 1
differenceVal = daysleftMonth
daysleftyear = 0
counter = 1
while counter <= 12
daysleftyear += (getNumDaysInMonth(counter,startYear))
counter += 1
end
differenceVal += daysleftyear
eachYear = 0
counter = startYear + 1
while counter < endYear
eachYear += (numDaysInYear(counter))
end
differenceVal += eachYear
daysleftyear = 0
counter = 1
while counter < endMonth
daysleftyear += (getNumDaysInMonth(counter, endYear))
end
differenceVal += daysleftyear
daysleftendMonth = 0
counter = 1
while counter <= endDay
daysleftendMonth += daysleftendMonth
end
differenceVal += daysleftendMonth
puts differenceVal
end
end
differenceBetweenDates()
First off, let's write Ruby like Ruby and cut your support methods down to a much more manageable bit of code. Remember that the value of the last expression in a method will be the method's return value. This includes conditional expressions like if/else.
def getNumDaysInMonth(month, year)
raise ArgumentError.new("Month mus be between 1 and 12") if month < 1 || month > 12
if [1, 3, 5, 7, 8, 10, 12].include?(month)
31
elsif month == 2
isLeapYear?(year) ? 29 : 28
else
30
end
end
def isLeapYear?(year)
year % 400 == 0 || (year % 100 != 0 && year % 4 == 0)
end
def numDaysInYear(year)
isLeapYear?(year) ? 366 : 365
end
Where you run into problems is this loop:
while counter < endYear
eachYear += numDaysInYear(counter)
end
counter is never updated, so this loop never ends. You have a few more loops like this.
If you fix these, your program will terminate, though the output is incorrect.
You may wish to create a method day_of_year which determines how far a current day is into the year.
def day_of_year(month, day, year)
raise ArgumentError.new("Month must be between 1 and 12") if month < 1 || month > 12
(1...month).map { |m| getNumDaysInMonth(m, year) }.sum + day
end
You can define a method in terms of that one to figure out how many days are left in he current year.
def days_left_in_year(month, day, year)
raise ArgumentError.new("Month must be between 1 and 12") if month < 1 || month > 12
numDaysInYear(year) - day_of_year(month, day, year)
end
These are both nice, simple methods that are easy to understand. The larger problem can them be expressed in terms of them by adding the days left in the year of the start date to the day of year for the end date, plus the sum of the days in any years in between.
def date_diff(startMonth, startDay, startYear, endMonth, endDay, endYear)
days_left_in_year(startMonth, startDay, startYear) +
day_of_year(endMonth, endDay, endYear) +
(startYear + 1 ... endYear).map { |y| numDaysInYear(y) }.sum
end

Leap Years. Program in Ruby. Can someone explain?

Recently started with Ruby (no previous programming background).
Task:
Write a program which will ask for a starting year and an ending year, and then puts all of the leap years between them (and including them, if they are also leap years). Leap years are years divisible by four (like 1984 and 2004). However, years divisible by 100 are not leap years (such as 1800 and 1900) unless they are divisible by 400 (like 1600 and 2000, which were in fact leap years)
code:
puts 'Input a starting year:'
start_year = gets.chomp
puts 'Input an ending year:'
end_year = gets.chomp
puts ''
while start_year.to_i <= end_year.to_i
if start_year.to_f%400 == 0
puts start_year
elsif start_year.to_f%100 == 0
elsif start_year.to_f%4 == 0
puts start_year
end
start_year = start_year.to_i + 1
end
Can someone explain me this code step by step (literally)? and how console prints out those dates? where do we ask program in this code to print it?
Let's go through the code line-by-line. I'll both explain what's going on and show how it could be improved.
puts 'Input a starting year:'
start_year = gets.chomp
It will be more convenient to make start_year an integer so we don't have to keep converting between a string and an integer. We therefore should change the second line to
start_year = gets.to_i
If you examine the doc for String#to_i you will see that this is the same as
start_year = gets.chomp.to_i
Next,
puts 'Input an ending year:'
end_year = gets.to_i
puts ''
while start_year <= end_year
The last line requires that Ruby continue the while loop as long as start_year <= end_year is true. She exits the loop when this expression becomes false (when start_year == end_year + 1). Notice the simplification in the this line and in the lines below.
if start_year % 400 == 0
puts start_year
If the year is divisible by 400 it is a leap year.
elsif start_year % 100 == 0
If the year is divisible by 100, but not by 400, it is not a leap year, so we do nothing, go to the end of the if/elsif/end clause and next execute start_year += 1.
elsif start_year % 4 == 0
puts start_year
end
If the year is divisible by 4, but not by 100, is is a leap year. If start_year % 4 != 0 it is not a leap year, so we do not print it.
start_year += 1
which Ruby expands to start_year = start_year + 1.
end
Return to the top of the loop and repeat.
Better
Here's a more Ruby-like way of writing that, converting the code to a method, using a single boolean expression and printing the return value of the method (an array of leap years).
def leap_years(start_year, end_year)
(start_year..end_year).select { |yr|
yr % 400 == 0 || (yr % 100 != 0 && yr % 4 == 0) }
end
puts 'Input a starting year:'
start_year = gets.to_i
puts 'Input an ending year:'
end_year = gets.to_i
p leap_years(start_year, end_year)
#=> [1904, 1908, 1912, 1916, 1920]
first_year..last_year is a range (an instance of the class Range). Range includes the module Enumerable which provides it with the instance method Enumerable#select.
Best
The following would be easier way to obtain the leap years between two given years, by making use of the class method Date::leap?.
require 'date'
def leaps_between(first_year, last_year)
(first_year..last_year).select { |y| Date.leap?(y) }
end
leaps_between(2000, 2016)
#=> [2000, 2004, 2008, 2012, 2016]
leaps_between(1900, 1916)
#=> [1904, 1908, 1912, 1916]
# Print the sentence "Input a starting year:"
puts 'Input a starting year:'
# Create a variable start_year and set its content to the user's input stripping it from the new line character
start_year = gets.chomp
# Read above
puts 'Input an ending year:'
end_year = gets.chomp
puts ''
# Do something while start_year is smaller or equal to end_year
while start_year.to_i <= end_year.to_i
# If start_year divided by 400 doesn't have any remainder
if start_year.to_f%400 == 0
# Print that year in the console
puts start_year
# Else if ...
elsif start_year.to_f%100 == 0
elsif start_year.to_f%4 == 0
puts start_year
end
# Increase start_year by 1
start_year = start_year.to_i + 1
end
You ask were the output comes from? puts prints to the console.
From the docs of Kernel#puts:
Equivalent to
$stdout.puts(obj, ...)
And from the docs of IO#puts:
Writes the given objects to ios as with IO#print. Writes a record separator (typically a newline) after any that do not already end with a newline sequence. If called with an array argument, writes each element on a new line. If called without arguments, outputs a single record separator.
$stdout.puts("this", "is", "a", "test")
produces:
this
is
a
test
I would also use an iterator:
puts "Please select 2 years to calculate the leap ones in between. What's your 1st one?"
start_year = gets.chomp.to_i
puts "...and your second one?"
end_year = gets.chomp.to_i
range = start_year..end_year
range.select do |year|
puts year if year % 4 == 0 or year % 400 == 0
end
The above while loop could also be rewritten as follows:
puts 'Input a starting year:'
start_year = gets.chomp.to_i
puts 'Input an ending year:'
end_year = gets.chomp.to_i
puts ''
while start_year <= end_year
if start_year % 400 == 0 or start_year % 4 == 0
puts start_year
end
start_year += 1
end

Having trouble with conditionals

I'm trying to define a method that returns "fizz" when the integer is divisible by 3, "buzz" when is divisible by 5 and "fuzzbuzz" when the number is divisible by both. I know 15 is divisible by both, however I don't know what I'm inputting wrong in my code. Thank you.
def fizzbuzz(int)
if int % 3 == 0
return "Fizz"
elsif int % 5 == 0
return "Buzz"
elsif int % 15 == 15
return "FizzBuzz"
else
end
end
So for conditionals, ruby evaluate the if, then the elsif, and finally the else in order. If anything condition evaluates to true, then the rest of the conditions are ignored.
def fizzbuzz(int)
if int % 3 == 0
return "Fizz"
elsif int % 5 == 0
return "Buzz"
elsif int % 15 == 15
return "FizzBuzz"
else
end
end
So let's look at a few examples based on your current code.
Say int = 3
We go to if int % 3 == 0, which is true, so the code return "Fizz" and nothing below that is evaluated.
Say int = 5,
int % 3 == 0 is false, we move onto the first elsif.
int % 5 == 0 is true, we return "Buzz"
Now let's look at 15,
int % 3 == 0 is true, so the code will return "Fizz" and ignores everything else, even if there are more conditions that will evaluate to be true after the if statement.
The order here is very important. You will want to check if int % 15 == 0 first, then move onto % 3 and % 5.

Implementing my own leap year class in ruby

I'm doing some of the exercises over on exercism.io and for the current one we need to implement our own leap year class (and ignore Ruby's built in feature). Tests are provided and I'm having trouble getting all of them to pass.
To summarise a leap year is any year that is divisible by four EXCEPT if that year is divisible by 100 UNLESS it's also divisible by 400. Here's what I have so far:
class Year
def self.leap?(year)
if (year % 400 == 0 && year % 100 == 0) || (year % 100 != 0 && year % 4 == 0)
return "Yes, #{year} is a leap year"
else
return "No, #{year} is not a leap year"
end
end
end
The tests that are failing are for 1900, 1997, and 1998. It's not a very large file so here's the test fixtures:
class YearTest < Minitest::Test
def test_leap_year
assert Year.leap?(1996), 'Yes, 1996 is a leap year'
end
def test_non_leap_year
refute Year.leap?(1997), 'No, 1997 is not a leap year'
end
def test_non_leap_even_year
refute Year.leap?(1998), 'No, 1998 is not a leap year'
end
def test_century
refute Year.leap?(1900), 'No, 1900 is not a leap year'
end
def test_fourth_century
assert Year.leap?(2400), 'Yes, 2400 is a leap year'
end
end
I'm not entirely convinced my logic is sound so any help at all would be much appreciated.
class Year
def self.leap?(year)
return "Yes, #{year} is a leap year" if (year % 400).zero?
return "No, #{year} is not a leap year" if (year % 100).zero?
return "Yes, #{year} is a leap year" if (year % 4).zero?
return "No, #{year} is not a leap year"
end
end
or
class Year
def self.leap?(year)
yes, no = "Yes, #{year} is a leap year", "No, #{year} is not a leap year"
return yes if (year % 400).zero?
return no if (year % 100).zero?
return yes if (year % 4).zero?
return no
end
end
The problem is Year.leap? should return true or false, not return a string, according to your tests.
class Year
def self.leap?(year)
(year % 400 == 0 && year % 100 == 0) || (year % 100 != 0 && year % 4 == 0)
end
end
Your tests will now pass.
The assert is looking for a truthy response, refute is looking for a falsy response.
We could've left the original code more intact, but saying...
if (condition_to_test) == true
return true
else
return false
end
is more succinctly rendered with just
condition_to_test
That will automatically return true or false as appropriate.
Following your description it should look like this
if year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
return "Yes, #{year} is a leap year"
else
return "No, #{year} is not a leap year"
end
This one finds the leap years within a range of years.
(start_year..end_year).each do |year|
next if year % 4 != 0
next if year % 100 == 0 && year % 400 != 0
puts year
end

Total newb here and other fizzbuzz issue

I'm trying to write a looping fizzbuzz code that ends with the user_input's number. So far the code works, but it loops the number of times you put in for the user_input, not end at the user_input's limit. For example, if I type in 25, it will loop 25 times, and not end at 25. How do I set the parameters/range?
Here is my code:
puts("Please select a number that is at least 25. This is the limit for the fizzbuzz game.")
user_input = gets().chomp().to_i
if
user_input < 25
puts("Please select a larger number.")
else
user_input >= 25
user_input = user_input
counter = 1
while counter < user_input
puts(counter)
counter = counter + 1
(1..user_input).step do |i|
if i % 3 == 0 && i % 5 == 0
puts("fizzbuzz")
elsif i % 3 == 0
puts("fizz")
elsif i % 5 == 0
puts("buzz")
else
puts(i)
end
end
end
end
It is optional to write () when you send no parameters to a method and usually discouraged
You shouldn't use else user_input >= 25, else is enough
user_input = user_input is totally useless line
while cycles with counters isn't the way rubists code, prefer iterators. Moreover, you shouldn't have while here at all
puts 'Please select a number that is at least 25. This is the limit for the fizzbuzz game.'
user_input = gets.chomp.to_i
if user_input < 25
puts 'Please select a larger number.'
else
1.upto(user_input) do |i|
if i % 3 == 0 && i % 5 == 0
puts 'fizzbuzz'
elsif i % 3 == 0
puts 'fizz'
elsif i % 5 == 0
puts 'buzz'
else
puts i
end
end
end
optionally, you can use case-when instead of multiple elsif statements:
puts 'Please select a number that is at least 25. This is the limit for the fizzbuzz game.'
user_input = gets.chomp.to_i
if user_input < 25
puts 'Please select a larger number.'
else
1.upto(user_input) do |i|
case
when [3, 5].all? { |n| i % n == 0 }; puts 'fizzbuzz'
when i % 3 == 0; puts 'fizz'
when i % 5 == 0; puts 'buzz'
else; puts i
end
end
end

Resources