Как эффективно случайным образом выбрать элемент массива без повторов?

Я знаю, что этот вопрос существует во многих обличьях, но я не смог найти ответ, связанный с моей конкретной проблемой эффективности.

У меня есть код ниже, который работает отлично.

У меня есть 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>