How to efficiently randomly select array item without repeats?
When you instantiate Shuffler, give it your array as a parameter. It will create a copy of the array and every time next() is called it will return a random element from a copy and remove it from the copy array so that no repeats are possible.
var Shuffler = function(a) {
var aCopy = [],
n = 0;
// Clone array
for (n=0; n<a.length; n++) {
aCopy.push(a[n]);
}
this.next = function() {
if (aCopy.length == 0) { return null; }
var nRandom = Math.floor(Math.random() * (aCopy.length + 1)),
mElement = aCopy[nRandom];
delete aCopy[nRandom];
return mElement;
}
}
var oShuffler = new Shuffler([/* names go here */]),
sRandomName = null;
while (sRandomName = oShuffler.next()) {
console.log(sRandomName);
}
I recommend you to use underscore.js, it will be very simple.
The function shuffle
is implemented in uniformly distributed way so the probability of repetition will be low if the array a
contains more data.
var a = ["Roger", "Russell", "Clyde", "Egbert", "Clare", "Bobbie", "Simon", "Elizabeth", "Ted", "Caroline"];
b = _.shuffle(a).slice(0,5);
console.log(b);
Whenever an item is selected, move it to the back of the array and randomly select from a slice of the original array array.slice(0, -5)
.
var a = ["Roger", "Russell", "Clyde", "Egbert", "Clare", "Bobbie", "Simon", "Elizabeth", "Ted", "Caroline"];
var chooseName = function () {
num = Math.floor(Math.random() * a.length - 5);
name = a.splice(num,1);
a.push(name);
}
window.addEventListener("keypress", function (e) {
var keycode = e.keyCode;
if (keycode == 13) {
chooseName();
}
}, false);
EDIT: This also has the side-effect of not giving whichever variables happen to tail the list the unfair disadvantage that they won't be considered in the first N calls. If that's a problem for you, maybe try hold a static variable somewhere to keep track of the size of the slice to use and max it out at B (in this case, 5). e.g.
var a = ["Roger", "Russell", "Clyde", "Egbert", "Clare", "Bobbie", "Simon", "Elizabeth", "Ted", "Caroline"];
B = 5; //max size of 'cache'
N = 0;
var chooseName = function () {
num = Math.floor(Math.random() * a.length - N);
N = Math.min(N + 1, B);
name = a.splice(num,1);
a.push(name);
}
I like commenter @YuriyGalanter's idea of choosing items randomly until all are taken and only then repeating, so here's an implementation:
function randomNoRepeats(array) {
var copy = array.slice(0);
return function() {
if (copy.length < 1) { copy = array.slice(0); }
var index = Math.floor(Math.random() * copy.length);
var item = copy[index];
copy.splice(index, 1);
return item;
};
}
var chooser = randomNoRepeats(['Foo', 'Bar', 'Gah']);
chooser(); // => "Bar"
chooser(); // => "Foo"
chooser(); // => "Gah"
chooser(); // => "Foo" -- only repeats once all items are exhausted.