0

I have for example these arrays:

a1 = ["1", "2", "3"];
a2 = ["a", "b"];
a3 = ["q", "w", "e"];

result = ["1aq", "1aw", "1ae", "1bq", "1bw", ... "3be"];

How could obtain this without nested loops (also using jquery, for example)? Thanks

andreaconsole
  • 430
  • 3
  • 17
  • Can you use recursion? How strict are the requirements not to use nested loops, can you use one loop? – cgatian Feb 02 '13 at 15:02
  • 2
    You really can't do it without some form of iteration. The only thing in jQuery that might be relevant is `$.each()`, but I don't know that that helps much. – Pointy Feb 02 '13 at 15:02
  • Why no nested loops? This sounds like an assignment to me. The only thing I can think of is recursion, unless you don't consider `$.each` to be a form of loop (?) – leftclickben Feb 02 '13 at 15:02
  • 2
    I usually avoid asking why. But why not use a nested loop? – Jack Feb 02 '13 at 15:03
  • 3
    You will need a nested loop somewhere. Even if you use a higher level of abstraction, internally it will require a nested loop. – Fabrício Matté Feb 02 '13 at 15:05
  • This seems relavent: http://stackoverflow.com/questions/14442010/how-to-convert-nested-for-loops-into-a-recursive-function – Adil Malik Feb 02 '13 at 15:22
  • 1
    possible duplicate of [Find all combinations of options in a loop](http://stackoverflow.com/questions/12152409/find-all-combinations-of-options-in-a-loop) – Bergi Feb 02 '13 at 15:34
  • Thank you for the answers. The reasons are the following: - with a scripting language, nested loop are slow compared to internally optimized solutions; - I indeed don't know how many arrays I have, since the number could increase and I would like to avoid to rewrite the code every time. – andreaconsole Feb 04 '13 at 07:36
  • 1
    If your reason was about performance, then plain `for` loops will nearly always be fastest in JavaScript. Functions require more overhead. My answer below used tail-recursive approaches to eliminate some or all of the loops since that was the question's requirement, but there will be a performance penalty for it. – the system Feb 04 '13 at 15:07
  • Oh, what a disappointment! – andreaconsole Feb 04 '13 at 18:52

5 Answers5

4

I don't see what's wrong with nested loops, but here is a generic solution:

var a = [a1, a2, a3];

var result = [""]; // start with the empty string,
for (var i=0; i<a.length; i++) { // and repeatedly
        var ai = a[i],
            l = ai.length;
    result = $.map(result, function(r) { // make result a new array of
        var ns = []; // new combinations of
        for (var j=0; j<l; j++) // each of the letters in ai
            ns[j] = r + ai[j]; // and the old results
        return ns;
    }); // using the odds of jQuery.map with returned arrays
}
return result;
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
2

No nested loops. Can handle as many arrays as needed.

var result = combine(a1, a2, a3);

function combine() {
    return processArrays([].slice.call(arguments), "", []);

    function processArrays(arrays, str, res) {
        for (var i = 0; i < arrays[0].length; i++) {
            if (arrays.length > 1) {
                processArrays(arrays.slice(1), str + arrays[0][i], res);
            } else {
                res.push(str + arrays[0][i]);
            }
        }
        return res;
    }
}

Or a slightly different take on the function:

function combine() {
    return processArrays([].slice.call(arguments), "", []);

    function processArrays(arrays, str, res) {
        if (arrays.length === 0)
            res.push(str)
        else
            for (var i = 0; i < arrays[0].length; i++)
                processArrays(arrays.slice(1), str + arrays[0][i], res);
        return res;
    }
}

And here's a no loops version:

var result = combine(a1, a2, a3);

function combine() {
    return processArrays(arguments[0], [].slice.call(arguments, 1), "", []);

    function processArrays(head, tail, str, res) {
        if (head === undefined)
            res.push(str)
        else
            processArray(head[0], head.slice(1), tail, str, res);
        return res;
    }
    function processArray(head, tail, arrays, str, res) {
        if (head) {
            processArrays(arrays[0], arrays.slice(1), str + head, res);
            processArray(tail[0], tail.slice(1), arrays, str, res)
        }
    }
}
the system
  • 9,244
  • 40
  • 46
  • 2
    @andreaconsole: Since function calls usually have some overhead, then very often the fewer calls the better, so that would mean the first two would be faster. But performance testing would be the only way to really find out. – the system Feb 04 '13 at 14:22
1

A generic recursive solution:

function combine() {
    var target = arguments[0];

    if (arguments.length === 1) {
        return target; // end of chain, just return the array
    }

    var result = [];
    // compute all combinations without the first array
    var combinations = combine.apply(null, Array.prototype.slice.call(arguments, 1));

    // put things together
    for (var i = 0, l = target.length; i < l; i++) {
        var element = target[i];
        for (var j = 0, lj = combinations.length; j < lj; j++) {
            result.push(element + combinations[j]);
        }
    }
    return result;
}

// Usage
var result = combine(a1, a2, a3);
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
0

Another generic solution.

var reduce = function(a, b) {
    var r = [];
    $.each(a, function(i, ai) {
        $.each(b, function(j, bj) {
            r.push(ai + bj);
        });
    });
    return r;
};

var result = reduce(reduce(a1, a2), a3);
Alexander
  • 23,432
  • 11
  • 63
  • 73
  • You should make the function accept an array of arrays, instead of repeatedly calling it to avoid recursion... Also, nested `$.each` is even slower than the slow unnested `$.each`. – Bergi Feb 02 '13 at 15:37
  • 1
    @Bergi, I find it better this way for readability and transliterating this to native JavaScript for performance boost is trivial – Alexander Feb 02 '13 at 15:39
-1
var outputArray = [];
for(var i = 0, finalLength = a1.length * a2.length * a3.length; i < finalLength; i++) {
   outputArray[i] = a1[i % a1.length].toString() + a2[i % a2.length].toString() + a3[i % a3.length].toString(); 
}

But this is really just a stunt. Why avoid the loops? I can guess: You don't know in advance how many arrays you'll have. But it's still going to be a challenge.

Neil JS Grump
  • 669
  • 5
  • 3