How to convert a Roman numeral to integer in PHP? - php

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.
Wikipedia on Roman numerals
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?

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 :-)

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();
Zend_Measure_Number on framework.zend.com

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;
}
}

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.

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);
?>

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;
}

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?
The Basics
Don't store multi-digit Roman numerals, like '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.
The Algorithm
Example: LIX (59)
Do a for loop on the numbers, starting at the end of the string of Roman numerals. In our example: We start on "X".
Greater-Than-Equal-To Case — If the value we are looking at is the same or greater than the last value, simply add it to a cumulative result. In our example: $result += $numeral_values["X"].
Less-Than Case — If the value we are subtracting is less than the previous number, we subtract it from our cumulative result. In our example IX, I is 1 and X is 10, so, since 1 is less than 10, we subtract it: giving us 9.
The Demo
Full Working Demo Online
The Code
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");

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;
}

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

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;

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;
}

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;
}

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);
}

Related

How do you find the closest value to a number in an associative array?

I have an associative array with 15 different companies and their stock prices, formatted as shown below:
$CloseStockPrice ('Business'=>50.5. 'Business two'=>100.5, .....)
I have found the average stock price:
$Average = (array_sum($CloseStockPrice)/count($CloseStockPrice));
The average ends up being 161.
But I now need to find the closest number (absolute terms) to that average value (161) within the associative array. I need to display the business and the stock value.
My most recent attempt:
function computeClosest(array $CloseStockPrice)
{
$closest = 161;
for ($i = 161; $i < count($CloseStockPrice) ; $i++)
{
if ($closest === 161)
{
$closest = $CloseStockPrice[$i];
} else if ($CloseStockPrice[$i] > 161 && $CloseStockPrice[$i] <= abs($closest))
{
$closest = $CloseStockPrice[$i];
} else if ($CloseStockPrice[$i] < 161 && -$CloseStockPrice[$i] < abs($closest))
{
$closest = $CloseStockPrice[$i];
return $closest;
}
}
}
Any suggestions?
While you loop through your array of business entries, cache the businesses with prices with the smallest absolute difference involving the average value.
While you might expect a single value in many cases, the fact that multiple qualifying businesses is possible means that you must keep an array of qualifying businesses for most accurate results.
A linear (single loop) process (O(n)) will outperform a sort algorithm (O(n log n)).https://stackoverflow.com/q/56506410/2943403
Code: (Demo)
$closeStockPrice = [
'A' => 50,
'B' => 155,
'C' => 75,
'D' => 245,
'E' => 300,
'F' => 100,
'G' => 153,
];
$average = array_sum($closeStockPrice) / count($closeStockPrice);
$bestDistances = [];
foreach ($closeStockPrice as $business => $price) {
$distance = abs($average - $price);
$current = current($bestDistances);
if (!$bestDistances || $current > $distance) {
$bestDistances = [$business => $distance]; // new best distance
} elseif ($current === $distance) {
$bestDistances[$business] = $distance; // push business with same distance
}
}
var_export([
'average' => $average,
'bestDistances' => $bestDistances,
'bestBusinessPrices' => array_intersect_key($closeStockPrice, $bestDistances)
]);
Output:
array (
'average' => 154,
'bestDistances' =>
array (
'B' => 1,
'G' => 1,
),
'bestBusinessPrices' =>
array (
'B' => 155,
'G' => 153,
),
)
I'm not here to agree with the stock market, but you may want to know what they're up to:
<?php
class AvgSorter{
public $sorted = [];
public function sort($array){
$s = &$this->sorted; array_splice($s, 0);
foreach($array as $k => $v){
$s[$k] = $v;
}
$avg = array_sum($s)/count($s);
uasort($s, function($a, $b) use($avg){
$a = abs($a-$avg); $b = abs($b-$avg);
if($a === $b){
return 0;
}
return $a > $b ? 1 : -1;
});
return $this;
}
public function firstKey(){
return array_keys($this->sorted)[0];
}
public function firstValue(){
return $this->sorted[$this->firstKey()];
}
public function lastKey(){
return array_keys($this->sorted)[count($this->sorted)-1];
}
public function lastValue(){
return $this->sorted[$this->lastKey()];
}
}
$array = ['business 1'=>1000.32, 'business 2'=>5.15, 'business 3'=>10.22, 'business 4'=>4.01, 'business 5'=>10.82, 'business 6'=>3.12, 'business 7'=>1.01];
$avgSorter = new AvgSorter; $firstKey = $avgSorter->sort($array)->firstKey();
$firstValue = $avgSorter->firstValue(); $lastKey = $avgSorter->lastKey();
$lastValue = $avgSorter->lastValue();
$test = 'first key:'.$firstKey.', first value:'.$firstValue.', last key:'.$lastKey.', last value:'.$lastValue;
?>

