Eder

Posted on

# Convert a Roman numeral into a number with TDD and javascript

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

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

export function romanNumeralGenerator() {
return true
}

Result:

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

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total

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

The test failed

Expected: 10

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

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
}

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

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

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

Result:

Expected: 16

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

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
}

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

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

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

Expected: 4

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)

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
}

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

### 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)}`)
})

Result:

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

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

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
}

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

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

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

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
}

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

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

Check out codesandbox