配列からいくつかのランダムな要素を取得するにはどうすればよいですか?


103

私は「javascriptの配列からランダムに要素にアクセスする方法」に取り組んでいます。私はこれに関する多くのリンクを見つけました。のように: JavaScript配列からランダムなアイテムを取得します

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

ただし、この場合、配列から選択できる項目は1つだけです。複数の要素が必要な場合、どうすればこれを実現できますか?配列から複数の要素を取得するにはどうすればよいですか?


5
複数回実行するだけですか?
ベルギ2013年

2
この声明から私たちはこれを行うことができますか?重複を生成するループ。
Shyam Dixit 2013年

1
その正確なステートメントから、複数の要素を取得することはできません。
セバスチャン・

1
ああ、重複したくないと言っておくべきだった。次に、O(1)の一意の乱数を確認しますか?重複を防ぐために履歴を保持しながら、範囲(
0〜X

1
ここでいくつかのソリューションをテストするためにJsPerfを作成しました。@Bergiは一般的に最高のようですが、配列から多くの要素が必要な場合は私の方がうまく機能します。jsperf.com/k-random-elements-from-array
Tibos

回答:


97

この非破壊(かつ高速)機能を試してください:

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

10
やあ、私はこのアルゴリズムの美しさを評価するのに約10分を費やしたと言いたかっただけです。
Prajeeth Emanuel 2017

速度を上げる場合は、Pythonの実装を使用することをお勧めします。jsperf.com
array –Derek朕會功夫2017

@Derek朕會功夫ああ、賢い、それは確かに広い範囲からの小さなサンプルに対してはるかにうまく機能します。特にES6を使用する場合Set('13では利用できませんでした:-/)
Bergi 2017

@AlexWhiteフィードバックをありがとう、私はこのバグが何年もの間皆を回避したとは信じられません。修繕。ただし、編集を提案するのではなく、コメントを投稿する必要があります。
ベルギ2018

2
@ cbdev420はい、それは単なる(部分的な)フィッシャーイェーツシャッフルです
ベルギ

187

たった2行:

// 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
天才!組み込みの機能を使用して、エレガント、短く、シンプル、高速。
vlad 2017

34
それは素晴らしいですが、ランダムにはほど遠いです。最初のアイテムは、最後のアイテムよりも多くのチャンスがあります。理由はこちらをご覧ください:stackoverflow.com/a/18650169/1325646
pomber 2018年

これは元の配列の種類を保持しません
almathie 2018年

3
すごい!配列をそのままにしておきたい場合は、最初の行を次のように変更できます。constshuffled = [... array] .sort(()=> 0.5 --Math.random());
YairLevy19年


12

.samplePython標準ライブラリからの移植:

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

受け入れられた回答に対するパフォーマンス:


このコードの最適化されたバージョンを以下に投稿しました。また、投稿の2番目のアルゴリズムの間違ったランダムパラメータを修正しました。何人の人が以前のバイアスバージョンを本番環境で使用しているのだろうか、重要なことは何も望んでいない。
ユーザー

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

1
この関数は、同じ要素をsourceArray複数回返すことができます。
サンポ

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 2013年

2
シャムディキシットは、に従ってMDN資料1deleteCount除去するために、古い配列要素の数を示します。(ちなみに、最後の2行をに減らしましたnewItems.push(items.splice(idx, 1)[0]))。
カートピーク2017年

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

コレクションからコレクションのサイズまでの一意のキーで1つまたは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 2013年

つまり、9要素をシャッフルする方が10要素をシャッフルするよりも速いということです。ところで、私はOPが彼の入力配列を破壊したくないと確信しています…
Bergi 2013年

9つの要素をシャッフルすることがこの問題にどのように役立つかを理解していないと思います。配列の半分以上が必要な場合は、必要な数が残るまでランダムな要素を単純にスライスしてから、シャッフルしてランダムな順序を取得できることを認識しています。見逃したことはありますか?PS:アレイの破壊を修正しました、ありがとう。
Tibos 2013年

「半分」とは何の関係もありません。取り戻したい要素と同じだけの作業を行う必要があります。どの時点でも配列全体を処理する必要はありません。現在のコードの複雑さはO(n+k)(配列内のn個の要素、そのうちのk個が必要です)O(k)可能ですが(最適です)。
ベルギ2013年

1
OK、コードには、ループをに変更して最後の(最初ではなく)要素をO(2n)削除したO(n+k)場合に減らすことができるようなものがあります。確かにありませんが、解決策はまだ可能です(私の答えを参照してください)。ただし、スペースの複雑さは残念ながらそのままですが、スパース配列の実装によっては複雑になる可能性があります。while (counter-- > len-k)ksplice(i, 1)O(1)O(k)O(n+k)O(2k)
ベルギ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、基本的に配列arrをシャッフルし、シャッフルされた配列をrandomItems返します。

デモ


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では少し低速ですが、現在は破壊的なオプション(最もパフォーマンスの高いオプション)があります。2番目のアルゴリズムは、単純に5〜15%高速です。kとnによって異なるため、具体的な数値は示さないようにしています。新しいブラウザバージョンでは、将来的には何の意味もありません。

アルゴリズム間の選択を行うヒューリスティックは、Pythonコードに由来します。遅いものを選択することもありますが、そのままにしておきます。JS用に最適化する必要がありますが、コーナーケースのパフォーマンスはブラウザーとそのバージョンに依存するため、複雑な作業です。たとえば、1000または1050から20を選択しようとすると、それに応じて1番目または2番目のアルゴリズムに切り替わります。この場合、最初のものはChrome 80では2番目のものより2倍速く実行されますが、Firefox74では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));


この関数は、ソース配列からすべての可能なランダム配列を生成するわけではないことに気付きました。他の世界では、結果は本来あるべきほどランダムではありません...改善のアイデアはありますか?
MAMYセバスチャン・

_shuffle機能はどこにありますか?
vanowm

0

srcArrayからランダムな要素を1つずつ抽出しますが、十分な数になるか、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)。フィッシャー-イェーツ法への切り替えを考えるかもしれません。それは私がしたことであり、今でははるかにうまく機能しています。
asetniop 2017年

これは、一意性チェックが不適切であり、ランダムな比較でソートされるため、すべてのアイテムを選択する可能性が等しくないため、2次の時間がかかります。
–ry-

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.