I want to convert Roman Numbers into Decimal Numbers Like:
"MCMVII" = 1907;
"MMCDXLVII" = 2447;
"MMCMLXXXIV" = 2984;
"MXCVI" = 1096;
"MCMIV" = 1904;
"MMDII" = 2502;
"M" = 1000;
"MMDLXXIX" = 2579;
"MMMLXXXVIII" = 3088;
"MMDCCXCIX" = 2799;
"MMDVI" = 2506;
"MMMDCCLVII" = 3757;
"MMMCCLXXXIII" = 3283;
"MCDXL" = 1440;
"MMD" = 2500;
"DCLI" = 651;
Can someone help me with this? I am struggling from where to start??
CodePudding user response:
There are good hints in the comments, but neither bluuf's helpful recipe nor the implementations pointed to Lee_Dailey amount to robust implementations.
A robust implementation that rules out syntactically invalid Roman numerals is surprisingly complex - find function ConvertFrom-RomanNumeral
in the bottom section.
Applied to your sample values, you'll get the corresponding decimal numbers as shown in your question:
@(
"MCMVII"
"MMCDXLVII"
"MMCMLXXXIV"
"MXCVI"
"MCMIV"
"MMDII"
"M"
"MMDLXXIX"
"MMMLXXXVIII"
"MMDCCXCIX"
"MMDVI"
"MMMDCCLVII"
"MMMCCLXXXIII"
"MCDXL"
"MMD"
"DCLI"
) | ConvertFrom-RomanNumeral
A helpful online resource is the Roman Numerals Converter, which offers conversion both to and from Roman numerals.
ConvertFrom-RomanNumeral
source code:
Any syntactically invalid Roman numeral results in a non-terminating error - do let us know if I missed any cases.
No limit is placed on the number of
M
digits (each representing1000
) in a row.
function ConvertFrom-RomanNumeral {
param(
[Parameter(Mandatory, ValueFromPipeline)]
[string] $RomanNumeral
)
begin {
function toValue {
param ([string] $RomanNumeral, [int] $i)
if ($i -ge $RomanNumeral.Length) { return $null }
$value = @{
[char] 'i' = 1
[char] 'v' = 5
[char] 'x' = 10
[char] 'l' = 50
[char] 'c' = 100
[char] 'd' = 500
[char] 'm' = 1000
}[[char]::ToLower($RomanNumeral[$i])]
if (-not $value) {
throw "Cannot convert '$RomanNumeral': not a Roman digit: '$($RomanNumeral[$i])'"
}
$value # output
}
}
process {
$num = 0; $maxDigitSoFar = 1001; $prevValue = 0
for ($i = 0; $i -lt $RomanNumeral.Length; $i) {
try {
$value = toValue $RomanNumeral $i
if ($isMultiDigitRun = $value -eq $prevValue) { # Check for too many digits in a row.
$runCount
if ($value -ne 1000 -and $runCount -gt 3) { throw "Cannot convert '$RomanNumeral': no more than 3 digits other than M allowed in sequence." }
if ($value -in 5, 50, 500) { throw "Cannot convert '$RomanNumeral': V, L, and D cannot be repeated." }
} else {
$runCount = 1
}
if (($nextValue = toValue $RomanNumeral ($i 1)) -gt $value) { # A subtraction, such as IV
$isValid = switch ($value) { # Verify that the next digit is valid; V, L, D cannot be used in subtractions.
1 { $nextValue -in 5, 10; break } # IV, IX
10 { $nextValue -in 50, 100; break } # XL, XC
100 { $nextValue -in 500, 1000; break } # CD, CM
}
if (-not $isValid) { throw "Cannot convert '$RomanNumeral': invalid subtraction sequence: '$($RomanNumeral[$i])$($RomanNumeral[$i 1])'" }
$isValid = $nextValue -le $maxDigitSoFar # Also check if the digit is too high.
$i # Skip the next digit, given that we've just processed it.
$prevValue = 0 # Signal that the next digit is by definition not part of a run.
$maxDigitSoFar = $value # Signal that this digit must not be used again later, except in another subtraction.
$value = $nextValue - $value
}
else { # A stand-alone digit.
$isValid = $isMultiDigitRun -or $value -lt $maxDigitSoFar
$prevValue = $value
$maxDigitSoFar = $value
}
if (-not $isValid) { throw "Cannot convert '$RomanNumeral': digits not in expected order." }
$num = $value
}
catch {
Write-Error "$_"; return
}
}
$num # output
}
}
CodePudding user response:
To complement the answer from @mklement0 and taking the complexity away by seeing each decimal number as a roman numeral pattern that represent the numbers 1
to 9
. In this, note that (as some other ancient number systems) roman numerals don't have/require zeros ( ➔ M000
M
equals 1000
) as each roman number is unique to its own position:
function ConvertFrom-RomanNumeral {
param(
[Parameter(Mandatory, ValueFromPipeline)]
[string] $RomanNumeral
)
begin {
$1 = 'i', 'ii', 'iii', 'iv', 'v', 'vi', 'vii', 'viii', 'ix'
$10 = 'x', 'xx', 'xxx', 'xl', 'l', 'lx', 'lxx', 'lxxx', 'xc'
$100 = 'c', 'cc', 'ccc', 'cd', 'd', 'dc', 'dcc', 'dccc', 'cm'
$Pattern = "^(m*)($($100 -Join '|'))?($($10 -Join '|'))?($($1 -Join '|'))?$"
}
process {
if ($_ -Match $Pattern) {
$Value = if ($Matches[1]) { 1000 * $Matches[1].Length }
if ($Matches[2]) { $Value = 100 * $100.IndexOf($Matches[2].ToLower()) 100 }
if ($Matches[3]) { $Value = 10 * $10.IndexOf($Matches[3].ToLower()) 10 }
if ($Matches[4]) { $Value = $1.IndexOf($Matches[4].ToLower()) 1 }
$Value
} else { throw "Cannot convert $_" }
}
}