Roman Numeral Converter Challenge

Oh cool, I’ll make a note to try switch later. Thanks!

@rmdawson71 I think it’s genius! I’ll probably delete my solution and try to replicate yours, since it’s much compact and elegant. Great job :slight_smile:

When I started working on this I had a series of if statements… it was ridiculous. Here’s what I finally came up with. It’s amazing to see the number of ways people have solved this challenge!

var roman = ['M','CM','D','CD','C','XC','L','XL','X','IX','V','IV','I'];
var arabic = [1000,900,500,400,100,90,50,40,10,9,5,4,1];

function convertToRoman(num) {
  var result = [];
    for (i = 0; i < roman.length; i++) {
      while ((num - arabic[i]) >= 0) {
          result.push(roman[i]);
          num -= arabic[i];
      } 
  }
  return result.join("");
}
1 Like

I think @paintingfire’s solution is great. Very clear, fast, and concise.

In this situation, where there are clear limits to the range (0 to 3999) and ways in which the values can be expressed, it makes more sense to use smart arrays than complicated formulas. Nevertheless, I took the formula approach.

I read on StackOverflow that if-then statements are faster than case-switch statements when dealing with values that are not exact (i.e. > 4, < 4), so I opted for the if-then approach.

I’m also impressed with all the different solutions others came up with!

function convertToRoman(num) {
  
  if (!(num > 0 && num < 4000)) return false; // just to be safe
  
  var numStr = num.toString();
  var x = (numStr.length - 1) * 2; // roman numeral array index
  var rnArr = ["I", "V", "X", "L", "C", "D", "M"];
  var rnStr = "";
  
  for (var i = 0; i < numStr.length; i++, x-=2) {
    
    var digit = parseInt(numStr.charAt(i), 10);
    
    if (digit == 9) {
      rnStr += rnArr[x] + rnArr[x + 2];
    } else if (digit > 4) {
      rnStr += rnArr[x + 1];
      rnStr += Array(digit - 4).join(rnArr[x]);
    } else if (digit == 4) {
      rnStr += rnArr[x] + rnArr[x + 1];
    } else {
      rnStr += Array(digit + 1).join(rnArr[x]);
    }
  }
  
  return rnStr;
}
1 Like

way cleaner than my solution was :))


