【问题标题】:How to convert a Roman numeral to integer in PHP?如何在 PHP 中将罗马数字转换为整数?
【发布时间】:2011-09-10 01:42:27
【问题描述】:

使用 PHP,我想将包含罗马数字的字符串转换为其整数表示。我需要这个,因为我需要对它们进行计算。

Wikipedia on Roman numerals

只识别基本的罗马数字字符就足够了,例如:

$roman_values=array(
    'I' => 1,
    'V' => 5,
    'X' => 10,
    'L' => 50,
    'C' => 100,
    'D' => 500,
    'M' => 1000,
);

这意味着可能的最大数字是 3999 (MMMCMXCIX)。我将使用N 来表示零,除了只支持正整数。

我无法将 PEAR 库用于罗马数字。

我在 SO 上发现了这个关于如何测试字符串是否包含有效罗马数字的好问题:

How do you match only valid roman numerals with a regular expression?

最好的编码方式是什么?

【问题讨论】:

  • 为什么不能使用 PEAR 库?当然,您至少可以看一下代码吗?它与 PHP 的许可证相同。
  • 因为 pear 不是广泛可用的,因为 example 不能安装在 php 命令行环境中。并且出于安全原因不允许:)
  • @stereofrog 服务器上没有安装 PEAR 包管理器,我没有安装它的权限。老实说,这一项简单的任务并不值得。
  • @stereofrog 您可以考虑将其添加为答案。

标签: php integer roman-numerals


【解决方案1】:

这个怎么样:

$romans = array(
    '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,
);

$roman = 'MMMCMXCIX';
$result = 0;

foreach ($romans as $key => $value) {
    while (strpos($roman, $key) === 0) {
        $result += $value;
        $roman = substr($roman, strlen($key));
    }
}
echo $result;

应该为提供的$roman 输出 3999。它似乎适用于我的有限测试:

MCMXC = 1990
MM = 2000
MMXI = 2011
MCMLXXV = 1975

您可能还想先进行一些验证 :-)

【讨论】:

  • 我喜欢你的解决方案有多短,但你需要向$romans 添加一些项目,例如,MIMMDCCCCLXXXXVIIII 两者都可以代表1999(因为对于什么构成有效罗马数字还没有达成共识)。
  • @andyb 我真的可以在我将在 MIT 许可下发布的项目中使用这个 sn-p。您是否有机会在 MIT 下许可您的答案?我不喜欢病毒许可,也不想使用 SE 默认的 cc by-sa 3.0。
  • 很高兴在 MIT 下发布。最简单/最好的方法是什么?
  • @akTed 实际上不,他们不能,因为:1. 十个字符(I、X、C 和 M)可以最多重复 3 次。在第 4 位,您需要从下一个最高的五字符中减去。所以“MDCCCCLXXXXVIIII”是无效号码。 ( CCCC 应该替换为 CD )。 2. 较大的值后面不应该有较小的值,所以“MIM”也是无效的。 1999 写成“MCMXCIX”。
  • @Alexandru Guzinschi 正如我在评论中提到的,对于什么是有效的没有共识。 XXXX 对 40 有效。没有一种“正确”的罗马数字书写方式。
【解决方案2】:

我不确定你是否有 ZF,但万一你(或任何正在阅读本文的人)在这里做我的 sn-p:

$number = new Zend_Measure_Number('MCMLXXV', Zend_Measure_Number::ROMAN);
$number->convertTo (Zend_Measure_Number::DECIMAL);
echo $number->getValue();

【讨论】:

【解决方案3】:

这是我想出来的,我也添加了有效性检查。

class RomanNumber {
    //array of roman values
    public static $roman_values=array(
        'I' => 1, 'V' => 5, 
        'X' => 10, 'L' => 50,
        'C' => 100, 'D' => 500,
        'M' => 1000,
    );
    //values that should evaluate as 0
    public static $roman_zero=array('N', 'nulla');
    //Regex - checking for valid Roman numerals
    public static $roman_regex='/^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$/';

    //Roman numeral validation function - is the string a valid Roman Number?
    static function IsRomanNumber($roman) {
         return preg_match(self::$roman_regex, $roman) > 0;
    }

    //Conversion: Roman Numeral to Integer
    static function Roman2Int ($roman) {
        //checking for zero values
        if (in_array($roman, self::$roman_zero)) {
            return 0;
        }
        //validating string
        if (!self::IsRomanNumber($roman)) {
            return false;
        }

        $values=self::$roman_values;
        $result = 0;
        //iterating through characters LTR
        for ($i = 0, $length = strlen($roman); $i < $length; $i++) {
            //getting value of current char
            $value = $values[$roman[$i]];
            //getting value of next char - null if there is no next char
            $nextvalue = !isset($roman[$i + 1]) ? null : $values[$roman[$i + 1]];
            //adding/subtracting value from result based on $nextvalue
            $result += (!is_null($nextvalue) && $nextvalue > $value) ? -$value : $value;
        }
        return $result;
    }
}

