【问题标题】:Maintaining String Variable during Function Recursions - JavaScript在函数递归期间维护字符串变量 - JavaScript
【发布时间】:2018-09-09 23:45:39
【问题描述】:

我创建了一个简单的函数,用于将数字转换为罗马数字。除了一件事,似乎一切都正常。

通过我的代码中的每次递归,保存罗马数字的字符串被重置为“”。

通过函数中的递归来维护此类变量的最佳做法是什么?

我尝试在全局范围内声明变量 romanStr = "",并删除程序中的条件声明,当然这奏效了。然而,我知道这是最糟糕的做法。

以数字1234为例,转换成罗马数字就是“MCCXXXIV”。我的程序只会返回最后一次递归的结果“IV”。

function convertToRoman(num) {
  console.log(`START FUNCTION FROM THE BEGINNING`);
  console.log(`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.log(`symbolSetIndex: ${symbolSetIndex}`);
  let symbolSet = romanSymbols[symbolSetIndex];
  console.log(`symbolSet: [${symbolSet}]`);
  let numString = num.toString();
  let numeral = parseInt(numString[0]);
  console.log(`numeral: ${numeral}`);
  let nextNum = parseInt(numString.substr(1));
  console.log(`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.log(convertToRoman(5214));

【问题讨论】:

  • 为什么不将正在进行的值作为参数传递给下一次调用?

标签: javascript string variables recursion scope


【解决方案1】:

您不需要在所有调用中保留变量。将当前值连接到递归返回的值。

function convertToRoman(num) {
  console.log(`START FUNCTION FROM THE BEGINNING`);
  console.log(`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.log(`symbolSetIndex: ${symbolSetIndex}`);
  let symbolSet = romanSymbols[symbolSetIndex];
  console.log(`symbolSet: [${symbolSet}]`);
  let numString = num.toString();
  let numeral = parseInt(numString[0]);
  console.log(`numeral: ${numeral}`);
  let nextNum = parseInt(numString.substr(1));
  console.log(`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.log(convertToRoman(5214));

【讨论】:

  • 这是一个很好的解决方案。无需创建全新的包装器 - 只需返回与函数调用连接的字符串。这是我不知道“如何”或“为什么”起作用的那些解决方案之一,只是它起作用了。你能解释一下为什么连接字符串有效吗?
  • 如果数字是532,你计算罗马数字为500,就是D。然后你为32递归调用它,它返回XXXII。你想要的结果是它们的串联,DXXXII
  • 这始终是您应该如何考虑递归:如何简化问题,以及如何将当前案例与更简单问题的结果结合起来。
【解决方案2】:

递归是一种函数式遗产,因此将其与函数式风格一起使用将产生最佳效果。罗马数字算法是我第一次看到它以函数式表示时真正让我大吃一惊的事情之一:CodeReview.SE: Converting to Roman Numerals

@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.log
  ( toRoman (0)       // N
  , toRoman (7)       // VII
  , toRoman (66)      // LXVI
  , toRoman (99)      // XCIX
  , toRoman (1984)    // MCMLXXXIV
  )

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多