const numeralGroups = [
  ['', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX'],
  ['', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC'],
  ['', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM'],
  ['', 'M', 'MM', 'MMM'],
]

function convertToRoman(number) {
    return number.toString()
                 .split('')
                 .map(char => parseInt(char, 10))
                 .reverse()
                 .map((v, i) => numeralGroups[i][v])
                 .reverse()
                 .join('')
}

So For say 1234. Convert to an array of digits. Work backwards, converting ones, then tens, then hundreds, then thousands. [4, 3, 2, 1]. so:

  • 4 is at index 0 - look in the first subarray of numerals, at the character at index 4, which is “IV” (this is why there’s an empty string at the start of each group).
  • 3 is at index 1 - look in the second subarray of numerals, at character at index 3, which is “XXX”
  • 2 is at index 2 - look in the third subarray of numerals, at character at index 2, which is “CC”
  • 1 is at index 3 - look in the fourth subarray of numerals, at character at index 1, which is “M”

This results in [“IV”,“XXX”,“CC”,“M”], which is backward, so reverse and join to give “MCCXXIV”

4 Likes

I just thought I’d share mine as well :slight_smile:

var romans = {
  1: 'I',
  4: 'IV',
  5: 'V',
  9: 'IX',
  10: 'X',
  40: 'XL',
  50: 'L',
  90: 'XC',
  100: 'C',
  400: 'CD',
  500: 'D',
  900: 'CM',
  1000: 'M',
};

function convertToRoman(num) {
  if(num <= 0) {
    return 'N';
  }
  
  var keys = Object.keys(romans).reverse();
  var m = keys.splice(0, 1)[0];
  var roman = '';
  
  while(num > 0) {
      if(num >= m) {
        roman += romans[m];
        num -= m;
      } else {
        m = keys.splice(0, 1)[0];
      }
  }
  
 return roman;
}

In my solution I didn’t convert a number to strings, instead I used some maths :wink:
In theory it converts numbers up to 1 million, but then some letters needs a dash over them.
I thought I was cheating using arrays, but it seemed so easy using them that I decided to use them.
Below is my code:

function convertToRoman(num) {
  
  var pow = 0;
  var mod = 0;
  var res = [];
  
  function getLetters(pow) {
    var arr;
    switch (pow) {
      case 0:
      arr = ["I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"];
      break;
      case 1:
      arr = ["X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"];
      break;
      case 2:
      arr = ["C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"];
      break;
      case 3:
      arr = ["M", "MM", "MMM", "MV", "V", "VM", "VMM", "VMMM", "MX"];
      break;
    }
    return arr;
  }
  
  while (num >= 10) {
    mod = num % 10;                   //getting the last digit as a mod
    num = Math.trunc(num / 10);       //and 'deleting' the last digit from the num
    var tmp = getLetters(pow)[mod-1]; //getting the corresponding letters from appropriate array
      if (tmp !== undefined) {        //if it wasn't possible do nothing
        res.unshift(tmp);             //otherwise put on front of res array
      }
    if (pow > 3) {                    //it alows taking letters from [1] array (instead of [4])
      pow -= 3;                       //and loop in that way over the last 3 arrays
    } else {                          //but no dash over the symbols... :(
      pow++;
    }
  }
  res.unshift(getLetters(pow)[num-1]);//sorting out the last digit (if num < 10)
  res = res.join("");                 //joining all letters from res array
  return res;
}

convertToRoman(36);
1 Like

I liked my solution until I realized that I had to replace values like IIII with IV. I think that is the precise moment when I began coding shit. I was able to pass all the tests, but I am not sure whether this is still a valid solution. Any feedback would be welcome:

function convertToRoman(num) {
  var alphabet = {
    "I": 1,
    "V": 5,
    "X": 10,
    "L": 50,
    "C": 100,
    "D": 500,
    "M": 1000
  }
  
  var feedRecursive = [ 1, 5, 10, 50, 100, 500, 1000];
  
  function recursiveConversion(value, n) {
    if(value % feedRecursive[n] == 0) {
      for (var i = 0; i < Math.trunc(value / feedRecursive[n]); i++) {
        erg.push(Object.keys(alphabet)[n]);
      }
      return console.log(value);
    }
    
    if(Math.trunc(value / feedRecursive[n]) != 0) {
      for (var i = 0; i < Math.trunc(value / feedRecursive[n]); i++) {
        erg.push(Object.keys(alphabet)[n]);
      }
      value -= (Math.trunc(value / feedRecursive[n])) * feedRecursive[n];
    }
    
    console.log(n, value)
    return recursiveConversion(value, n-1);
  }
  
  var erg = [];
  recursiveConversion(num, Object.keys(alphabet).length - 1);
  
  ergString = erg.join("").replace(/(IIIIV)|(IIII)\b/g, "IV").replace(/(VIIII)|(IIIIX)|(VIV)/g, "IX").replace(/(XXXXL)|(XXXX)/g, "XL").replace(/(XXXXC)|(LXXXX)|(LXL)/g,"XC").replace(/(CCCCD)/g, "CD").replace(/(CCCCM)|(DCCCC)/g, "CM");
  console.log(ergString);
  return ergString;
}

convertToRoman(3999);

Thanks! This solution looks very elegant.
The arrays also provide a quick way to lookup values in the future.

Not mine, but I remember coming across this one:

leetcode constrains the input Integer to between 1 and 3999.

public static String intToRoman(int num) {
    String M[] = {"", "M", "MM", "MMM"};
    String C[] = {"", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"};
    String X[] = {"", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"};
    String I[] = {"", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"};
    return M[num/1000] + C[(num%1000)/100] + X[(num%100)/10] + I[num%10];
}

Like tmpbit’s conceptually

1 Like

Mine is prolly the worst one on here because of my egregious logic. I even have nested loops. I also didn’t want to hard code 4s and 9s. I felt like that took away from the challenge. There are much better ways to write it.

function convertToRoman(num) {
   var numerals = {
    "1": "I",
    "5": "V",
    "10": "X",
    "50": "L",
    "100": "C",
    "500": "D",
    "1000": "M"
  };
//var numer = [1,5,10,50,100,500,1000], arr = [], 
var counter = [], romanNumeral = [], factorArray = [] ;

function romanFactors(num){
  var arry = [], numer = [1,5,10,50,100,500,1000];
  for(var i = 0; numer[i]<=num; i++ ){
    arry.push(numer[i]);
  }
  factorArray = arry.reverse();
  //console.log(factorArray);
  return factorArray;
}
  
  
 romanFactors(num).filter(function (item){
  if ((num/item)>=1){
    var modulus = num%item;
    var factor = (num-modulus)/item;
    counter.push(factor);
    //console.log(counter);
    num = num - (factor*item);
    //console.log(num);
    return factor;
  }
  else {
    counter.push(0);
    return 0;
  }
  
});
  for (var j = 0; j < factorArray.length; j++){
  for (var i = 0; i < counter[j]; i++){

    if(counter[j]==4){
      if (((counter[j-1]) === 0)||((counter[j-1])===undefined)){
      //console.log((factorArray[j]));
      //console.log(numerals[factorArray[j]*5]);
      romanNumeral.push(numerals[factorArray[j]], numerals[(factorArray[j]*5)]);
      break;
      }
      else{
        romanNumeral.pop();
      //console.log((factorArray[j]));
      //console.log(numerals[factorArray[j]*10]);
      romanNumeral.push(numerals[factorArray[j]], numerals[(factorArray[j]*10)]);
      break;
        
      }
      
    }
    else{
     //console.log(numerals[factorArray[j]]);
    romanNumeral.push(numerals[factorArray[j]]); 
    }
  }
}

  return romanNumeral.join("");
}


convertToRoman(891);


I feel you. I also allways shiver when using nested loops, but I guess at this stage of web development experience, it’s okay. The 4’s and 9’s nearly broke me, and I guess I cheated out by using regex, but I couldn’t look at that challenge anymore ^^

Mine is pretty ugly but works. I would like to see some performance evaluations about my code versus other codes. I look at other peoples and think they may be a little more clever but… I am happy to break through this one. I may add a for loop to clean up some of the repetitive code. It does not get much more basic than this but I am still happy with it. :sunglasses:

function convertToRoman(num) {
 var p = num;
    
  var dig = p.toString();
  
  var ans = "";
  var ans1 = "";
  var ans2 = "";
  var ans3 = "";
  //return dig;
  
  var y = dig.slice(-1);
  y = parseInt(y);
  
  switch (y) {
    case 0:
      ans = "";
      break;
    case 1:
      ans = "I";
      break;
    case 2:
      ans = "II";
      break;
    case 3:
      ans = "III";
      break;
    case 4:
      ans = "IV";
      break;
    case 5:
      ans = "V";
      break;
    case 6:
      ans = "VI";
      break;
    case 7:
      ans = "VII";
      break;
    case 8:
      ans = "VIII";
      break;
    case 9: 
      ans = "IX";
      break;
    default:
      ans = '';
        
  
  }
  
  var z = dig.slice(-2, -1);
  z = parseInt(z);
  //return z;
  switch (z) {
    case 0:
      ans1 = "";
      break;
    case 1:
      ans1 = "X";
      break;
    case 2:
      ans1 = "XX";
      break;
    case 3:
      ans1 = "XXX";
      break;
    case 4:
      ans1 = "XL";
      break;
    case 5:
      ans1 = "L";
      break;
    case 6:
      ans1 = "LX";
      break;
    case 7:
      ans1 = "LXX";
      break;
    case 8:
      ans1 = "LXXX";
      break;
    case 9: 
      ans1 = "XC";
      break;
    default:
      ans1 = '';
        
  
  }
  
    var z1 = dig.slice(-3, -2);
  z1 = parseInt(z1);
  //return z;
  switch (z1) {
    case 0:
      ans2 = "";
      break;
    case 1:
      ans2 = "C";
      break;
    case 2:
      ans2 = "CC";
      break;
    case 3:
      ans2 = "CCC";
      break;
    case 4:
      ans2 = "CD";
      break;
    case 5:
      ans2 = "D";
      break;
    case 6:
      ans2 = "DC";
      break;
    case 7:
      ans2 = "DCC";
      break;
    case 8:
      ans2 = "DCCC";
      break;
    case 9: 
      ans2 = "CM";
      break;
    default:
      ans2 = '';
        
  
  }
      var z2 = dig.slice(-4, -3);
  z2 = parseInt(z2);
  //return z;
  switch (z2) {
    case 0:
      ans3 = "";
      break;
    case 1:
      ans3 = "M";
      break;
    case 2:
      ans3 = "MM";
      break;
    case 3:
      ans3 = "MMM";
      break;
    default:
      ans3 = '';
        
  
  }
  
 
return ans3 + ans2 + ans1 + ans;   

 
}

convertToRoman(389);

Oh god.
This challenge almost crushed me… I was literally desperate because the algorithms that came to my mind naturally were super complicated (I guess my mind is broken… I knew but… xD) finally I had to have a look to some of the solutions of other campers and after that tried to make my own.

This is what I did, I feel very proud but really had a bad time with this challenge.

1 Like
1 Like

I non-apologetically used a super long series of switch functions and moved on. I may come back to this later when I have more skills. This problem really scared me at first so I’m fine with my answer looking like a s*** if it works and I coded it myself.

convertToRoman algorithm

Let’s take a big number as an example -> 3999. We can write this number like so: 3999 = 3000 + 900 + 90 + 9, or better 3*1000 + 9*100 + 9*10 + 9*1.
Using roman numerals results that 3999 = 3*M + 9*C + 9*X + 9*I
In other words: 3999 = MMM CCCCCCCCC XXXXXXXXX IIIIIIIII
We have written 3999 as a sequence of roman numerals (using only M, C, X and I). However some of these strings are incorrect (e.g. IIIIIIIII). We need to apply some rules to fix these strings.

Let’s implement a function called fixMainNumeral that will fix incorrect strings according to one rule:
"Don’t use the same symbol more than 3 times in a row, unless the symbol is M"
According to this rule the sequence CCCC or IIII are incorrect but MMMM is correct.

Now our number can be written as a sum of these correct sequences:
3999 = fixMainNumeral(3, M) + fixMainNumeral(9, C) + fixMainNumeral(9, X) + fixMainNumeral(9, I)

    const aNumerals = ['I', 'V', 'X', 'L', 'C', 'D', 'M'];
    const numbersMap = {
      1: 'I',
      5: 'V',
      10: 'X',
      50: 'L',
      100: 'C',
      500: 'D',
      1000: 'M'
    };

    const convertToRoman = num => {

      // get number's digits
      const digits = getDigits(num);
      const digitsLen = digits.length;

      let i, cRomNum;
      let finalResult = '';

      for (i=0; i<digitsLen; i++) {
        cRomNum = numbersMap[Math.pow(10, digitsLen - 1 - i)];
        finalResult += fixMainNumeral(digits[i], cRomNum);
      }

      return finalResult;
    }

    const getDigits = num => ('' + num).match(/[0-9]/g).map(d => parseInt(d, 10));

    const createSequence = (char, times) => {
      let i, seq = '';
      for (i=0; i<times; i++) {
        seq += char;
      }
      return seq;
    }

    const fixMainNumeral = (n, numeral) => {

      // get numeral position
      const pos = aNumerals.indexOf(numeral);

      if (n < 5) {
        if (n < 4) return createSequence(numeral, n);

        // check if there is a next numeral
        if (pos + 1 >= aNumerals.length) {
          return createSequence(numeral, n);
        }
        return  numeral + aNumerals[pos + 1];
      }

      if (n == 5) {
        if (pos < aNumerals.length - 1) return aNumerals[pos + 1];
        return createSequence(numeral, n);
      }

      if (n > 5) {
        if(n < 9) {
          return aNumerals[pos + 1] + createSequence(numeral, n - 5);
        }

        // check if there is a next numeral
        if (pos + 2 >= aNumerals.length) {
          return createSequence(numeral, n);
        }
        return numeral + aNumerals[pos + 2];
      }
    }

    const valuesToTest = {
      2: 'II',
      3: 'III',
      4: 'IV',
      5: 'V',
      9: 'IX',
      12: 'XII',
      16: 'XVI',
      29: 'XXIX',
      44: 'XLIV',
      45: 'XLV',
      68: 'LXVIII',
      83: 'LXXXIII',
      97: 'XCVII',
      99: 'XCIX',
      500: 'D',
      501: 'DI',
      649: 'DCXLIX',
      798: 'DCCXCVIII',
      891: 'DCCCXCI',
      1000: 'M',
      1004: 'MIV',
      1006: 'MVI',
      1023: 'MXXIII',
      2014: 'MMXIV',
      3999: 'MMMCMXCIX',
      4235: 'MMMMCCXXXV',
      5293: 'MMMMMCCXCIII'
    };

    let val, res, testRes;

    // loop through each key in valuesToTest
    for (val in valuesToTest) {
      res = convertToRoman(val);
      if (res == valuesToTest[val]) {
        testRes = 'PASS'
      } else {
        testRes = 'FAIL'
      }
      console.log(val + ' = ' + res + ' -> ' + testRes);
    }

I don’t know ES6, so here is my ES5 approach, that took a few attempts to get it working.

/*
** Function: Convert a number into a Roman Numeral.
** @function convertToRoman
** @summary  Convert a number into a Roman Numeral.
** @param 	 {number} 
** @see {@link https://jsperf.com/convertToRoman-eggs} 
** @returns  {string} Roman Numeral
*/
function convertToRoman(num) {
	'use strict';
  
  var base  = [1000,500,100,50,10,5,1];
  var roman = ['M','D','C','L','X','V','I'];
  var result = "";
  // 'I' = 1, 'V' = 5, 'X' = 10, 'L' = 50, 'C' = 100, 'D' = 500, 'M' = 1000
  
  base.forEach( function(value, i) {
    
    var ch = roman[i];
    if ( ch === 'V' || ch === 'L' || ch === 'D' ) {
      if ( num >= base[i+1]*9 ) {
        result += roman[i+1] + roman[i-1];
        num -= ( base[i-1] - base[i+1] );
      }
      else if ( num >= value - base[i+1] && num < value ) {
        result += roman[i+1] + roman[i];
        num -= ( base[i] - base[i+1] );
      }
    } 

    while ( num >= value ) {
      result += roman[i];
      num -= value;
    }
  });
  return result;
}

I did something a bit similar.
I really like the .forEach() method.

function convertToRoman(num) {
var romanNum = [
[“I”,“II”,“III”,“IV”,“V”,“VI”,“VII”,“VIII”,“IX”],
[“X”,“XX”,“XXX”,“XL”,“L”,“LX”,“LXX”,“LXXX”,“XC”],
[“C”,“CC”,“CCC”,“CD”,“D”,“DC”,“DCC”,“DCCC”,“CM”,],
[“M”,“MM”,“MMM”]
] ;

var arr = num.toString().split("").reverse();
var rNums = [];
romanNum.forEach(function (a, i){
romanNum[i].forEach(function (b, j){
if((j + 1) == arr[i]){
rNums.unshift(b);
}
});

});
var result = rNums.join(’’);
return result;
}

convertToRoman(3643);

1 Like