Роман с арабскими цифрами конвертер в JavaScript


Мне дали домашнее задание в качестве технического тестирования компании, где я хотел работать на..., который я не до такой степени не будучи в состоянии даже открыть часть обзора кода процесса интервьюирования.

Их отзывы : Команда сказала, что за цифры были преобразованы в целые числа и там было много магических чисел в разных местах, было трудно следовать.

Но я действительно не понимаю, что это означает, с точки зрения моего кодекса. Может кто-то пожалуйста, объясните, что это означает, в частности, в плане кода ?

Вот пример ожидаемого поведения:

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’

Кроме того, было указано, что если библиотека называется функция (т. е. без префикса), я должен убедиться, что я все-таки сдать обратно новый объект. Также было указано, что я должна бросать исключения, а также, какие исключения и в этом случае.

Так что мое решение работает и прошел все испытания я писал, но как я сказал Раньше, считалось трудно следовать, и я действительно хотел бы понять, почему, чтобы убедиться, если снова не произойдет.

Вот мое решение :

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


Комментарии
1 ответ

Вы написали слишком много кода. Интервьюеры ненавижу пробираться через много кода. Ваше решение, вероятно, был отвергнут только на основании плохого впечатлений от его длины.

Спецификация, которая требует один и тот же объект, чтобы поддержать преобразования в обоих направлениях, немного странный на мой вкус. Тем не менее, мне кажется, что так вы написали, что все тип-обнаружение и проверка кода _getType() Это перебор. Если упражнение не указать, как должны обрабатываться вне диапазона или недопустимые значения, это честная игра, чтобы рассмотреть это неопределенное поведение, и сделать самое простое, что можно.

Спецификация не призывает к .setValue() мутатор, так что вы не должны писать.

Я согласен с их оценкой, что магия цифр не должна быть жестко повсюду. Вы создали VALUES в качестве таблицы подстановки, который был хорошим. Однако, в _getType()у вас тоже есть /^\-?[0-9]*$/ и /^([M|D|C|L|X|V|I])*$/ проверка регулярных выражений. (Последнее выражение свидетельствует об отсутствии понимания того, как классы персонажей и захватывающих скобок работа: она должна просто быть /^[MDCLXVI]+$/.) Даже хуже, _buildArrayFromNb() довольно витиевато, и дела, основанные на магии чисел 1, 4, 5, 6, 9.

Наконец, алгоритмы кажутся слишком сложными. Оба преобразования можно сделать без преобразования входного сигнала в представление промежуточного массива.

Использование термина "арабский" является технически неверным. Этот объект преобразует между римскими цифрами и числами. 40 это количество; строка '40' будет его арабские цифры представления.

Предлагаемое решение

Эти алгоритмы преобразования полностью управляется таблицы подстановки. При желании, можно восстановить некоторые проверки, выдавая исключение о неудачных поисков в течение процесса преобразования.



function RomanNumber(value) {
if (typeof value === 'string') {
let num = parse(value);
this.toInt = function() { return num; }
this.toString = function() { return value; }
} else if (typeof value === 'number') {
let str = format(value);
this.toInt = function() { return value; }
this.toString = function() { return str; }
} else {
throw 'Invalid value';
}

function parse(str) {
const VALUES = {
'M' : 1000,
'D' : 500, 'C' : 100,
'L' : 50, 'X' : 10,
'V' : 5, 'I' : 1,
};

let prev = 0, sum = 0;
for (let i = 0; i < str.length; i++) {
let curr = VALUES[str[i]];
sum += (prev < curr) ? curr - 2 * prev
: curr;
prev = curr;
}
return sum;
}

function format(num) {
const LETTERS = [
[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'],
];

let str = '';
for (let i = 0; i < LETTERS.length; i++) {
while (num >= LETTERS[i][0]) {
str += LETTERS[i][1];
num -= LETTERS[i][0];
}
}
return str;
}
}

////////////////////////////////////////////

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’




6
ответ дан 9 апреля 2018 в 10:04 Источник Поделиться