32

javascriptの配列から置換せずに、ランダムサンプルを取得するクリーンな方法は何ですか? 配列があるとします

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

5つの一意の値をランダムにサンプリングしたい; つまり、長さ 5 のランダムなサブセットを生成します。ランダムなサンプルを 1 つ生成するには、次のようにします。

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

ただし、これを複数回行うと、同じエントリを複数回取得するリスクがあります。

4

15 に答える 15

54

Fisher-Yates シャッフルを使用して配列のコピーをシャッフルし、スライスを取得することをお勧めします。

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);
}
于 2012-08-13T13:30:07.827 に答える
5

または...underscore.jsを使用する場合...

_und = require('underscore');

...

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

十分に単純です。

于 2012-08-13T14:26:05.693 に答える
5

私はフィッシャー・イェーツ・シャッフルの使用を強く支持しますが、ティム・ダウンが提案したように、空のセットと指定されたセット自体を含む、数学的に正しい、要求されたランダムなサブセットを達成するための非常に短い方法があります。

解決策はlodash / underscoreに依存することに注意してください:

ロダッシュv4

const _ = require('loadsh')

function subset(arr) {
    return _.sampleSize(arr, _.random(arr.length))
}

ロダッシュv3

const _ = require('loadsh')

function subset(arr) {
    return _.sample(arr, _.random(arr.length));
}
于 2015-01-11T12:41:53.680 に答える
5

この方法で 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); };
于 2018-09-08T07:12:25.967 に答える
2

これは、Fisher-Yates 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;
}
于 2016-06-15T11:31:12.980 に答える
1

要素を選択すると、配列のコピーから要素を削除できます。パフォーマンスはおそらく理想的ではありませんが、必要なものには問題ない可能性があります。

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;
}
于 2012-08-13T13:41:01.080 に答える