什么是采用随机样本的简洁方法,而无需在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)];
但如果这样做多次,则存在多次抓取相同条目的风险。
答案 0 :(得分:41)
我建议使用Fisher-Yates shuffle对数组的副本进行洗牌并进行切片:
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 :(得分:11)
派对有点晚了,但这可以通过下划线的新sample方法解决(下划线1.5.2 - 2013年9月):
var x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
var randomFiveNumbers = _.sample(x, 5);
答案 2 :(得分:6)
或者......如果你使用underscore.js ......
_und = require('underscore');
...
function sample(a, n) {
return _und.take(_und.shuffle(a), n);
}
足够简单。
答案 3 :(得分:2)
您可以在选择数组时删除数组副本中的元素。性能可能并不理想,但它可能适合您的需求:
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;
}
答案 4 :(得分:2)
这是基于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 :(得分:2)
在我看来,我认为不需要改变整个套牌。你只需要确保你的样本是随机的而不是你的套牌。您可以做的是从前面选择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
提供了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;
}
与所有这些帖子一样,这使用了Fisher-Yates Shuffle。但是,我删除了复制数组的负责人。
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 = [];
答案 6 :(得分:1)
虽然我强烈支持使用Fisher-Yates Shuffle,suggested by Tim Down,但这是一种非常短的方法,用于实现所请求的随机子集,数学上正确,包括空集和给定集本身
注意解决方案取决于lodash / underscore:
function subset(arr) {
return _.sample(arr, _.random(arr.length));
}
答案 7 :(得分:1)
如果您使用的是lodash,则在4.x中更改了API:
const oneItem = _.sample(arr);
const nItems = _.sampleSize(arr, n);
答案 8 :(得分:1)
您可以通过以下方式获得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); };
答案 9 :(得分:0)
也许我错过了一些东西,但是似乎有一种解决方案不需要洗牌的复杂性或潜在的开销:
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;
}
答案 10 :(得分:0)
许多这些答案都涉及克隆,改组,切片原始数组。我很好奇为什么从熵/分布的角度来看这会有所帮助。
我不是专家,但是我确实使用索引编写了一个示例函数,以避免任何数组突变—尽管它确实添加到Set中。我也不知道该如何随机分布,但是代码很简单,我认为在这里可以找到答案。
function sample(array, size = 1) {
const { floor, random } = Math;
let sampleSet = new Set();
for (let i = 0; i < size; i++) {
let index;
do { index = floor(random() * array.length); }
while (sampleSet.has(index));
sampleSet.add(index);
}
return [...sampleSet].map(i => array[i]);
}
const words = [
'confused', 'astonishing', 'mint', 'engine', 'team', 'cowardly', 'cooperative',
'repair', 'unwritten', 'detailed', 'fortunate', 'value', 'dogs', 'air', 'found',
'crooked', 'useless', 'treatment', 'surprise', 'hill', 'finger', 'pet',
'adjustment', 'alleged', 'income'
];
console.log(sample(words, 4));
答案 11 :(得分:0)
对于非常大的数组,使用索引比使用数组成员更有效。
这就是我在此页面上找不到我喜欢的任何内容后的结果。
/**
* Get a random subset of an array
* @param {Array} arr - Array to take a smaple of.
* @param {Number} sample_size - Size of sample to pull.
* @param {Boolean} return_indexes - If true, return indexes rather than members
* @returns {Array|Boolean} - An array containing random a subset of the members or indexes.
*/
function getArraySample(arr, sample_size, return_indexes = false) {
if(sample_size > arr.length) return false;
const sample_idxs = [];
const randomIndex = () => Math.floor(Math.random() * arr.length);
while(sample_size > sample_idxs.length){
let idx = randomIndex();
while(sample_idxs.includes(idx)) idx = randomIndex();
sample_idxs.push(idx);
}
sample_idxs.sort((a, b) => a > b ? 1 : -1);
if(return_indexes) return sample_idxs;
return sample_idxs.map(i => arr[i]);
}