How to echo array values which are incremented PHP

I have an array like this:
$aArray = array('one' => 0, 'two' => 0, 'three' =>0);
And a while loop like this:
$x = 50;
$y = 400;
$current = current($aArray);
while ($x<$y) {
$current++;
$x+=50;
if($x==$y) {
$current = next($aArray);
}
}
Now what I want is to show the array with the total times incrementation, but I don't know how. And is it able to show it without using a loop?
You can extract the keys of the array and then run according to index.
Consider the following modification:
$aArray = array('one' => 0, 'two' => 0, 'three' =>0);
$keys = array_keys($aArray);
$x = 50;
$y = 400;
$i = 0;
while ($x<$y) {
$aArray[$keys[$i]]++;
$x+=50;
if($x==$y) {
$i++;
}
}
print_r($aArray); // array('one' => 7, 'two' => 0, 'three' =>0);
If you don't want to loop you can just do:
$delta = $y - $x;
$cnt = intval($delta / 50);
if ($delta % 50 != 0)
$cnt++
Now $cnt will be 7 and you can set it in: $aArray["one"] = $cnt;
If you want the count the number of array incremented I have added a some line of coding
$x = 50;
$y = 400;
$aArray = array('one' => 0, 'two' => 0, 'three' =>0);
$countInc=0;
$current = current($aArray);
while ($x<$y) {
$current++;
$x+=50;
$countInc++;
if($x==$y) {
$current = next($aArray);
}
}
echo "Total Number of Increment from that Array: ". $countInc;

PHP - get array value if next element is not series

Here is my array
$array = array( 0 => 10, 1 => 9, 2 => 8, 3 => 6, 4=> 4 );
I want to get array value 6. because, 7 is missing before this value/series is break.
Please help me how can I do it easy & fast method.
The sequence could be calculated by subtracting the first value from the second value. Then you could for example use a for loop to loop through the values of the array and check if there is also a next value available by checking if there is an index + 1.
Then if that is the case you can subtract the current value in the loop from the next value and check if that result equals the step size.
If that is not the case, the next value of the iteration is the value that breaks the sequence and you can break out of the loop.
$array = [10,9,8,6,4];
if (count($array) > 2) {
$step = $array[0] - $array[1];
for ($i = 0; $i < count($array); $i++) {
if (isset($array[$i + 1]) && $array[$i] - $array[$i + 1] !== $step) {
$wrongValue = $array[$i + 1];
echo sprintf(" The step count is %d, but after %d comes %d which breaks the sequence.",
$step, $array[$i], $wrongValue
);
break;
}
}
}
Demo
<?php
$sequence =
[
0 => 10,
1 => 9,
3 => 8,
4 => 6,
5 => 4
];
$last = null;
foreach($sequence as $k => $v)
{
if(!is_null($last) && $last - $v > 1)
break;
$last = $v;
}
var_dump($k, $v);
Output:
int(4)
int(6)
Loop through it, save the previous values and compare with current one:
<?php
function findMissing($array) {
$missing = [];
foreach($array as $key => $val) {
if(isset($previousValue) && $previousValue-1!=$val) {
echo "not in series: ".($previousValue-1) .", returning ".$val."<br>\n";
$missing[] = $val;
}
$previousValue=$val;
}
return $missing;
}
// USAGE:
$array = array( 0 => 10, 1 => 9, 2 => 8, 3 => 6, 4=> 4 );
findMissing($array);
// not in series: 7, returning 6
// not in series: 5, returning 4
$array2 = array( 10, 9, 8, 6, 5 );
$missingValues = findMissing($array2);
// not in series: 7, returning 6
var_dump($missingValues);
// array(1) { [0]=> int(6) }
I'm not sure I understand what you want to achieve, but let's start it here.
UPDATED:
for($i = 0; $i < count($array); ++$i) {
if($i > 0) {
if($array[$i] != ($array[$i-1]-1)) {
echo($array[$i]);
break;
}
}
}
Output:
6

