什么是与JavaScript中的数组采取随机抽样,无需更换一个干净的方式? 因此,假设有一个数组
x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
我想随机样品5个的唯一值; 即产生长度为5的随机子集来生成一个随机样本,一个可以这样做:
x[Math.floor(Math.random()*x.length)];
但是,如果这样做了多次,有一个相同的入口多次抢夺的风险。
什么是与JavaScript中的数组采取随机抽样,无需更换一个干净的方式? 因此,假设有一个数组
x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
我想随机样品5个的唯一值; 即产生长度为5的随机子集来生成一个随机样本,一个可以这样做:
x[Math.floor(Math.random()*x.length)];
但是,如果这样做了多次,有一个相同的入口多次抢夺的风险。
我建议洗牌使用数组的副本费雪耶茨洗牌 ,并采取切片:
function getRandomSubarray(arr, size) {
var shuffled = arr.slice(0), i = arr.length, temp, index;
while (i--) {
index = Math.floor((i + 1) * Math.random());
temp = shuffled[index];
shuffled[index] = shuffled[i];
shuffled[i] = temp;
}
return shuffled.slice(0, size);
}
var x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
var fiveRandomMembers = getRandomSubarray(x, 5);
请注意,这不会是获得了大阵的一个小随机子集最有效的方法,因为它打乱了整个阵列不必要的。 为了获得更好的性能,你可以做一个部分,而不是洗牌:
function getRandomSubarray(arr, size) {
var shuffled = arr.slice(0), i = arr.length, min = i - size, temp, index;
while (i-- > min) {
index = Math.floor((i + 1) * Math.random());
temp = shuffled[index];
shuffled[index] = shuffled[i];
shuffled[i] = temp;
}
return shuffled.slice(min);
}
有点迟到了,但是这可能是与下划线的新的需要解决的样品方法(下划线1.5.2日- 9月2013年):
var x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
var randomFiveNumbers = _.sample(x, 5);
或者......如果你使用underscore.js ...
_und = require('underscore');
...
function sample(a, n) {
return _und.take(_und.shuffle(a), n);
}
够简单。
当您选择它们时,可以从阵列中的副本中删除的元素。 性能可能不理想,但它可能是你所需要的确定:
function getRandom(arr, size) {
var copy = arr.slice(0), rand = [];
for (var i = 0; i < size && i < copy.length; i++) {
var index = Math.floor(Math.random() * copy.length);
rand.push(copy.splice(index, 1)[0]);
}
return rand;
}
虽然我使用费雪耶茨洗牌大力支持,如由Tim唐氏建议 ,这里是实现的要求,数学上正确,包括空集,并给出自己设定一个随机子集在很短的方法。
注意解决方案取决于lodash / 下划线 :
function subset(arr) {
return _.sample(arr, _.random(arr.length));
}
在我看来,我不认为有必要洗牌整个甲板。 你只需要确保你的样本是随机的不是你的甲板。 你可以做的,就是选择size
从正面量然后交换每一个采样阵列,以在它的另一个位。 所以,如果你允许更换你会得到越来越多的洗牌。
function getRandom(length) { return Math.floor(Math.random()*(length)); }
function getRandomSample(array, size) {
var length = array.length;
for(var i = size; i--;) {
var index = getRandom(length);
var temp = array[index];
array[index] = array[i];
array[i] = temp;
}
return array.slice(0, size);
}
此算法仅2*size
的步骤,如果包括slice
方法,来选择随机样本。
为了使样本更随机,我们可以随机选择样本的起点。 但它是一个更贵一点,以获得样品。
function getRandomSample(array, size) {
var length = array.length, start = getRandom(length);
for(var i = size; i--;) {
var index = (start + i)%length, rindex = getRandom(length);
var temp = array[rindex];
array[rindex] = array[index];
array[index] = temp;
}
var end = start + size, sample = array.slice(start, end);
if(end > length)
sample = sample.concat(array.slice(0, end - length));
return sample;
}
是什么让这个更随机的是,当你永远只是洗牌前的项目,你往往会在样品中没有得到他们很多时候如果采样阵列大,样本较小的事实。 如果数组是不应该始终是相同的,这将不会是一个问题。 那么,究竟这种方法确实是改变了这个位置,洗牌后的区域开始。
为了不具有复制采样阵列,而不用担心更换,你可以做以下的,但它确实给你3*size
VS的2*size
。
function getRandomSample(array, size) {
var length = array.length, swaps = [], i = size, temp;
while(i--) {
var rindex = getRandom(length);
temp = array[rindex];
array[rindex] = array[i];
array[i] = temp;
swaps.push({ from: i, to: rindex });
}
var sample = array.slice(0, size);
// Put everything back.
i = size;
while(i--) {
var pop = swaps.pop();
temp = array[pop.from];
array[pop.from] = array[pop.to];
array[pop.to] = temp;
}
return sample;
}
要应用给了一点点随机抽样的没有替换函数算法:
function getRandomSample(array, size) {
var length = array.length, start = getRandom(length),
swaps = [], i = size, temp;
while(i--) {
var index = (start + i)%length, rindex = getRandom(length);
temp = array[rindex];
array[rindex] = array[index];
array[index] = temp;
swaps.push({ from: index, to: rindex });
}
var end = start + size, sample = array.slice(start, end);
if(end > length)
sample = sample.concat(array.slice(0, end - length));
// Put everything back.
i = size;
while(i--) {
var pop = swaps.pop();
temp = array[pop.from];
array[pop.from] = array[pop.to];
array[pop.to] = temp;
}
return sample;
}
像所有的这些帖子,这个使用费雪耶茨洗牌。 但是,我删除复制阵列的头顶。
function getRandomSample(array, size) {
var r, i = array.length, end = i - size, temp, swaps = getRandomSample.swaps;
while (i-- > end) {
r = getRandom(i + 1);
temp = array[r];
array[r] = array[i];
array[i] = temp;
swaps.push(i);
swaps.push(r);
}
var sample = array.slice(end);
while(size--) {
i = swaps.pop();
r = swaps.pop();
temp = array[i];
array[i] = array[r];
array[r] = temp;
}
return sample;
}
getRandomSample.swaps = [];
如果您使用lodash在API中4.x的改变:
const oneItem = _.sample(arr);
const nItems = _.sampleSize(arr, n);
https://lodash.com/docs#sampleSize
这是基于Fisher-Yater Shuffle的另一种实现方式。 但是,这是为其中样品尺寸比所述阵列长度显著小的情况下最优化。 此实现不扫描整个阵列也不分配阵列一样大原始数组。 它采用稀疏矩阵,以减少内存分配。
function getRandomSample(array, count) {
var indices = [];
var result = new Array(count);
for (let i = 0; i < count; i++ ) {
let j = Math.floor(Math.random() * (array.length - i) + i);
result[i] = array[indices[j] === undefined ? j : indices[j]];
indices[j] = indices[i] === undefined ? i : indices[i];
}
return result;
}
你可以通过这种方式,5个元素示例:
var sample = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
.map(a => [a,Math.random()])
.sort((a,b) => {return a[1] < b[1] ? -1 : 1;})
.slice(0,5)
.map(a => a[0]);
你可以将其定义为在代码中使用函数:
var randomSample = function(arr,num){ return arr.map(a => [a,Math.random()]).sort((a,b) => {return a[1] < b[1] ? -1 : 1;}).slice(0,num).map(a => a[0]); }
或将其添加到Array对象本身:
Array.prototype.sample = function(num){ return this.map(a => [a,Math.random()]).sort((a,b) => {return a[1] < b[1] ? -1 : 1;}).slice(0,num).map(a => a[0]); };
如果你愿意,你可以分开的有2个功能(随机播放和样品)的代码:
Array.prototype.shuffle = function(){ return this.map(a => [a,Math.random()]).sort((a,b) => {return a[1] < b[1] ? -1 : 1;}).map(a => a[0]); };
Array.prototype.sample = function(num){ return this.shuffle().slice(0,num); };
也许我失去了一些东西,但似乎是不需要复杂或洗牌的潜在开销的解决方案:
function sample(array,size) {
const results = [],
sampled = {};
while(results.length<size && results.length<array.length) {
const index = Math.trunc(Math.random() * array.length);
if(!sampled[index]) {
results.push(array[index]);
sampled[index] = true;
}
}
return results;
}