Как эффективно случайным образом выбрать элемент массива без повторов?
Я знаю, что этот вопрос существует во многих обличьях, но я не смог найти ответ, связанный с моей конкретной проблемой эффективности.
У меня есть код ниже, который работает отлично.
У меня есть 10 массивов элементов, из которых я случайно выбираю элемент (при нажатии клавиши ввода). Код хранит массив из 5 самых последних выборов, которые не могут быть выбраны случайным образом (чтобы избежать слишком много повторений с течением времени).
Если функция selectName() изначально выбирает имя, которое использовалось в последних 5, оно просто ломается и снова вызывает себя, повторяя, пока не найдет "уникальное" имя.
У меня есть два вопроса:
-
Можно ли сказать, что это "рекурсивная функция"?
-
Я обеспокоен тем, что теоретически это может продолжаться в течение длительного времени, прежде чем найти уникальное имя - есть ли более эффективный способ сделать это?
Спасибо за любую помощь.
var a = ["Roger", "Russell", "Clyde", "Egbert", "Clare", "Bobbie", "Simon", "Elizabeth", "Ted", "Caroline"];
var b = [];
var chooseName = function () {
var unique = true;
b.length = 5;
num = Math.floor(Math.random() * a.length);
name = a[num];
for (i = 0; i < a.length; i++) {
if (b[i] == name) {
chooseName();
unique = false;
break;
}
}
if (unique == true) {
alert(name);
b.unshift(name);
}
}
window.addEventListener("keypress", function (e) {
var keycode = e.keyCode;
if (keycode == 13) {
chooseName();
}
}, false);
Ответы
Ответ 1
Всякий раз, когда элемент выбран, переместите его в обратную сторону массива и произвольно выберите из среза исходного массива array.slice(0, -5)
.
var a = ["Roger", "Russell", "Clyde", "Egbert", "Clare", "Bobbie", "Simon", "Elizabeth", "Ted", "Caroline"];
var chooseName = function () {
var unique = true;
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);
РЕДАКТИРОВАТЬ: Это также имеет побочный эффект от того, что какие-либо переменные не совпадают с списком несправедливого недостатка, которого они не будут рассматривать в первых N вызовах. Если это проблема для вас, возможно, попробуйте сохранить статическую переменную где-нибудь, чтобы отслеживать размер используемого фрагмента и максимально использовать его в B (в данном случае 5).
например.
var a = ["Roger", "Russell", "Clyde", "Egbert", "Clare", "Bobbie", "Simon", "Elizabeth", "Ted", "Caroline"];
B = 5; //max size of 'cache'
N = 0;
var chooseName = function () {
var unique = true;
num = Math.floor(Math.random() * a.length - N);
N = Math.min(N + 1, B);
name = a.splice(num,1);
a.push(name);
}
Ответ 2
Мне нравится комментатор @YuriyGalanter идея выбора элементов случайным образом до тех пор, пока все не будут приняты и только затем повторяются, поэтому здесь реализация:
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.
Ответ 3
Я рекомендую вам использовать underscore.js, это будет очень просто.
Функция shuffle
реализуется равномерно распределенным способом, поэтому вероятность повторения будет низкой, если массив a
содержит больше данных.
var a = ["Roger", "Russell", "Clyde", "Egbert", "Clare", "Bobbie", "Simon", "Elizabeth", "Ted", "Caroline"];
b = _.shuffle(a).slice(0,5);
console.log(b);
Ответ 4
Когда вы создаете экземпляр Shuffler, дайте ему свой массив в качестве параметра. Он создаст копию массива и каждый раз, когда будет вызываться next(), он вернет случайный элемент из копии и удалит его из массива копий, чтобы не было повторений.
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);
}
Ответ 5
Вместо случайного выбора из списка массивов, как перетасовать массив в случайном порядке в начале?
Ответ 6
Да, это рекурсивно, и поскольку оно не уменьшает состояние, которое теоретически может продолжаться вечно.
Я предполагаю, что изменение массива недопустимо, так как в противном случае вы могли бы просто удалить последние варианты из массива и отбросить их обратно в качестве недавнего переполнения буфера выбора.
Вместо этого: Исключить элементы буферизации в конце массива из выделения. (Buffersize начинается с 0 и увеличивается до вашего предустановленного буферизамакса, когда в буфер добавляются последние варианты.) Когда вы делаете выбор, вы сравниваете его с вашими избранными вариантами bufffersize. Если вы найдете совпадение, вы выбираете соответствующий исключенный элемент.
Очевидно, что до сих пор неэффективна необходимость проверять каждый недавний выбор в буфере, чтобы избежать совпадения. Но у него есть эффективность, позволяющая избежать возможной рекурсии.
Ответ 7
Эта работа как прелесть для меня без повторения.
var Random_Value = Pick_Random_Value(Array);
function Pick_Random_Value(IN_Array)
{
if(IN_Array != undefined && IN_Array.length > 0)
{
var Copy_IN_Array = JSON.parse(JSON.stringify(IN_Array));
if((typeof window.Last_Pick_Random_Index !== 'undefined') && (window.Last_Pick_Random_Index !== false))
{
if(Copy_IN_Array[Last_Pick_Random_Index] != undefined)
{
Copy_IN_Array.splice(Last_Pick_Random_Index,1);
}
}
var Return_Value = false;
if(Copy_IN_Array.length > 0)
{
var Random_Key = Math.floor(Math.random() * Copy_IN_Array.length);
Return_Value = Copy_IN_Array[Random_Key];
}
else
{
Return_Value = IN_Array[Last_Pick_Random_Index];
}
window.Last_Pick_Random_Index = IN_Array.indexOf(Return_Value);
if(window.Last_Pick_Random_Index === -1)
{
for (var i = 0; i < IN_Array.length; i++)
{
if (JSON.stringify(IN_Array[i]) === JSON.stringify(Return_Value))
{
window.Last_Pick_Random_Index = i;
break;
}
}
}
return Return_Value;
}
else
{
return false;
}
}
Ответ 8
Я знаю, что это старый вопрос, но я работал над чем-то похожим, готовясь к курсу веб-разработки. В моем конкретном сценарии я хотел изменить цвет блока случайным образом, но не иметь последовательных повторов одного и того же цвета. Это решение, которое я придумал. Используя цикл while, я смог достичь желаемого результата. Надеюсь, это кому-нибудь поможет.
var colors = ["black","red","yellow","green","blueviolet","brown","coral","orchid","olivedrab","purple"];
function getRandomColor(){
num = Math.floor(Math.random() * 10); // get a random number between 0-9
return colors[num];
}
function randomizeColor(){
currentColor = document.getElementById("box").style.backgroundColor; // got the current color of the box on the page.
randomColor = getRandomColor();
while (randomColor == currentColor){ // if we get the same color as the current color, try again.
randomColor = getRandomColor();
}
document.getElementById("box").style.backgroundColor = randomColor; // change box to new color
}
<!DOCTYPE html>
<html>
<head>
<title>Random Color Box</title>
</head>
<body>
<p>Press the buttons to change the box!</p>
<div id="box" style="height:150px; width:150px; background-color:red; margin:25px; opacity:1.0;"></div>
<button id="button" onclick="randomizeColor()">Random Color</button>
<script type="text/javascript" src="javascript.js"></script>
</body>
</html>