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
Related
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
I have this greedy algorithm :
banknotes = { 5 => 6, 10 => 7, 20 => 8, 50 => 9 }
amount = 335
a = CardCalculation.new(banknotes: banknotes)
a.greedy_alg( amount: amount )
class CardCalculation
def greedy_alg( amount )
result = {}
banknotes.each do | face, value |
needed_value = amount - hash_summator( result )
quantity = ( needed_value / face )
quantity = value if quantity > value
result[face] = quantity
end
result
end
def hash_summator(hash)
hash.inject(0){|memo, (k, v)| memo += k * v}
end
and in a result I see
result = { 50 => 6, 20 => 1, 10 => 1, 5 => 1 }
how you can see, I took 335 (amount:), went through the hash and selected the maximum from each key and its value.
but now I need ‘spreading’ algorithm, I need this result, for example:
result = { 50 => 4, 20 => 4, 10 => 4, 5 => 3}
I need the middle number of each key .. I have thoughts about the loop but maybe someone has ideas?
This solution could be a starting point, I didn't test for avoiding infinite loop:
banknotes = { 5 => 6, 10 => 7, 20 => 8, 50 => 9 }
amount = 335
min = banknotes.keys.min
init_avg = amount / banknotes.keys.sum # calculate the initial average
result = banknotes.transform_values { init_avg } # initialize with average
delta = amount - result.sum { |k,v| k * v }
if delta > 0 # start distribution
loop do
banknotes.keys.reverse_each do |val|
if delta - val >= 0 and result[val] + 1 < banknotes[val]
delta -= val
result[val] += 1
end
end
break if delta < min
end
end
result
#=> {5=>3, 10=>4, 20=>4, 50=>4}
finished version
banknotes = {5 => 10, 10 => 10, 20 => 10, 50 => 10, 100 => 5}
amount = 435
def spreaded_alg(amount)
validate_money!(amount: amount)
min = banknotes.keys.min
avg = amount / banknotes.keys.sum.round(0)
result = banknotes.transform_values {avg}
delta = amount - result.sum {|k, v| k * v}
if delta > 0
loop do
banknotes.keys.each do |value|
if delta - value >= 0 && result[value] > banknotes[value]
delta += value
result[value] -= 1
end
if delta - value >= 0 && result[value] + 1 <= banknotes[value]
delta -= value
result[value] += 1
end
end
break if delta < min
end
end
result
end
This instructions are as follow:
NUMBER CRUNCHER
Write a method that takes a number as an argument
If the number is greater than 20
count down from the number by 2's
If the number is less than 20
count down from the number by 1's
Display the numbers as they count down to 0.
I have written this, but it's not doing what it's supposed. Any help?
def num_cruncher(num)
count = num
until count == 0 do
if num > 20
puts count - 2
else
puts "#{count}"
end
count -= 1
end
end
Here's your code, with as few changes as possible :
def num_cruncher(num)
count = num
until count < 0 do
puts count
if num > 20
count -= 2
else
count -= 1
end
end
end
num_cruncher(10)
# 10
# 9
# 8
# 7
# 6
# 5
# 4
# 3
# 2
# 1
# 0
num_cruncher(21)
# 21
# 19
# 17
# 15
# 13
# 11
# 9
# 7
# 5
# 3
# 1
By extracting the if-statement outside of the loop, the code becomes a bit shorter :
def num_cruncher(num)
if num > 20
step = 2
else
step = 1
end
until num < 0 do
puts num
num -= step
end
end
You can use Numeric#step here. Something like this:
def num_cruncher n
s = n > 20 ? -2 : -1
n.step(by: s, to: 0).entries
end
num_cruncher 23
#=> [23, 21, 19, 17, 15, 13, 11, 9, 7, 5, 3, 1]
I'm a beginner and wrote a script for the following question below in ruby. I read that repetition isn't recommended and would like to reduce the repetition of if, elsif, else statements but can't seem to find a way.
Old-school Roman numerals. In the early days of Roman numer- als, the Romans didn’t bother with any of this new-fangled sub- traction “IX” nonsense. No sir, it was straight addition, biggest to littlest—so 9 was written “VIIII,” and so on. Write a method that when passed an integer between 1 and 3000 (or so) returns a string containing the proper old-school Roman numeral. In other words, old_roman_numeral 4 should return 'IIII'. Make sure to test your method on a bunch of different numbers. Hint: Use the inte- ger division and modulus methods on page 37.
For reference, these are the values of the letters used:
I =1 V=5 X=10 L=50 C=100 D=500 M=1000
Here is my script...
puts "What is your number?"
n = gets.chomp
num = n.to_i
number = ""
l = n.length
i = 0
while true
if num > 3000
puts "Enter another number."
elsif l == 0
break
else
if l == 4
number += "M" * n[i].to_i
l -= 1
i += 1
elsif l == 3
if 1 <= n[i].to_i && n[i].to_i <= 4
number += "C" * n[i].to_i
elsif n[i].to_i == 5
number += "D"
elsif 6 <= n[i].to_i && n[i].to_i <= 9
number += "D" + "C" * (n[i].to_i - 5)
end
l -= 1
i += 1
elsif l == 2
if 1 <= n[i].to_i && n[i].to_i <= 4
number += "X" * n[i].to_i
elsif n[i].to_i == 5
number += "L"
elsif 6 <= n[i].to_i && n[i].to_i <= 9
number += "L" + "X" * (n[i].to_i - 5)
end
l -= 1
i += 1
else
if 1 <= n[i].to_i && n[i].to_i <= 4
number += "I" * n[i].to_i
elsif n[i].to_i == 5
number += "V"
elsif 6 <= n[i].to_i && n[i].to_i <= 9
number += "V" + "I" * (n[i].to_i - 5)
end
l -= 1
i += 1
end
end
end
This doesn't use integer division or modulus, but it might be instructive.
puts "What is your number?"
input = gets.to_i
numerals = {
1000 => "M",
500 => "D",
100 => "C",
50 => "L",
10 => "X",
5 => "V",
1 => "I"
}
digits = []
numerals.each do |n, digit|
while input >= n
digits << digit
input = input - n
end
end
puts digits.join
Another way, that builds a string, as #sawa suggested, rather than constructing an array and then using join:
numerals = {
1000 => "M",
500 => "D",
100 => "C",
50 => "L",
10 => "X",
5 => "V",
1 => "I"
}
input = 9658
numerals.each_with_object('') do |(n, digit),str|
nbr, input = input.divmod(n)
str << digit*nbr
end
#=> "MMMMMMMMMDCLVIII"
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 9 years ago.
Improve this question
I'm working on a Ruby exercise taking a number and returning the word equivalent. Below is code for a function with an if statement I'm working on. I've puts different strings to help me trace where my errors are.
The first three conditionals seem to function as I've intended them to. However, the conditional that returns 'puts The teens translator works' is somehow true despite it not meeting the conditions of the numbers I pass in.
I've included some of the numbers I've been passing to illustrate what I mean. My intention is for the condition to be true if the number is greater than 13 but less than 20, not evenly divisible by 10, and not 11, 12, 13 or 15. If I pass in 41 for example, it appears the condition is true because "The teens translator works" prints. I feel like I'm missing something simple.
Thanks in advance for your help!
def in_english
if #number == 11 || #number == 12 || #number == 13 || #number == 15
puts "The number is 11, 12, 13 or 15"
elsif (#number >0 && #number < 10)
puts "Hello I made it hear"
elsif (#number >= 10 && #number < 100 && #number%10 == 0)
puts "The number is greater than equal 10 & < 100 and divisible by 10"
elsif ((#number > 13 && #number < 20 ) && #number%10 != 0 && #number != 11 || #number != 12 || #number != 13 || #number != 15)
puts "The teens translator works"
elsif (#number > 20)
puts "Over twenty_trans"
else
puts "End of if block"
end
end
Say.new(47).in_english
Say.new(41).in_english
Say.new(52).in_english
Instead of this:
if #number == 11 || #number == 12 || #number == 13 || #number == 15
...you can write this:
if [11, 12, 13, 15].include? #number
And, instead of this mess:
elsif ((#number > 13 && #number < 20 ) && #number%10 != 0 && #number != 11 || #number != 12 || #number != 13 || #number != 15)
...you can write this:
elsif [14, 16, 17, 18, 19].include? #number
...or this:
elsif #number == 14 or (16..19).include? #number
My intention is for the condition to be true if the number is greater
than 13 but less than 20, not evenly divisible by 10,
What numbers between 13 and 20 are evenly divisible by 10?
First of all, if the number is more than 13 and less then 20, it can't be 11, 12 and 13 anyway (it's already more than 13). So, you don't have to check.
Second problem are Parentheses.
This:
((#number > 13 && #number < 20 ) && #number%10 != 0 && #number != 11 || #number != 12 || #number != 13 || #number != 15)
will be avaluated following way:
(#number > 13 && #number < 20 ) && #number%10 != 0 && #number != 11
OR
#number != 12
OR
#number != 13
OR
#number != 15
It checks 41 for a first condition and it will be false. However, as soon as it checks it for second condition 47 != 12 is true. So, it will show teens translator.