【问题标题】:Part random retrieval, part minimize repeats部分随机检索,部分最小化重复
【发布时间】:2017-09-21 22:08:01
【问题描述】:

我有一个包含 10 个数字的数组:

var array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

我编写了一个函数(如下)来从数组中返回一个随机元素,而不重复返回相同的元素,直到返回 8 个其他元素。它可以工作,但似乎效率很低,因为随机数生成器通常需要多次调用才能继续代码。有没有更有效的方法来实现相同的结果?

var array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var recentlyReturned = [];

function minimalRepeat() {

var i = Math.floor(Math.random() * 10);
while( recentlyReturned.indexOf(i) != -1 ) {
    i = Math.floor(Math.random() * 10);
}

recentlyReturned.push(i);
while(recentlyReturned.length>8) {
    recentlyReturned.shift();
}
return array[i];
}


for(var i=1; i<20; i++) {
    console.log(minimalRepeat());
}

* 编辑 *

我根据 Tudor Ilisoi 的反馈重写了代码。它肯定更有效率。但是,有可能在 shuffle 边界上重复。

版本 2:

function shuffle(arr) {
    var copyArr = arr.slice();
    for (let i = copyArr.length; i; i--) {
        let j = Math.floor(Math.random() * i);
        [copyArr[i - 1], copyArr[j]] = [copyArr[j], copyArr[i - 1]];
    }
    return copyArr;
}

var a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var b = shuffle(a);

for(var i=1; i<20; i++) {
    if(b.length == 0) {
        b = shuffle(a);
    }
    console.log(b.pop());
}

【问题讨论】:

    标签: javascript algorithm random


    【解决方案1】:

    费舍尔-耶茨:

    function shuffle(array) {
      var m = array.length, t, i;
    
      while (m) {
    
        i = Math.floor(Math.random() * m--);
    
        t = array[m];
        array[m] = array[i];
        array[i] = t;
      }
    
      return array;
    }
    
    var origArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    var numbers = origArray;
    var randomNumbers = shuffle(numbers);
    
    var result = randomNumbers.slice(0, 8);
    
    for (var i = 0; i < 11; i++) {
      result.push(randomNumbers[Math.floor(Math.random() * (origArray.length-1))]);
    }
    

    前 8 个数字应该是唯一的(来自预定义的集合)。
    接下来的 11 个号码可以在所选号码内和预定义集中重复。

    (如果我理解正确的话。)

    基于 cmets 编辑

    由于 no-repeat-window 是 8,因此数组中剩下两个数字;最后两个数字。

    下一个随机数是数组中的第一个数字。

    您可以做的是将popshift 这些数字放入一个临时数组中,将临时数组和push 临时数组打乱回随机数组。

    var origArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    var randomNumbers = shuffle(origArray);
    
    for (var i = 0; i < 19; i++)
    {
        var tmp = [];
        tmp.push(randomNumbers.pop());   // First number not in no-repeat-window
        tmp.push(randomNumbers.pop());   // Second number not in no-repeat-window
        tmp.push(randomNumbers.shift()); // Current random number.
    
        randomNumbers.push.apply(randomNumbers, shuffle(tmp));
    }
    

    jsFiddle

    【讨论】:

    • 我的问题应该更清楚。也许更好的说法是,最近返回的 8 个项目始终不能重复。
    • @Timothy Seevers 那么对于第 i 个返回的项目,只能在第 [i-9] 个和第 [i-10] 个项目之间进行选择吗?
    • 是的,没错。因此,选择在第九或第十项之前生成随机数的低效率。但这让我觉得我只能在剩下的 2 个数字之间随机选择,而不是整个集合。我认为那会奏效。我将不得不尝试验证。
    • @TimothySeevers:您可以在剩余的两个数字之间随机选择当前随机数(数组中的第一个)。查看我的编辑。
    • 是的,这有效并且避免尝试直到你不失败。非常感谢!
    【解决方案2】:

    你可以这样做:

    • 复制数组并将其随机化。我们就叫它copy
    • 您的 minimumRepeat 将从带有copy.pop() 的随机副本中提取,直到副本为空(或提取了 8 个元素)。满足此条件后,再制作一份副本

    通过从随机副本中提取,您可以确保在再次生成副本之前不会返回相同的元素。

    根据评论反馈进行编辑

    function shuffle(arr) {
      var copyArr = arr.slice();
      for (let i = copyArr.length; i; i--) {
        let j = Math.floor(Math.random() * i);
        [copyArr[i - 1], copyArr[j]] = [copyArr[j], copyArr[i - 1]];
      }
      return copyArr;
    }
    
    var latest = []
    var a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    var b = shuffle(a);
    
    for (var i = 1; i < 20; i++) {
      if (b.length == 0) {
        b = shuffle(a);
      }
      var popped = b.pop()
    
      //if found in recents
      if (latest.indexOf(popped) !== -1) {
        continue;
      }
    
      //push the popped element
      latest.push(popped)
      if (latest.length > 8) {
        latest.shift() //remove the oldest "recent" to keep the "latest" length at 8
      }
    
      console.log(popped);
    }

    【讨论】:

    • 啊,确实有道理。我会尝试根据您的建议重写我的代码并发布结果。
    • 我确实编写了代码来对数组进行洗牌,直到为空,并在为空时再次洗牌。我将编辑我的帖子以分享它。它肯定更有效,但是,当你从一个洗牌组交叉到下一个组时,可能会得到重复。最坏的例子是,在 shuffle 副本 1 中,堆栈中的最后一项最终成为 shuffle 副本 2 的堆栈中的第一项。
    • 确实……你还需要做一件事。将最新提取的 8 个保留在另一个数组中,如果您刚刚弹出的元素在那里,pop() 另一个。原数组必须大于8,否则会死循环
    • 已编辑,现在似乎可以使用。我现在看到您在最初的问题中有最新/最近返回的代码。无论如何,它有效。
    • 这确实有效,而且比我原来的要好。但是,我试图避免尝试直到你没有失败的场景。
    【解决方案3】:

    您可以在数组中组织循环队列:

    Randomize array A[] with Fisher-Yates algo
    Set Idx to 0
    At every step generate random r = 0 or 1 
    If r=1 swap A[Idx % 10] and A[(Idx + 1) % 10]
    Output A[Idx % 10] 
    Increment Idx
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-07-08
      • 2021-01-07
      • 2012-12-21
      • 1970-01-01
      • 2013-01-13
      • 2022-07-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多