如何从数组中获取一些随机元素?


102

我正在研究“如何从javascript中的数组随机访问元素”。我发现了许多与此有关的链接。像: 从JavaScript数组中获取随机项目

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

但是在这种情况下,我们只能从数组中选择一项。如果我们需要多个元素,那么我们如何实现呢?我们如何从数组中获得多个元素?


5
只是执行多次?
Bergi 2013年

2
从这个陈述中我们可以做到这一点吗?循环生成重复项。
Shyam Dixit

1
从该确切的语句中,您不能获得多个元素。
2013年

1
啊,您应该说过不要重复。然后检查O(1)中的唯一随机数?和我在生成(0-X)范围内的唯一编号方面的
Bergi 2013年

1
我做了一个JsPerf来测试这里的一些解决方案。一般而言,@ Bergi似乎是最好的,而如果您需要数组中的许多元素,则我的效果更好。jsperf.com/k-random-elements-from-array
Tibos 2013年

Answers:


96

尝试以下无损(快速)功能:

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;
}

9
嘿,我只是想说我花了大约十分钟的时间来欣赏这种算法的美丽。
Prajeeth Emanuel


@Derek朕会功夫啊,聪明,对于大范围的小样本确实更好。特别是使用ES6 Set(在'13:-/中不可用)
Bergi '17

@AlexWhite感谢您的反馈,我无法相信这个错误多年来一直在绕过所有人。固定。您应该已经发布了评论,但不建议进行编辑。
Bergi

2
@ cbdev420是的,它只是(部分)fisher-
yates

186

仅两行:

// 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);

演示


22
非常好!当然也可以使用一种班轮:let random = array.sort(() => .5 - Math.random()).slice(0,n)
unitario

1
天才!使用内置功能,美观,简短,简单,快速。
弗拉德

34
很好,但是不是随机的。与最后一个相比,第一个项目被拣选的机会更多。此处查看原因:stackoverflow.com/a/18650169/1325646
庞贝

这不会保留原始数组的种类
almatie

2
惊人!如果要保持数组完整,可以像这样更改第一行:const shuffled = [... array] .sort(()=> 0.5-Math.random());
Yair Levy


12

.sample从Python标准库移植:

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, 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进行调整,但该算法仍将按预期运行。
  • 根据ECMAScript规范,由于滥用,本页中描述的其他一些答案并不安全Array.prototype.sort。但是,可以保证该算法在有限时间内终止。
  • 对于尚未Set实现的旧版浏览器,可以将替换为,Array然后.has(j)替换为.indexOf(j) > -1

针对公认答案的效果:


我在下面发布了此代码的优化版本。还纠正了帖子中第二个算法中的错误随机参数。我想知道有多少人在生产中使用以前的有偏见的版本,希望没有什么是关键的。
用户

11

在不更改原始数组的情况下获得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);

(请勿将其用于大型列表)


我们能对此有一个更好的解释吗?
卡西姆

10

创建一个可以做到这一点的功能:

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是否有足够的元素要返回。如果要返回唯一元素,则应从sourceArray中删除选定的元素。


好答案!看一下我的答案,复制您的代码并添加“仅唯一元素”功能。
evilReiko '19

1
此函数可以sourceArray多次返回相同的元素。
Sampo

6

ES6语法

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

4

如果要在循环中从数组中随机获取项目而不重复,可以使用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);


1
为什么在语句items.splice(idx,1)中使用此“ 1”?拼接??
Shyam Dixit

2
Shyam Dixit,根据MDN文档1deleteCount值指示要删除的旧数组元素的数量。(顺便说一句,我将最后两行减少为newItems.push(items.splice(idx, 1)[0]))。
库尔特·皮克

2
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();

2

这是一个很好键入的版本。它不会失败。如果样本大小大于原始数组的长度,则返回经过改组的数组。

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);

2

lodash(https://lodash.com/_.sample_.sampleSize

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

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

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

什么_啊 这不是标准的Javascript对象。
vanowm

@vanowm,您好,这是lodash lodash.com
nodejh,

1

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

如果要使用非重复的随机元素,则可以对数组进行混洗,然后仅获取所需的数目:

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


这取决于阵列中所需的随机项目的百分比。如果您希望从10个元素的数组中提取9个随机元素,那么洗牌肯定快于一个又一个地提取9个随机元素。如果有用的百分比小于50%,则在某些情况下此解决方案最快。否则,我承认这是没有用的:)。
Tibos

我的意思是改组9个元素比改组10个元素要快。顺便说一句,我有信心OP不想破坏他的输入阵列…
Bergi

我不认为我了解改组9个元素如何解决此问题。我知道,如果您想要的数组多于一半,您可以简单地将随机元素切出,直到剩下想要的数量,然后随机排列以获得随机顺序。我有什么想念的吗?PS:修复了阵列破坏问题,谢谢。
Tibos

它不必对“一半”做任何事情。您只需要做与要返回的元素一样多的工作即可,无需在任何时候处理整个数组。您当前的代码的复杂度为O(n+k)(数组中有n个元素,您希望有k个),而O(k)这是可能的(并且是最佳的)。
Bergi

1
好的,您的代码更像O(2n)O(n+k)将循环更改为while (counter-- > len-k)并从中取出最后一个(而不是第一个)k元素的代码。确实splice(i, 1)没有O(1),但是O(k)仍然可以解决(请参阅我的答案)。O(n+k)不幸的是,空间复杂度仍然存在,但可能O(2k)取决于稀疏数组的实现。
Bergi 2013年

1

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

    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基本上,那么您将对数组进行改组arrrandomItems返回经过改组的数组。

演示版


1

在这个答案中,我想与您分享测试,我必须知道最好的方法,该方法可以使所有元素具有随机子数组的机会均等。

方法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


1

这是@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, 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 => population[i]);
    }
}

与Derek的实现相比,第一种算法在Firefox中速度更快,而在Chrome中则稍慢,尽管现在它具有破坏性的选项-性能最高。第二种算法仅提高了5-15%。我尝试不提供任何具体数字,因为它们取决于k和n,并且在新的浏览器版本中将来可能毫无意义。

在算法之间进行选择的启发式方法源自Python代码。尽管它有时会选择较慢的一个,但我将其保留原样。应该针对JS对其进行优化,但这是一项复杂的任务,因为特殊情况的性能取决于浏览器及其版本。例如,当您尝试从1000或1050中选择20时,它将相应地切换到第一个或第二个算法。在这种情况下,第一个运行速度比Chrome 80中的第二个运行速度快2倍,但在Firefox 74中运行的速度慢3倍。


log(k*3, 4)由于JS没有base参数,因此存在错误。应该是log(k*3)/log(4)

此外,在将您pool作为复用的部分中,我看到了一个缺点result。由于您截断了pool它,所以它不能再用作采样源,下次使用时,sample您将不得不pool再次从某个源重新创建。Derek的实现只会改组池,因此可以完美地将其重新用于采样而无需重新创建。我相信这是最常见的用例。
贬值

1

2020年
无损功能编程风格,在不变的环境中工作。

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));


我意识到该函数不会从源数组生成所有可能的随机数组。在另一个世界中,结果并不像应有的那样随机...任何改进的想法?
MAMYSébastien

_shuffle功能在哪里?
vanowm

抱歉,我编辑了该帖子
MAMYSébastien

0

它会在srcArray够用时或从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;
}


欢迎来到SO!发布答案时,重要的是要提到您的代码如何工作和/或它如何解决OP的问题:)
Joel

0

2019年

这与LaurynasMališauskas的答案相同,只是元素是唯一的(无重复)。

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;
}

现在回答原始问题“如何通过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');

0

这是我使用的一个函数,可让您轻松地对数组进行抽样,无论是否进行替换:

  // 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]


0
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;
}

0

我简直干净利落,简直无法相信没有人提到这种方法。

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

-2

这是最正确的答案,它将为您提供随机+唯一元素。

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

在这个随机化过程中发生了一些奇怪的事情-我在9次尝试中有6次显示了相同的结果(n为8,数组大小为148)。您可能会考虑切换到Fisher-Yates方法。这就是我所做的,现在效果更好。
asetniop

这花费了二次时间,因为它进行了糟糕的唯一性检查,并且没有机会选择每一项,因为它使用随机比较进行排序。
Ry-

这在很多方面都是不好的。
disfated

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.