【讨论】:

    【解决方案4】:

    快速的想法 - 从右到左遍历罗马数字,如果 $current 的值(更左边)小于$previous,则从结果中减去它,如果更大,则添加它。

    $romanValues=array(
        'I' => 1,
        'V' => 5,
        'X' => 10,
        'L' => 50,
        'C' => 100,
        'D' => 500,
        'M' => 1000,
    );
    $roman = 'MMMCMXCIX';
    
    // RTL
    $arabic = 0;
    $prev = null;
    for ( $n = strlen($roman) - 1; $n >= 0; --$n ) {
        $curr = $roman[$n];
        if ( is_null($prev) ) {
            $arabic += $romanValues[$roman[$n]];
        } else {
            $arabic += $romanValues[$prev] > $romanValues[$curr] ? -$romanValues[$curr] : +$romanValues[$curr];
        }
        $prev = $curr;
    }
    echo $arabic, "\n";
    
    // LTR
    $arabic = 0;
    $romanLength = strlen($roman);
    for ( $n = 0; $n < $romanLength; ++$n ) {
        if ( $n === $romanLength - 1 ) {
            $arabic += $romanValues[$roman[$n]];
        } else {
            $arabic += $romanValues[$roman[$n]] < $romanValues[$roman[$n+1]] ? -$romanValues[$roman[$n]] : +$romanValues[$roman[$n]];
        }
    }
    echo $arabic, "\n";
    

    还应该添加一些罗马数字的验证,虽然你说你已经找到了怎么做。

    【讨论】:

    • 是的,在这种情况下它确实很重要,因为“当前字母”的含义取决于“下一个字母”的值 - 如果下一个字母更小或与当前相同,则将当前添加到结果,如果 next 更大,则从结果中减去当前。如果我们使用 RTL,我们将“下一个字母”存储在 $prev 变量中,因此除了第一个(最右边的)字母之外,它总是可以访问的,基本的 is_null($prev) 检查就足够了。如果我们去 LTR,我们必须检查下一个字母的值以及下一个字母的存在。
    • 请记住,这可能也适用于 invalid 罗马字母,例如,IVL 将被视为 -1-5+50 并导致 44,这应该写成XLIV。因此,应添加对数字结构的验证,如答案中所述。
    • @binaryLV If we go LTR, we have to check value of next letter as well as existance of next letter 是什么意思。您正在使用 RTL,但您仍然检查下一个字母(三元)的值。 LTR 还有什么必要的?
    • 该检查必须以不同的方式进行。在 RTL 中,您可以使用上一个循环迭代的值,因为 当前 迭代中的“当前”将是 下一个 迭代中的“上一个”。在 LTR 中,在每次迭代中,您都会获得在 next 迭代中“当前”的值,因为它还没有存储在任何地方。我已经使用此代码的 LTR 版本更新了答案。
    • @binaryLV 嗯,是的,我明白了。然后,您也可以保存 Roman 值,减少一次数组查找。很好的解决方案。
    【解决方案5】:

    版权属于此博客(顺便说一句!) http://scriptsense.blogspot.com/2010/03/php-function-number-to-roman-and-roman.html

    <?php
    
    function roman2number($roman){
        $conv = array(
            array("letter" => 'I', "number" => 1),
            array("letter" => 'V', "number" => 5),
            array("letter" => 'X', "number" => 10),
            array("letter" => 'L', "number" => 50),
            array("letter" => 'C', "number" => 100),
            array("letter" => 'D', "number" => 500),
            array("letter" => 'M', "number" => 1000),
            array("letter" => 0, "number" => 0)
        );
        $arabic = 0;
        $state = 0;
        $sidx = 0;
        $len = strlen($roman);
    
        while ($len >= 0) {
            $i = 0;
            $sidx = $len;
    
            while ($conv[$i]['number'] > 0) {
                if (strtoupper(@$roman[$sidx]) == $conv[$i]['letter']) {
                    if ($state > $conv[$i]['number']) {
                        $arabic -= $conv[$i]['number'];
                    } else {
                        $arabic += $conv[$i]['number'];
                        $state = $conv[$i]['number'];
                    }
                }
                $i++;
            }
    
            $len--;
        }
    
        return($arabic);
    }
    
    
    function number2roman($num,$isUpper=true) {
        $n = intval($num);
        $res = '';
    
        /*** roman_numerals array ***/
        $roman_numerals = array(
            '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
        );
    
        foreach ($roman_numerals as $roman => $number)
        {
            /*** divide to get matches ***/
            $matches = intval($n / $number);
    
            /*** assign the roman char * $matches ***/
            $res .= str_repeat($roman, $matches);
    
            /*** substract from the number ***/
            $n = $n % $number;
        }
    
        /*** return the res ***/
        if($isUpper) return $res;
        else return strtolower($res);
    }
    
    /* TEST */
    echo $s=number2roman(1965,true);
    echo "\n and bacK:\n";
    echo roman2number($s);
    
    
    ?>
    

    【讨论】:

    • 无需花费太多时间来尝试理解算法,它似乎有缺陷 - 将 800 写为 CCM(尽管通常被认为是不好的风格)以及 DCCC 是有效的,方法应该是后面的任何数字应从后者中减去而不是相加。
    【解决方案6】:

    我迟到了,但这是我的。假定字符串中的数字有效,但不测试有效的罗马数字,不管是什么......似乎没有达成共识。此函数适用于罗马数字,如 VC (95)、MIM (1999) 或 MMMMMM (6000)。

    function roman2dec( $roman ) {
        $numbers = array(
            'I' => 1,
            'V' => 5,
            'X' => 10,
            'L' => 50,
            'C' => 100,
            'D' => 500,
            'M' => 1000,
        );
    
        $roman = strtoupper( $roman );
        $length = strlen( $roman );
        $counter = 0;
        $dec = 0;
        while ( $counter < $length ) {
            if ( ( $counter + 1 < $length ) && ( $numbers[$roman[$counter]] < $numbers[$roman[$counter + 1]] ) ) {
                $dec += $numbers[$roman[$counter + 1]] - $numbers[$roman[$counter]];
                $counter += 2;
            } else {
                $dec += $numbers[$roman[$counter]];
                $counter++;
            }
        }
        return $dec;
    }
    

    【讨论】:

      【解决方案7】:

      偶然发现了这个美女,不得不把它全部贴出来:

      function roman($N)
      {
          $c = 'IVXLCDM';
          for ($a = 5, $b = $s = ''; $N; $b++, $a ^= 7)
          {
              for (
                  $o = $N % $a, $N = $N / $a ^ 0;
      
                  $o--;
      
                  $s = $c[$o > 2 ? $b + $N - ($N &= -2) + $o = 1 : $b] . $s
              );
          }
          return $s;
      }
      

      【讨论】:

      • 你应该发布一个你偶然发现的链接:)。这似乎很有趣,但不是描述性变量名称的一个很好的例子。
      • 为了可读性必须对其进行格式化,原件在代码中,但我刚刚搜索了php roman IVXLCDM,实际上找到了original on the PHP manual(格式与我们的代码相同)大声喊出到 JR 以及 100 个网点!
      【解决方案8】:

      定义您自己的架构! (可选)

      function rom2arab($rom,$letters=array()){
          if(empty($letters)){
              $letters=array('M'=>1000,
                             'D'=>500,
                             'C'=>100,
                             'L'=>50,
                             'X'=>10,
                             'V'=>5,
                             'I'=>1);
          }else{
              arsort($letters);
          }
          $arab=0;
          foreach($letters as $L=>$V){
              while(strpos($rom,$L)!==false){
                  $l=$rom[0];
                  $rom=substr($rom,1);
                  $m=$l==$L?1:-1;
                  $arab += $letters[$l]*$m;
              }
          }
          return $arab;
      }
      

      灵感来自 andyb 的回答

      【讨论】:

      • 这对 $rom 期望什么样的输入?我根本想不通。
      【解决方案9】:

      我只用了大约 10 分钟写了这个,它并不完美,但似乎适用于我给出的少数测试用例。我没有强制执行允许从什么中减去哪些值,这只是一个基本循环,它将当前字母值与序列中的下一个值(如果存在)进行比较,然后添加该值或添加减去的数量总计:

      $roman = strtolower($_GET['roman']);
      
      $values = array(
      'i' => 1,
      'v' => 5,
      'x' => 10,
      'l' => 50,
      'c' => 100,
      'd' => 500,
      'm' => 1000,
      );
      $total = 0;
      for($i=0; $i<strlen($roman); $i++)
      {
          $v = $values[substr($roman, $i, 1)];
          $v2 = ($i < strlen($roman))?$values[substr($roman, $i+1, 1)]:0;
      
          if($v2 && $v < $v2)
          {
              $total += ($v2 - $v);
              $i++;
          }
          else
              $total += $v;
      
      }
      
      echo $total;
      

      【讨论】:

      • 您应该使用错误报告来测试您的代码(或在必要时禁止它),它会在您的$v2... 行大多数执行时抛出一个Notice: Undefined offset: 0
      【解决方案10】:
      function Romannumeraltonumber($input_roman){
        $di=array('I'=>1,
                  'V'=>5,
                  'X'=>10,
                  'L'=>50,
                  'C'=>100,
                  'D'=>500,
                  'M'=>1000);
        $result=0;
        if($input_roman=='') return $result;
        //LTR
        for($i=0;$i<strlen($input_roman);$i++){ 
          $result=(($i+1)<strlen($input_roman) and 
                $di[$input_roman[$i]]<$di[$input_roman[$i+1]])?($result-$di[$input_roman[$i]]) 
                                                              :($result+$di[$input_roman[$i]]);
         }
       return $result;
      }
      

      【讨论】:

      • 你应该添加解释。
      【解决方案11】:
      function rom_to_arabic($number) {
      
      $symbols = array( 
          'M'  => 1000,  
          'D'  => 500, 
          'C'  => 100, 
          'L'  => 50, 
          'X'  => 10, 
          'V'  => 5, 
          'I'  => 1);
      
      $a = str_split($number);
      
      $i = 0;
      $temp = 0;
      $value = 0;
      $q = count($a);
      while($i < $q) {
      
          $thys = $symbols[$a[$i]];
          if(isset($a[$i +1])) {
              $next = $symbols[$a[$i +1]];
          } else {
              $next = 0;
          }
      
          if($thys < $next) {
              $value -= $thys;
          } else {
              $value += $thys;
          }
      
          $temp = $thys;
          $i++;
      }
      
      return $value;
      
      }
      

      【讨论】:

        【解决方案12】:
        function parseRomanNumerals($input)
        {
        $roman_val = '';
        $roman_length = strlen($input);
        $result_roman = 0;
        for ($x = 0; $x <= $roman_length; $x++) {
        $roman_val_prev = $roman_val;
        $roman_numeral = substr($input, $roman_length-$x,1);
        
        switch ($roman_numeral) {
        case "M":
        $roman_val = 1000;
        break;
        case "D":
        $roman_val = 500;
        break;
        case "C":
        $roman_val = 100;
        break;
        case "L":
        $roman_val = 50;
        break;
        case "X":
        $roman_val = 10;
        break;
        case "V":
        $roman_val = 5;
        break;
        case "I":
        $roman_val = 1;
        break;
        default:
        $roman_val = 0;
        }
        if ($roman_val_prev<$roman_val) {
        $result_roman = $result_roman - $roman_val;
        }
        else {
        $result_roman = $result_roman + $roman_val;
        }
        }
        return abs($result_roman);
        }
        

        【讨论】:

          【解决方案13】:

          哇!这些答案很多,而且由它们组成的代码繁重!在我给出答案之前,我们先为此定义一个算法如何?

          基础知识

          • 不要在数组中存储多位罗马数字,如 'CM' =&gt; 900 或类似的东西。 如果您知道 M - C (1000 - 100) 等于900,那么最终,您应该只存储1000100 的值。你不会有像CMI 这样的多位罗马数字901,对吗?对于理解罗马语法的人来说,这样做的任何答案都是低效的。

          算法

          示例:LIX (59)

          • 对数字执行 for 循环,从 罗马数字字符串的末尾开始。 在我们的示例中:我们从“X”开始。
          • Greater-Than-Equal-To Case — 如果我们正在查看的值等于或大于最后一个值,只需将其添加到累积结果中即可。在我们的示例中:$result += $numeral_values["X"]
          • Less-Than Case — 如果我们减去的值小于前一个数字,我们从累积结果中减去它。在我们的示例中,IXI1X10,因此,由于 1 小于 10,我们减去它:得到 9。

          演示

          Full Working Demo Online

          代码

          function RomanNumeralValues() {
              return [
                  'I'=>1,
                  'V'=>5,
                  'X'=>10,
                  'L'=>50,
                  'C'=>100,
                  'D'=>500,
                  'M'=>1000,
              ];
          }
          
          function ConvertRomanNumeralToArabic($input_roman){
              $input_length = strlen($input_roman);
              if($input_length === 0) {
                  return $result;
              }
              
              $roman_numerals = RomanNumeralValues();
              
              $current_pointer = 1;
              $result = 0;
              
              for($i = $input_length - 1; $i > -1; $i--){ 
                  $letter = $input_roman[$i];
                  $letter_value = $roman_numerals[$letter];
                  
                  if($letter_value === $current_pointer) {
                      $result += $letter_value;
                  } elseif ($letter_value < $current_pointer) {
                      $result -= $letter_value;
                  } else {
                      $result += $letter_value;
                      $current_pointer = $letter_value;
                  }
              }
              
              return $result;
          }
          
          print ConvertRomanNumeralToArabic("LIX");
          

          【讨论】:

            猜你喜欢
            • 2019-01-09
            • 2011-10-25
            • 2012-10-09
            • 1970-01-01
            • 1970-01-01
            • 2020-05-11
            • 2021-12-15
            相关资源
            最近更新 更多