That's not simple, I must admit. Let's start with some helper functions:
Array.prototype.unique = function() {
var i = 0, t = 1, l = (this.length >>> 0) - 1;
for (; i < l; t = ++i + 1)
while ((t = this.indexOf(this[i], t)) !== -1)
this.splice(t, 1);
return this;
};
Array.prototype.count = function(item) {
var i = 0, t = 0;
while (i = this.indexOf(item, i) + 1) t++;
return t;
};
Array.prototype.remove = function(item) {
var i = this.indexOf(item);
if (i !== -1) this.splice(i, 1);
return this;
};
I hope the meaning is clear, even if the code may look obscure. There are polyfill for Array.prototype.indexOf
for older browsers (IE8-, basically), too.
Now the code:
function shuffleArray(array, previous) {
if (!array.length) return array;
// The list of unique items in the array
var univals = array.slice().unique(),
idx, pivot, rest;
if (previous != null)
// The array can't start with the previous element
univals.remove(previous);
while (univals.length) {
// We choose an element from the possible ones
idx = Math.floor(Math.random() * univals.length);
pivot = univals[idx];
// We try to shuffle the rest of the array.
rest = shuffleArray(array.slice().remove(pivot), pivot);
// If we got a shuffled array, we're done.
// Else, we remove our pivot element from the possible ones
if (rest != null)
return [pivot].concat(rest);
else univals.splice(idx, 1);
}
return null;
}
The usage would be:
shuffleArray(array);
You can add a second optional parameter to make it avoid to start with a certain element. It's used recursively inside the function itself. If you get null
, then it's not possible to shuffle the array with the given constraints.
Keep in mind that this algorithm can be optimized. In particular, you you can change it so it's not recursive, but rather iterative and more suitable for larger arrays.