如何从数组中获取多个随机元素?

IT技术 javascript jquery html arrays
2021-01-14 07:52:59

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

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

但在这里,我们只能从数组中选择一项。如果我们想要多个元素,那么我们如何实现这一目标?我们如何从数组中获取多个元素?

6个回答

只有两行:

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

演示

这很好,但远非随机。第一个项目比最后一个项目有更多的机会被选中。在这里查看原因:stackoverflow.com/a/18650169/1325646
2021-03-17 07:52:59
惊人!如果你想保持数组完整,你可以像这样改变第一行: const shuffled = [...array].sort(() => 0.5 - Math.random());
2021-03-18 07:52:59
天才!优雅,简短,简单,快速,使用内置功能。
2021-03-21 07:52:59
这不会保留原始数组的排序
2021-04-04 07:52:59
非常好!一个班轮当然也可能:let random = array.sort(() => .5 - Math.random()).slice(0,n)
2021-04-08 07:52:59

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

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;
}
嘿伙计,我只想说我花了大约十分钟来欣赏这个算法的美妙之处。
2021-03-13 07:52:59
@Derek 我会功夫啊,聪明,对于大范围的小样本确实效果更好。特别是使用 ES6 Set(在 '13 中不可用:-/)
2021-03-18 07:52:59
@AlexWhite 感谢您的反馈,我不敢相信这个错误多年来一直躲避所有人。固定的。不过,您应该发表评论,而不是建议进行编辑。
2021-03-30 07:52:59
@cbdev420 是的,这只是(部分)fisher-yates 洗牌
2021-03-30 07:52:59
jsPerf 链接目前似乎已损坏。
2021-04-06 07:52:59

这里有一个单线独特的解决方案

 array.sort(() => Math.random() - Math.random()).slice(0, n)
虽然它可以工作,但对于更大的阵列来说也可能很慢。
2021-03-17 07:52:59
这不会让你得到均匀的分布。请参阅robweir.com/blog/2010/02/microsoft-random-browser-ballot.html
2021-03-24 07:52:59

.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) / 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 进行调整,但算法仍会按预期运行。
  • 根据 ECMAScript 规范,由于Array.prototype.sort. 然而,该算法保证在有限时间内终止。
  • 对于尚未Set实现的旧浏览器,可以将集合替换为Array.has(j)替换为.indexOf(j) > -1

针对已接受答案的表现:

我在下面发布了此代码优化版本还更正了您帖子中第二个算法中错误的随机参数。我想知道有多少人在生产中使用以前有偏见的版本,希望没什么。
2021-03-30 07:52:59

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]
@vanowm 这是 lodash,通常使用_别名导入
2021-03-13 07:52:59
什么是_它不是标准的 Javascript 对象。
2021-04-06 07:52:59