I was given some homework as a technical test by a company I wanted to work for... Which I failed to the point of not being able to even access the code review portion of the interviewing process.
Their feedback was : The team had said because the numerals were converted to integers and there were lots of magic numbers in different places, it was hard to follow.
But I really do not understand what it means, in regards to my code. Can someone please explain, what this means specifically, in terms of code ?
Here is an example of the expected behaviour:
var romanNumber1 = new RomanNumber('XX');
var romanNumber2 = new RomanNumber(40);
console.log(romanNumber1.toInt()); // => 20
console.log(romanNumber1.toString()); // => 'XX'
console.log(romanNumber2.toInt()); // => 40
console.log(romanNumber2.toString()); // => ‘XL’
Also, it was specified that if the library is called as a function (i.e. without the new prefix), I should ensure I still pass back a new object. It was also specified that I should throw exceptions and also, what exceptions and in which case.
So my solution works and passed all the extensive tests I wrote, but as I said earlier, it was deemed hard to follow, and I really would like to understand why to ensure if doesn't happen again.
Here is my solution :
const FAILURE = -1
const ROMAN = 1
const ARABIC = 2
const VALUES = {
'M' : 1000,
'D' : 500,
'C' : 100,
'L' : 50,
'X' : 10,
'V' : 5,
'I' : 1
};
const KEY_ENTRIES = Object.keys(VALUES)
const VALUE_ENTRIES = KEY_ENTRIES.map(k=> VALUES[k]);
function RomanNumber(value) {
/*
** _getType(value)
** Returns numeral type, FAILURE, ROMAN or ARABIC,
** Throws exceptions if invalid
*/
this._getType = function() {
let value = this._value;
if (value == null || (typeof value == 'string' && !value)) {
this._type = FAILURE;
throw("value required");
}
if (typeof value == 'string') {
if (/^\-?[0-9]*$/.test(value)) {
value = parseInt(value)
if (value < 1 || value > 3999) {
this._type = FAILURE;
throw("invalid range");
}
this._value = value;
this._type = ARABIC;
}
else if (/^([M|D|C|L|X|V|I])*$/.test(value)) {
if (this._checkRomanIsValid(value))
this._type = ROMAN;
else {
this._type = FAILURE;
throw("invalid value");
}
}
else {
this._type = FAILURE;
throw("invalid value");
}
}
else if (typeof value == 'number') {
if (value < 1 || value > 3999) {
this._type = FAILURE;
throw("invalid range");
}
if (value % 1) {
this._type = FAILURE;
throw("invalid value");
}
this._type = ARABIC;
}
return this._type;
}
/*
** _checkRomanIsValid(str)
** Checks if the roman numeral string is valid,
** eg: IIX is valid, while IIIIX is not
*/
this._checkRomanIsValid = function(str) {
let count = 0;
let last = null;
for (var i = 0; i < str.length; i++) {
if (last != null && last == str[i])
count += 1;
else if (last != null)
count = 0;
if (count >= 3)
return false;
last = str[i];
}
return true;
}
/*
** _buildArrayFromStr(value)
** Builds a value array from a roman numeral string
** eg MMMDXXVII => [1000,1000,1000,500,10,10,5,1,1]
*/
this._buildArrayFromStr = function() {
let last = null;
let res = [];
for (var i = 0; i < this._value.length; i++) {
if (last != null && last < VALUES[this._value[i]])
res[res.length - 1] = VALUES[this._value[i]] - last;
else
res.push(VALUES[this._value[i]]);
last = VALUES[this._value[i]];
}
this._valueArray = res;
}
/*
** _buildArrayFromNb(value)
** Builds a value array from an arabic number
** eg 1992 => [1000, 100, 1000, 10, 100, 1, 1]
*/
this._buildArrayFromNb = function() {
let res = [];
let str = this._value.toString();
let len = str.length - 1;
for (var i = 0; i < str.length; i++) {
let pow = 1;
for (var j = 0; j < len; j++)
pow *= 10;
if ((str[i] >= 1 && str[i] < 4)) {
for (var k = 0; k < str[i]; k++)
res.push(pow);
}
else if ((str[i] == 4)) {
res.push(pow);
res.push(pow / 2 * 10);
}
else if ((str[i] == 5)) {
res.push(pow / 2 * 10);
}
else if ((str[i] >= 6 && str[i] < 9)) {
res.push(pow / 2 * 10);
for (var k = 0; k < str[i] - 5; k++)
res.push(pow);
}
else if ((str[i] == 9)) {
res.push(pow);
res.push(pow * 10);
}
len -= 1;
}
this._valueArray = res;
}
/*
** toString()
** Returns the roman numeral value
*/
this.toString = function() {
if (this._type == FAILURE)
return FAILURE;
if (this._type == ROMAN)
return this._value;
this._buildArrayFromNb(this._value);
let res = "";
this._valueArray.forEach(function(elem){
res += KEY_ENTRIES[VALUE_ENTRIES.indexOf(elem)];
});
return res;
}
/*
** toInt()
** Returns the arabic numeral value
*/
this.toInt = function() {
if (this._type == FAILURE)
return FAILURE;
if (this._type == ARABIC)
return this._value;
this._buildArrayFromStr(this._value);
let res = 0;
this._valueArray.forEach(function(elem){
res += elem;
});
return res;
}
/*
** getters/setters
*/
this.setValue = function(value) {
this._value = value;
}
/*
** ctor : RomanNumber(value)
*/
this._value = value;
if (value)
this._type = this._getType();
}
module.exports = function(value) {
return new RomanNumber(value);
};
this
this making them public. I know it's just starters and not answering your question, that's why I'm only commenting :) \$\endgroup\$