折叠函数参数的最佳方法是什么?

4

我有一个函数f,可以用任意参数调用。当使用2个参数调用它时,它会执行操作。当使用>2个参数调用它时,它必须自己折叠其他参数。也就是说,当我们调用f(a,b,c,d)时,函数应该重新排列为f(f(f(a,b),c,d)。我需要这个尽可能优化。我提供了两种解决方案并对它们进行了基准测试:

alphabet = 'abcdefhijklmnopqrstuvwxyz'.split('');
var last_mark;
benchmark = function(msg){ 
  alert(msg.replace('$TIMEDIFF',Date.now()-last_mark)); 
  last_mark=Date.now(); 
};
fa = function(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z){
    if (c) return fa(fa(a,b),c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z);
    return a+b;
};
fb = function(a,b,rest){
    if (rest) return fb.apply(this,[fb(a,b)].concat(Array.prototype.slice.call(arguments,2)));
    return a+b;
};
benchmark("Starting benchmark:");
for (var i=0; i<100000; ++i) fa.apply(this,alphabet);
benchmark("Function 1: $TIMEDIFF");
for (var i=0; i<100000; ++i) fb.apply(this,alphabet);
benchmark("Function 2: $TIMEDIFF");

第一种解决方案更快(在node.js上为200毫秒,而不是4000毫秒)。这个可以进一步优化吗?

2
可能更适合于http://codereview.stackexchange.com/。 - Felix Kling
谢谢。有一些我不知道的有趣的StackExchange。 - MaiaVictor
1
说实话,你不能真正比较这两个函数,因为它们的工作方式不同。fa不能接受无限数量的参数。fb需要更多时间,因为它在分配数组,但如果你想要一个可变函数,它似乎是必要的。 - Felix Kling
没错。我会用荒谬的长度来测试并检查结果。 - MaiaVictor
@FelixKling 是的,这是一个问题。当有26*26+26-4个参数时,它花费的时间几乎相同。 - MaiaVictor
4个回答

2

ES5引入了一个.reduce()函数来完成这个任务。它会调用一些函数来处理前两个项目,然后再次调用该函数,传递第一个调用的返回值和第三个项目,然后再次调用该函数,传递 那个 调用的返回值和第四个项目,以此类推。

var f = function () {
    return toArray( arguments ).reduce(function ( a, b ) {
        return a + b;    
    });
};

在线演示: http://jsfiddle.net/hpAtB/1/


我喜欢使用reduce。不知道为什么我没想到用它。顺便说一下,我将你的代码添加到了这个测试中。在Firefox上有点慢,但在Chrome中表现非常好。http://jsperf.com/varargs-fold/4 - I Hate Lazy

1

类似这样的操作可以减少数组的创建和操作。

fa = function(a,b){
    function recursive(a, b, rest) {
        if (rest && rest.length) 
            return recursive(recursive(a,b), rest.pop(), rest);
        return a+b;
    }
    return recursive(a, b, Array.prototype.slice.call(arguments, 2).reverse());
};

或者像这样将函数调用次数减半:

fa = function(a,b) {
    function recursive(a, b, rest) {
        var result = a + b;
        return rest.length ? recursive(result, rest.pop(), rest) : result;
    }
    return recursive(a, b, Array.prototype.slice.call(arguments, 2).reverse());
};

我假设使用.pop()代替.shift()的收益值得.reverse()的成本。你可以尝试两种方式。


当然,您也可以重复使用该函数:

fa = (function() {
    function recursive(a, b, rest) {
        var result = a + b;
        return rest.length ? recursive(result, rest.pop(), rest) : result;
    }
    return function(a,b) {
        return recursive(a, b, Array.prototype.slice.call(arguments, 2).reverse());
    };
})();

1
为什么不使用对象作为参数?
var o = {
  a:value1,
  b:value2,
  ...
  z:value26
};

fa(o);

function fa(obj){
  if(obj.c){  // or any other logic
    return fa(obj);
  }
  return o;
}

或全局变量(无参数)

fa();

function fa(){
  if(o.c){  // or any other logic
    o.c = o.d + o.e;
    fa();
  }
}

1
最快的方法似乎是用迭代替换递归。
定义高阶函数 folder
var folder = function(func) {
   return function() {
            var args = Array.prototype.slice.call(arguments), len = args.length, result = args[0], i = 1;
            for(; i < len; i++){
               result = func.call(this, result, args[i]); 
            }

           return result;
   };
};

请查看这个性能测试:http://jsperf.com/varargs-fold


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接