Сохранение строковой переменной во время рекурсии функций - JavaScript

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

В каждой рекурсии в моем коде строка, содержащая римские цифры, сбрасывается на "".

Как лучше всего поддерживать подобные переменные с помощью рекурсий в функции?

Я попытался объявить переменную romanStr = "" в глобальной области видимости и удалить условное объявление в программе, и, конечно же, это сработало. Однако я знаю, что это худшая практика.

Например, возьмите число 1234, которое преобразовано в римские цифры как «MCCXXXIV». Моя программа вернет только "IV", результат последней рекурсии.

function convertToRoman(num) {
  console.info(`START FUNCTION FROM THE BEGINNING`);
  console.info(`current num: ${num}`);
  if (typeof romanStr === "undefined") {
    var romanStr = "";
  }
  const bNumbers = [1000, 500, 100, 50, 10, 5, 1];
  const romanSymbols = {
    0: ["M"],
    2: ["C", "D", "M"],
    4: ["X", "L", "C"],
    6: ["I", "V", "X"]
  };
  const arraySelector = arrNum => num >= arrNum;
  let symbolSetIndex = bNumbers.findIndex(arraySelector);
  console.info(`symbolSetIndex: ${symbolSetIndex}`);
  let symbolSet = romanSymbols[symbolSetIndex];
  console.info(`symbolSet: [${symbolSet}]`);
  let numString = num.toString();
  let numeral = parseInt(numString[0]);
  console.info(`numeral: ${numeral}`);
  let nextNum = parseInt(numString.substr(1));
  console.info(`nextNum: ${nextNum}`);

  // CONDITIONAL STATEMENTS //
  if (symbolSetIndex === 0) {
    for (let i = 1; i <= numeral; i++) {
      romanStr = `${romanStr}${symbolSet[0]}`;
    }
    return convertToRoman(nextNum);
  }
  if (numeral < 4) {
    for (let i = 1; i <= numeral; i++) {
      romanStr = `${romanStr}${symbolSet[0]}`;
    }
  }
  if (numeral === 4) {
    romanStr = `${romanStr}${symbolSet[0]}${symbolSet[1]}`;
  }
  if (numeral === 5) {
    romanStr = `${romanStr}${symbolSet[1]}`;
  }
  if (numeral === 6) {
    romanStr = `${romanStr}${symbolSet[1]}${symbolSet[0]}`;
  }
  if (numeral > 6) {
    romanStr = `${romanStr}${symbolSet[1]}`; // requires the 5 numeral first
    for (let i = 1; i <= numeral - 6; i++) {
      romanStr = `${romanStr}${symbolSet[0]}`;
    }
  }
  if (numeral === 9) {
    romanStr = `${romanStr}${symbolSet[2]}${symbolSet[1]}`;
  }

  if (numString.length === 1) {
    return romanStr;
  }
  return convertToRoman(nextNum);
}

console.info(convertToRoman(5214));

Почему бы не передать текущее значение в качестве параметра следующему вызову?

Tripp Kinetics 31.03.2018 01:52
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
0
1
44
2

Ответы 2

Вам не нужно сохранять переменную во всех вызовах. Объедините текущее значение со значением, возвращаемым рекурсией.

