簡體   English   中英

如何有效地隨機 select 數組項而不重復?

[英]How to efficiently randomly select array item without repeats?

我知道這個問題有很多種形式,但我無法找到與我的具體效率問題相關的答案。

我有以下代碼可以正常工作。

我有一個 10 項數組,我從中隨機 select 一個項目(按回車鍵)。 該代碼保留了不能隨機選擇的 5 個最近選擇的數組(以避免隨時間重復太多)。

如果 chooseName() function 最初選擇了最近 5 次中使用過的名稱,它就會中斷並再次調用自身,重復直到找到“唯一”名稱。

我有兩個問題:

  1. 說這是一個“遞歸函數”是否正確?

  2. 我擔心從理論上講這可能會在找到唯一名稱之前循環很長時間 - 有沒有更有效的方法來做到這一點?

感謝您的任何幫助。

    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);

我喜歡評論者@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.

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 () {
    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);
}

我推薦你使用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);

當您實例化 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);
}

在我的特定場景中,我想隨機更改一個框的顏色,但沒有任何連續重復的相同顏色。 這是我想出的解決方案。 使用 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>

是的,這是遞歸的,因為它不會減少狀態,理論上它可以永遠持續下去。

我假設不允許更改數組,否則您可以簡單地從數組中刪除最近的選擇並將它們推回,因為最近的選擇緩沖區溢出。

相反:從選擇中排除數組末尾的緩沖區大小項目。 (Buffersize 從 0 開始,隨着最近的選擇添加到緩沖區,增長到您預設的 buffersizemax。)當您做出選擇時,您將它與您最近的選擇 bufffersize 進行比較。 如果您找到匹配項,則選擇相應的排除項目。

顯然,這仍然具有必須檢查緩沖區中最近的每個選擇以避免匹配的低效率。 但它確實具有避免可能的遞歸的效率。

這項工作對我來說就像一個魅力,沒有任何重復。

   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;
    }
}

選擇的解決方案很好,但如果您不想弄亂陣列的順序,請使用此解決方案:

創建一個數組,其中包含用於保存要從中隨機選擇的數據的數組索引。 然后從該索引數組中隨機選擇一個項目,並使用其存儲的值從數據數組中檢索該項目。 然后刪除索引項,使索引數組繼續變小。

像這樣的東西:

let items = ["red", "blue", "yellow"];
let randomItems = [];
let arrayIndexes =  [...Array(items.length).keys()];

let totalItems = items.length;


for (let i = 0; i < totalItems; i++) {
    let item;

    let mapIndex = Math.floor(Math.random() * (arrayIndexes.length - 1));
    let index = arrayIndexes[mapIndex];
    item = items[index];
    arrayIndexes.splice(mapIndex, 1);

    if ((i === (totalItems - 1)) && (arrayIndexes.length === 0)) {
        // If you choose to set totalItems to a value larger than your dataset,
        // this will re-initialize your array indexes, but you will end up with
        // duplicates in your results. If you don't want duplicates, just make
        // sure totalItems is set to items.length.

        arrayIndexes = [...Array(items.length).keys()];
    }


    randomItems.push(item);
}
var a = ["Roger", "Russell", "Clyde", "Egbert", "Clare", "Bobbie", "Simon", 
"Elizabeth", "Ted", "Caroline","Brezza","Elephant","Jack","Virat"];    
let b=[a[Math.floor(Math.random() * a.length)]];

for(let i=1; i<=12; i++){
  let t = a[Math.floor(Math.random() * a.length)];
  const n = b.indexOf(t);
   if (n >= 0) {
      b = b.filter((it, i) => it !== t);
    } else {
     b = [...b, t];
    } 
      if(b.length === 12 ){
         break;
       }else{
         if(i === 12){
             i--;
         }
      }
   }

洗牌數組的最簡單方法:

['aaa', 'bbb', 'ccc'].sort(() => 0.5 - Math.random())

要訪問,請保存隨機數組,然后:

  1. 跟蹤您所在的索引並僅訪問那里的值,根據需要增加/減少索引,或者
  2. 當你想要一個值時,只需.pop()

像最被接受的答案一樣,我的解決方案沒有實現 OP 對“最近的 5 個選擇”的引用,而是簡單地“隨機選擇項目,直到所有項目都被采用,然后才重復”。 這個解決方案不是遞歸的,它不可能“在找到唯一名稱之前保持循環很長時間”。 它利用了 filter() 函數,因此避免了使用 for 循環。 我相信從句法簡潔的角度來看它是有效的。

const a = ["Roger", "Russell", "Clyde", "Egbert", "Clare", "Bobbie", "Simon", "Elizabeth", "Ted", "Caroline"];
let b = a;

const chooseName = () => {
  let name = b[Math.floor(Math.random() * b.length)]
  b = b.filter((v) => v !== name)
  if (b.length === 0) {
    b = a
  }
  return name
}

洗牌的標准方法稱為Fisher-Yates 洗牌 只要隨機數生成器是公平的,它就是公平的。

它的工作原理是從未洗牌數組的副本中隨機刪除項目,直到沒有剩余。

 let arr = [1,2,3,4,5,6,7] let shuffled = arr.reduce(([a,b])=> (b.push(...a.splice(Math.random()*a.length|0, 1)), [a,b]),[[...arr],[]])[1] console.log(shuffled)

試試看。

function doShuffle(a) {
   for (var i = a.length - 1; i > 0; i--) {
       var j = Math.floor(Math.random() * (i + 1));
       [a[i], a[j]] = [a[j], a[i]];
   }
   return a;
}

//嘗試這個:

var a = [“羅傑”、“羅素”、“克萊德”、“埃格伯特”、“克萊爾”、“博比”、“西蒙”、“伊麗莎白”、“泰德”、“卡羅琳”];

變量 b = [];

b = shuffle(a).slice(0,5); // 如果你想要 5 個數字,這是前提條件。
控制台.log(b);

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM