JavaScript - 如何在不放回的情况下随机采样项目?

问题描述 投票:0回答:10

JavaScript

我尝试过寻找类似的东西,但找不到。

这是一个简单的想法:

a.取 0 到 10 之间的随机数。

b.假设滚动的随机数是 3。

c.然后,保存数字(3)。

d。现在,再次在 0 到 10 之间取另一个随机数,但它不能是 3,因为它已经出现了。

javascript probability random
10个回答
20
投票

一种解决方案是生成一个数组(一个“桶”),其中包含您要选取的所有值,在本例中为 0 到 10 之间的所有数字。然后从数组中随机选取一个并将其从桶中删除。请注意,下面的示例不会检查存储桶是否为空,因此如果您调用下面的函数超过 10 次,您将收到错误。

var bucket = [];

for (var i=0;i<=10;i++) {
    bucket.push(i);
}

function getRandomFromBucket() {
   var randomIndex = Math.floor(Math.random()*bucket.length);
   return bucket.splice(randomIndex, 1)[0];
}

// will pick a random number between 0 and 10, and can be called 10 times
console.log(getRandomFromBucket());

1
投票

使用d3

var bucket = d3.shuffle(d3.range(11));

while(bucket.length) {
  console.log(bucket.pop());
}

0
投票

你可以使用这样的东西:

/**
* range Get an array of numbers within a range
* @param min {number} Lowest number in array
* @param max {number} Highest number in array
* @param rand {bool} Shuffle array
* @return {array}
*/
range: function( min, max, rand ) {
  var arr = ( new Array( ++max - min ) )
    .join('.').split('.')
    .map(function( v,i ){ return min + i })
  return rand
    ? arr.map(function( v ) { return [ Math.random(), v ] })
       .sort().map(function( v ) { return v[ 1 ] })
    : arr
}

并像这样使用它:

var arr = range( 1, 10, true )

现在你有一个数组,其中包含从 1 到 10 的 10 个数字,按随机顺序排列并且从不重复。接下来你可以这样做:

arr.forEach(function( num, i ) { 
  // do something, it will loop 10 times 
  // and num will always be a different number
  // from 1 to 10
});

0
投票

只是为了好玩:源自@Strilles回答“桶构造函数”

function RandomBucket(from,until){
  min = (Number(from) || 0);
  max = (Number(until) || 10)+1;
  this.bucket = String(Array(max-min)).split(',').map(function(i){
     return min++;
  });

  if (!RandomBucket.prototype.get){
   RandomBucket.prototype.get = function(){
      var randomValue = 
        this.bucket.length < 2
        ? this.bucket.shift()
        : this.bucket.splice(Math.floor(Math.random()*this.bucket.length),1);
       return randomValue || 'bucket empty';
      };
  }
}

请参阅JsFiddle了解使用示例


0
投票

大多数时候,我会坚持使用其他答案建议的方法 - 即创建一个可能性数组,创建它的打乱版本,然后将前 n 个值作为样本(所有操作都是简单且通用的,并且可以不可变地实现)。

但是,如果与您想要使用的内存量相比,或者与您想要绘制的随机值的数量相比,可能性的范围很大(尽管@Strilles解决方案使用内存,但不会绘制很多),那么这并不是很好随机值,所以即使对于我下面的用例来说也可能是最好的)。

您的问题似乎建议的解决方案可能如下所示:

// select n integers from the range [from, to] (inclusive at both sides),
// don't use this approach for large values of n
// taking random values from the randomSource as needed
function randomNumbersWithoutReplacement(n, from, to, randomSource = Math.random) {
    const result = [];
    for (let i = 0; i < n; ++i) {
        // i values have already been taken
        // the +1 makes it inclusive
        const rangeWidth = to - from - i + 1

        let value = Math.floor(rangeWidth * randomSource()) + from

        // correct the value compared to the already sampled integers
        for (let j = 0; j < result.length; ++j) {
            if (result[j] <= value) {
                value++
            }
        }

        result.push(value)

        // sorting makes the correction loop simpler
        // (and it's nice to report the result sorted too)
        result.sort((a, b) => a - b)
    }
    return result
}

你为什么想要这个?

const quantumLottoNumbers = randomNumbersWithoutReplacement(6, 1, 59, quantumRandomSource)

0
投票
Var rnd = getRnd();

While(rnd != lastRnd)
  rnd = getRnd();

其中

getRnd()
是生成随机数的函数。