function convertToRoman(num) {
  console.info(`START FUNCTION FROM THE BEGINNING`);
  console.info(`current num: ${num}`);
  var romanStr;
  const bNumbers = [1000, 500, 100, 50, 10, 5, 1];
  const romanSymbols = {
    0: ["M"],
    2: ["C", "D", "M"],
    4: ["X", "L", "C"],
    6: ["I", "V", "X"]
  };
  const arraySelector = arrNum => num >= arrNum;
  let symbolSetIndex = bNumbers.findIndex(arraySelector);
  console.info(`symbolSetIndex: ${symbolSetIndex}`);
  let symbolSet = romanSymbols[symbolSetIndex];
  console.info(`symbolSet: [${symbolSet}]`);
  let numString = num.toString();
  let numeral = parseInt(numString[0]);
  console.info(`numeral: ${numeral}`);
  let nextNum = parseInt(numString.substr(1));
  console.info(`nextNum: ${nextNum}`);

  // CONDITIONAL STATEMENTS //
  if (symbolSetIndex === 0) {
    for (let i = 1; i <= numeral; i++) {
      romanStr = `${romanStr}${symbolSet[0]}`;
    }
    return romanStr + convertToRoman(nextNum);
  }
  if (numeral < 4) {
    for (let i = 1; i <= numeral; i++) {
      romanStr = `${romanStr}${symbolSet[0]}`;
    }
  }
  if (numeral === 4) {
    romanStr = `${romanStr}${symbolSet[0]}${symbolSet[1]}`;
  }
  if (numeral === 5) {
    romanStr = `${romanStr}${symbolSet[1]}`;
  }
  if (numeral === 6) {
    romanStr = `${romanStr}${symbolSet[1]}${symbolSet[0]}`;
  }
  if (numeral > 6) {
    romanStr = `${romanStr}${symbolSet[1]}`; // requires the 5 numeral first
    for (let i = 1; i <= numeral - 6; i++) {
      romanStr = `${romanStr}${symbolSet[0]}`;
    }
  }
  if (numeral === 9) {
    romanStr = `${romanStr}${symbolSet[2]}${symbolSet[1]}`;
  }

  if (numString.length === 1) {
    return romanStr;
  }
  return romanStr + convertToRoman(nextNum);
}

console.info(convertToRoman(5214));

Это отличное решение. Нет необходимости создавать совершенно новую оболочку - просто верните строку, связанную с вызовом функции. Это одно из тех решений, где я не знаю «КАК» или «ПОЧЕМУ» это работает, просто я знаю, что это работает. Не могли бы вы объяснить, почему работает объединение строки?

Victor Evangelista 31.03.2018 02:06

Если число 532, вы вычисляете римскую цифру 500, с D. Затем вы вызываете его рекурсивно для 32, который возвращает XXXII. Результат, который вам нужен, - это их объединение, DXXXII.

Barmar 31.03.2018 02:08

Вы всегда должны думать о рекурсии так: как упростить задачу и как совместить текущий случай с результатом более простой задачи.

Barmar 31.03.2018 02:10

Рекурсия - это функциональное наследие, поэтому ее использование в функциональном стиле даст наилучшие результаты. Алгоритм римских цифр был одной из тех вещей, которые действительно поразили меня, когда я впервые увидел, что он выражен в функциональном стиле: CodeReview.SE: преобразование в римские цифры.

@sdcvvc обеспечивает красивую кодировку

toRoman :: Integer -> String
toRoman 0 = "N"
toRoman x | x > 0 = snd $ foldl f (x,[]) convMap
  where f (n,s) (rn, rs) = (l, s ++ concat (genericReplicate k rs))
              where (k,l) = divMod n rn

Простота поистине поразительна. Я не могу взять на себя ответственность за какую-либо часть алгоритма, представленного выше, но я могу перевести его на JavaScript для вас.

Я разделяю это, потому что это показывает вам способ подойти к вашей проблеме с совершенно другой точки зрения. Другие ответы, представленные в ветке CodeReview, дают еще больше информации. Я настоятельно рекомендую вам проверить их: D

const divMod = (n, d, k) =>
  k (n / d >> 0, n % d)

const foldl = (f, init, xs) =>
  xs.reduce (f, init)

const replicate = (n, s) =>
  s.repeat (n)

const snd = ([ _, x ]) =>
  x

const convMap =
  [ [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 toRoman = (x = 0) =>
  x === 0
    ? "N"
    : snd ( foldl ( ([ n, s ], [ rn, rs ]) =>
                      divMod (n, rn, (k, l) =>
                        [ l, s + replicate (k, rs) ])
                  , [ x, [] ]
                  , convMap
                  )
          )

console.info
  ( toRoman (0)       // N
  , toRoman (7)       // VII
  , toRoman (66)      // LXVI
  , toRoman (99)      // XCIX
  , toRoman (1984)    // MCMLXXXIV
  )

Другие вопросы по теме