JavaScript
我尝试过寻找类似的东西,但找不到。
这是一个简单的想法:
a.取 0 到 10 之间的随机数。
b.假设滚动的随机数是 3。
c.然后,保存数字(3)。
d。现在,再次在 0 到 10 之间取另一个随机数,但它不能是 3,因为它已经出现了。
一种解决方案是生成一个数组(一个“桶”),其中包含您要选取的所有值,在本例中为 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());
使用d3:
var bucket = d3.shuffle(d3.range(11));
while(bucket.length) {
console.log(bucket.pop());
}
你可以使用这样的东西:
/**
* 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
});
只是为了好玩:源自@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了解使用示例
大多数时候,我会坚持使用其他答案建议的方法 - 即创建一个可能性数组,创建它的打乱版本,然后将前 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)
Var rnd = getRnd();
While(rnd != lastRnd)
rnd = getRnd();
其中
getRnd()
是生成随机数的函数。
实际上,您必须检查当前的随机数是否在数组中......并且如果您可能的随机数列表很小,请注意无限循环。
只需使用以下函数,它将根据样本大小在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
正如一位伟人(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()
新值。
对于仅选择两个数字的大空间,我认为您可以通过选择一个数字和一个偏移量来实现这一目标,而无需使用大数组并且仍然具有统一的概率(并且固定时间 - 无 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;
除此之外,我认为您可以按排序顺序累积选择,然后根据跳过的数字数量来调整后续选择(如果内存效率和运行时效率很重要) - 尽管它会变得更加复杂。
对于
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
尝试性能测试。