# Avoid embedded for loops

Can the following be rewritten in a more programmatic way, possibly getting rid of the embedded for loops?

function createCards() {
var cards = [];
var i, j, k, l;

for(i = 0; i < 3; i += 1)
for(j = 0; j < 3; j += 1)
for(k = 0; k < 3; k += 1)
for(l = 0; l < 3; l += 1)
cards.push([i, j, k, l]);

return cards;
}


Since this is code is targetting Node, I'm happy to use EcmaScript 6 features.

-

## migrated from stackoverflow.comJan 24 '13 at 19:13

This question came from our site for professional and enthusiast programmers.

What is the purpose of the code? Are those playing cards in a game? –  Robert Harvey Jan 24 '13 at 19:10
Yes. It's a representation for the cards in the game of Set. –  Randomblue Jan 24 '13 at 19:11
What does the data represent? –  Ṣhmiddty Jan 24 '13 at 19:12
Each card has four features, and each feature has three variations. –  Randomblue Jan 24 '13 at 19:12
Is the code doing what it's supposed to do? Because if it is, and you don't have to throw any of the cards away later, I'd say the code is as good as it's going to get. –  Robert Harvey Jan 24 '13 at 19:12

I'm happy to use EcmaScript 6 features

var vals = range(0, 3);
return [ [i, j, k, l] for (i of vals) for (j of vals) for (k of vals) for (l of vals) ]


Still, it translates to the same nested loops. If you need a variable nesting level, you can use a recursive solution.

-
Unfortunately Node.JS does not yet have array comprehensions. –  Randomblue Jan 24 '13 at 23:11
Woah! I didn't know JavaScript could do that! –  Rocket Hazmat Jan 25 '13 at 1:11
that is beautiful code! –  jsanc623 Jan 25 '13 at 19:02
Some of us who left Python were hoping to never see that kind of nightmare in our adopted homeland of Javascript…! –  Ahmed Fasih May 27 at 3:41

I thought of a neat solution to this. Not sure how efficient it is, but I thought it was neat.

If you look at your result array, you'll see results from "0000" to "2222". These are the numbers from 0 to 80. In base 3!

So, using that, I whipped up a function to create all numbers in that range as an array.

function createCards(length, max){
var cards = [],
// How many array elements
total = Math.pow(max, length),
// This will help with left padding
// since JavaScript doesn't have str_repeat('0', length)
// temp loop variables
index, value;

for(index = 0; index < total; index++){
// Convert number into its new base
value = index.toString(max);
// Left pad it so each string is the same length
// Push into the array
cards.push(value.split('').map(function(a){
// Convert result to ints
return +a;
}));
}

return cards;
}


It's called using createCards(4,3).

-
Great answer! While trying to come up with a solution, I went with modulo and didn't even consider base 3. –  Ṣhmiddty Jan 24 '13 at 19:31
@Shmiddty: I noticed his loops were making all possible 4 digit numbers with the digits 0-2. Suddenly, I realized: those are 4 digit base 3 numbers! :-) –  Rocket Hazmat Jan 24 '13 at 19:33
You can easily work in base-3 without creating hundreds of strings and lambda-functions... –  BlueRaja - Danny Pflughoeft Jan 24 '13 at 22:41
I like the original code more than this... –  Honza Brabec Jan 25 '13 at 11:13
@HonzaBrabec: It's not the best solution, but it was the 1st thing I could come up with :-P –  Rocket Hazmat Jan 25 '13 at 14:24

If you wanted the number and size of dimensions for your final cards array to be dynamic, you could use a recursive solution like this (DEMO):

function createCards(dimensions) {
if (dimensions.length == 0) {
return [];
} else if (dimensions.length == 1) {
var cards = [];
for (var i = 0; i < dimensions[0]; i++)
cards.push([i]);
return cards;
} else {
var subDimensions = dimensions.splice(1);
var subCards = createCards(subDimensions);
var cards = [];
for (var i = 0; i < dimensions[0]; i++)
for (var k = 0; k < subCards.length; k++) {
cards.push([i].concat(subCards[k]));
}
return cards;
}
}


You would use it by passing in an array of dimensions:

var cards = createCards([3, 3, 3, 3]);
console.log(cards);


This solution is not the most efficient due to the re-creation of all the arrays with every dimension. However, it is very flexible and should work ok for small sets.

-

Check check it out http://jsfiddle.net/GpAUP/

Here is the meat of the code:

var maxSize = 3*3*3*3;
for(var i = 0; i < maxSize; ++i){
var a = Math.floor(i/27) % 3;
var b = Math.floor(i/9) % 3;
var c = Math.floor(i/3) % 3;
var d = i % 3;
$("body").append(a + "," + b + "," + c + "," + d );$("body").append("<br />");
}


essentially using modulus and a little linear math. You want cards.push([a,b,c,d]) instead of appending to body. You can change the ordering if you want it a different way.

-

A flexible solution is to start with a card with all the features set to zero, then use a for loop which gets the next card in the sequence, within an outer while loop that terminates when no more valid cards can be generated. (jsfiddle).

function createCards(dim) {
var card = [], cards = [];
while (card.length < dim.length) card.push(0);
while (card.length === dim.length) {
cards.push(card.slice(0));
for (var feature = 0; ++card[feature] === dim[feature]; feature++) {
card[feature] = 0;
}
}
return cards;
}
console.log(createCards([3, 3, 3, 3]));


Or similarly (but counting down instead of up)

function createCards(dim) {
var cards = [], card = dim.slice(0);
while (true) {
cards.push(card.slice(0));
for (var feature = dim.length - 1; !--card[feature]; card[feature] = dim[feature--]) {
if (feature < 1) return cards;
}
}
}


Alternatively, loop through the current set of cards, progressively adding variations of each feature.

function createCards(dim) {
var cards = [dim.slice(0)];
for (var feature = 0; feature < dim.length; feature++) {
var numberOfCards = cards.length;
for (var variation = dim[feature] - 1; variation > 0; variation--) {
for (var i = 0; i < numberOfCards; i++) {
var card = cards[i].slice(0);
card[feature] = variation;
cards.push(card);
}
}
}
return cards;
}


These should have the same result as mellamokb's answer.

-