Используя PHP, я хотел бы преобразовать строку, содержащую римское число, в его целочисленное представление. Мне нужно это, потому что мне нужно делать вычисления на них.
Википедия на римские цифры
Достаточно было бы распознать основные римские цифры, например:
$roman_values=array( 'I' => 1, 'V' => 5, 'X' => 10, 'L' => 50, 'C' => 100, 'D' => 500, 'M' => 1000, );
Это означает, что максимально возможное число – 3999 (MMMCMXCIX). Я буду использовать N
для представления нуля, кроме того, что поддерживаются только положительные целые числа.
Я не могу использовать библиотеку PEAR для римских чисел.
Я нашел этот большой вопрос о SO о том, как проверить, содержит ли строка действительную римскую цифру:
Как вы сопоставляете только действительные римские цифры с регулярным выражением?
Что было бы лучшим способом кодирования этого?
Как насчет этого:
$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;
который должен выводить 3999 для поставляемого $roman
. Кажется, это работает для моего ограниченного тестирования:
MCMXC = 1990 MM = 2000 MMXI = 2011 MCMLXXV = 1975
Возможно, сначала вы захотите выполнить некоторую проверку 🙂
Я не уверен, есть ли у вас ZF или нет, но если вы (или кто-либо из вас, кто читает это), вот мой фрагмент:
$number = new Zend_Measure_Number('MCMLXXV', Zend_Measure_Number::ROMAN); $number->convertTo (Zend_Measure_Number::DECIMAL); echo $number->getValue();
Это тот, с которым я столкнулся, я добавил проверку действительности.
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; } }
Быстрая идея – пройдите римское число справа налево, если значение $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";
Также следует добавить некоторую проверку римского номера, хотя вы сказали, что уже нашли, как это сделать.
Авторские права для этого блога (кстати!) 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); ?>
Я опаздываю на вечеринку, но вот моя. Предполагает действительные цифры в строке, но не проверяет действительный номер римского, что бы это ни было …, похоже, не существует консенсуса. Эта функция будет работать для римских чисел, таких как 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; }
Просто наткнулся на эту красоту и должен опубликовать ее повсюду:
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; }
Определите свою схему! (необязательный)
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; }
в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
Я только что написал это примерно через 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;
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; }
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; }