不适合递归
虽然已经提出的小修正修复了您的功能,但我想建议这个版本显示出对递归的误解。
您的函数正在使用递归来更新 while 循环中的局部变量。可以这样改写:
var reverse = function (s) {
var r = "";
var n = s.length - 1;
var i = 0;
if (n < 1) {
return s;
}
while (i <= n) {
r = r + s[n - i]
i++
}
return r
};
console .log (reverse ('abcde'))
您使用的递归只是编写相同代码的另一种方式。
递归意味着一些不同的东西。我们希望我们的算法匹配我们的数据结构。为了使递归真正有意义,我们应该在递归数据结构上使用它。
但我们可以解决这个问题。
递归地思考一个字符串
通常有许多不同的方式来考虑单个数据结构。我们可以这样认为字符串是一个递归结构:
一个字符串要么是
我们可以将非空字符串的两个部分称为head 和tail。
在递归算法中使用递归定义
通过这个公式,我们可以编写一个匹配结构的递归算法:
字符串的反转要么是
- 空字符串的反转(仍然是空字符串),或者
- 尾部反转,然后是头部
然后代码实际上是自己编写的:
const reverse = (string) =>
string .length == 0
? ""
: reverse (string .slice (1)) + string[0]
console .log (reverse ('abcdefg'))
变体
这当然有可能的变化。当尾调用优化最终在 JS 环境中普遍存在时,我们可能希望做好准备,并且可以将其更改为尾递归版本,例如
const reverse = (string, result = "") =>
string .length == 0
? result
: reverse (string .slice (1), string [0] + result)
(请注意,此样式存在潜在问题。默认参数可能会妨碍在某些上下文中使用该函数。例如,您无法使用此版本成功调用 strings .map (reverse)。您可以通过以下方式执行此操作把它分成一个辅助函数和一个主函数,但这把我们带到了一个领域。)
或者,利用字符串是可迭代的事实,我们可以使用参数解构和默认值编写一个非常简单的版本:
const reverse = ([head = undefined, ...tail]) =>
head == undefined
? ""
: reverse (tail) + head
递归的优点
很难想象会比这简单得多。这个版本非常直接地捕获了算法。该算法与我们对数据结构的描述完全相关。
请注意这些函数都不使用的一件事:局部变量。简单递归通常可以以不需要它们的方式编写。这是一个重大的祝福,因为这意味着可变状态无处可藏。而可变状态是很多很多编程错误的根源。