Numbers to Roman Numbers with php

I need to transform ordinary numbers to Roman numerals with php and I have this code:
<?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(6,true);
echo "\n and bacK:\n";
echo roman2number($s);
?>
try this way but does not work:
echo $s=number2roman((.$row['id'].),true);
echo "\n and bacK:\n";
echo roman2number($s);
the problem is that I need to change numbers are readings of my sql database and do not know how to, from and through.
I found this code here: http://php.net/manual/en/function.base-convert.php
Optimized and prettified function:
/**
* #param int $number
* #return string
*/
function numberToRomanRepresentation($number) {
$map = 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);
$returnValue = '';
while ($number > 0) {
foreach ($map as $roman => $int) {
if($number >= $int) {
$number -= $int;
$returnValue .= $roman;
break;
}
}
}
return $returnValue;
}
Another way to do that
<?php
function ConverToRoman($num){
$n = intval($num);
$res = '';
//array of roman numbers
$romanNumber_Array = 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 ($romanNumber_Array 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 result
return $res;
}
echo ConverToRoman(23);
?>
function rome($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;
}
// from polish wiki
CHECKED AND VERIFIED BY PHP UNIT
Make a class having name RomanNumerials and add a protected static property as defined:
protected static $lookup = [
1000 => 'M',
900 => 'CM',
500 => 'D',
400 => 'CD',
100 => 'C',
90 => 'XC',
50 => 'L',
40 => 'XL',
10 => 'X',
9 => 'IX',
5 => 'V',
4 => 'IV',
1 => 'I',
];
then add a method as follows
public function output ($number)
{
$solution = '';
foreach(static::$lookup as $limit => $glyph){
while ($number >= $limit) {
$solution .= $glyph;
$number -= $limit;
}
}
return $solution;
}
You can format integers into Roman numeral symbols with the ICU intl library's NumberFormatter class by setting the locale parameter to #numbers=roman with decimal style format:
function intToRomanNumeral(int $num) {
static $nf = new NumberFormatter('#numbers=roman', NumberFormatter::DECIMAL);
return $nf->format($num);
}
Output examples:
echo intToRomanNumeral(2); // II
echo intToRomanNumeral(5); // V
echo intToRomanNumeral(10); // X
echo intToRomanNumeral(50); // L
echo intToRomanNumeral(57); // LVII
echo intToRomanNumeral(58); // LVIII
echo intToRomanNumeral(100); // C
echo intToRomanNumeral(150); // CL
echo intToRomanNumeral(1000); // M
echo intToRomanNumeral(10000); // ↂ
Alternatively, you can use the MessageFormatter class, but in my own testing it appears to have significantly lower performance than NumberFormatter:
function intToRomanNumeral(int $num) {
static $nf = new MessageFormatter('#numbers=roman', '{0, number}');
return $nf->format([$num]);
}
I improve rome() function of Jasiek
function rome2($N)
{
// support for numbers greater than a thousand
$ss = '';
while ($N > 1000) {
$ss .= 'M';
$N -= 1000;
}
$c = 'IVXLCDM';
for ($a = 5, $b = 0, $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 $ss.$s;
}
My own function it has the best performance:
function romanNumber($n)
{
// support for numbers greater than a thousand
$ret1 = '';
while ($n >= 1000) {
$ret1 .= 'M';
$n -= 1000;
}
$ret = '';
if ($n > 0) {
$n = (string) $n;
$l = 'IVXLCDM';
$j = 0; // goes by roman letters
for ($i = strlen($n)-1; $i >= 0; --$i) { // goes by decimal number
switch ($n[$i]) {
case 0: $s = ''; break;
case 1: $s = $l[$j]; break;
case 2: $s = $l[$j].$l[$j]; break;
case 3: $s = $l[$j].$l[$j].$l[$j]; break;
case 4: $s = $l[$j].$l[$j+1]; break;
case 5: $s = $l[$j+1]; break;
case 6: $s = $l[$j+1].$l[$j]; break;
case 7: $s = $l[$j+1].$l[$j].$l[$j]; break;
case 8: $s = $l[$j+1].$l[$j].$l[$j].$l[$j]; break;
case 9: $s = $l[$j].$l[$j+2]; break;
}
$j += 2;
$ret = $s.$ret;
}
}
return $ret1.$ret;
}
While appreciate the succinct mathematical nested loop approach posted by #user2095686, I agree more with #HaiderLasani's approach of writing the foreach() as the outer loop because it never unnecessarily revisits previously processed elements in the translation array. Haider's answer does not explain why this adjustment is ideal. I've created a demo to echo out all of the unnecessary conditional checks that #user2095686's answer makes. Gone one step further than #Haider's answer, I've written a condition inside of the while() loop to break both loops as soon as the modified input integer is reduced to zero.
Code: (Demo)
function numberToRoman(int $integer): string {
static $conversions = [
1000 => 'M',
900 => 'CM',
500 => 'D',
400 => 'CD',
100 => 'C',
90 => 'XC',
50 => 'L',
40 => 'XL',
10 => 'X',
9 => 'IX',
5 => 'V',
4 => 'IV',
1 => 'I'
];
$romanString = '';
foreach ($conversions as $int => $roman) {
while ($integer >= $int) {
$integer -= $int;
$romanString .= $roman;
if (!$integer) {
break 2;
}
}
}
return $romanString;
}
If you are not turned off by the style of performing assignment arithmetic inside of a conditional expression, the script can be condensed a little more...
foreach ($conversions as $int => $roman) {
while ($integer >= $int) {
$romanString .= $roman;
if (!($integer -= $int)) {
break 2;
}
}
}
Check out my solution here https://github.com/frostymarvelous/Whisppa-Libs/blob/master/Misc/Numeralo.php . It works both ways.
<?php
/**
* #package Whisppa
* #subpackage Misc
* #license http://opensource.org/licenses/MIT MIT License
* #author Stefan (frostymarvelous) Froelich <sfroelich#gmail.com>
* #copyright Copyright (c) 2015, Stefan (frostymarvelous) Froelich
*/
namespace Whisppa\Misc;
/**
* This class allows you to convert from Roman numerals to natural numbers and vice versa.
* I decided to do this as a fun challenge after reading http://thedailywtf.com/articles/Roman-Enumeration
* Took me about 30 minutes to come up with, research and code the solution.
* It can convert numbers up to 3,999,999 because I couldn't find any numerals for 5,000,000 above.
* Due to my inability to get the correct accented characters 5000 above, I resulted to using the pipe (|) to represent accent.
*/
class Numeralo
{
/**
* #var string[] A notation map to represent the common Roman numeral values.
* #static
*/
protected static $NOTATION =
[
'|', //one
'[', //five
']', //ten
];
/**
* #var \ArrayObject[] A map of Roman numerals based on place value. Each item ends with the first numeral in the next place value.
* #static
*/
protected static $NUMERALS_BY_PLACE_VALUE =
[
['I', 'V', 'X',], //ones
['X', 'L', 'C',], //tens
['C', 'D', 'M',], // hundreds
['M', 'V|', 'X|',], //thousands
['X|', 'L|', 'C|',], //tens of thousands
['C|', 'D|', 'M|',], //hundreds of thousands
['M|', '~', '~',], // millions. there are no values for the last two that I could find
];
/**
* #var string[] sA map of numbers and their representative Roman numerals in notation format. This map allows us to make any numeral by replacing the the notation with the place value equivalent.
* #static
*/
protected static $NUMBER_TO_NOTATION =
[
'0' => '',
'1' => '|',
'2' => '||',
'3' => '|||',
'4' => '|[',
'5' => '[',
'6' => '[|',
'7' => '[||',
'8' => '[|||',
'9' => '|]',
];
/**
* #var int[] A map of the major Roman numerals and the number equivalent.
* #static
*/
protected static $NUMERALS_TO_NUMBER =
[
'I' => 1,
'V' => 5,
'X' => 10,
'L' => 50,
'C' => 100,
'D' => 500,
'M' => 1000,
'V|' => 5000,
'X|' => 10000,
'L|' => 50000,
'C|' => 100000,
'D|' => 500000,
'M|' => 1000000,
];
/**
* Converts natural numbers to Roman numerals.
*
* #static
* #param int|string $number a number or numeric string less than 3,999,999
* #throws \InvalidArgumentException if the provided $number argument is not numeric or greater than 3,999,999.
* #return string Roman numeral equivalent
*/
public static function number_to_numerals($number) {
if(!is_numeric($number))
throw new \InvalidArgumentException('Only numbers allowed');
if($number > 3999999)
throw new \InvalidArgumentException('Number cannot be greater than 3,999,999');
$numerals = '';
$number_string = strrev((string) $number);
$length = strlen($number_string);
for($i = 0; $i < $length; $i++) {
$char = $number_string[$i];
$num_map = self::$NUMERALS_BY_PLACE_VALUE[$i];
$numerals = str_replace(self::$NOTATION, $num_map, self::$NUMBER_TO_NOTATION[$char]) . $numerals;
}
return $numerals;
}
/**
* Converts Roman numerals to natural numbers.
*
* #static
* #param string $numerals the Roman numerals to be converted
* #throws \InvalidArgumentException if the provided $numerals argument contains invalid characters.
* #return int the equivalent number
*/
public static function numerals_to_number($numerals) {
$number = 0;
$numeral_string = strrev((string) $numerals);
$length = strlen($numeral_string);
$prev_number = false;
$is_accented = false;
for($i = 0; $i < $length; $i++) {
$char = $numeral_string[$i];
if($char == '|') //check if it is an accent character
{
$is_accented = true;
continue;//skip this iteration and process it in the next one as the accent applies to the next char
}
else if($is_accented)
{
$char .= '|';
$is_accented = false;
}
//TODO Make a check using maybe regex at the beginning of the method.
if(!isset(self::$NUMERALS_TO_NUMBER[$char]))
throw new \InvalidArgumentException("Invalid character '{$char}' in numeral string");
$num = self::$NUMERALS_TO_NUMBER[$char];
//this is where the magic happens
//if the previous number divided by 5 or 10 is equal to the current number, then we subtract eg. 9 = IX. I = 1, X = 10, 10/10 = 1
if($prev_number)
{
if(($prev_number / 5) == $num || ($prev_number / 10) == $num)
$number -= $num;
else
$number += $num;
}
else
$number += $num;
$prev_number = $num;
}
return $number;
}
}

Check and return duplicates array php

I would like to check if my array has any duplicates and return the duplicated values in an array.
I want this to be as efficient as possible.
Example:
$array = array( 1, 2, 2, 4, 5 );
function return_dup($array); // should return 2
$array2 = array( 1, 2, 1, 2, 5 );
function return_dup($array2); // should return an array with 1,2
Also the initial array is always 5 positions long
this will be ~100 times faster than array_diff
$dups = array();
foreach(array_count_values($arr) as $val => $c)
if($c > 1) $dups[] = $val;
You can get the difference of the original array and a copy without duplicates using array_unique and array_diff_assoc:
array_diff_assoc($arr, array_unique($arr))
function array_dup($ar){
return array_unique(array_diff_assoc($ar,array_unique($ar)));
}
Should do the trick.
You can do like this:
function showDups($array)
{
$array_temp = array();
foreach($array as $val)
{
if (!in_array($val, $array_temp))
{
$array_temp[] = $val;
}
else
{
echo 'duplicate = ' . $val . '<br />';
}
}
}
$array = array(1,2,2,4,5);
showDups($array);
Output:
duplicate = 2
function returndup($array)
{
$results = array();
$duplicates = array();
foreach ($array as $item) {
if (in_array($item, $results)) {
$duplicates[] = $item;
}
$results[] = $item;
}
return $duplicates;
}
in addition to gumbo's answer:
function returndup($arr)
{
return array_diff_key($arr, array_unique($arr));
}
I did some tests and indeed #user187291's variant is the fastest. But, it turns out that #Gumbo's and #faebser's alternative are almost as fast, #faebser's being just slightly faster than #Gumbo's and sometimes even fastest of all.
Here's the code I used
$array = array(1, "hello", 1, "world", "hello");
$times = 1000000;
$start = microtime(true);
for ($i = 0; $i < $times; $i++) {
$dups = array();
foreach(array_count_values($array) as $val => $c)
if( $c > 1) $dups[] = $val;
}
$end = microtime(true);
echo 'variant 1 (user187291): ' . ($end - $start);
echo '<br><br><br>';
$start = microtime(true);
for ($i = 0; $i < $times; $i++)
$dups = array_unique(array_diff_assoc($array, array_unique($array)));
$end = microtime(true);
echo 'variant 2 (JAL): ' . ($end - $start);
echo '<br><br><br>';
$start = microtime(true);
for ($i = 0; $i < $times; $i++)
$dups = array_diff_assoc($array, array_unique($array));
$end = microtime(true);
echo 'variant 3 (Gumbo): ' . ($end - $start);
echo '<br><br><br>';
$start = microtime(true);
for ($i = 0; $i < $times; $i++)
$dups = array_diff_key($array, array_unique($array));
$end = microtime(true);
echo 'variant 4 (faebser): ' . ($end - $start);
echo '<br><br><br>';
I have found another way to return duplicates in an array
function printRepeating($arr, $size)
{
$i;
$j;
for($i = 0; $i < $size; $i++)
for($j = $i + 1; $j < $size; $j++)
if($arr[$i] == $arr[$j])
echo $arr[$i], " ";
}
printRepeating($array, sizeof($array,0);
If you need a solution that will work with an array of arrays (or any array values other than integers or strings) try this:
function return_dup( $arr ) {
$dups = array();
$temp = $arr;
foreach ( $arr as $key => $item ) {
unset( $temp[$key] );
if ( in_array( $item, $temp ) ) {
$dups[] = $item;
}
}
return $dups;
}
$arr = array(
array(
0 => 'A',
1 => 'B',
),
array(
0 => 'A',
1 => 'B',
),
array(
0 => 'C',
1 => 'D',
),
array(
0 => 'C',
1 => 'D',
),
array(
0 => 'E',
1 => 'F',
),
array(
0 => 'F',
1 => 'E',
),
array(
0 => 'Y',
1 => 'Z',
),
);
var_export( return_dup( $arr ) );
/*
array (
0 => array (
0 => 'A',
1 => 'B',
),
1 => array (
0 => 'C',
1 => 'D',
),
)
*/
As per your problem if you have duplicate values then you have to return those values. I write a function for this problem. If your array has duplicate values this function returns these values within the array otherwise it returns null values. Here is an example:
function containsDuplicate($array_values) {
$duplicates_values = [];
for($i = 0; $i < count($array_values); $i++){
for ($j=$i+1; $j <count($array_values) ; $j++) {
if ($array_values[$i] == $array_values[$j]) {
$duplicates_values[] = $array_values[$i];
}
}
}
if(count($duplicates_values) > 0){
return $duplicates_values;
}
}
$duplicate_array = array();
for($i=0;$i<count($array);$i++){
for($j=0;$j<count($array);$j++){
if($i != $j && $array[$i] == $array[$j]){
if(!in_array($array[$j], $duplicate_array)){
$duplicate_array[] = $array[$j];
}
}
}
}

Categories