All Downloads are FREE. Search and download functionalities are using the official Maven repository.

laika.ast.RomanNumerals.scala Maven / Gradle / Ivy

/*
 * Copyright 2013-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package laika.ast

/** Converts Roman numerals to integers and vice versa.
 *  Since there never have been universally accepted rules for Roman numerals,
 *  the conversion functions do not apply strict error checking, so some unusual or 
 *  illegal constructs may be supported, too. They do not prevent using the same
 *  symbol more than three times in a row for example. 
 * 
 *  @author Jens Halm
 */
object RomanNumerals {

  private case class Symbol (roman: String, value: Int, repeatable: Boolean = false)
  
  private val symbols = List(
    Symbol("M", 1000, true), 
    Symbol("CM", 900), 
    Symbol("D", 500), 
    Symbol("CD", 400), 
    Symbol("C", 100, true), 
    Symbol("XC", 90), 
    Symbol("L", 50), 
    Symbol("XL", 40), 
    Symbol("X", 10, true), 
    Symbol("IX", 9), 
    Symbol("V", 5), 
    Symbol("IV", 4), 
    Symbol("I", 1, true)
  ) 
  
  /** Converts from an integer to Roman numerals.
   *  The integer has to be between 1 and 4999.
   */
  def intToRoman (value: Int): String = {
    require(value > 0 && value < 5000, "Number must be between 1 and 4999") 
    
    symbols.foldLeft((value, "")) {
      case ((remaining, result), symbol) => 
        val occurrences = remaining / symbol.value
        (remaining - occurrences * symbol.value, result + symbol.roman * occurrences)
    }._2 
  }
  
  /** Converts from Roman numerals to integer.
   */
  def romanToInt (roman: String): Int = {
    
    def convert (roman: String, lastSymbol: Symbol): Int = {
      symbols.filter(roman startsWith _.roman).sortBy(-_.roman.length) match {
        case (s @ Symbol(romanSymbol, value, repeatable)) :: _ =>
          if (s == lastSymbol && !repeatable) 
            throw new IllegalArgumentException(s"Symbol $romanSymbol cannot be repeated")
          else if (value > lastSymbol.value) 
            throw new IllegalArgumentException(s"Illegal ordering of symbols: ${lastSymbol.roman}$romanSymbol")
          value + convert(roman.substring(romanSymbol.length), s)
        case Nil if roman.isEmpty => 0
        case Nil => throw new IllegalArgumentException(s"Illegal Roman Numeral: $roman")
      }
    }
    
    convert(roman, symbols.head)
  }        
  
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy