35 votes

Échantillonner un sous-ensemble aléatoire à partir d'un tableau

Quelle est une manière propre de prendre un échantillon aléatoire, sans remplacement, à partir d'un tableau en javascript? Supposons donc qu'il y a un tableau

x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]

et je veux échantillonner de manière aléatoire 5 valeurs uniques; c'est-à-dire générer un sous-ensemble aléatoire de longueur 5. Pour générer un échantillon aléatoire, on pourrait faire quelque chose comme:

x[Math.floor(Math.random()*x.length)];

Mais si cela est fait plusieurs fois, il y a un risque de saisir la même entrée plusieurs fois.

63voto

Tim Down Points 124501

Je suggère de mélanger une copie du tableau en utilisant le mélange de Fisher-Yates et de prendre une tranche :

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

Notez que cela ne sera pas la méthode la plus efficace pour obtenir un petit sous-ensemble aléatoire d'un grand tableau car elle mélange inutilement tout le tableau. Pour de meilleures performances, vous pourriez plutôt faire un mélange partiel :

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

22voto

alengel Points 540

Un peu en retard pour la fête mais cela pourrait être résolu avec la nouvelle méthode sample d'underscore (underscore 1.5.2 - Sept 2013) :

var x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];

var randomFiveNumbers = _.sample(x, 5);

8voto

tkellehe Points 589

À mon avis, je ne pense pas que mélanger tout le jeu soit nécessaire. Vous devez juste vous assurer que votre échantillon est aléatoire et non votre jeu de cartes. Ce que vous pouvez faire, c'est sélectionner la quantité de size depuis l'avant, puis échanger chacun des éléments dans le tableau d'échantillonnage avec une autre position. Ainsi, si vous autorisez le remplacement, vous obtenez de plus en plus mélangé.

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

Cet algorithme comporte seulement 2*size étapes, si vous incluez la méthode slice, pour sélectionner l'échantillon aléatoire.


Plus aléatoire

Pour rendre l'échantillon plus aléatoire, nous pouvons sélectionner aléatoirement le point de départ de l'échantillon. Mais cela coûte un peu plus cher pour obtenir l'échantillon.

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

Ce qui rend cela plus aléatoire, c'est le fait que lorsque vous mélangez toujours juste les éléments de tête, vous avez tendance à ne pas les obtenir très souvent dans l'échantillon si le tableau d'échantillonnage est grand et l'échantillon est petit. Ce ne serait pas un problème si le tableau n'était pas censé être toujours le même. Ainsi, cette méthode modifie la position où commence la région mélangée.


Pas de remplacement

Pour ne pas avoir à copier le tableau d'échantillonnage et ne pas se soucier du remplacement, vous pouvez faire ce qui suit mais cela vous donne 3*size contre 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);

    // Remettre tout en place.
    i = size;
    while(i--) {
         var pop = swaps.pop();
         temp = array[pop.from];
         array[pop.from] = array[pop.to];
         array[pop.to] = temp;
    }

    return sample;
}

Sans remplacement et plus aléatoire

Pour appliquer l'algorithme qui a donné des échantillons un peu plus aléatoires à la fonction sans remplacement :

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

    // Remettre tout en place.
    i = size;
    while(i--) {
         var pop = swaps.pop();
         temp = array[pop.from];
         array[pop.from] = array[pop.to];
         array[pop.to] = temp;
    }

    return sample;
}

Plus rapide...

Comme tous les articles de cette publication, cela utilise le mélange de Fisher-Yates. Mais j'ai supprimé le surplis de copie du tableau.

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 = [];

7voto

Luis Marin Points 89

Vous pouvez obtenir un échantillon de 5 éléments de cette façon:

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

Vous pouvez le définir comme une fonction à utiliser dans votre code:

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

Ou l'ajouter à l'objet Array lui-même:

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

Si vous le souhaitez, vous pouvez séparer le code pour avoir 2 fonctionnalités (Mélanger et Échantillonner):

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

5voto

ntalbs Points 4583

Ou... si vous utilisez underscore.js...

_und = require('underscore');

...

function sample(a, n) {
    return _und.take(_und.shuffle(a), n);
}

Assez simple.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X