class RomanNumbers::RomanNumber

Constants

MAX_ALLOWED_REPETITION

based on above non_repeatable units, allowed repetition is 3

ROMAN_DOUBLE_UNITS
ROMAN_NON_REPEATABLE_UNITS
ROMAN_NON_REPEATABLE_UNITS_1
ROMAN_NON_REPEATABLE_UNITS_2
ROMAN_REPEATABLE_UNITS
ROMAN_SINGLE_UNITS
ROMAN_UNITS

Attributes

input_integer[RW]
input_string[RW]
output_integer[RW]
output_roman[RW]
staged_roman_hash[RW]

Public Class Methods

new(input) click to toggle source

initializing element

# File lib/roman_numbers/roman_number.rb, line 37
def initialize(input)
  # checking input type and setting instance variables
  case input
    when Integer
      @input_integer = input
      @staged_roman_hash = Array.new
      @output_roman = String.new
    when String
      @input_string = input.upcase
      @output_integer = 0
  end
end

Public Instance Methods

convert_decimal_to_roman(passed_integer=input_integer) click to toggle source

converts arabic to roman

# File lib/roman_numbers/roman_number.rb, line 53
def convert_decimal_to_roman(passed_integer=input_integer)
  # validating input
  unless (1..3999).include? passed_integer
    raise InvalidInputError, "Invalid Input: #{passed_integer}"
  end
  # getting staged roman hash
  calculate_staged_roman_hash(passed_integer)
  # extracting hash from staged roman hash
  staged_roman_hash.each do |element|
    output_roman << (element[:largest_element][:unit].to_s)*(element[:times])
  end
  output_roman
end
convert_roman_to_decimal(passed_roman=input_string.clone) click to toggle source

converts given arabic number (in string form) to corresponding integer

# File lib/roman_numbers/roman_number.rb, line 68
def convert_roman_to_decimal(passed_roman=input_string.clone)
  # generating regex expressions
  double_units_array = ROMAN_NON_REPEATABLE_UNITS_2.map { |element| ('^' + element[:unit].to_s) }
  single_units_array = (ROMAN_REPEATABLE_UNITS + ROMAN_NON_REPEATABLE_UNITS_1).map { |element| ('^' + element[:unit].to_s) }
  double_units_regex = Regexp.new(double_units_array.join('|'))
  single_units_regex = Regexp.new(single_units_array.join('|'))
  # validation
  # TODO: to add more validations
  if passed_roman =~ /(.)\1{#{MAX_ALLOWED_REPETITION},}/
    raise InvalidInputError, "Invalid Input: #{passed_roman}"
  end
  # processing
  if passed_roman.length > 0
    if unit = passed_roman.slice!(double_units_regex)
      self.output_integer += ROMAN_DOUBLE_UNITS.find { |element| element[:unit] == unit.to_sym }[:value]
      convert_roman_to_decimal(passed_roman)
    elsif unit = passed_roman.slice!(single_units_regex)
      self.output_integer += ROMAN_SINGLE_UNITS.find { |element| element[:unit] == unit.to_sym }[:value]
      convert_roman_to_decimal(passed_roman)
    else
      # invalid input
      raise InvalidInputError, "Invalid Input: #{passed_roman}"
    end
  else
    # process is complete
    self.output_integer
  end
end

Private Instance Methods

calculate_staged_roman_hash(passed_input_integer) click to toggle source

returns an array of hashed containing info on desired output roman

# File lib/roman_numbers/roman_number.rb, line 102
def calculate_staged_roman_hash(passed_input_integer)
  begin
    temp_hash = largest_repeatable_element(passed_input_integer)
  rescue StartsWithNonRepeatableRomanUnitError => ex
    temp_hash = largest_non_repeatable_element(passed_input_integer)
  end
  if temp_hash
    staged_roman_hash << temp_hash
    passed_input_integer = temp_hash[:reduced_integer]
    calculate_staged_roman_hash(passed_input_integer)
  else
    # processing done
    staged_roman_hash
  end
end
largest_non_repeatable_element(passed_input_integer) click to toggle source

returns largest non-repeatable element

# File lib/roman_numbers/roman_number.rb, line 147
def largest_non_repeatable_element(passed_input_integer)
  if passed_input_integer > 0
    largest_element = ROMAN_NON_REPEATABLE_UNITS.find { |element| passed_input_integer >= element[:value] }
    # TODO: make it efficient by removing elements before largest_element
    # TODO: to use binary search instead
    if largest_element
      reduced_integer = passed_input_integer%largest_element[:value]
      {:reduced_integer => reduced_integer, :largest_element => largest_element, :times => 1}
    else
      # no non_repeatable element preset, but process is not complete yet
      {:reduced_integer => passed_input_integer, :largest_element => nil, :times => 0}
    end
  elsif passed_input_integer == 0
    # process completed
    nil
  else
    # non-reachable code
    # passed_input_integer has to be >=0
    raise NonReachableCodeError, 'ReceivedNegativeInteger'
  end
end
largest_repeatable_element(passed_input_integer) click to toggle source

returns reduced_integer, largest repeatable element, and number of times it can be repeated

# File lib/roman_numbers/roman_number.rb, line 119
def largest_repeatable_element(passed_input_integer)
  if passed_input_integer > 0
    largest_element = ROMAN_REPEATABLE_UNITS.find { |element| passed_input_integer >= element[:value] }
    # TODO: make it efficient by removing elements before largest_element
    # TODO: to use binary search instead
    if largest_element
      times = passed_input_integer/largest_element[:value]
      reduced_integer = passed_input_integer%largest_element[:value]
      if times > MAX_ALLOWED_REPETITION
        # given integer starts with non_repeatable roman unit
        raise StartsWithNonRepeatableRomanUnitError
      end
      {:reduced_integer => reduced_integer, :largest_element => largest_element, :times => times}
    else
      # non-reachable code
      raise NonReachableCodeError, 'LargestElementIsNil'
    end
  elsif passed_input_integer == 0
    # process completed
    nil
  else
    # non-reachable code
    # passed_input_integer has to be >=0
    raise NonReachableCodeError, 'ReceivedNegativeInteger'
  end
end