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
Related
Invoice numbers are numeric only with any number of digits. To format one correctly, group the digits in group of three plus a group of any remainder, but never leave one digit by itself, unless it's a one digit number. Eg these are all correct formatting
123
12-34
6
783-907-23-45
And these are not
123-4
98-456
There's one more catch user input is passed directly to the function and you never know what characters users might type. Ignore any part of the input that is not digit
Invoice.format_number should always return a string
module Invoice
def self.format_number(str)
return ""
end
end
puts Invoice.format_number("ab1234")
What I have tried
1st approach
arr = []
str.chars.each do |elem|
val = elem =~ /\A[-+]?[0-9]*\.?[0-9]+\Z/
arr << elem if val == 0
end
num_of_digits = arr.length
pairs_of_two = 0
pairs_of_three = 0
if num_of_digits > 5
while num_of_digits > 0 do
break if num_of_digits <= 3
if num_of_digits >= 3 && (num_of_digits % 3 == 0 || num_of_digits % 3 == 2)
pairs_of_three += 1
num_of_digits -= 3
elsif num_of_digits % 2 == 0 || num_of_digits % 2 == 1
pairs_of_two += 1
num_of_digits -= 2
end
end
end
2nd approach
arr = []
str.chars.each do |elem|
val = elem =~ /\A[-+]?[0-9]*\.?[0-9]+\Z/
arr << elem if val == 0
end
len = arr.length - 1
if arr.length > 4
str = ""
i = 0
while i < len do
if arr[i..i+3].length == 4
str << arr[i..i+2].join + "-"
i += 3
elsif arr[i..i+2].length == 3
str << arr[i..i+1].join + "-"
i += 2
elsif arr[i..i+1].length == 2
str << arr[i..i+1].join
i += 2
elsif !arr[i].nil?
str << arr[i]
i += 1
end
end
puts str
else
if arr.length <= 3
puts arr.join
else
puts arr[0..1].join + "-" + arr[2..3].join
end
end
But none of them is correct
Here is the function invoice_number in python
def invoice_number(invoice):
s = ''.join(x for x in invoice if x <= '9' and x >= '0')
n = len(s)
if n <= 3:
return s
w = ''
i = 0
while i + 3 <= n:
for j in range(0, 3):
w += s[i + j]
i += 3
w += ('-')
m = n - i
if m == 0: return w[:-1]
if m == 1: return w[:m-3] + '-' + s[-2:]
return w + s[i:]
Testing
print(invoice_number('1234567'))
print(invoice_number('12345678'))
print(invoice_number('abc123456789'))
print(invoice_number('1234abc5678xyz9foobar'))
123-45-67
123-456-78
123-456-789
123-456-789
Eliminating non-digits is easy with re. For your format, the key is to figure our the "right" splitting indices.
Here is a try:
import re
def splits(n, k):
idx = [(i, min(n, i+k)) for i in range(0, n, k)]
if len(idx) > 1:
(a, b), (c, d) = idx[-2:]
if d - c < 2:
idx[-2:] = [(a, b - 1), (c - 1, d)]
return idx
def myformat(s):
s = re.sub(r'[^0-9]+', '', s)
parts = [s[a:b] for a, b in splits(len(s), 3)]
return '-'.join(parts)
Tests:
>>> myformat('123')
123
>>> myformat('1234')
12-34
>>> myformat('6')
6
>>> myformat('7839072345')
783-907-23-45
As the question was asked for ruby, adding solution for ruby. (The inspiration of the code is mostly from #yuri answer)
def format_invoice(invoice)
# only numbers are allowed
invoice = invoice.tr("^0-9","")
#puts invoice
return invoice if(invoice.length <= 3)
formatted_invoice = ''
i = 0
# Loop to divide the invoice in group of 3
while i + 3 <= invoice.length do
for j in 0..2 do
formatted_invoice += invoice[i + j]
end
i += 3
formatted_invoice += ('-')
end
m = invoice.length - i
return formatted_invoice[0..-2] if m == 0
return formatted_invoice[0..m-4] + '-' + invoice[-2..-1] if m == 1
return formatted_invoice + invoice[i..-1]
end
Testing
puts format_invoice('abc1') # 1
puts format_invoice('abc123') # 123
puts format_invoice('abc123A4') # 12-34
puts format_invoice('1234567') # 123-45-67
puts format_invoice('12345678') # 123-456-78
puts format_invoice('abc123456789') # 123-456-789
puts format_invoice('1234a#c5678xyz9foobar') # 123-456-789
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)
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
if i run the code, it will stop and not do anything and i am unable to type. seems to be an infinite loop.
the problem seems to be the end until loop, however if i take that out, my condition will not be met.
can anyone find a solution? i have tried all the loops that i can think of.
/. 2d array board ./
board = Array.new(10) { Array.new(10, 0) }
/. printing board ./
if board.count(5) != 5 && board.count(4) != 4 && board.count(3) != 3
for i in 0..9
for j in 0..9
board[i][j] = 0
end
end
aircraftcoord1 = (rand*10).floor
aircraftcoord2 = (rand 6).floor
aircraftalign = rand
if aircraftalign < 0.5
for i in 0..4
board[aircraftcoord2+i][aircraftcoord1] = 5
end
else
for i in 0..4
board[aircraftcoord1][aircraftcoord2+i] = 5
end
end
cruisercoord1 = (rand*10).floor
cruisercoord2 = (rand 7).floor
cruiseralign = rand
if cruiseralign < 0.5
for i in 0..3
board[cruisercoord2+i][cruisercoord1] = 4
end
else
for i in 0..3
board[cruisercoord1][cruisercoord2+i] = 4
end
end
destroyercoord1 = (rand*10).floor
destroyercoord2 = (rand 8).floor
destroyeralign = rand
if destroyeralign < 0.5
for i in 0..2
board[destroyercoord2+i][destroyercoord1] = 3
end
else
for i in 0..2
board[destroyercoord1][destroyercoord2+i] = 3
end
end
end until board.count(5) == 5 && board.count(4) == 4 && board.count(3) == 3
print " "
for i in 0..9
print i
end
puts
for i in 0..9
print i
for j in 0..9
print board[i][j]
end
puts
end
The line board.count(5) == 5 ... will never be true because board is a two-dimensional array. I can't tell what the condition should be, but it could look something like:
board[5].count(5) == 5
So I have this code:
def self.age_to_bucket(age)
age = age.to_i
if age >= 0 && age <= 12
1
elsif age >= 13 && age <= 17
2
elsif age >= 18 && age <= 24
3
elsif age >= 25 && age <= 29
4
elsif age >= 30 && age <= 34
5
elsif age >= 35 && age <= 39
6
elsif age >= 40 && age <= 49
7
elsif age >= 50 && age <= 64
8
elsif age >= 65
9
else
0
end
end
How can I improve this code without losing its readability?
I know I can use #in? with ranges, like this:
if age.in? (0..12)
but #in? is in ActiveSupport, and I'd rather use more independent way.
One way is to use case
result = case age
when 0..12 then 1
when 13..17 then 2
when 18..24 then 3
when 25..29 then 4
-------- so on
else 0
end
Another way would be to eliminate the redundant && in the condition.
if age < 0
0
elsif age < 13
1
elsif age < 18
2
elsif age < 25
3
elsif age < 30
4
elsif age < 35
5
elsif age < 40
6
elsif age < 50
7
elsif age < 65
8
else
9
def self.age_to_bucket age
case age=age.to_i
when 0..12 then 1
when 13..17 then 2
when 18..24 then 3
when 25..29 then 4
when 30..34 then 5
when 35..39 then 6
when 40..49 then 7
when 50..64 then 8
else age >= 65 ? 9 : 0
end
end
You can rewrite if age.in? (0..12) to (0..12).include? age, which is vanilla Ruby.
Just for fun (this is not the efficient way, but for small arrays is just fine):
ranges = [0, 13, 18, 25, 30, 35, 40, 50, 65, Float::INFINITY].each_cons(2).map { |a, b| (a..b) }
n = ranges.map.with_index { |range, idx| idx if range.include?(15) }.compact.first + 1
#=> 2
Note that if the intervals were dynamic you'd have to implement it in a similar fashion.
irb(main):010:0> a = {1 => 0..12, 2 => 13..17} # insert other values here
=> {1=>0..12, 2=>13..17}
irb(main):011:0> age = 16
=> 16
irb(main):012:0> a.keys.find {|k| a[k].include?(age) }
=> 2