认为这个问题很有趣,这是我的看法。
它应该(希望)处理不超过划线字符上限的数字。添加任何其他约定应该只是配置新频段和调整ConfigureNext
链的问题。
数字生成器.cs
public static class NumeralGenerator
{
private static readonly INumeralBand RootNumeralBand = ConfigureMapping();
private static INumeralBand ConfigureMapping()
{
var unitBand = new FinalBand(1, "I");
var fiveBand = new NumeralBand(5, "V", unitBand);
var tenBand = new NumeralBand(10, "X", unitBand);
var fiftyBand = new NumeralBand(50, "L", tenBand);
var hundredBand = new NumeralBand(100, "C", tenBand);
var fiveHundredBand = new NumeralBand(500, "D", hundredBand);
var thousandBand = new NumeralBand(1000, "M", hundredBand);
var thousandUnitBand = new NumeralBand(1000, "I\u0305", thousandBand);
var fiveThousandBand = new NumeralBand(5000, "V\u0305", thousandUnitBand);
var tenThousandBand = new NumeralBand(10000, "X\u0305", thousandUnitBand);
var fiftyThousandBand = new NumeralBand(50000, "L\u0305", tenThousandBand);
var hundredThousandBand = new NumeralBand(100000, "C\u0305", tenThousandBand);
var fiveHundredThousandBand = new NumeralBand(500000, "D\u0305", hundredThousandBand);
var millionBand = new NumeralBand(1000000, "M\u0305", hundredThousandBand);
millionBand
.ConfigureNext(fiveHundredThousandBand)
.ConfigureNext(hundredThousandBand)
.ConfigureNext(fiftyThousandBand)
.ConfigureNext(tenThousandBand)
.ConfigureNext(fiveThousandBand)
.ConfigureNext(thousandBand)
.ConfigureNext(fiveHundredBand)
.ConfigureNext(hundredBand)
.ConfigureNext(fiftyBand)
.ConfigureNext(tenBand)
.ConfigureNext(fiveBand)
.ConfigureNext(unitBand);
return millionBand;
}
public static string ToNumeral(int number)
{
var numerals = new StringBuilder();
RootNumeralBand.Process(number, numerals);
return numerals.ToString();
}
}
InumeralBand.cs
public interface INumeralBand
{
int Value { get; }
string Numeral { get; }
void Process(int number, StringBuilder numerals);
}
数字带.cs
public class NumeralBand : INumeralBand
{
private readonly INumeralBand _negatedBy;
private INumeralBand _nextBand;
public NumeralBand(int value, string numeral, INumeralBand negatedBy)
{
_negatedBy = negatedBy;
Value = value;
Numeral = numeral;
}
public int Value { get; }
public string Numeral { get; }
public void Process(int number, StringBuilder numerals)
{
if (ShouldNegateAndStop(number))
{
numerals.Append(NegatedNumeral);
return;
}
var numeralCount = Math.Abs(number / Value);
var remainder = number % Value;
numerals.Append(string.Concat(Enumerable.Range(1, numeralCount).Select(x => Numeral)));
if (ShouldNegateAndContinue(remainder))
{
NegateAndContinue(numerals, remainder);
return;
}
if (remainder > 0)
_nextBand.Process(remainder, numerals);
}
private string NegatedNumeral => $"{_negatedBy.Numeral}{Numeral}";
private bool ShouldNegateAndStop(int number) => number == Value - _negatedBy.Value;
private bool ShouldNegateAndContinue(int number) => number >= Value - _negatedBy.Value;
private void NegateAndContinue(StringBuilder stringBuilder, int remainder)
{
stringBuilder.Append(NegatedNumeral);
remainder = remainder % (Value - _negatedBy.Value);
_nextBand.Process(remainder, stringBuilder);
}
public T ConfigureNext<T>(T nextBand) where T : INumeralBand
{
_nextBand = nextBand;
return nextBand;
}
}
FinalBand.cs
public class FinalBand : INumeralBand
{
public FinalBand(int value, string numeral)
{
Value = value;
Numeral = numeral;
}
public int Value { get; }
public string Numeral { get; }
public void Process(int number, StringBuilder numerals)
{
numerals.Append(new string(Numeral[0], number));
}
}
测试:
FinalBandTests.cs
public class FinalBandTests
{
[Theory]
[InlineData(1, "I")]
[InlineData(2, "II")]
[InlineData(3, "III")]
[InlineData(4, "IIII")]
public void Process(int number, string expected)
{
var stringBuilder = new StringBuilder();
var numeralBand = new FinalBand(1, "I");
numeralBand.Process(number, stringBuilder);
Assert.Equal(expected, stringBuilder.ToString());
}
}
NumeralBandTests.cs
public class NumeralBandTests
{
private Mock<INumeralBand> _nextBand;
private Mock<INumeralBand> _negatedBy;
private StringBuilder _stringBuilder;
public NumeralBandTests()
{
_stringBuilder = new StringBuilder();
_nextBand = new Mock<INumeralBand>();
_negatedBy = new Mock<INumeralBand>();
}
[Fact]
public void Process_NegateAndStop()
{
var numeral = new NumeralBand(10, "X", _negatedBy.Object);
_negatedBy.Setup(x => x.Value).Returns(1);
_negatedBy.Setup(x => x.Numeral).Returns("I");
numeral.Process(9, _stringBuilder);
Assert.Equal("IX", _stringBuilder.ToString());
_nextBand.Verify(x => x.Process(It.IsAny<int>(), It.IsAny<StringBuilder>()), Times.Never);
}
[Fact]
public void Process_Exact()
{
var numeral = new NumeralBand(10, "X", _negatedBy.Object);
_negatedBy.Setup(x => x.Value).Returns(1);
_negatedBy.Setup(x => x.Numeral).Returns("I");
numeral.Process(10, _stringBuilder);
Assert.Equal("X", _stringBuilder.ToString());
_nextBand.Verify(x => x.Process(It.IsAny<int>(), It.IsAny<StringBuilder>()), Times.Never);
}
[Fact]
public void Process_NegateAndContinue()
{
var numeral = new NumeralBand(50, "L", _negatedBy.Object);
numeral.ConfigureNext(_nextBand.Object);
_negatedBy.Setup(x => x.Value).Returns(10);
_negatedBy.Setup(x => x.Numeral).Returns("X");
numeral.Process(54, _stringBuilder);
Assert.Equal("L", _stringBuilder.ToString());
_nextBand.Verify(x => x.Process(4, _stringBuilder), Times.Once);
}
}
NumeralGeneratorTests.cs
public class NumeralGeneratorTests
{
private readonly ITestOutputHelper _output;
public NumeralGeneratorTests(ITestOutputHelper output)
{
_output = output;
}
[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(15, "XV")]
[InlineData(1490, "MCDXC")]
[InlineData(1480, "MCDLXXX")]
[InlineData(1580, "MDLXXX")]
[InlineData(1590, "MDXC")]
[InlineData(1594, "MDXCIV")]
[InlineData(1294, "MCCXCIV")]
[InlineData(3999, "MMMCMXCIX")]
[InlineData(4000, "I\u0305V\u0305")]
[InlineData(4001, "I\u0305V\u0305I")]
[InlineData(5002, "V\u0305II")]
[InlineData(10000, "X\u0305")]
[InlineData(15000, "X\u0305V\u0305")]
[InlineData(15494, "X\u0305V\u0305CDXCIV")]
[InlineData(2468523, "M\u0305M\u0305C\u0305D\u0305L\u0305X\u0305V\u0305MMMDXXIII")]
public void ToNumeral(int number, string expected)
{
var sw = Stopwatch.StartNew();
var actual = NumeralGenerator.ToNumeral(number);
sw.Stop();
_output.WriteLine(sw.ElapsedMilliseconds.ToString());
Assert.Equal(expected, actual);
}
}