【问题标题】:How to efficiently randomly select array item without repeats?如何有效地随机选择数组项而不重复?
【发布时间】:2013-07-27 07:16:30
【问题描述】:

我知道这个问题有很多形式,但我无法找到与我的具体效率问题相关的答案。

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

我有一个包含 10 个项目的数组,我从中随机选择一个项目(按回车键)。该代码保留了一个包含 5 个最新选项的数组,这些选项不能随机选择(以避免随着时间的推移过多重复)。

如果 chooseName() 函数最初选择了最近 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);

【问题讨论】:

  • 如何创建数组的临时副本并在选中后简单地从中删除元素?当临时数组为空时 - 再次重新创建它。这样,在数组耗尽之前,您将永远不会重复
  • 当您从数组中选择一个项目时,将其删除以便您无法再次选择它,并将其添加到选定项目的数组中。当该数组大于 5 时,将最旧的数组添加回原始数组,以便再次选择它。

标签: javascript


【解决方案1】:

我喜欢评论者@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.

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案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);
      

      【讨论】:

      • 谢谢。我目前正试图远离图书馆,因为我想学习纯 JavaScript 以确保我知道发生了什么。我以后会检查的。
      【解决方案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】:

        是的,这是递归的,因为它不会减少状态,理论上它可以永远持续下去。

        我假设不允许更改数组,否则您可以简单地从数组中删除最近的选择,然后在最近的选择缓冲区溢出时将它们推回。

        改为:从选择中排除数组末尾的缓冲区大小项目。 (Buffersize 从 0 开始,随着最近的选择添加到缓冲区中,增长到您预设的 buffersizemax。)当您做出选择时,您将其与您最近的选择 bufffersize 进行比较。如果您找到匹配项,则选择相应的排除项。

        显然,这仍然具有必须检查缓冲区中最近的每个选择以避免匹配的低效率。但它确实具有避免可能的递归的效率。

        【讨论】:

          【解决方案6】:

          这项工作对我来说就像一个魅力,没有任何重复。

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

          【讨论】:

            【解决方案7】:

            我知道这是一个较老的问题,但我在为 Web 开发课程做一些准备工作时正在解决类似的问题。在我的特定场景中,我想随机更改一个框的颜色,但没有任何连续重复的相同颜色。这是我想出的解决方案。使用 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>

            【讨论】:

              【解决方案8】:

              选择的解决方案很好,但如果您不想弄乱数组的顺序,请使用以下解决方案:

              创建一个数组,其中包含用于保存要从中随机选择的数据的数组索引。然后从该索引数组中随机选择一个项目,并使用其存储的值从数据数组中检索该项目。然后移除索引项,让索引数组继续变小。

              类似这样的:

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

              【讨论】:

                【解决方案9】:
                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--;
                         }
                      }
                   }
                

                【讨论】:

                • 这个答案并没有真正解决 OP 的 2 个问题。解释为什么这种方法比他们当前的代码更有效的实现可能是有意义的。
                【解决方案10】:

                试试看。

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

                【讨论】:

                  【解决方案11】:

                  //试试这个:

                  var a = [“Roger”、“Russell”、“Clyde”、“Egbert”、“Clare”、“Bobbie”、“Simon”、“Elizabeth”、“Ted”、“Caroline”];

                  var b = [];

                  b = shuffle(a).slice(0,5); // 如果您需要 5 个数字,则此设置。
                  控制台.log(b);

                  【讨论】:

                  • Uncaught ReferenceError: shuffle is not defined"
                  猜你喜欢
                  • 1970-01-01
                  • 2016-02-01
                  • 1970-01-01
                  • 2011-02-01
                  • 2015-04-07
                  • 2019-01-28
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多