6

Consider following arrays:

var array1 = [true, false];
var array2 = [1, 2];
var array3 = ["a", "b", "c"];

I want to call my function myFunc(arg1, arg2, arg3) with all argument combinations. But I want to avoid to "foreach" hell.

Is it possible write function that allows me that, so i can call it some like:

cartesianCall(array1, array2, array3, myFunc);

ideally with variable count of arrays (myFunc arguments)?

EDIT: so function would be called:

myFunc(true, 1, "a");
myFunc(true, 1, "b");
myFunc(true, 1, "c");
myFunc(true, 2, "a");
myFunc(true, 2, "b");
myFunc(true, 2, "c");
myFunc(false, 1, "a");
myFunc(false, 1, "b");
myFunc(false, 1, "c");
myFunc(false, 2, "a");
myFunc(false, 2, "b");
myFunc(false, 2, "c");
zs2020
  • 53,766
  • 29
  • 154
  • 219
vaclav_o
  • 1,705
  • 3
  • 15
  • 24

2 Answers2

5

Declare you function without parameters and use arguments keyword:

function cartesianCall() {
  for (var i = 0; i < arguments.length; i++) {
     // do something with arguments[i]
  }
}
Yuriy Galanter
  • 38,833
  • 15
  • 69
  • 136
  • This is the best way to go about things, just be careful not to define any helper functions within the loop. – Aaron Cronin Jul 30 '13 at 19:10
  • 2
    This doesn't really answer the question. He'll still need to do a lot of nasty looping in there. – Trevor Dixon Jul 30 '13 at 19:30
  • @TrevorDixon My answer was posted before OP question was updated with additional info. So this is the answer to the question: *Is it possible write function that allows me that, so i can call it some like: cartesianCall(array1, array2, array3, myFunc);* – Yuriy Galanter Jul 30 '13 at 19:50
3

http://jsfiddle.net/trevordixon/zEqKy/

function cartesianCall(func, args) {
    var combos = allCombos.apply(this, args);

    for (var i = 0; i < combos.length; i++) {
        func.apply(null, combos[i]);
    }
}

function allCombos(first) {
    var isArray = toString.call(first) === "[object Array]";
    if (!isArray) first = [first]; // Convert non-array to an array with the value
                                   // as the only element
    else if (first.length === 0) first = [undefined]; // Convert empty array to an
                                                      // array with undefined as
                                                      // the only element

    if (arguments.length === 1) return first; // base case for recursion

    var result = [],
        rest = allCombos.apply(this, Array.prototype.slice.call(arguments, 1));

    for (var i = 0; i < first.length; i++) {
        for (var j = 0; j < rest.length; j++) {
            result.push([first[i]].concat(rest[j]));
        }
    }

    return result;
}

Then use it like this:

function printArgs() { console.log('Called with arguments:', arguments); }

cartesianCall(printArgs, [
    [true, false],
    undefined,
    [1, 2],
    [],
    'a string',
    ['a', 'b', 'c']
]);

Prints:

Called with arguments: [true, undefined, 1, undefined, "a string", "a"] 
Called with arguments: [true, undefined, 1, undefined, "a string", "b"] 
Called with arguments: [true, undefined, 1, undefined, "a string", "c"] 
Called with arguments: [true, undefined, 2, undefined, "a string", "a"] 
Called with arguments: [true, undefined, 2, undefined, "a string", "b"] 
Called with arguments: [true, undefined, 2, undefined, "a string", "c"] 
Called with arguments: [false, undefined, 1, undefined, "a string", "a"] 
Called with arguments: [false, undefined, 1, undefined, "a string", "b"] 
Called with arguments: [false, undefined, 1, undefined, "a string", "c"] 
Called with arguments: [false, undefined, 2, undefined, "a string", "a"] 
Called with arguments: [false, undefined, 2, undefined, "a string", "b"] 
Called with arguments: [false, undefined, 2, undefined, "a string", "c"]

Notice that empty arrays are treated the same as undefined.

Trevor Dixon
  • 23,216
  • 12
  • 72
  • 109
  • When any array is an empty list, it doesn't permute, it should be able to permute with the value being null – zs2020 Jul 30 '13 at 19:30
  • I thing that when one of array is empty, function wouldn't be called – vaclav_o Jul 30 '13 at 19:42
  • I think it should work with empty arrays, but maybe not. I'm sure there are other edge cases where it breaks. It assumes everything is array and will die badly if something isn't. Suggest an edit to the answer if you improve the function. – Trevor Dixon Jul 30 '13 at 19:45
  • 'cause i maybe i want to know, that items of third array would be applied as third argument every time.. But it depends on usage.. still awesome work, thanks! – vaclav_o Jul 30 '13 at 19:53
  • I see. Maybe you can pass `[undefined]` as one of the arrays in that case. Or change `if (left.length === 0) return right;` to `if (left.length === 0) left = [undefined];` (and do the same for the `right.length` check). – Trevor Dixon Jul 30 '13 at 19:59
  • 1
    @the.ufon I updated the answer to behave the way you want, i.e. if you pass an empty array, it will keep the right position in the argument list and use `undefined` as the argument each time. – Trevor Dixon Jul 31 '13 at 16:57