freeCodeCamp Algorithm Challenge Guide: Roman Numeral Converter

freeCodeCamp Algorithm Challenge Guide: Roman Numeral Converter
0

#24

My Code :


#25
//  key-value store for the numbers and their Roman equivalent
var objArr = {1: "I", 2: "II", 3: "III", 4: "IV", 5: "V", 6: "VI", 
 7: "VII", 8: "VIII", 9: "IX", 10: "X", 20: "XX", 30: "XXX", 40: "XL",
 50: "L", 60: "LX", 70: "LXX", 80: "LXXX", 90: "XC", 100: "C", 
 200: "CC", 300: "CCC", 400: "CD", 500: "D", 600: "DC", 700: "DCC", 
 800: "DCCC", 900: "CM", 1000: "M", 2000: "MM", 3000: "MMM"};



function convertToRoman(num) {

// decompose num into multiples of 1, 10, 100, 1000
  var arr = num.toString().split("").reverse().map(
  function(item) {
    return parseInt(item);
  });

 arr[0] = arr[0] * 1;
 arr[1] = arr[1] * 10;
 arr[2] = arr[2] * 100;
 arr[3] = arr[3] * 1000;

 // find the Roman equivalent of each number
 var j, i, item;
 var newArr = [];
 for (j = 0; j < arr.length; j++) {
   // loop the keys
   for (item in objArr) {
     // check if a key is present in arr
     if (item === arr[j].toString()) {
       // output the corresponding value
       newArr.push(objArr[item]);
     }
   }
 }

 // output the sum of items in newArr: the Roman number
 num = newArr.reverse().join("");
 return num;
 }

 convertToRoman(891);

#26

cant go more basic than this,sry iam new to this:)

function convertToRoman(num) {
var count=[];
  while (num>0){
    
    if(num>=1000){
      num=num-1000;
      count.push('M');
    }
    if(num>=900&&num<1000){
      num=num-900;
      count.push('CM');
    }
    
      if(num>=500&&num<1000){
      num=num-500;
      count.push('D');
    }
    if(num>=100&&num<500){
      num=num-100;
      count.push('C');
    }
    if(num>=90&&num<100){
      num=num-90;
      count.push('XC');
    }
      
    if(num>=50&&num<100){
      num=num-50;
      count.push('L');
    }
    if(num>=40&&num<50){
      num=num-40;
      count.push('XL');
    }
    if(num>=10&&num<50){
      num=num-10;
      count.push('X');
    }
    
    if(num==9){
      num=num-9;
      count.push('IX');
    }
      
    if(num>=5&&num<10){
      num=num-5;
      count.push('V');
    }
    
    if(num==4){
      num=num-4;
      count.push('IV');
    }
    
    if(num>=1&&num<5){
      num=num-1;
      count.push('I');
    }
    
  }return count.join('');
}

convertToRoman(3543);

#27

that’s makes the most sense out of all the ones I’ve tried to read. I’ve noticed a real aversion to recursion… but it seems to work when it needs to.


#28

Here is what I was able to put together:

function convertToRoman(num) {
var arrOne = ["",“I”,“II”,“III”,“IV”,“V”,“VI”,“VII”,“VIII”,“IX”];
var arrTen = ["",“X”,“XX”,“XXX”,“XL”,“L”,“LX”,“LXX”,“LXXX”,“XC”];
var arrHun = ["",“C”,“CC”,“CCC”,“CD”,“D”,“DC”,“DCC”,“DCCC”,“CM”];
var romThou = “”;
var romOne = “”;
var romTen = “”;
var romHun = “”;
var numArr = num.toString().split("");
for (var i = 0; i<=numArr.length; i++) {
var one = numArr.pop();
if (one > 0){
romOne = arrOne[one];
}
var ten = numArr.pop();
if (ten > 0) {
romTen = arrTen[ten];
}
var hun = numArr.pop();
if (hun > 0) {
romHun = arrHun[hun];
}
var thou = numArr.pop();
for (var j = 0; j<thou; j ++) {
romThou += “M”;
}
}

return romThou+romHun+romTen+romOne;
}

convertToRoman(36);


#29

My code looks pretty understandable.

