【问题标题】:Generating a random value, keeping a history of returned values生成随机值,保留返回值的历史记录
【发布时间】:2012-11-28 20:04:26
【问题描述】:

对于我正在处理的项目,我需要一个 Javascript 函数,它会在给定范围内返回一个随机数,而不会重复自身,直到整个范围“耗尽”。由于周围没有这样的东西,我设法自己创造了它。

该函数还需要传递id。这样,如果您需要多个随机数,每个都有自己的历史记录,id 会跟踪它们。

该功能有效,但我需要一些建议;

  1. 这是实现我想要实现的“正确”方式吗?
  2. inArray() 在非常大的范围 (maxNum) 值下的执行速度有多快?我有一种感觉,大数字会减慢函数的速度,因为它会随机化数字,直到它生成一个仍然“有效”的数字(即不在历史数组中)。但我想不出另一种方法来做到这一点..

脚本:

var UniqueRandom = {
    NumHistory: [],
    generate: function (maxNum, id) {
        if (!this.NumHistory[id]) this.NumHistory[id] = [];
        if (maxNum >= 1) {
            var current = Math.round(Math.random() * (maxNum - 1)), x = 0;
            if (maxNum > 1 && this.NumHistory[id].length > 0) {
                if (this.NumHistory[id].length !== maxNum) {
                    while ($.inArray(current, this.NumHistory[id]) !== -1) {
                        current = Math.round(Math.random() * (maxNum - 1));
                        x = x + 1;
                    }
                    this.NumHistory[id].push(current);
                } else {
                    //reset
                    this.NumHistory[id] = [current];
                }
            } else {
                //first time only
                this.NumHistory[id].push(current);
            }
            return current;
        } else {
            return maxNum;
        }
    },
    clear: function (id) {
        this.NumHistory[id] = [];
    }
};

用法是:(100 是 (0-100) 范围,the_id 是.. id)

UniqueRandom.NumHistory[100, 'the_id']

我已经设置了一个Fiddle 有一个演示。

