3
\$\begingroup\$

I recently wrote this up and am wondering if there is a better way to achieve the results I want.

Essentially, we're taking a given year and adding 'th, nd, rd, st' depending on the digit.

Any suggestions on how to shorten this code and make it better?

Keep in mind that this is taking centuries that change every 01st year. For example, year 2000 is the 20th century, but 2001 is the 21st century.

def which_century(string)
  year = string.to_i
  modifier = 1

  return "1st century" if year < 101

  if year > 100 && string.end_with?("00")
    modifier -= 1
  end

  century = year / 100 + modifier
  p century
  if century > 10 && century < 20 
    return century.to_s << "th century."
  end

  second_digit = century % 10

  case second_digit
  when 1 then century.to_s << "st century"
  when 2 then century.to_s << "nd century"
  when 3 then century.to_s << "rd century"
  else
    century.to_s << "th century"
  end
end

puts "Type in a year:"
answer = gets.chomp
p which_century(answer)
\$\endgroup\$

3 Answers 3

3
\$\begingroup\$

Let's clarify the logic. The code simplification will follow. The rules are:

  1. "th" years are those ending in 4, 5, 6, 7, 8, 9, or 0. Additionally, the 11th, 12th, and 13th centuries are exceptions also ending in "th".
  2. Years ending in 1 are "st" years.
  3. Years ending in 2 are "nd" years.
  4. Years ending in 3 are "rd" years.

Now just translate the above into code:

def which_century(string)
  century  = (string.to_f / 100).ceil
  year     = century % 10
  nth_year = (11..13).include?(century) || !(1..3).include?(year)
  return "#{century}th century" if nth_year
  return "#{century}st century" if year == 1
  return "#{century}nd century" if year == 2
  return "#{century}rd century" if year == 3
end
\$\endgroup\$
0
2
\$\begingroup\$

I would be more formulaic regarding the century calculation:

century = (year - 1) / 100 + 1

Also, consider using .ordinalize from ActiveSupport (via require 'active_support/core_ext/integer' - you can require more of ActiveSupport but this keeps it minimal)

Then your code gets much shorter:

def which_century(year)
  century = (year.to_i - 1)/100 + 1
  century.ordinalize + " century"
end

Test cases:

> which_century(2001)
=> "21st century"
> which_century(2000)
=> "20th century"
> which_century(1999)
=> "20th century"
> which_century(100)
=> "1st century"
> which_century(101)
=> "2nd century"
> which_century(99)
=> "1st century"
\$\endgroup\$
0
\$\begingroup\$

I wonder if it would be more intuitive if, instead of dividing by 100 to get a century, and using second_digit, you instead used ranges.

For example:

year.between?(1001,2000)

You do still need century for the final result, but you use that logic quite a bit so maybe define a method for that ...

def year_to_century_string(year, ordinal)
  (year/100+1).to_s + ordinal + " century"
end

The calculation of the century for a year might be a separate method also.

If you wanted to be a bit more object-ey, you could define a class Year that encapsulates these methods, so you can:

Year.new(2001).century

... and ...

Year.new(2001).century_string

In other words, name the concept that the string/number represents, and add behaviour to it that is specific to that concept.

The method, as it stands, does quite a lot of processing and names a few concepts that it does not expose (e.g. century) -- these can often be clues that the method should be converted to a class with its own behaviour.

You could also define a refinement on FixNum that would allow you to ...

1995.to_year.ordinalised_century
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.