Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

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.

share|improve this question

migrated from stackoverflow.com Jan 24 '13 at 19:13

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

4  
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? –  Shmiddty 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

5 Answers 5

up vote 13 down vote accepted

I'm happy to use EcmaScript 6 features

Try array comprehensions!

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.

share|improve this answer
    
Unfortunately Node.JS does not yet have array comprehensions. –  Randomblue Jan 24 '13 at 23:11
2  
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

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)
        pad = Math.pow(10, length).toString().substring(1),
        // 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
        value = pad.substring(0, pad.length - value.length) + value;
        // 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).

share|improve this answer
    
Great answer! While trying to come up with a solution, I went with modulo and didn't even consider base 3. –  Shmiddty 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
1  
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.

share|improve this answer

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.

share|improve this answer

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.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.