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
Related
I am having a difficult time figuring ordinal in the program below. I need to use the ordinal in the loop statement. When I print statement, It print out backward like 3rd, 2nd, 1st, and so on.
I tried changing my loop statment from (number -= 1) to (number += 1) but that would make infinite while loop. Can someone give me feedback on what I can do here? Thank you.
class Integer
def ordinal
case self % 10
when 1
return "#{self}st"
when 2
return "#{self}nd"
when 3
return "#{self}rd"
else
return "#{self}th"
end
end
end
puts "Let's play a numbers game."
print "How many numbers would you like to enter? >"
number = gets.chomp.to_i
while number >= 1
print "\n\nEnter the #{number.ordinal} positive integer:"
user_int = gets.chomp.to_i
number -= 1
if user_int % 3 == 0
print "#{user_int} is divisible by 3."
else
print "#{user_int} is not divisible by 3."
end
end
puts "\n\nEnd of the Game"
First I'd suggest you to patch Integer class in this way:
module MyIntegerPatch # <------ a custom module
def to_ordinal # <------ to_ordinal is better
case self % 10
when 1
return "#{self}st"
when 2
return "#{self}nd"
when 3
return "#{self}rd"
else
return "#{self}th"
end
end
end
Integer.include MyIntegerPatch # <------ "patch" the class
Then for your loop just use a Range:
(1..number).each do |n| # <---- a range here
puts "\n\nEnter the #{n.to_ordinal} positive integer:"
user_int = gets.chomp.to_i
if user_int % 3 == 0
puts "#{user_int} is divisible by 3."
else
puts "#{user_int} is not divisible by 3."
end
end
Maybe you can try below, use the ruby style times to control
class Integer
def ordinal
case self % 10
when 1
"#{self}st"
when 2
"#{self}nd"
when 3
"#{self}rd"
else
"#{self}th"
end
end
end
puts "Let's play a numbers game."
print 'How many numbers would you like to enter? >'
number = gets.chomp.to_i
number.times do |time|
print "\n\nEnter the #{(time + 1).ordinal} positive integer:"
user_int = gets.chomp.to_i
if user_int % 3 == 0
print "#{user_int} is divisible by 3."
else
print "#{user_int} is not divisible by 3."
end
end
puts "\n\nEnd of the Game"
I am trying to write some code (using a while loop) that will generate random numbers (between 1 and 10) until the number 7 is generated.
Here is what I've got so far, but it's just stuck in a loop of printing random numbers between 1 and 10.
num = ""
while num != 7
print rand(1..10).to_s
end
puts "over"
I understand why it's looping without end, but I'm unsure of how else to generate the random numbers and still end the loop when a 7 appears.
There are many ways to do it, for example:
loop do
num = rand(1..10)
print num
break if num == 7
end
Also, to make your code work:
num = ""
while num != '7'
num = rand(1..10).to_s
print num
end
puts "over"
reason in that you convert num to string and compare it with integer
puts (number = rand(1..10)) until number == 7
The problem for this code is that you generate an infinite loop because num is always "", you need to update the variable num inside the while loop like this:
num = ""
while num != 7
num = rand(1..10)
puts num
end
puts "over"
in this case, you do not know when the loop will stop, but in each loop it can stop with probability 1/10, this is not a good practice, you nedd to know when the loop stops, so will be better to add a variable for maximum number of tries like this. And also you do not need to transform dthe number to string to print it:
num = 0
max_tries = 100
try = 1
guess = 7
while (try < max_tries && num != guess)
num = rand(1..10)
try = try + 1
puts num
end
if (try >= max_tries)
puts "you do not get the guess"
else
puts "over"
end
and you get this:
[4] pry(main)> 9
9
3
4
9
6
9
8
4
5
9
10
4
3
7
over
=> true
Try this out
$num = 0
while $num != 7
$num = rand(1..10)
print $num.to_s + "\n"
end
puts "over"
You have to assign the rand generated value into num variable,
$ num = ''
$ while num != 7
$ num = rand(1..10)
$ print num
$ end
$ # 6168510267=> nil
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
As in the title, I require help understanding an issue regarding the calculation of leap years between two starting years, when the program is executed from the command line, I always receive the same output regardless of input.
My code is as follows:
while true
puts 'Input starting year:'
year_1 = gets.chomp
num_year_1 = year_1.to_i
puts 'Input second year:'
year_2 = gets.chomp
num_year_2 = year_1.to_i
puts 'These are the leap years between these two years:'
if num_year_1 > num_year_2
while num_year_1 >= num_year_2
if num_year_1 % 4 == 0 && num_year_1 % 100 == 0
puts num_year_1
end
num_year_1 -= num_year_1 - 1
end
elsif num_year_2 > num_year_1
while num_year_2 >= num_year_1
if num_year_2 % 4 == 0 && num_year_2 % 100 == 0
puts num_year_2
end
num_year_2 -= 1
end
elsif num_year_1 == num_year_2
puts 'These 2 years are the same, please try again.'
end
if year_1 == 'end'
break
end
end
And I always receive this as the output:
These are the leap years between these 2 years:
These 2 years are the same, try again
The only input that affects this is the 'end' command, which replies with the same output but does complete the break in the loop.
I do not understand why this is happening, because in my mind, my program has correct ruby grammar, and so help would be greatly appreciated.
Your existing code:
puts 'Input starting year:'
year_1 = gets.chomp
num_year_1 = year_1.to_i
puts 'Input second year:'
year_2 = gets.chomp
num_year_2 = year_1.to_i
Should instead be:
puts 'Input starting year:'
year_1 = gets.chomp
num_year_1 = year_1.to_i
puts 'Input second year:'
year_2 = gets.chomp
num_year_2 = year_2.to_i
The issue is that you are assigning both num_year_1 and num_year_2 to year_1.to_i, when the latter should be assigned to year_2.to_i
Your 7th line of code, num_year_2 = year_1.to_i has a typo: I'm sure you meant to put num_year_2 = year_2.to_i.
Far easier and implementing the correct algorithm for calculating leap years:
def isLeapYear(yearVar)
((yearVar % 4 == 0 && !(yearVar % 100 == 0)) || (yearVar % 400 == 0))
end
while true
puts 'Input starting year:'
num_year_1 = gets.to_i
puts 'Input second year:'
num_year_2 = gets.to_i
if num_year_1 < num_year_2
puts 'These are the leap years between these two years:'
for i in num_year_1..num_year_2
puts i if isLeapYear(i)
end
elsif num_year_1 == num_year_2
puts 'These two years are the same. Try again...'
else
puts 'Starting year must be less than second year. Try again...'
end
end
Here's a concise one.
(start_year..end_year).each do |year|
next if year % 4 != 0
next if year % 100 == 0 && year % 400 != 0
puts year
end
I've built a Yahtzee game for Ruby Quiz #19. I have the game up and running, however, there are 2 bugs.
When a player chooses to use a roll of >=3 of a kind as a "small straight" (sequence of 4 dice) to "scratch" (score it as zero) that section an error occurs. Here is the code for the small straight:
def sm_straight
#roll = #roll.sort.uniq
if (0..1).any? {|x| (#roll[x+3] - #roll[x+2] == 1) && (#roll[x+2] - #roll[x+1] == 1) && (#roll[x+1] - #roll[x] == 1)}
#scorecard["sm. straight"] = 30
else
puts "Your roll is not a sm. straight! Please select another section or type scratch to score 0 for this section."
scratch = gets.chomp
if scratch == "scratch"
#scorecard["sm. straight"] = "scratch"
elsif #scorecard.has_key?(scratch)
#turn -= 1
section_to_score(scratch)
else
sm_straight
end
end
end
This is the error:
NoMethodError: undefined method -' for nil:NilClass
from Yahtzee_test.rb:209:inblock in sm_straight'
Line 209 is the "if statement" line
When a player incorrectly enters which dice to keep. I am trying to figure out a better way to ask the player how to enter the dice to keep or catch the error and have them re-enter the numbers with the current system. Here is the code"
def roll_again
puts "Which dice would you like to keep from this roll? (1, 2, 3, 4, 5)"
dice_to_keep = gets.chomp.split(',').map {|x| (x.to_i) - 1}.map {|x| #roll[x]}
new_roll = Array.new(5 - dice_to_keep.size) {rand(6) + 1}
#roll = new_roll + dice_to_keep
p #roll
#roll_count += 1
puts "That was roll number #{#roll_count}, you have #{3-#roll_count} remaining."
if #roll_count < 3
more_rolls?
else
section(#roll)
end
end
Any advice on how to write this code better and make it bug free would be greatly appreciated!
To check for a straight of at least 4 out of 5 dice, you could replace:
#roll = #roll.sort.uniq
if (0..1).any? {|x| (#roll[x+3] - #roll[x+2] == 1) && (#roll[x+2] - #roll[x+1] == 1) && (#roll[x+1] - #roll[x] == 1)}
with this:
if has_straight(roll, 4)
And define has_straight:
def has_straight( roll, need )
num = 1
roll = roll.sort.uniq
roll.each_with_index do |e, i|
if i < roll.length-1 then
if (roll[i+1] - roll[i]) > 1 then
break if num >= need
num = 1
end
num += 1
end
end
num >= need
end
There may be a slightly more clever Ruby-ism that will do this, but it fixes your array out-of-bounds issue.