Last active
January 4, 2016 16:49
-
-
Save ceottaki/8650187 to your computer and use it in GitHub Desktop.
An utility class that handles numbers written in Roman numerals, with methods for converting Hindu-Arabic numbers into Roman numbers and vice-versa.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ---------------------------------------------------------------------------------------------------------------------------- | |
// <copyright file="RomanizerUtility.cs" company="Felipe Ceotto">Copyright (c) Felipe Ceotto. All rights reserved.</copyright> | |
// | |
// This file is a standalone utility and was created as part of an exercise. | |
// | |
// Although it is not a 'software' in itself, it is freely provided and you can redistribute it and/or modify it under the | |
// terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, | |
// or (at your option) any later version. | |
// | |
// This utility is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the | |
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more | |
// details. | |
// | |
// Please refer to http://www.gnu.org/licenses/ for a copy of the GNU General Public License. | |
// ---------------------------------------------------------------------------------------------------------------------------- | |
namespace Romanizer | |
{ | |
using System; | |
using System.Collections.Generic; | |
using System.Globalization; | |
using System.Linq; | |
/// <summary> | |
/// Represents an utility that handles numbers written in Roman numerals. | |
/// </summary> | |
public static class RomanizerUtility | |
{ | |
/// <summary> | |
/// Gets the Roman number equivalent to a given decimal number in Hindu-Arabic format. | |
/// </summary> | |
/// <param name="number">The decimal number in Hindu-Arabic format.</param> | |
/// <returns>The Roman number equivalent to the given <paramref name="number"/>.</returns> | |
/// <remarks> | |
/// <para>Negative numbers are presented with a minus in front, e.g. -2 => -II.</para> | |
/// <para>Zero returns an empty string as there is no representation for zero in classic Roman numerals.</para> | |
/// </remarks> | |
public static string GetRomanNumber(int number) | |
{ | |
// Short-circuit exit clause, if number is zero returns an empty string. | |
if (number == 0) | |
{ | |
return string.Empty; | |
} | |
// Initialises the result as an empty string and gets the dictionary of roman numerals. | |
string result = string.Empty; | |
IDictionary<int, string> romanNumbers = GetDictionaryOfRomanNumerals(true); | |
// Checks if given number is negative and adds a minus to the start of result if it is... | |
// ...then inverts the number to guarantee that we are always working with positive numbers after here. | |
if (number < 0) | |
{ | |
result += "-"; | |
number = number * -1; | |
} | |
// Runs through the number dividing it by the biggest key in the dictionary that is not bigger than the number itself. | |
// The result of the division is the number of characters equivalent to that key to add to the Roman number, which are added to the result. | |
// The number is then reduced by that number of characters times the value of the key, which is the value already added to the Roman number. | |
while (number > 0) | |
{ | |
int currentDivisor = romanNumbers.Keys.Where(n => n <= number).Max(); | |
int numberOfCharacters = number / currentDivisor; | |
for (int i = 0; i < numberOfCharacters; i++) | |
{ | |
result += romanNumbers[currentDivisor]; | |
} | |
number -= currentDivisor * numberOfCharacters; | |
} | |
return result; | |
} | |
/// <summary> | |
/// Gets the Hindu-Arabic number equivalent to a given number in Roman format. | |
/// </summary> | |
/// <param name="romanNumber">The roman number.</param> | |
/// <returns>The Hindu-Arabic number equivalent to the given <paramref name="romanNumber"/>.</returns> | |
/// <remarks> | |
/// <para>Negative numbers are presented with a minus in front, e.g. -II => -2.</para> | |
/// <para>Invalid Roman numbers such as ones with invalid characters or characters in an incorrect order will return zero. Empty or <c>null</c> strings will also return zero.</para> | |
/// </remarks> | |
public static int GetHinduArabicNumber(string romanNumber) | |
{ | |
// Short-circuit exit clause, if Roman number is an null or an empty string, returns zero. | |
if (string.IsNullOrWhiteSpace(romanNumber)) | |
{ | |
return 0; | |
} | |
// Initialises the result as zero and gets the dictionary of roman numerals. | |
int result = 0; | |
IDictionary<int, string> romanNumbers = GetDictionaryOfRomanNumerals(true); | |
// Checks if roman number starts with a negative sign and sets the multiplier accordingly, removing the negative sign from the number if present. | |
// This allows the algorithm to work only with positive numbers from here onwards. | |
int multiplier = 1; | |
if (romanNumber.StartsWith("-", StringComparison.OrdinalIgnoreCase)) | |
{ | |
multiplier = -1; | |
romanNumber = romanNumber.Substring(1); | |
} | |
// Checks if the given roman number contains any characters that are not part of the roman numerals, in which case returns zero. | |
if (romanNumber.Any(c => romanNumbers.Values.All(v => v != c.ToString(CultureInfo.InvariantCulture)))) | |
{ | |
return 0; | |
} | |
// Runs through each character of the roman number, processing each one individually. | |
int subtractor = 0; | |
for (int i = 1; i < romanNumber.Length + 1; i++) | |
{ | |
// Gets the decimal value of the current character. | |
int currentCharValue = romanNumbers.Where(n => n.Value == romanNumber[i - 1].ToString()).First().Key; | |
// Gets the decimal value of the character next to the current character if there is one; zero otherwise. | |
int nextCharValue = i < romanNumber.Length ? romanNumbers.Where(n => n.Value == romanNumber[i].ToString()).First().Key : 0; | |
// Checks if current character has a smaller value than the next character... | |
if (currentCharValue < nextCharValue) | |
{ | |
// ... in which case checks if this combination of characters is a valid one, to avoid cases like XM or IC, and returns zero if the combination is invalid. | |
if (romanNumbers.All(n => n.Value != string.Concat(romanNumber[i - 1], romanNumber[i]))) | |
{ | |
return 0; | |
} | |
// If the combination is valid, the value of the current character is set as the subtractor for when the next character is processed. | |
subtractor = currentCharValue; | |
} | |
else | |
{ | |
// If current character has the same or higher value than its next character then simply adds that value to the result. | |
// The subtractor is taken into consideration when adding the value and then it is reset to zero. | |
result += currentCharValue - subtractor; | |
subtractor = 0; | |
} | |
} | |
// Applies the multiplier. | |
result *= multiplier; | |
return result; | |
} | |
/// <summary> | |
/// Gets the dictionary of roman numerals. | |
/// </summary> | |
/// <param name="includeSubtractions">If set to <c>true</c>, include subtractions in the dictionary such as 4 => IV.</param> | |
/// <returns> | |
/// A dictionary of roman numerals with the key being the equivalent Hindu-Arabic numeral and the value being the roman numeral. | |
/// </returns> | |
/// <example> | |
/// A call to this function returns exactly this (with items in <i>italic</i> returned only if <paramref name="includeSubtractions" /> is <c>true</c>): | |
/// <table> | |
/// <thead><tr><th>Key</th><th>Value</th></tr></thead> | |
/// <tbody> | |
/// <tr><td>1</td><td>I</td></tr> | |
/// <tr><td><i>4</i></td><td><i>IV</i></td></tr> | |
/// <tr><td>5</td><td>V</td></tr> | |
/// <tr><td><i>9</i></td><td><i>IX</i></td></tr> | |
/// <tr><td>10</td><td>X</td></tr> | |
/// <tr><td><i>40</i></td><td><i>XL</i></td></tr> | |
/// <tr><td>50</td><td>L</td></tr> | |
/// <tr><td><i>90</i></td><td><i>XC</i></td></tr> | |
/// <tr><td>100</td><td>C</td></tr> | |
/// <tr><td><i>400</i></td><td><i>CD</i></td></tr> | |
/// <tr><td>500</td><td>D</td></tr> | |
/// <tr><td><i>900</i></td><td><i>CM</i></td></tr> | |
/// <tr><td>1000</td><td>M</td></tr> | |
/// </tbody> | |
/// </table> | |
/// </example> | |
private static IDictionary<int, string> GetDictionaryOfRomanNumerals(bool includeSubtractions) | |
{ | |
SortedDictionary<int, string> romanNumbers = new SortedDictionary<int, string>(); | |
romanNumbers.Add(1, "I"); | |
romanNumbers.Add(5, "V"); | |
romanNumbers.Add(10, "X"); | |
romanNumbers.Add(50, "L"); | |
romanNumbers.Add(100, "C"); | |
romanNumbers.Add(500, "D"); | |
romanNumbers.Add(1000, "M"); | |
if (includeSubtractions) | |
{ | |
romanNumbers.Add(4, "IV"); | |
romanNumbers.Add(9, "IX"); | |
romanNumbers.Add(40, "XL"); | |
romanNumbers.Add(90, "XC"); | |
romanNumbers.Add(400, "CD"); | |
romanNumbers.Add(900, "CM"); | |
} | |
return new Dictionary<int, string>(romanNumbers); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment