如何从一个数组中获取大量的随机元素?

我正在研究“如何在 javascript 中从数组中随机访问元素”。我找到了很多关于这个的链接。比如: 从 JavaScript 数组中获取随机项

var item = items[Math.floor(Math.random()*items.length)];

但在这里,我们只能从数组中选择一项。如果我们想要一个以上的要素,那么我们如何才能实现这一点?我们如何从一个数组中获得多个元素?

162558 次浏览

创建一个函数:

var getMeRandomElements = function(sourceArray, neededElements) {
var result = [];
for (var i = 0; i < neededElements; i++) {
result.push(sourceArray[Math.floor(Math.random()*sourceArray.length)]);
}
return result;
}

您还应该检查 sourceArray 是否有足够的元素可以返回。如果希望返回唯一的元素,则应从 source 数组中删除选定的元素。

编辑 : 如果您只想获取少量元素,那么这个解决方案比这里提供的其他解决方案(它将源数组拼接起来)要慢。此解决方案的速度仅取决于原始数组中的元素数,而拼接解决方案的速度则取决于输出数组中所需的元素数。

如果你想要非重复的随机元素,你可以洗牌你的数组,然后只得到你想要的数量:

function shuffle(array) {
var counter = array.length, temp, index;


// While there are elements in the array
while (counter--) {
// Pick a random index
index = (Math.random() * counter) | 0;


// And swap the last element with it
temp = array[counter];
array[counter] = array[index];
array[index] = temp;
}


return array;
}


var arr = [0,1,2,3,4,5,7,8,9];


var randoms = shuffle(arr.slice(0)); // array is cloned so it won't be destroyed
randoms.length = 4; // get 4 random elements

演示: http://jsbin.com/UHUHuqi/1/edit

洗牌功能取自这里: https://stackoverflow.com/a/6274398/1669279

如果你想在一个循环中从数组中随机获取项而不重复,你可以用 splice从数组中移除选定的项:

var items = [1, 2, 3, 4, 5];
var newItems = [];


for (var i = 0; i < 3; i++) {
var idx = Math.floor(Math.random() * items.length);
newItems.push(items[idx]);
items.splice(idx, 1);
}


console.log(newItems);

Array.prototype.getnkill = function() {
var a = Math.floor(Math.random()*this.length);
var dead = this[a];
this.splice(a,1);
return dead;
}


//.getnkill() removes element in the array
//so if you like you can keep a copy of the array first:


//var original= items.slice(0);




var item = items.getnkill();


var anotheritem = items.getnkill();

试试这个非破坏性(且快速)功能:

function getRandom(arr, n) {
var result = new Array(n),
len = arr.length,
taken = new Array(len);
if (n > len)
throw new RangeError("getRandom: more elements taken than available");
while (n--) {
var x = Math.floor(Math.random() * len);
result[n] = arr[x in taken ? taken[x] : x];
taken[x] = --len in taken ? taken[len] : len;
}
return result;
}

只有两句台词:

// Shuffle array
const shuffled = array.sort(() => 0.5 - Math.random());


// Get sub-array of first n elements after shuffled
let selected = shuffled.slice(0, n);

演示 :

这是最正确的答案,它会给你随机 + 唯一的元素。

function randomize(array, n)
{
var final = [];
array = array.filter(function(elem, index, self) {
return index == self.indexOf(elem);
}).sort(function() { return 0.5 - Math.random() });


var len = array.length,
n = n > len ? len : n;


for(var i = 0; i < n; i ++)
{
final[i] = array[i];
}


return final;
}


// randomize([1,2,3,4,5,3,2], 4);
// Result: [1, 2, 3, 5] // Something like this

从 Python 标准库移植 .sample:

function sample(population, k){
/*
Chooses k unique random elements from a population sequence or set.


Returns a new list containing elements from the population while
leaving the original population unchanged.  The resulting list is
in selection order so that all sub-slices will also be valid random
samples.  This allows raffle winners (the sample) to be partitioned
into grand prize and second place winners (the subslices).


Members of the population need not be hashable or unique.  If the
population contains repeats, then each occurrence is a possible
selection in the sample.


To choose a sample in a range of integers, use range as an argument.
This is especially fast and space efficient for sampling from a
large population:   sample(range(10000000), 60)


Sampling without replacement entails tracking either potential
selections (the pool) in a list or previous selections in a set.


When the number of selections is small compared to the
population, then tracking selections is efficient, requiring
only a small set and an occasional reselection.  For
a larger number of selections, the pool tracking method is
preferred since the list takes less space than the
set and it doesn't suffer from frequent reselections.
*/


if(!Array.isArray(population))
throw new TypeError("Population must be an array.");
var n = population.length;
if(k < 0 || k > n)
throw new RangeError("Sample larger than population or is negative");


var result = new Array(k);
var setsize = 21;   // size of a small set minus size of an empty list


if(k > 5)
setsize += Math.pow(4, Math.ceil(Math.log(k * 3) / Math.log(4)))


if(n <= setsize){
// An n-length list is smaller than a k-length set
var pool = population.slice();
for(var i = 0; i < k; i++){          // invariant:  non-selected at [0,n-i)
var j = Math.random() * (n - i) | 0;
result[i] = pool[j];
pool[j] = pool[n - i - 1];       // move non-selected item into vacancy
}
}else{
var selected = new Set();
for(var i = 0; i < k; i++){
var j = Math.random() * n | 0;
while(selected.has(j)){
j = Math.random() * n | 0;
}
selected.add(j);
result[i] = population[j];
}
}


return result;
}

Lib/Random. py移植的实现。

备注:

  • 为了提高效率,setsize是基于 Python 中的特性设置的。虽然它还没有为 JavaScript 进行调整,但是这个算法仍然可以正常运行。
  • 由于滥用 Array.prototype.sort,本页中描述的其他一些答案根据 ECMAScript 规范是不安全的。然而,该算法保证在有限的时间内终止。
  • 对于没有实现 Set的旧浏览器,可以用 Array.has(j)替换该集合,用 .indexOf(j) > -1替换 Array.has(j)

与公认答案相比的表现:

在不改变原始数组的情况下获取5个随机项:

const n = 5;
const sample = items
.map(x => ({ x, r: Math.random() }))
.sort((a, b) => a.r - b.r)
.map(a => a.x)
.slice(0, n);

(不要把这个用于大的列表)

它从 srcArray 中一个接一个地提取随机元素,同时获取足够的元素或者 srcArray 中没有更多的元素可以提取。 快速可靠。

function getNRandomValuesFromArray(srcArr, n) {
// making copy to do not affect original srcArray
srcArr = srcArr.slice();
resultArr = [];
// while srcArray isn't empty AND we didn't enough random elements
while (srcArr.length && resultArr.length < n) {
// remove one element from random position and add this element to the result array
resultArr = resultArr.concat( // merge arrays
srcArr.splice( // extract one random element
Math.floor(Math.random() * srcArr.length),
1
)
);
}


return resultArr;
}

二零一九年

这与 劳伦娜斯,马里,奥斯卡斯答案相同,只是元素是唯一的(没有重复)。

var getMeRandomElements = function(sourceArray, neededElements) {
var result = [];
for (var i = 0; i < neededElements; i++) {
var index = Math.floor(Math.random() * sourceArray.length);
result.push(sourceArray[index]);
sourceArray.splice(index, 1);
}
return result;
}

现在回答最初的问题“ 如何通过 < em > jQuery 获得多个随机元素”,开始吧:

var getMeRandomElements = function(sourceArray, neededElements) {
var result = [];
for (var i = 0; i < neededElements; i++) {
var index = Math.floor(Math.random() * sourceArray.length);
result.push(sourceArray[index]);
sourceArray.splice(index, 1);
}
return result;
}


var $set = $('.someClass');// <<<<< change this please


var allIndexes = [];
for(var i = 0; i < $set.length; ++i) {
allIndexes.push(i);
}


var totalRandom = 4;// <<<<< change this please
var randomIndexes = getMeRandomElements(allIndexes, totalRandom);


var $randomElements = null;
for(var i = 0; i < randomIndexes.length; ++i) {
var randomIndex = randomIndexes[i];
if($randomElements === null) {
$randomElements = $set.eq(randomIndex);
} else {
$randomElements.add($set.eq(randomIndex));
}
}


// $randomElements is ready
$randomElements.css('backgroundColor', 'red');

我需要一个函数来解决这类问题,所以我在这里分享它。

    const getRandomItem = function(arr) {
return arr[Math.floor(Math.random() * arr.length)];
}


// original array
let arr = [4, 3, 1, 6, 9, 8, 5];


// number of random elements to get from arr
let n = 4;


let count = 0;
// new array to push random item in
let randomItems = []
do {
let item = getRandomItem(arr);
randomItems.push(item);
// update the original array and remove the recently pushed item
arr.splice(arr.indexOf(item), 1);
count++;
} while(count < n);


console.log(randomItems);
console.log(arr);

注意: 如果 n = arr.length,那么基本上就是对数组 arr进行洗牌,而 randomItems返回洗牌后的数组。

演示

ES6语法

const pickRandom = (arr,count) => {
let _arr = [...arr];
return[...Array(count)].map( ()=> _arr.splice(Math.floor(Math.random() * _arr.length), 1)[0] );
}

这是一个打得很好的版本。不会失败的。如果样本大小大于原始数组的长度,则返回混合数组。

function sampleArr<T>(arr: T[], size: number): T[] {
const setOfIndexes = new Set<number>();
while (setOfIndexes.size < size && setOfIndexes.size < arr.length) {
setOfIndexes.add(randomIntFromInterval(0, arr.length - 1));
}
return Array.from(setOfIndexes.values()).map(i => arr[i]);
}


const randomIntFromInterval = (min: number, max: number): number =>
Math.floor(Math.random() * (max - min + 1) + min);

下面是我使用的一个函数,它允许您轻松地对一个数组进行取样,不管是否替换:

  // Returns a random sample (either with or without replacement) from an array
const randomSample = (arr, k, withReplacement = false) => {
let sample;
if (withReplacement === true) {  // sample with replacement
sample = Array.from({length: k}, () => arr[Math.floor(Math.random() *  arr.length)]);
} else { // sample without replacement
if (k > arr.length) {
throw new RangeError('Sample size must be less than or equal to array length         when sampling without replacement.')
}
sample = arr.map(a => [a, Math.random()]).sort((a, b) => {
return a[1] < b[1] ? -1 : 1;}).slice(0, k).map(a => a[0]);
};
return sample;
};

使用它很简单:

没有替换(默认行为)

randomSample([1, 2, 3], 2)可能返回 [2, 1]

用替代品

randomSample([1, 2, 3, 4, 5, 6], 4)可能返回 [2, 3, 3, 2]

var getRandomElements = function(sourceArray, requiredLength) {
var result = [];
while(result.length<requiredLength){
random = Math.floor(Math.random()*sourceArray.length);
if(result.indexOf(sourceArray[random])==-1){
result.push(sourceArray[random]);
}
}
return result;
}

这里有一个一行程序的独特解决方案

 array.sort(() => Math.random() - Math.random()).slice(0, n)

在这个答案中,我想与你们分享一个测试,我必须知道最好的方法,给予平等的机会,所有元素有随机子数组。

方法01

array.sort(() => Math.random() - Math.random()).slice(0, n)

使用这种方法,一些元素比其他元素有更高的机会。

calculateProbability = function(number=0 ,iterations=10000,arraySize=100) {
let occ = 0
for (let index = 0; index < iterations; index++) {
const myArray= Array.from(Array(arraySize).keys()) //=> [0, 1, 2, 3, 4, ... arraySize]
   

/** Wrong Method */
const arr = myArray.sort(function() {
return val= .5 - Math.random();
});
     

if(arr[0]===number) {
occ ++
}


    

}


console.log("Probability of ",number, " = ",occ*100 /iterations,"%")


}


calculateProbability(0)
calculateProbability(0)
calculateProbability(0)
calculateProbability(50)
calculateProbability(50)
calculateProbability(50)
calculateProbability(25)
calculateProbability(25)
calculateProbability(25)

方法2

使用这种方法,元素具有相同的概率:

 const arr = myArray
.map((a) => ({sort: Math.random(), value: a}))
.sort((a, b) => a.sort - b.sort)
.map((a) => a.value)

calculateProbability = function(number=0 ,iterations=10000,arraySize=100) {
let occ = 0
for (let index = 0; index < iterations; index++) {
const myArray= Array.from(Array(arraySize).keys()) //=> [0, 1, 2, 3, 4, ... arraySize]
   



/** Correct Method */
const arr = myArray
.map((a) => ({sort: Math.random(), value: a}))
.sort((a, b) => a.sort - b.sort)
.map((a) => a.value)
    

if(arr[0]===number) {
occ ++
}


    

}


console.log("Probability of ",number, " = ",occ*100 /iterations,"%")


}


calculateProbability(0)
calculateProbability(0)
calculateProbability(0)
calculateProbability(50)
calculateProbability(50)
calculateProbability(50)
calculateProbability(25)
calculateProbability(25)
calculateProbability(25)

正确答案张贴在以下链接: https://stackoverflow.com/a/46545530/3811640

Sort ((() = > (Math.Random () > 0.5? 1: -1))

我不敢相信没有人没有提到这个方法,非常干净和直接。

const getRnd = (a, n) => new Array(n).fill(null).map(() => a[Math.floor(Math.random() * a.length)]);

下面是@Derek 从 Python 移植过来的 密码的优化版本,添加了破坏性(就地)选项,如果您使用它,它将是最快的算法。否则,它要么生成完整的副本,要么对于从大数组请求的少量项目,切换到基于选择的算法。

// Chooses k unique random elements from pool.
function sample(pool, k, destructive) {
var n = pool.length;
    

if (k < 0 || k > n)
throw new RangeError("Sample larger than population or is negative");
    

if (destructive || n <= (k <= 5 ? 21 : 21 + Math.pow(4, Math.ceil(Math.log(k*3) / Math.log(4))))) {
if (!destructive)
pool = Array.prototype.slice.call(pool);
for (var i = 0; i < k; i++) { // invariant: non-selected at [i,n)
var j = i + Math.random() * (n - i) | 0;
var x = pool[i];
pool[i] = pool[j];
pool[j] = x;
}
pool.length = k; // truncate
return pool;
} else {
var selected = new Set();
while (selected.add(Math.random() * n | 0).size < k) {}
return Array.prototype.map.call(selected, i => pool[i]);
}
}

与 Derek 的实现相比,第一个算法在 Firefox 中运行速度要快得多,而在 Chrome 中运行速度要慢一些,尽管现在它有了一个破坏性的选项——性能最好的选项。第二种算法只快了5-15% 。我尽量不给出任何具体的数字,因为它们随着 k 和 n 的变化而变化,而且在新的浏览器版本中可能不会有任何意义。

在算法之间做出选择的启发式方法源于 Python 代码。我让它保持原样,尽管它有时会选择慢一点的那个。它应该针对 JS 进行优化,但这是一项复杂的任务,因为角落用例的性能是依赖于浏览器的,而且它们的版本也是如此。例如,当您尝试从1000或1050中选择20时,它将相应地切换到第一个或第二个算法。在这种情况下,第一个比 Chrome 80中的第二个快2倍,但是在 Firefox 74中慢3倍。

LODASH _.sample_.sampleSize

获取从集合到集合大小的唯一键处的一个或 n 个随机元素。

_.sample([1, 2, 3, 4]);
// => 2


_.sampleSize([1, 2, 3], 2);
// => [3, 1]
 

_.sampleSize([1, 2, 3], 3);
// => [2, 3, 1]

二零二零年
非破坏性的函数式编程风格,在不可变的上下文中工作。

const _randomslice = (ar, size) => {
let new_ar = [...ar];
new_ar.splice(Math.floor(Math.random()*ar.length),1);
return ar.length <= (size+1) ? new_ar : _randomslice(new_ar, size);
}




console.log(_randomslice([1,2,3,4,5],2));

抽样 有可能是复制品:

const sample_with_duplicates = Array(sample_size).fill().map(() => items[~~(Math.random() * items.length)])

抽样 没有副本:

const sample_without_duplicates = [...Array(items.length).keys()].sort(() => 0.5 - Math.random()).slice(0, sample_size).map(index => items[index]);

由于 没有副本首先需要对整个索引数组进行排序,因此对于大的 items输入数组,它比 有可能是复制品慢得多。

显然,没有副本的最大大小是 < = items.length

检查这个小提琴: https://jsfiddle.net/doleron/5zw2vequ/30/

const items = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'I', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 1, 2, 3, 4, 5];


const fetchRandomArray = ({pool=[], limit=1})=>{
let query = []
let selectedIndices = {}


while(query.length < limit){
const index = Math.floor(Math.random()*pool.length)
if(typeof(selectedIndices[index])==='undefined'){
query.push(items[index])
selectedIndices[index] = index
}
}


console.log(fetchRandomArray({pool:items, limit:10})