Memoized递归Fibonacci中的大整数(Y)

时间:2014-08-28 13:50:45

标签: javascript

我编写了一个Y版本,它使用memoization自动将旧值缓存在闭包中。

var Y = function (f, cache) {
    cache = cache || {};
    return function (x) {
        if (x in cache) return cache[x];
        var result = f(function (n) {
            return Y(f, cache)(n);
        })(x);
        return cache[x] = result;
    };
};

现在,当almostFibonacci(定义如下)传递给上面的函数时,它会很容易地返回一个大的Fibonacci数的值。

var almostFibonacci = function (f) {
    return function (n) {
        return n === '0' || n === '1' ? n : f(n - 1) + f(n - 2);
    };
};

然而,在某个值(Number.MAX_SAFE_INTEGER)之后,JavaScript中的整数(由于它们的 IEEE-754双精度格式)不准确。因此,考虑到上面Fibonacci函数中唯一的数学运算是加法和减法这一事实,并且因为运算符不能在JavaScript中重载,我编写了和函数和差函数的天真实现(两者都使用字符串支持大整数)如下。

String.prototype.reverse = function () {
    return this.split('').reverse().join('');
};

var difference = function (first, second) {
    first = first.reverse();
    second = second.reverse();
    var firstDigit,
    secondDigit,
    differenceDigits = [],
        differenceDigit,
        carry = 0,
        index = 0;
    while (index < first.length || index < second.length || carry !== 0) {
        firstDigit = index < first.length ? parseInt(first[index], 10) : 0;
        secondDigit = index < second.length ? parseInt(second[index], 10) : 0;
        differenceDigit = firstDigit - secondDigit - carry;
        differenceDigits.push((differenceDigit + (differenceDigit < 0 ? 10 : 0)).toString());
        carry = differenceDigit < 0 ? 1 : 0;
        index++;
    }
    differenceDigits.reverse();
    while (differenceDigits[0] === '0') differenceDigits.shift();
    return differenceDigits.join('');
};

var sum = function (first, second) {
    first = first.reverse();
    second = second.reverse();
    var firstDigit,
    secondDigit,
    sumDigits = [],
        sumDigit,
        carry = 0,
        index = 0;
    while (index < first.length || index < second.length || carry !== 0) {
        firstDigit = index < first.length ? parseInt(first[index], 10) : 0;
        secondDigit = index < second.length ? parseInt(second[index], 10) : 0;
        sumDigit = firstDigit + secondDigit + carry;
        sumDigits.push((sumDigit % 10).toString());
        carry = sumDigit > 9 ? 1 : 0;
        index++;
    }
    sumDigits.reverse();
    while (sumDigits[0] === '0') sumDigits.shift();
    return sumDigits.join('');
};

现在,这些功能本身就完美无缺。 1

我现在更新了almostFibonacci函数,如下所示,使用 sum 函数代替 + 差异函数而不是 - 运算符。

var almostFibonacci = function (f) {
    return function (n) {
        return n === '0' || n === '1' ? n : sum(f(difference(n, '1')), f(difference(n, '2')));
    };
};

正如您可能已经猜到的那样,这确实有效。如果像10这样的小数字,它会崩溃小提琴。

问题:可能出现什么问题?这里的所有功能都完美无瑕。但同时,他们似乎失败了。这里的任何人都可以帮我调试这个特别复杂的场景吗?

1 除了差函数的边缘情况。它要求第一个参数大于第二个参数。

2 个答案:

答案 0 :(得分:2)

  

现在,它们本身都可以完美地工作 - 除了差异函数的边缘情况。它要求第一个参数大于第二个参数。

这就是问题所在。在您的斐波纳契算法中,您在某个时刻计算difference("2", "2"),这需要让"0"生效。但它会返回空字符串"",它不会作为递归的保护条件进行测试。在计算difference("", "1")的下一步中,该函数将陷入无限循环。

解决方案:

  • 修复边缘情况(您仍然不需要处理负数)
  • 不要使用字符串作为序数,但仅限于斐波纳契数本身。你几乎不会尝试计算(2 53 +1)的斐波那契数,你呢?我认为这也是一个显着的速度提升。

    var fibonacci = Y(function(fib) {
        return function(n) {
            if (n == 0) return "0";
            if (n == 1) return "1";
            return sum(fib(n-1), fib(n-2));
        };
    });
    

答案 1 :(得分:-1)

以下是我解决手头问题的方法。

<强>更改

  • 我删除了while (differenceDigits[0] === '0') differenceDigits.shift();声明。即使输出差异而没有截断的前导零,如果出现像'0'这样的边缘情况,它会输出difference('2', '2')
  • 我将almostFibonacci函数中的return语句编辑为return n == 0 || n == 1 ? n : sum(f(difference(n, '1')), f(difference(n, '2')));。请注意,我使用非严格相等运算符检查0而不是'0'。 1

1 我正在n == 0而不是n === '0'的原因是因为在JavaScript中,'00000' == 0只有'00000' !== '0'在我新更新的差异函数中,没有截断的前导零,我无法保证零输出的零数。嗯,实际上我可以。 n的长度将为零。

100th Fibonacci - JSFiddle