Reputation: 78731
Using PHP, I'd like to convert a string containing a Roman number into its integer representation. I need this because I need to make calculations on them.
It would suffice to only recognize the basic Roman numeral characters, like:
$roman_values=array(
'I' => 1,
'V' => 5,
'X' => 10,
'L' => 50,
'C' => 100,
'D' => 500,
'M' => 1000,
);
That means the highest possible number is 3999 (MMMCMXCIX). I will use N
to represent zero, other than that only positive integers are supported.
I cannot use the PEAR library for Roman numbers.
I found this great question on SO on how to test whether the string contains a valid Roman numeral:
How do you match only valid roman numerals with a regular expression?
What would be the best way of coding this?
Upvotes: 32
Views: 32474
Reputation: 31
function romanToInt($s) {
$array = ["I"=>1,"V"=>5,"X"=>10,"L"=>50,"C"=>100,"D"=>500,"M"=>1000];
$sum = 0;
for ($i = 0; $i < strlen($s); $i++){
$curr = $s[$i];
$next = $s[$i+1];
if ($array[$curr] < $array[$next]) {
$sum += $array[$next] - $array[$curr];
$i++;
} else {
$sum += $array[$curr];
}
}
return $sum;
}
Upvotes: 2
Reputation: 20911
Whew! Those are quite a few answers, and made of them are code-heavy! How about we define an algorithm for this first, before I give an answer?
'CM' => 900
, or anything like that in an array. If you know that M - C
(1000 - 100
) equals 900, then ultimately, you should only be storing the values of 1000
and 100
. You wouldn't have multi-digit Roman numerals like CMI
for 901
, would you? Any answer that does this will be inefficient from one that understands the Roman syntax.Example: LIX
(59
)
$result += $numeral_values["X"]
.IX
, I
is 1
and X
is 10
, so, since 1 is less than 10, we subtract it: giving us 9.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");
Upvotes: 1
Reputation: 185
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);
}
Upvotes: 0
Reputation: 1237
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;
}
Upvotes: 0
Reputation: 11
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;
}
Upvotes: 0
Reputation: 72
Just stumbled across this beauty and have to post it all over:
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;
}
Upvotes: 0
Reputation: 249
I'm late to the party, but here's mine. Assumes valid Numerals in the string, but doesn't test for a valid Roman number, whatever that is...there doesn't seem to be a consensus. This function will work for Roman numbers like VC (95), or MIM (1999), or 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;
}
Upvotes: 2
Reputation: 9
I just wrote this in about 10 mins, it's not perfect, but seems to work for the few test cases I've given it. I'm not enforcing what values are allowed to be subtracted from what, this is just a basic loop that compares the current letter value with the next one in the sequence (if it exists) and then either adds the value or adds the subtracted amount to the total:
$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;
Upvotes: 0
Reputation: 78731
This is the one I came up with, I added the validity check as well.
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;
}
}
Upvotes: 10
Reputation: 9122
Quick idea - go through the Roman number from right to left, if value of $current
(more to the left) is smaller than $previous
, then subtract it from the result, if larger, then add it.
$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";
Some validation of roman number should also be added, though you said that you already have found how to do it.
Upvotes: 4
Reputation: 16060
I am not sure whether you've got ZF or not, but in case you (or any of you who's reading this) do here is my snippet:
$number = new Zend_Measure_Number('MCMLXXV', Zend_Measure_Number::ROMAN);
$number->convertTo (Zend_Measure_Number::DECIMAL);
echo $number->getValue();
Upvotes: 11
Reputation: 961
Copyrights is for this blog (btw!) 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);
?>
Upvotes: 3
Reputation: 15461
Define your own schema! (optional)
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;
}
Inspired by andyb's answer
Upvotes: 0
Reputation: 43823
How about this:
$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;
which should output 3999 for the supplied $roman
. It seems to work for my limited testing:
MCMXC = 1990
MM = 2000
MMXI = 2011
MCMLXXV = 1975
You might want to do some validation first as well :-)
Upvotes: 49