Home > Software design >  How to convert roman numbers into decimal in PowerShell?
How to convert roman numbers into decimal in PowerShell?

Time:05-09

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 representing 1000) 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 (M000M 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 $_" }
    }
}
  • Related