DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for Roman Numeral Converter FCC Solution
MichaelX
MichaelX

Posted on

Roman Numeral Converter FCC Solution

JUPITER IS UPON US

Who knew ancient Romans where gonna be involved in modern day math? Well I didn't πŸ€·β€β™‚οΈ... Some dudes in skirts making us write more code :'( perhaps Jupiter taught them more JavaScript than we know today... I wouldn't be surprised honestly.

We're continuing our FreeCodeCamp's JavaScript Algorithms and Data Structures Series with the one and only Roman Numeral Converter Algorithm. This is the second project in the series and it's a little bit tougher than the first, well not if you're Jupiter! But sorry, Dev.to only accepts humans for now, so it's just gonna be us coffee drinkers today.

Let's start with looking at the code and then I'll explain or perhaps solve it as we go.

function convertToRoman(num) {
 return num;
}

convertToRoman(36);
Enter fullscreen mode Exit fullscreen mode

This is the code we are to work with, and we're to take in a number as an input and then return the value of the number in roman numerals as the output. Quite ingenious of FreeCodeCamp I'd say.

I have a feeling we're going to be creating some loops soon enough, but we'll see along the way.

The first thing I think we should do is to create a container to hold the values of the roman numerals as strings and their corresponding values in digits. For this we would use a JavaScript object.

function convertToRoman(num) {
  const numeralsObj = {
    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'
  }

}
Enter fullscreen mode Exit fullscreen mode

Here we're using an object numeralsObj to store the characters that make up any and every roman numeral value. Since we can use numbers as the key or property name to hold a value or property in a JavaScript object, we're using the corresponding digit values of each roman numeral as the key for each numeral.

Finally we have the first step down, but what is next?

Well the next thing I think we should do is have something to loop over in order to access each value of our numeralsObj object. I think using an array would do us some good in this situation so that's what we'll do next.

function convertToRoman(num) {
  let numeralsObj = {
    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'
  }

  const numberDivision = Object.keys(numeralsObj);
  // returns an array containing all the key names in the numeralsObj variable
// [1, 4, 5, 9, 10, ......, 1000]

}
Enter fullscreen mode Exit fullscreen mode

Here we are using a variable numberDivision to store an array containing each and every key of the numeralsObj variable, hence providing us with every digit value we need to loop over as the keys in our object are numbers and not strings. How exactly are we getting this array? Well the Object.keys() built in JavaScript method provides us with this functionality. It takes an object and returns an array consisting of the keys or names of all the properties of that object. And what do you know, we somehow managed to pick better names than Jupiter would have after all.
ps: sorry jupiter :')

There is a slight issue with this though. We have our array that stores the values we need, but this array is sorted by Object.keys() from lowest number to highest number. We want our array to be sorted from highest to lowest number and you'll see just why in a jiff.

We are going to use JavaScript's Array.prototype.sort() method for this. The sort method is a little bit too complex for me to fully explain in this blog, but you can always check its documentation to know more about it otherwise, this is my short explanation of what it does. It takes an array and sorts it depending on the callback function passed into it. Otherwise if there is no callback, it performs a default sorting operation.

Here is the code with the sorting method applied:

const numberDivision = Object.keys(numeralsObj).sort((a, b) => b - a);
// sorts the numbers in the array from biggest number to smallest number [1000, 900, 500, 400, ........., 1]
Enter fullscreen mode Exit fullscreen mode

In summary, the callback function we are passing into the sort method tells it to sort the numbers in the array from biggest to smallest.

Next we are going ahead to implement our loop.
we're a step ahead of you Jupiter :)... prolly cos u wearing a skirt lol...

function convertToRoman(num) {
  const numeralsObj = {
    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'
  }

  const numberDivision = Object.keys(numeralsObj).sort((a, b) => b - a);
  // returns an array containing all the key names (numbers) in the numeralsObj variable and sorts the numbers from biggest number to smallest number [1000, 900, 500, 400, ........., 1]

  let value = num
  let result = ""

  // loops over each number in the numberDivision variable
  numberDivision.forEach(division => {

  })

  return result
}
Enter fullscreen mode Exit fullscreen mode

Here we are using an Array.prototype.forEach() to loop over the numberDivision array and check all its values to see which of their corresponding roman numerals will be added to our result string, and just when we should do so.

I also created two variables, value and result.
We are using value to store the input number so we do not have to modify the original input we abide in the laws of functional programming. We are also creating result in order to store our final roman numeral string.
Notice this variables are created with let and not const because we are going to change them later in the code.

We are also returning what would be our final result at the end of the function.

hey Jupiter, it's whooping time, we wrote this code like a piece of pie
Hey, what do you think all programmers become rappers. It'd be pretty cool actually, we'd be prograppers. If you don't think that's cool, wait until you realize that rn you're just a pro grandma. How cool is that? :(...
shhhh: Jupiter can't be aware of this.

Now let's complete the code with the final puzzle piece.

function convertToRoman(num) {
  const numeralsObj = {
    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'
  }

  const numberDivision = Object.keys(numeralsObj).sort((a, b) => b - a);
  // returns an array containing all the key names (numbers) in the numeralsObj variable and sorts the numbers from biggest number to smallest number [1000, 900, 500, 400, ........., 1]

  let value = num
  let result = ""

  // loops over each number in the numberDivision variable
  numberDivision.forEach(division => {
    // checks the current number and performs code if value is greater than or equal to the number
    while (value >= division) {
      result += numeralsObj[division] // adds the numeral to our result string
      value -= division // subtracts the corresponding digit from the value variable
    }
  })

  return result // final roman numeral
}

convertToRoman(36) // returns "XXXVI"


Enter fullscreen mode Exit fullscreen mode

We have created a while loop which checks the numbers in the numberDivision array and adds their corresponding roman numerals to result if value is greater than or equal to the current number being checked. After adding the roman numeral to result, it then subtracts the corresponding digit from value and checks again until value is zero.

This is how it works:

Say we have an input of 5381, we store this input in value, then the while loop starts checking numberDivision from its first item which is 1000. It checks if 5381 is greater than or equal to 1000 and it is, so it runs the code block which adds the value of 1000 in numeralsObj which is "M" to result, then subtracts the current number being checked in numberDivision from value i.e it subtracts 1000 from value and value is now left with only 4381. It then runs the while loop again and again until value is not greater than or equal to i.e value is less than 1000, which means value is now only 381 and result is now "MMMMM" because the while loop condition returned true five times for 1000. It then checks for 900, 500 and 400 which all returns false.

100 is now checked which returns true three times until value is 81 and result is "MMMMMCCC". 90 is now checked, and returns false, then 50 is checked and returns true, so the code runs. 40 is checked which is also false, as value is now 31 and result is "MMMMMCCCL".
10 returns true three times when checked and leaves value with just 1 and result with "MMMMMCCCLXXX", then 9, 5 and 4 are checked, they all return false until the code checks 1 which returns true so value is subtracted by 1, and is now zero and result is "MMMMMCCCLXXXI".

Since value is zero and all the numbers in numberDivision have been checked, the loop stops and the JavaScript proceeds to the next step which is returning result and voila, we have our Roman Numeral.

This is why we needed our array to be in highest to lowest order because when checking, we start with a high value and not a low one or else all our results would be a series of I's to as much as input as we put in. But with the highest to lowest order our array uses, it checks the big numbers first and runs code for them if needed before going to the smaller numbers as value decreases.

These are the steps we followed:

  • First we created an object to store the roman numerals an their corresponding digits.
  • Next, we created a variable to store an array of all the digits we need to loop over.
  • Next we sorted this digits array from highest to lowest
  • Next we created two variables to store our input value and final result.
  • Next we created a forEach loop to loop over every element in our digits array.
  • Next we used a while loop to check if value is greater than or equal to the current digit in the digits array.
  • Next we passed in the required numeral to result and decreased value.
  • Lastly, we return our final result which holds the our roman numeral.

Here is the final code:

function convertToRoman(num) {
  const numeralsObj = {
    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'
  }

  const numberDivision = Object.keys(numeralsObj).sort((a, b) => b - a);

  let value = num
  let result = ""

  numberDivision.forEach(division => {
    while (value >= division) {
      result += numeralsObj[division]
      value -= division
    }
  })

  return result
}

convertToRoman(36)
Enter fullscreen mode Exit fullscreen mode

There are far more faster and lesser code demanding ways of doing this but I find this solution easier to understand, especially for newbies in development.

And just so you know Jupiter, your skirts don't scare us, we have the power of StackOverflow. And you know what they say,

he who steals code, eats gold

and feels bold btw, and probably doesn't feel cold or smthn lyk dat.

Ok enough with the rhymes I've told, they're just too much I don't think anyone is sold, I might just need to hit the road.

Thanks for reading and make sure you do not subscribe or like the post so I'll get notified to post more. And just like before, see you in the next post.

Top comments (1)

Collapse
lukeshiru profile image
Luke Shiru

A few suggestions:

At a minimum, you should move some of the constants you defined inside the function, outside the function. You don't need to re-create that every time the function is called:

const numeralsObj = {
    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",
};
const numberDivision = Object.keys(numeralsObj).sort((a, b) => b - a);

const convertToRoman = num => {
    let value = num;
    let result = "";

    numberDivision.forEach(division => {
        while (value >= division) {
            result += numeralsObj[division];
            value -= division;
        }
    });

    return result;
};
Enter fullscreen mode Exit fullscreen mode

Instead of Array.prototype.forEach you could use Array.prototype.reduce, and you could use the number you received directly instead of creating a new variable for it:

const numeralsObj = {
    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",
};
const numberDivision = Object.keys(numeralsObj).sort((a, b) => b - a);

const convertToRoman = num =>
    numberDivision.reduce((result, division) => {
        while (num >= division) {
            result += numeralsObj[division];
            num -= division;
        }
        return result;
    }, "");
Enter fullscreen mode Exit fullscreen mode

Finally, because you're using both the keys and values of numeralsObj, you could simply just store its entries using Object.entries and use that instead:

const decimalRomanTuples = Object.entries({
    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",
}).sort(([keyA], [keyB]) => keyB - keyA);

const convertToRoman = number =>
    decimalRomanTuples.reduce((result, [decimal, roman]) => {
        while (number >= decimal) {
            result += roman;
            number -= decimal;
        }
        return result;
    }, "");
Enter fullscreen mode Exit fullscreen mode

If you're interested, last month on a similar post I shared a function that would also work with negative and float numbers, and it handles the case of NaN. It requires less "nested loops" and uses more static values.

Cheers!

🌚 Friends don't let friends browse without dark mode.

Sorry, it's true.