Convert a Roman numeral into a number with TDD and javascript

emunhoz

Eder

Posted on June 3, 2022

Convert a Roman numeral into a number with TDD and javascript

About the challenge

Convert a Roman numeral into a number is a classic coding challenge! We are going to solve this challenge using TDD.

Write a function to convert from Roman Numerals to normal numbers.

First test scenario

Let's get start creating a simple test thats return true:

describe('[Function]: romanNumeralGenerator', () => {
  test('return true', () => {
    expect(romanNumeralGenerator()).toBe(true)
  })
})
Enter fullscreen mode Exit fullscreen mode

Then, let's create the function romanNumeralGenerator():

export function romanNumeralGenerator() {
  return true
}
Enter fullscreen mode Exit fullscreen mode

Result:

 PASS  ./index.test.js
  [Function]: romanNumeralGenerator
     return true (1 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Enter fullscreen mode Exit fullscreen mode

Ok! Nothing new so far...

Second test scenario

Let's do the first valid test. I want receive a valid integer number if I pass only one roman numeral as argument:

test('convert roman number into a number', () => {
  expect(romanNumeralGenerator('X')).toBe(10)
})
Enter fullscreen mode Exit fullscreen mode

The test failed

Expected: 10
Received: true

      3 | describe('[Function]: romanNumeralGenerator', () => {
      4 |   test('convert one roman number into a number', () => {
    > 5 |     expect(romanNumeralGenerator('X')).toBe(10)
        |                                        ^
      6 |   })
Enter fullscreen mode Exit fullscreen mode

Time to do the first refactor in our function. Let's create a const with all the roman numerals and assign their respective values to each number.

const romanNumbers = {
  I: 1,
  V: 5,
  X: 10,
  L: 50,
  C: 100,
  D: 500,
  M: 1000
}
Enter fullscreen mode Exit fullscreen mode

Now the return of our function will receive the const romanNumbers and let's go through the object looking for the value of the argument romanNumbers[number]

export function romanNumeralGenerator(number) {
  return romanNumbers[number]
}

console.log(romanNumeralGenerator('X')) // 10
Enter fullscreen mode Exit fullscreen mode

Result:

PASS  ./index.test.js
  [Function]: romanNumeralGenerator
     convert one roman number into a number (1 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Enter fullscreen mode Exit fullscreen mode

Third test case

What about more than one roman numeral?

test('convert more than one roman number into a number', () => {
  expect(romanNumeralGenerator('XVI')).toBe(16)
})
Enter fullscreen mode Exit fullscreen mode

Result:

Expected: 16
Received: undefined

       7 |
       8 |   test('convert more than one roman number into a number', () => {
    >  9 |     expect(romanNumeralGenerator('XVI')).toBe(16)
         |                                          ^
      10 |   })
Enter fullscreen mode Exit fullscreen mode

Let's make our function return values when receive more than one digit.
First we use spread operator to create an array with all values and then we use map function to convert each roman value in integer. Then we calculate all converted values. We'll use reduce for it.

export function romanNumeralGenerator(number) {
  const convertRomanNumbersToIntegers = [...number].map(romanValue => romanNumbers[romanValue])
  const calculateConvertedRomanValues = convertRomanNumbersToIntegers.reduce((accumulator, currentValue) => accumulator + currentValue, 0)

  return calculateConvertedRomanValues
}
Enter fullscreen mode Exit fullscreen mode

Result:

PASS  ./index.test.js
  [Function]: romanNumeralGenerator
     convert one roman number into a number (1 ms)
     convert more than one roman number into a number 😊😊😊

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Enter fullscreen mode Exit fullscreen mode

Fourth test scenario

The next step is about how to subtract values in case we have something like this: IV, IX, XIV

test('subtract the value if the next is greater than the current value', () => {
  expect(romanNumeralGenerator('IV')).toBe(4)
  expect(romanNumeralGenerator('IX')).toBe(9)
  expect(romanNumeralGenerator('XIV')).toBe(14)
  expect(romanNumeralGenerator('XXIX')).toBe(29)
})
Enter fullscreen mode Exit fullscreen mode

Result:

     subtract the value if the next is greater than the current value (1 ms)

   [Function]: romanNumeralGenerator  subtract the value if the next is greater than the current value

    expect(received).toBe(expected) // Object.is equality

    Expected: 4
    Received: 6 😓😓😓😓

      11 |
      12 |   test('subtract the value if the next is greater than the current value', () => {
    > 13 |     expect(romanNumeralGenerator('IV')).toBe(4)
         |                                         ^
      14 |     expect(romanNumeralGenerator('IX')).toBe(9)
Enter fullscreen mode Exit fullscreen mode

We need to check if the next value (inside calculateConvertedRomanValues) is greater than the current value, if so, we subtract the current value from the acc. E.g: the next value is 5 and the current one is 1, so, 5 - 1 = 4.

In a reduce function we have 4 default arguments reduce((accumulator, currentValue, index, array). First, we will grab the next value, then, the condition to check if next value is greater than the current value and subtract:

export function romanNumeralGenerator(number) {
  const convertRomanNumbersToIntegers = [...number].map(romanValue => romanNumbers[romanValue])

  const calculateConvertedRomanValues = convertRomanNumbersToIntegers.reduce((accumulator, currentValue, index, array) => {
    const nextValue = array[index + 1] // =====> get next value

    if (nextValue > currentValue) { // =====> check next value is greater than currentValue
      return accumulator - currentValue
    }

    return accumulator + currentValue
  }, 0)

  return calculateConvertedRomanValues
}
Enter fullscreen mode Exit fullscreen mode

Result:

PASS  ./index.test.js
  [Function]: romanNumeralGenerator
     convert one roman number into a number (1 ms)
     convert more than one roman number into a number
     subtract the value if the next is greater than the current value (2 ms) 🚀🚀🚀

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Enter fullscreen mode Exit fullscreen mode

Bonus

Let's verify if the argument has only valid roman numbers and then transform the value in uppercase.

test('verify each value is a valid roman number', () => {
    expect(romanNumeralGenerator('WRONG')).toBe(`Invalid value! Make sure you are using only roman numbers ${Object.keys(romanNumbers).map(value => value)}`)
})
Enter fullscreen mode Exit fullscreen mode

Result:

Expected: "Invalid value! Make sure you are using only roman numbers I,V,X,L,C,D,M"
Received: NaN

      19 |
      20 |   test('verify each value is a valid roman number', () => {
    > 21 |     expect(romanNumeralGenerator('WRONG')).toBe(`Invalid value! Make sure you are using only roman numbers ${Object.keys(romanNumbers).map(value => value)}`)
         |                                            ^
      22 |   })
Enter fullscreen mode Exit fullscreen mode

Refactor!
Let's go back to the function and check each value. For the first case, we need verify if convertRomanNumbersToIntegers has an undefined value cause in the romanNumbers we already have all roman numbers available.

export function romanNumeralGenerator(number) {
  const convertRomanNumbersToIntegers = [...number].map(romanValue => romanNumbers[romanValue])

// +++
  if (convertRomanNumbersToIntegers.includes(undefined)) return `Invalid value! Make sure you are using only roman numbers ${Object.keys(romanNumbers).map(value => value)}`
// +++

  const calculateConvertedRomanValues = convertRomanNumbersToIntegers.reduce((accumulator, currentValue, index, array) => {
    const nextValue = array[index + 1] // =====> get next value

    if (nextValue > currentValue) { // =====> check next value is greater than currentValue
      return accumulator - currentValue
    }

    return accumulator + currentValue
  }, 0)

  return calculateConvertedRomanValues
}
Enter fullscreen mode Exit fullscreen mode

Test result:

PASS  ./index.test.js
  [Function]: romanNumeralGenerator
     convert one roman number into a number (1 ms)
     convert more than one roman number into a number
     subtract the value if the next is greater than the current value (1 ms)
     verify each value is a valid roman number 😊😊😊

Test Suites: 1 passed, 1 total
Tests:       4 passed, 4 total
Enter fullscreen mode Exit fullscreen mode

Last but not least, let's convert the argument received in the function to uppercase.

test('convert the argument received in the function to uppercase', () => {
  expect(romanNumeralGenerator('xxvi')).toBe(26)
})
Enter fullscreen mode Exit fullscreen mode

Test result:

Expected: 26
Received: "Invalid value! Make sure you are using only roman numbers I,V,X,L,C,D,M"

      23 |
      24 |   test('convert the argument received in the function to uppercase', () => {
    > 25 |     expect(romanNumeralGenerator('xxvi')).toBe(26)
         |                                           ^
      26 |   })
      27 | })
Enter fullscreen mode Exit fullscreen mode

Refactor!
Let's use str.toUpperCase() to do it:

export function romanNumeralGenerator(number) {
// +++
  const transformValuesInUppercase = number.toUpperCase()
// +++

  const convertRomanNumbersToIntegers = [...transformValuesInUppercase].map(romanValue => romanNumbers[romanValue])

  if (convertRomanNumbersToIntegers.includes(undefined)) return `Invalid value! Make sure you are using only roman numbers ${Object.keys(romanNumbers).map(value => value)}`

  const calculateConvertedRomanValues = convertRomanNumbersToIntegers.reduce((accumulator, currentValue, index, array) => {
    const nextValue = array[index + 1] // =====> get next value

    if (nextValue > currentValue) { // =====> check next value is greater than currentValue
      return accumulator - currentValue
    }

    return accumulator + currentValue
  }, 0)

  return calculateConvertedRomanValues
}
Enter fullscreen mode Exit fullscreen mode

Test result:

PASS  ./index.test.js
  [Function]: romanNumeralGenerator
     convert one roman number into a number (1 ms)
     convert more than one roman number into a number (1 ms)
     subtract the value if the next is greater than the current value
     verify each value is a valid roman number
     convert the argument received in the function to uppercase 😊😊😊😊

Test Suites: 1 passed, 1 total
Tests:       5 passed, 5 total
Enter fullscreen mode Exit fullscreen mode

That's it.
It's was a simple case in how we can start thinking in TDD.
Hope you enjoy it!

Check out codesandbox

💖 💪 🙅 🚩
emunhoz
Eder

Posted on June 3, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related