var romanNums = [
    [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']
];
var romNum = "";

function ToRoman(num) {
    if (num === 0) {
        return '';
    }
    for (var i = 0; i < romanNums.length; i++) {
        if (num >= romanNums[i][0]) {
            romNum += romanNums[i][1];
            return romanNums[i][1] + ToRoman(num - romanNums[i][0]);
        }
    }
    return romNum;
}
ToRoman(1900);

#30

After 2-3 hours of overthinking, I came up with this not too sophisticated method :slight_smile:

function convertToRoman(num) {
 
  var number = [];
  
  number[0] = num % 10; // ones
  number[1] = num % 100 - number[0]; // tens
  number[2] = num % 1000 - number[1] - number[0]; // hundreds
  number[3] = num % 10000 - number[2] - number[1] - number[0]; // thousands
  
  var romanNumber =
      [
        ["", "", "", ""],
        ["I", "X", "C", "M"],
        ["II", "XX", "CC", "MM"],
        ["III", "XXX", "CCC", "MMM"],
        ["IV", "XL", "CD", ""],
        ["V", "L", "D", ""],
        ["VI", "LX", "DC", ""],
        ["VII", "LXX", "DCC", ""],
        ["VIII", "LXXX", "DCCC", ""],
        ["IX", "XC", "CM", ""]
      ];
  
  var result = "";
  
  for (var x = 0; x < 4; x++) {
    number[x] = number[x] / Math.pow(10, x);
  }
  
  for (var i = 3; i >= 0; i--) {
    result += romanNumber[number[i]][i];
  }
  
  return result;
  
  
}

convertToRoman(2136);

Basically, I’m creating an array about all the possible numbers (ones, tens, hundreds, thousands), and the for loop finds the correct array item (“row” is based on the number of ones, tens, hundreds, thousands in the number, and “column” is based on if it’s about ones, tens, hundreds or thousands).


#31

Basic / Intermediate solution using 3 functions and for loops:

Background: I wrote the code as an improvement of an if-else nightmare function I previously wrote which was inelegant and error prone.

Program function convertToRoman(number) { var Romulus = ""; var num = number.toString(); //turn it into a makeshift array var ArrEnd = num.length - 1; //get Array max index var Temp; //Variable that is commonly mutated var compArr = [1,4,5,9,0]; var catch1; //Bad var name; IS either I, X, C, M or undefined (when not called). var j; //functionally useless but sped up typing.

for(i = 0 ;i <= ArrEnd; ++i){
Temp = parseInt(num[i]);
if(Temp != 0){
Romulus += ArrEnd == i ? Zero(Temp): NotZero(Temp,ArrEnd);}
else{Romulus += “”;} //has to == 0 or NaN to trigger this code.
}
return Romulus;
}
//Function handles elements > 9; scales by doing val * 10 to the power of it’s position
function NotZero(Temp,ArrEnd){
var compArr = [1,4,5,9];
var arr1 = [’’,‘I’,‘IV’,‘V’,‘IX’,‘X’,‘XL’,‘L’,‘XC’,‘C’,‘CD’,‘D’,‘CM’,‘M’];
var arr2 = [0,1,4,5,9,10,40,50,90,100,400,500,900,1000];
var catch1, j = Temp;
for(x = 0; x < 4; x++){
if(compArr.indexOf(j-x) !== -1){
Temp = arr1[arr2.indexOf((j-x) * Math.pow(10,ArrEnd -i))];
catch1 = arr1[arr2.indexOf(1 * Math.pow(10,ArrEnd -i))];
Temp += catch1.repeat(x);
return Temp;}
}
}
//handles values between 1 and 9;
function Zero(Temp){
var arr1 = [’’,‘I’,‘IV’,‘V’,‘IX’];
var arr2 = [0,1,4,5,9];//shortened as only values 1-9 will be input
var compArr = [1,4,5,9];
var catch1,j = Temp;
for(x = 0; x < 4; x++){
if(compArr.indexOf(j-x) !== -1){
Temp = arr1[arr2.indexOf(j-x)];
catch1 = arr1[1];
Temp += catch1.repeat(x);
return Temp;
}
}
}
convertToRoman(2017);//>>> MMXVII;

Reason: There are several much better solutions in previous posts with much shorter code, however there are also several solutions written that list every number in it’s roman representation (I,II,III,IV,V,VI etc.).

Problems:

  • Code snippet may be hard to read and counter-intuitive to some readers.
  • Faster solutions available.
  • the current code works reliably until the value equals 4000, at which it becomes partially undefined.

#32

More hardcoding (four arrays, not two), but less actual code:

var onez=["","I","II","III","IV","V","VI","VII","VIII","IX","X"];
var tenz=["","X","XX","XXX","XL","L","LX","LXX","LXXX","XC","C"];
var hundz=["","C","CC","CCC","CD","D","DC","DCC","DCCC","CM","M"];
var thz=["","M","MM","MMM","MMMM"];

function convertToRoman(num) {
  var output="";
  var th=Math.floor(num/1000);
  num=num%1000;
  var hund=Math.floor(num/100);
  num=num%100;
  var tens=Math.floor(num/10);
  var ones=num%10;
  output+=thz[th]+hundz[hund]+tenz[tens]+onez[ones];
 return output;
}

#33

I came with a solution that looks pretty small:

    function convertToRoman(num) {
  
     // Store Roman number to an object
     const romanNums = {'1': 'I', '2': 'II', '3': 'III', '4': 'IV', '5': 'V', '6': 'VI', '7': 'VII', '8': 'VIII', '9': 'IX', '10': 'X', '20': 'XX', '30': 'XXX', '40': 'XL', '50': 'L', '60': 'LX', '70': 'LXX', '80': 'LXXX', '90': 'XC', '100': 'C', '200': 'CC', '300': 'CCC', '400': 'CD', '500': 'D', '600': 'DC', '700': 'DCC', '800': 'DCCC', '900': 'CM', '1000': 'M', '2000': 'MM', '3000': 'MMM'};
      
     // turn it into a string
     const strArr = (num + "").split('');
     
    // splitting characters and adding zeros
     const separateNumArr = strArr.map((currentVal, index, array) => currentVal +=  Array((array.length - index - 1 ) + 1).join('0'));
     
    // converting into roman characters and joining toghether
     const roman = (separateNumArr.map((val) => romanNums[val])).join(''); 
     return roman;
    }

    convertToRoman(36);

#34

yeah I think the advanced version should always be the most simple , shortest and easy to understand…


#35

I see the trick here is just to put the Roman & Arabic numerals that are in the test. I was adding everything


#36

Oh my god! I was very hard for me! :slight_smile:

function convertToRoman(num) {
var arr1 = [1, 4, 5, 9 ,10, 40, 50, 90, 100, 400, 500, 900, 1000].reverse();
var arr2 = [“I”,“IV”,“V”,“IX”,“X”,“XL”,“L”,“XC”,“C”,“CD”,“D”,“CM”,“M”].reverse();
var roman = [];

for(var i=0;i<=arr1.length;i++){
while (arr1[i]<=num) {
roman.push(arr2[i]);
num-=arr1[i];
}
}

return roman.join(’’);

}

convertToRoman(16);


#37

I love this. So simple.


#38

I tried essentially doing the basic solution (case statements for 40, 90, 400, 9000, etc), but couldn’t exactly figure out where to go with it

I opted for a solution similar to that of 1st intermediate code solution, but I think mine is a more straight forward version of it

See explanation below;

function convertToRoman(num) {
  //Numeral Reference
  rom = ["I","V","X","L","C","D","M"];
  
  //Convert to string to number array
  var arr= num.toString().split("").map(Number);
  
  //See image / notes after
  var l = 2*arr.length;

  //Conversions
  for (let i = 0; i<arr.length; i++){
    switch(arr[i]){
      case 0:
        arr[i]="";
        break;
      case 1:
        arr[i] = rom[l-2];
        break;
      case 2:
        arr[i] = rom[l-2]+rom[l-2];
        break;
      case 3:
        arr[i] = rom[l-2]+rom[l-2]+rom[l-2];
        break;
      case 4:
        arr[i] = rom[l-2]+rom[l-1];
        break;
      case 5:
        arr[i] = rom[l-1];
        break;
      case 6:
        arr[i] = rom[l-1]+rom[l-2];
        break;
      case 7:
        arr[i] = rom[l-1]+rom[l-2]+rom[l-2];
        break;
      case 8:
        arr[i] = rom[l-1]+rom[l-2]+rom[l-2]+rom[l-2];
        break;
      case 9:
        arr[i] = rom[l-2]+rom[l];
        break;
      default:
        break;
    }
    l-=2;
  }
  
  return arr.join("");
}
convertToRoman(501);

THINGS I USED / DREW TO HELP UNDERSTAND

I also referenced this page a lot too

EXPLANATION OF LOGIC + MATH

Some patterns about numbers and roman numerals

Numbers are in the decimal (or base 10) format.

You can write numbers like 361 like so,

361 = 300 + 60 + 1

or in scientific notation:

3x10^2 + 6x10^1 + 1x10^0

Notice the exponential here. 2,1,0.

  • 2 is the hundredths place
  • 1 is the tenths place
  • 0 is the ones place

Now if we look at 361, and expressed it in array format

[3, 6, 1]

We can line them up together so it looks like this:

[3,6,1] ← value
2 1 0 ← index position

This is how I went about solving the problem. What this basically does is let you take a number, say 361, and put it in array format and know that:

  • 3 is in the hundredths place,
  • 6 is in the tenths place,
  • 1 is in the ones place.

Understanding Array element potential values

Well, let’s look at each array element

[3,6,1]

Better yet let’s represent it as XXX, where X is a number

[X,X,X]

X can only have a value between 0 to 9

This is why you use a switch 10 switch statements, each one being a value from 0 to

Breaking down numerals

We covered basically how to represent a number in array form, and how to index it properly.

I avoided talking about roman numerals until now since its important to understand how numbers work in general

Okay, onto roman numerals. This is the dictionary:

I is 1
V is 5
X is 10
L is 50
C is 100
D is 500
M is 1000

Now let’s take a simple example. The ones place. Numbers like 1,2,3,4,5,6,7,8,9

Let’s represent this out

I is 1
II is 2
III is 3
IV is 4
V is 5
VI is 6
VII is 7
VIII is 8
IX is 9

Did you notice that only three numerals are used? e.g. I, V, X and that they are all next to each other?

We can take test out values in the tens place. Numbers like 10,20,30,40,50,60,70,80,90

X is 10
XX is 20
XXX is 30
XL is 40
L is 50
LX is 60
LXX is 70
LXXX is 80
XC is 90

Notice that this also has three numerals X,L,C and that between the ones place the value of X is overlapping

Combine it all together:

If you drew this all out, you would get a diagram like this

Now that you understand all the logic behind numbers⇔array index⇔Roman numerals

Can be solved now using some switch statements, for loops, etc

This is why you skip two values everytime you change ones place to tens place for example


#39

The codes in three different levels are doing exactly same thing. I wonder what the classes depend on, running efficiency or mind-storming or something else? May somebody explain this to me?


#40

I am pretty sure this is the not the most efficient code, but at least I got it working :slight_smile:

function convertToRoman(num) {
var digit;
var tenth;
var hundred;
var thousand;

var newArr = [];

//NESTED FUNCITON - Change number to ROMAN NUMERALS

function numberToRoman(digitVal, th) {

var first;
var second;
var third;
var fourth;

if (th == 1) {
  first = "I";
  second = "IV";
  third = "V";
  fourth = "IX";
} else if (th == 10) {
  first = "X";
  second = "XL";
  third = "L";
  fourth = "XC";
} else if (th == 100) {
  first = "C";
  second = "CD";
  third = "D";
  fourth = "CM";
} else if (th == 1000) {
  first = "M";
//       second = "CD";
//       third = "D";
//       fourth = "CM";
}

if (digitVal < 4*th) {
for (var i = th; i <= digitVal; i += 1*th) {
  newArr.push(first);
}
  } else if (digitVal == 4*th) {
newArr.push(second);
} else if (4*th < digitVal && digitVal < 9*th) {
newArr.push(third);
var newDigit = digitVal - 5*th;
for (var j = 0; j < newDigit; j += 1*th) {
  newArr.push(first);
} 
} else if (digitVal == 9*th) {
  newArr.push(fourth);
  }
}
//END OF NESTED FUNCTION


 if (num >= 0 && num < 10) {
   digit = num;

 numberToRoman(digit, 1);

} else if (num >= 10 && num < 100) {
tenth = Math.floor(num / 10)*10;
digit = num - tenth;

numberToRoman(tenth, 10);
numberToRoman(digit, 1);

} else if (num >= 100 && num < 1000) {
hundred = Math.floor(num / 100)*100;
tenth = Math.floor((num-hundred) / 10)*10;
digit = num - hundred - tenth;

numberToRoman(hundred, 100);
numberToRoman(tenth, 10);
numberToRoman(digit, 1);

} else if (num >= 1000 && num < 10000) {
thousand = Math.floor(num / 1000)*1000;
hundred = Math.floor((num-thousand) / 100)*100;
tenth = Math.floor((num-thousand-hundred) / 10)*10;
digit = num - thousand - hundred - tenth;

numberToRoman(thousand, 1000);
numberToRoman(hundred, 100);
numberToRoman(tenth, 10);
numberToRoman(digit, 1);

}

newArr = newArr.join("");

return newArr;
}


convertToRoman(61);

#41

I think I found an easy but long solution, got a suggestion to minify it?

function convertToRoman(num) {
  var romNum = "";
  while(num/1000 >= 1){
    num -= 1000;
    romNum += "M";
  }
  while(num/900 >= 1){
    num -= 900;
    romNum += "CM";
  }
  while(num/500 >= 1){
    num = num-500;
    romNum += "D";
  }
  while(num/400 >= 1){
    num = num-400;
    romNum += "CD";
  }
  while(num/100 >= 1){
    num = num-100;
    romNum += "C";
  } 
  while(num/90 >= 1){
    num = num-90;
    romNum += "XC";
  }
  while(num/50 >= 1){
    num = num-50;
    romNum += "L";
  }
  while(num/40 >= 1){
    num = num-40;
    romNum += "XL";
  }
  while(num/10 >= 1){
    num = num-10;
    romNum += "X";
  }
  while(num/9 >= 1){
    num = num-9;
    romNum += "IX";
  }
  while(num/5 >= 1){
    num = num-5;
    romNum += "V";
  }
  while(num/4 >= 1){
    num = num-4;
    romNum += "IV";
  }
  while(num/1 >= 1){
    num = num-1;
    romNum += "I";
  }
  
 return romNum;
}

#42

It’s so interesting to see everyone’s solutions! Here’s mine.

function makeArray(one, five, ten) {
  arr = ["", one, one + one, one + one + one, one + five, five, five + one, five + one + one, five + one + one + one, one + ten];
  return arr;
}

var onesArray = makeArray(“I”,“V”,“X”);
var tensArray = makeArray(“X”,“L”,“C”);
var hundredsArray = makeArray(“C”,“D”,“M”);
var thousandsArray = makeArray(“M”,"","");

function convertToRoman(num) {
  num = thousandsArray[Math.floor((num % 10000) / 1000)] + hundredsArray[Math.floor((num % 1000) / 100)] + tensArray[Math.floor((num % 100) / 10)] + onesArray[num % 10];
  return num;
}

#43

My solution:

Not focused on code efficiency, but mathematical coolness (number systems are rad!)

I noticed that Roman numerals are composed of two separate components (which I’ve called a and b) which can be generated with a sawtooth wave (a) and a step function (b).

It takes no hard-coded mapping of number to symbols, just an array of the Roman Numeral systems.

If anyone has questions or feedback, please let me know. I realized I didn’t comment very clearly.

var romanSymbols = ['I', 'V', 'X', 'L', 'C', 'D', 'M'];

function expandDecimal(int) {
  return int.toString().split('').map(function(e) {
    return parseInt(e);
  }).map(function(e) {
    return codeDecimal(e);
  });
}

function codeDecimal(int) {
  var a = int - 5*Math.floor((int+1)/5), // sawtooth
      b = 5*Math.floor((int-4)/5) + 5; // step
  
  if (a < 0) return [Math.abs(a), b];
  else return [b, a];
}

function convertToSymbol(pairArr, place) {
  var symbol;
  
  return pairArr.map(function(e) {
    if (e === 0) return '';
    else if (e === 5) return romanSymbols[place + 1];
    else if (e === 10) return romanSymbols[place + 2];
    else return romanSymbols[place].repeat(e);
  }).join('');
}

function replaceNumbers(arr) {
  return arr.reverse().map(function(e, i) {
    return convertToSymbol(e, i*2);
  }).reverse().join('');
}

function convertToRoman(num) {
  return replaceNumbers(expandDecimal(num));
}