7

I am using the below code to assign a random class (out of five) to each individual image on my page.

$(this).addClass('color-' + (Math.floor(Math.random() * 5) + 1));

It's working great but I want to make it so that there are never two of the same class in a row.

Even better would be if there were never two of the same in a row, and it also did not use any class more than once until all 5 had been used... As in, remove each used class from the array until all of them have been used, then start again, not allowing the last of the previous 5 and the first of the next 5 to be the same color.

Hope that makes sense, and thanks in advance for any help.

Soul Ec
  • 819
  • 7
  • 11
user2860129
  • 75
  • 1
  • 1
  • 4

4 Answers4

20

You need to create an array of the possible values and each time you retrieve a random index from the array to use one of the values, you remove it from the array.

Here's a general purpose random function that will not repeat until all values have been used. You can call this and then just add this index onto the end of your class name.

var uniqueRandoms = [];
var numRandoms = 5;
function makeUniqueRandom() {
    // refill the array if needed
    if (!uniqueRandoms.length) {
        for (var i = 0; i < numRandoms; i++) {
            uniqueRandoms.push(i);
        }
    }
    var index = Math.floor(Math.random() * uniqueRandoms.length);
    var val = uniqueRandoms[index];

    // now remove that value from the array
    uniqueRandoms.splice(index, 1);

    return val;

}

Working demo: http://jsfiddle.net/jfriend00/H9bLH/


So, your code would just be this:

$(this).addClass('color-' + (makeUniqueRandom() + 1));

Here's an object oriented form that will allow more than one of these to be used in different places in your app:

// if only one argument is passed, it will assume that is the high
// limit and the low limit will be set to zero
// so you can use either r = new randomeGenerator(9);
// or r = new randomGenerator(0, 9);
function randomGenerator(low, high) {
    if (arguments.length < 2) {
        high = low;
        low = 0;
    }
    this.low = low;
    this.high = high;
    this.reset();
}

randomGenerator.prototype = {
    reset: function() {
        this.remaining = [];
        for (var i = this.low; i <= this.high; i++) {
            this.remaining.push(i);
        }
    },
    get: function() {
        if (!this.remaining.length) {
            this.reset();
        }
        var index = Math.floor(Math.random() * this.remaining.length);
        var val = this.remaining[index];
        this.remaining.splice(index, 1);
        return val;        
    }
}

Sample Usage:

var r = new randomGenerator(1, 9);
var rand1 = r.get();
var rand2 = r.get();

Working demo: http://jsfiddle.net/jfriend00/q36Lk4hk/

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • If you could show me exactly how to "call this" and "add the index" to the class name I would be eternally grateful. I have no experience with javascript, but bought a book this week and just started it today so I'm trying to fix that. Thanks again, what you posted is already really helpful. – user2860129 Oct 14 '13 at 00:57
  • @user2860129 - I added that to the end. – jfriend00 Oct 14 '13 at 00:57
  • One enhancement is to keep the original array and make a copy for splicing. When all members have been spliced, make another copy (if required). – RobG Oct 14 '13 at 01:02
  • @RobG - why is copying easier than just creating a new one when needed? – jfriend00 Oct 14 '13 at 01:05
  • Because the original may not just be a sequence of digits, or have some order like 'colour1', 'colour2', etc. – RobG Oct 14 '13 at 01:41
  • @RobG - OK, I was pursuing an approach of a generic number-producing method that could be combined with whatever text one wanted after the fact. – jfriend00 Oct 14 '13 at 01:45
  • I added an object oriented form so that the array that keeps track of the numbers generated is not global and thus this can be used in multiple places in the code with different random sequences. – jfriend00 Aug 20 '15 at 02:30
3

You can do something like this using an array and the splice method:

var classes = ["color-1", "color-2", "color-3", "color-4", "color-5"];
for(i = 0;i < 5; i++){
  var randomPosition = Math.floor(Math.random() * classes.length);
  var selected = classes.splice(randomPosition,1);
  console.log(selected);
  alert(selected);
}
Giacomo1968
  • 25,759
  • 11
  • 71
  • 103
Johann Echavarria
  • 9,695
  • 4
  • 26
  • 32
1
var used = [];
var range = [0, 5];

var generateColors = (function() {
    var current;
    for ( var i = range[0]; i < range[5]; i++ ) {

        while ( used.indexOf(current = (Math.floor(Math.random() * 5) + 1)) != -1 ) ;
        used.push(current);
        $("   SELECTOR     ").addClass('color-' + current);
    }
});
Deepsy
  • 3,769
  • 7
  • 39
  • 71
1

Just to explain my comment to jfriend00's excellent answer, you can have a function that returns the members of a set in random order until all have been returned, then starts again, e.g.:

function RandomList(list) {
  var original = list;
  this.getOriginal = function() {
    return original;
  }
}

RandomList.prototype.getRandom = function() {
  if (!(this.remainder && this.remainder.length)) {
    this.remainder = this.getOriginal().slice();
  }
  return this.remainder.splice(Math.random() * this.remainder.length | 0,1);
}

var list = new RandomList([1,2,3]);

list.getRandom(); // returns a random member of list without repeating until all
                  // members have been returned.

If the list can be hard coded, you can keep the original in a closure, e.g.

var randomItem = (function() {
  var original = [1,2,3];
  var remainder;
  return function() {
    if (!(remainder && remainder.length)) {
      remainder = original.slice();
    }
    return remainder.splice(Math.random() * remainder.length | 0, 1);
  };
}());
RobG
  • 142,382
  • 31
  • 172
  • 209