实际上,您必须检查当前的随机数是否在数组中......并且如果您可能的随机数列表很小,请注意无限循环。


0
投票

只需使用以下函数,它将根据样本大小在2个数字之间抽取样本,并且无需替换:

function random_sample_without_replacement(options) {
    const arr = [];
    while(arr.length < options.sample_size){
        var r = Math.floor(Math.random() * options.population_size) + 1;
        if(arr.indexOf(r) === -1) {
            arr.push(r);
        }
    }
    return(arr)
}

用法

random_sample = random_sample_without_replacement({
    population_size : 1000,
    sample_size : 100
})

[950, 725, 239, 273, 814, 325, 834, 702, 209, 740, 539, 281, 799, 459, 443, 758, 567, 124, 428, 462, 576, 234, 35, 344, 441, 580, 461, 371, 354, 616, 704, 233, 486, 296, 182, 63, 57, 357, 226, 969, 396, 879, 904, 718, 22, 121, 835, 52, 310, 359, 593, 793, 421, 870, 719, 959, 639, 755, 85, 10, 365, 189, 457, 895, 168, 574, 115, 176, 252, 284, 840, 721, 962, 780, 851, 71, 144, 827, 843, 643, 54, 246, 838, 100, 452, 303, 20, 572, 259, 102, 909, 471, 642, 8, 716, 388, 374, 338, 425, 880]

检查是否确实无需更换:

[...new Set(random_sample)].length

100

0
投票

正如一位伟人(Joma)曾经说过的,“Hashmap,我会使用 Hashmap!”。
您可以简单地将已获取的值存储为对象键,并在每次获取新值时进行检查。如果它存在,则循环增加它,直到它变成未取值。如果达到长度,请将其设置为零。

function sample(options, count) {
  if (options < count) {
    throw new Error(
      `Random sample error: can't sample ${count} items without repetition from ${options} options`
    );
  }

  const result = [];
  const exclude = {};

  for (let i = 0; i < count; i++) {
    let index = Math.floor(Math.random() * options);
    while (exclude[index]) {
      index += 1;
      index %= options;
    }
    exclude[index] = true;
    result.push(index);
  }
  return result;
}

sample(10, 10);
// [8, 4, 6, 5, 7, 9, 0, 1, 3, 2]
sample(10, 3);
// [1, 6, 7]

检查下一个索引的计算成本并不是那么大,因为它使用对象而不是数组。
我不知道您是否可以通过先行确定所需的结果大小,但如果不能,您可以将内部

for
代码和
exclude
变量分开。或者甚至生成整个序列和
.pop()
新值。


0
投票

对于仅选择两个数字的大空间,我认为您可以通过选择一个数字和一个偏移量来实现这一目标,而无需使用大数组并且仍然具有统一的概率(并且固定时间 - 无 while 循环),例如:

const range = 1000000000;
const firstPick = Math.trunc(Math.random()*range);
// range -1 so the offset won't wrap
const offset= Math.trunc(Math.random()*(range-1));
const secondPick = (firstPick+offset)%range;

除此之外,我认为您可以按排序顺序累积选择,然后根据跳过的数字数量来调整后续选择(如果内存效率和运行时效率很重要) - 尽管它会变得更加复杂。


0
投票

对于

k / n < 0.02
(或多或少)来说,@Cybernetic 的
indexOf
方法似乎要快得多。因此,有效的实施方式是:

const sampleWithoutReplacementLargeK = (arr, k) => {
  arr = arr.slice();
  const n = arr.length;
  for (let i = 0; i < k; ++i) {
    const index = Math.floor(Math.random() * (n - i)) + i;
    const t = arr[index];
    arr[i] = arr[index];
    arr[index] = t;
  }
  return arr.slice(0, k);
}
const sampleWithoutReplacementSmallK = (arr, k) => {
  const idxs = [];
  const n = arr.length;
  while (idxs.length < k) {
    const r = Math.floor(Math.random() * n);
    if (idxs.indexOf(r) === -1) {
      idxs.push(r);
    }
  }
  return idxs.map(i => arr[i]);
}
const sampleWithoutReplacement = (arr, k) => {
  if (k / arr.length < 0.02) { 
    return sampleWithoutReplacementSmallK(arr, k); 
  }
  return sampleWithoutReplacementLargeK(arr, k);
}

我很高兴看到对上述内容的评论和修复。

使用 kOverN = 0.01

kOverN = 0.5
尝试
性能测试

© www.soinside.com 2019 - 2024. All rights reserved.