【问题讨论】:

    标签: javascript jquery random


    【解决方案1】:
    1. 这不是最佳做法。在我看来,最好为每个需要生成的数字系列实例化一个对象。
    2. 我建议生成一个包含所有可能值的数组并将其改组。然后你就可以弹出它了。

    【讨论】:

    • +1;我还建议不要使用Math.rounddeveloper.mozilla.org/en-US/docs/JavaScript/Reference/…
    • 可能值的数组,然后弹出它,确实更有意义。因此,如果我理解正确,我将不得不按照 Jack 的建议重写函数,“历史”变量是一个预先填充的数组(在第一个实例中填充)?
    【解决方案2】:

    我倾向于认为它确实不是最有效的。我没有立即收到//first time only
    此外,您可以通过跳过else return .. 并将条件编写为相反来使代码更具可读性,例如:

    if (maxNum >= 1) {
        //code
    } else {
        return maxNum;
    }
    

    变成

    if (maxNum < 1) { // or maybe even if maxNum == 0
        return maxNum;
    }
    
    //code
    

    而且你的x 变量似乎是多余的。

    【讨论】:

      【解决方案3】:

      我可能会这样实现它,使用随机生成器的实际实例。这样可以将每个生成器的历史分开。

      function RandomGenerator(maxNum)
      {
          this.max = maxNum;
          this.history = {};
          this.histn = 0;
      }
      
      // generate random number in range [0..maxNum)
      RandomGenerator.prototype.generate = function()
      {
          var value;
      
          if (this.histn == this.max ) {
              return false;
          }
      
          do {
              value = Math.floor(Math.random() * this.max );
          } while (this.history[value]);
      
          this.history['' + value] = 1;
          ++this.histn;
      
          return value;
      }
      
      var mygen = new RandomGenerator(100);
      console.log(mygen.generate());
      

      在我的实现中,我为历史选择了一个普通对象而不是数组;通过测试属性而不是 $.inArray() 来测试之前是否生成过值。

      【讨论】:

      • 这似乎工作得很好!我把它和我的小提琴结合起来了:jsfiddle.net/DMm5B。你认为重写它以符合 Alex 的建议,即使用所有可能的组合预先填充数组,一次弹出一个,仍然会提供更好的性能吗?
      • 我认为this.histn == maxNum 应该是this.histn == this.max
      • 也注意到了,看我的小提琴
      【解决方案4】:

      我同意 Alex 的观点,在大多数用例中,您希望生成一个包含所有值的数组,将它们随机排列,然后在需要时弹出它们。

      这是一个例子:

      var getShuffledUniqueRandoms = function(count, suffix) {
          var values = [];
      
          for (var i = 1; i < count+1; i++) {
              values.push(i + suffix);
          }
      
          // Shuffle function originally from:
          //+ Jonas Raoni Soares Silva
          //@ http://jsfromhell.com/array/shuffle [v1.0]
      
          return (function(o){ //v1.0
              for(var j, x, i = o.length; i; j = parseInt(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
              return o;
          })(values);
      }
      
      var values = getShuffledUniqueRandoms(10, "index");
      
      
      $('button').click(function() {
      
          if (values.length == 0) {
              $('body').append('<p>Out of values.</p>');
          } else {
              $('body').append('<p>' + values.pop() + '</p>');
          }
      });
      ​
      

      FIDDLE

      使用这种算法,它的前期成本更高,但至少它有一个已知的完成时间(大约 O(n))。

      使用不断检查随机值是否在数组中的算法,每次新的迭代都会变得越来越慢。

      现在,如果您的数据集总是相对较小,您的算法可能会运行得更好一些,但任何大于 10 左右的数据都会开始失去优势。

      【讨论】:

        【解决方案5】:

        我采用了 Jack 的代码并对其进行了调整以使用弹出数组方法。

        function fisherYates ( myArray ) {
          var i = myArray.length;
          if ( i == 0 ) return false;
          while ( --i ) {
             var j = Math.floor( Math.random() * ( i + 1 ) );
             var tempi = myArray[i];
             var tempj = myArray[j];
             myArray[i] = tempj;
             myArray[j] = tempi;
           }
        }
        
        function RandomGenerator(maxNum) {
        
            this.max = maxNum;
            this.initRandomArray();
        
        }
        
        RandomGenerator.prototype.generate = function() {
        
            // if no more numbers available generate new array
            if( this.left === 0 ) this.initRandomArray();
        
            this.last = this.arr.pop();
            this.left = this.arr.length;
            this.history.push( this.last );
            return this.last;
        }
        
        RandomGenerator.prototype.initRandomArray = function() {
        
            this.arr = [];
            this.history = [];
            this.last = null;
            this.left = this.max;
        
            for( var i = 0; i < this.max; i++ ) {
                this.arr.push( i );
            }
        
            fisherYates( this.arr );
        
        }
        
        var mygen = new RandomGenerator(100);
        console.log( mygen.generate() );
        

        我从here得到了fisherYates算法。

        如果已在历史对象中找到新随机数,则生成新随机数的方法将导致不必要的循环。

        小提琴here

        【讨论】:

        • 谢谢!这真的很有用。唯一的问题是我需要历史可以访问以供其他用途。在这种情况下,“arr”会很好,因为它实际上是一个“反转历史”,对吗?我唯一需要的是,一旦返回所有唯一数字,函数就会自行重置,所以我认为我需要检查数组长度,当它变为 0 时重新填充,对吗?
        • @c_kick 添加了一个历史对象以及一个新方法initRandomArray,允许您在必要时重新初始化数组。
        • 注意到您的编辑,非常感谢您的努力!这正是我所需要的。编辑:在 undefined 上划掉那条评论,我犯了一个错误。非常感谢您合并以下所有出色的答案!谢谢亚历克斯、杰克和布鲁诺!
        • @c_kick 我添加了一个小提琴,所以你可以看到它的实际效果。
        猜你喜欢
        • 1970-01-01
        • 2017-02-14
        • 2011-07-03
        • 1970-01-01
        • 1970-01-01
        • 2012-10-28
        • 1970-01-01
        • 2022-01-12
        • 1970-01-01
        相关资源
        最近更新 更多