Skip to content

Commit

Permalink
Merge pull request #89 from faisalr/Roman
Browse files Browse the repository at this point in the history
Roman Numerals
  • Loading branch information
MehdiK committed Feb 13, 2014
2 parents b6883f1 + c14fdea commit 91d7813
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 0 deletions.
27 changes: 27 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,32 @@ This is kind of mixing `ToWords` with `Ordinalize`. You can call `ToOrdinalWords
121.ToOrdinalWords() => "hundred and twenty first"
```

###Roman numerals
Humanizer can change numbers to Roman numerals using the `ToRoman` extension. The numbers 1 to 10 can be expressed in Roman numerals as follows:

```C#
1.ToRoman() => "I"
2.ToRoman() => "II"
3.ToRoman() => "III"
4.ToRoman() => "IV"
5.ToRoman() => "V"
6.ToRoman() => "VI"
7.ToRoman() => "VII"
8.ToRoman() => "VIII"
9.ToRoman() => "IX"
10.ToRoman() => "X"
```

Also the reverse operation using the `FromRoman` extension.

```C#
"I".FromRoman() => 1
"II".FromRoman() => 2
"III".FromRoman() => 3
"IV".FromRoman() => 4
"V".FromRoman() => 5
```

###Mix this into your framework to simplify your life
This is just a baseline and you can use this to simplify your day to day job. For example, in Asp.Net MVC we keep chucking `Display` attribute on ViewModel properties so `HtmlHelper` can generate correct labels for us; but, just like enums, in vast majority of cases we just need a space between the words in property name - so why not use `"string".Humanize` for that?!

Expand Down Expand Up @@ -468,3 +494,4 @@ Mehdi Khalili ([@MehdiKhalili](http://twitter.com/MehdiKhalili))

###License
Humanizer is released under the MIT License. See the [bundled LICENSE](https://github.com/MehdiK/Humanizer/blob/master/LICENSE) file for details.

1 change: 1 addition & 0 deletions src/Humanizer.Tests/Humanizer.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
<Compile Include="Localisation\es\TimeSpanHumanizeTests.cs" />
<Compile Include="Localisation\pt-BR\DateHumanizeTests.cs" />
<Compile Include="Localisation\pt-BR\TimeSpanHumanizeTests.cs" />
<Compile Include="RomanNumeralTests.cs" />
<Compile Include="RunnableInDebugModeOnlyAttribute.cs" />
<Compile Include="ToQuantityTests.cs" />
<Compile Include="TransformersTests.cs" />
Expand Down
49 changes: 49 additions & 0 deletions src/Humanizer.Tests/RomanNumeralTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using Xunit;
using Xunit.Extensions;
using Humanizer;

namespace Humanizer.Tests
{
public class RomanNumeralTests
{
[Theory]
[InlineData(1, "I")]
[InlineData(2, "II")]
[InlineData(3, "III")]
[InlineData(4, "IV")]
[InlineData(5, "V")]
[InlineData(6, "VI")]
[InlineData(7, "VII")]
[InlineData(8, "VIII")]
[InlineData(9, "IX")]
[InlineData(10, "X")]
[InlineData(11, "XI")]
[InlineData(12, "XII")]
[InlineData(100, "C")]
[InlineData(3999, "MMMCMXCIX")]
public void CanRomanize(int input, string expected)
{
Assert.Equal(expected, input.ToRoman());
}

[Theory]
[InlineData(1, "I")]
[InlineData(2, "II")]
[InlineData(3, "III")]
[InlineData(4, "IV")]
[InlineData(5, "V")]
[InlineData(6, "VI")]
[InlineData(7, "VII")]
[InlineData(8, "VIII")]
[InlineData(9, "IX")]
[InlineData(10, "X")]
[InlineData(11, "XI")]
[InlineData(12, "XII")]
[InlineData(100, "C")]
[InlineData(3999, "MMMCMXCIX")]
public void CanUnromanize(int expected, string input)
{
Assert.Equal(expected, input.FromRoman());
}
}
}
1 change: 1 addition & 0 deletions src/Humanizer/Humanizer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
<DependentUpon>On.Days.tt</DependentUpon>
</Compile>
<Compile Include="Localisation\ArabicFormatter.cs" />
<Compile Include="RomanNumeralExtensions.cs" />
<Compile Include="ToQuantityExtensions.cs" />
<Compile Include="Transformer\To.cs" />
<Compile Include="Transformer\IStringTransformer.cs" />
Expand Down
104 changes: 104 additions & 0 deletions src/Humanizer/RomanNumeralExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

namespace Humanizer
{
public static class RomanNumeralExtensions
{
private const int NumberOfRomanNumeralMaps = 13;

private static readonly Dictionary<string, int> romanNumerals = new Dictionary<string, int>(NumberOfRomanNumeralMaps)
{
{ "M", 1000 },
{ "CM", 900 },
{ "D", 500 },
{ "CD", 400 },
{ "C", 100 },
{ "XC", 90 },
{ "L", 50 },
{ "XL", 40 },
{ "X", 10 },
{ "IX", 9 },
{ "V", 5 },
{ "IV", 4 },
{ "I", 1 }
};

private static readonly Regex validRomanNumeral = new Regex("^(?i:(?=[MDCLXVI])((M{0,3})((C[DM])|(D?C{0,3}))" + "?((X[LC])|(L?XX{0,2})|L)?((I[VX])|(V?(II{0,2}))|V)?))$", RegexOptions.None);

/// <summary>
/// Converts Roman numbers into integer
/// </summary>
/// <param name="value">Roman number</param>
/// <returns>Human-readable number</returns>
public static int FromRoman(this string value)
{
if (value == null)
throw new ArgumentNullException("value");

value = value.ToUpper().Trim();
var length = value.Length;

if (length == 0 || IsInvalidRomanNumeral(value))
throw new ArgumentException("Empty or invalid Roman numeral string.", "value");

var total = 0;
var i = length;

while (i > 0)
{
var digit = romanNumerals[value[--i].ToString()];

if (i > 0)
{
var previousDigit = romanNumerals[value[i - 1].ToString()];

if (previousDigit < digit)
{
digit -= previousDigit;
i--;
}
}

total += digit;
}

return total;
}

/// <summary>
/// Converts the input to Roman number
/// </summary>
/// <param name="value">Human-readable number</param>
/// <returns>Roman number</returns>
public static string ToRoman(this int value)
{
const int MinValue = 1;
const int MaxValue = 3999;
const int MaxRomanNumeralLength = 15;

if ((value < MinValue) || (value > MaxValue))
throw new ArgumentOutOfRangeException();

var sb = new StringBuilder(MaxRomanNumeralLength);

foreach (var pair in romanNumerals)
{
while (value / pair.Value > 0)
{
sb.Append(pair.Key);
value -= pair.Value;
}
}

return sb.ToString();
}

private static bool IsInvalidRomanNumeral(string value)
{
return !validRomanNumeral.IsMatch(value);
}
}
}

0 comments on commit 91d7813

Please sign in to comment.