4

I have a function f that can be called with arbitrary arguments. When it is called with 2 arguments, it performs an operation. When it is called with >2 arguments, it must itself fold the others. That is, when we call f(a,b,c,d), the function should rearrange as f(f(f(a,b),c,d). I need this to be as optimized as possible. I come with 2 solutions and benchmarked them:

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");

The first solution was faster (200ms vs 4000ms on node.js). Can this be optimized even further?

Lee Taylor
  • 7,761
  • 16
  • 33
  • 49
MaiaVictor
  • 51,090
  • 44
  • 144
  • 286

4 Answers4

2

ES5 introduced a .reduce() function for that exact task. It invokes some function for the first two items, then invokes it again passing the return value of the first call and the third item, then invokes it again passing the return value of that call and the fourth item, and so on.

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

Live demo: http://jsfiddle.net/hpAtB/1/

Šime Vidas
  • 182,163
  • 62
  • 281
  • 385
  • I love using reduce. Don't know why I didn't think to use it. FWIW, I added your code to this test. A little slow in FF, but performs very nicely in Chrome. http://jsperf.com/varargs-fold/4 – I Hate Lazy Nov 18 '12 at 22:17
1

Something like this will reduce the Array creation and manipulation

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());
};

Or like this to cut the number of function calls in half:

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());
};

I'm assuming the cost of the .reverse() is worth the gain of using .pop() instead of .shift(). You may want to try it both ways.


Of course you could reuse the function as well:

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());
    };
})();
I Hate Lazy
  • 47,415
  • 13
  • 86
  • 77
1

Why do you not use object as argument?

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;
}

or global variable (without arguments)

fa();

function fa(){
  if(o.c){  // or any other logic
    o.c = o.d + o.e;
    fa();
  }
}
Boris Gappov
  • 2,483
  • 18
  • 23
1

The fastest way seems to replace recursion with iteration.

Define higher order function 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;
   };
};

Check this perf test: http://jsperf.com/varargs-fold

Yury Tarabanko
  • 44,270
  • 9
  • 84
  • 98