Я создал простую функцию, которая должна переводить число в римские цифры. Похоже, что все работает нормально, кроме одного.
В каждой рекурсии в моем коде строка, содержащая римские цифры, сбрасывается на "".
Как лучше всего поддерживать подобные переменные с помощью рекурсий в функции?
Я попытался объявить переменную 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));


![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Вам не нужно сохранять переменную во всех вызовах. Объедините текущее значение со значением, возвращаемым рекурсией.
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));Это отличное решение. Нет необходимости создавать совершенно новую оболочку - просто верните строку, связанную с вызовом функции. Это одно из тех решений, где я не знаю «КАК» или «ПОЧЕМУ» это работает, просто я знаю, что это работает. Не могли бы вы объяснить, почему работает объединение строки?
Если число 532, вы вычисляете римскую цифру 500, с D. Затем вы вызываете его рекурсивно для 32, который возвращает XXXII. Результат, который вам нужен, - это их объединение, DXXXII.
Вы всегда должны думать о рекурсии так: как упростить задачу и как совместить текущий случай с результатом более простой задачи.
Рекурсия - это функциональное наследие, поэтому ее использование в функциональном стиле даст наилучшие результаты. Алгоритм римских цифр был одной из тех вещей, которые действительно поразили меня, когда я впервые увидел, что он выражен в функциональном стиле: 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
)
Почему бы не передать текущее значение в качестве параметра следующему вызову?