web-dev-qa-db-ja.com

配列からn個の要素をランダムに取得する方法

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

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

質問:しかし、この場合、配列から選択できる項目は1つだけです。複数の要素が必要な場合、どのようにこれを達成できますか。このステートメントから、配列から複数の要素を取得する方法を教えてください。

57
Shyam Dixit

この非破壊(および fast )関数を試してください:

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

わずか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);

[〜#〜] demo [〜#〜]

104
Abdennour TOUMI

それを行う関数を作成します:

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から削除する必要があります。

10

元の配列を変更せずに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);

(大きなリストにはこれを使用しないでください)

8
pomber

Python標準ライブラリからの_.sample_の移植:

_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 - i) | 0;
            while(selected.has(j)){
                j = Math.random() * (n - i) | 0;
            }
            selected.add(j);
            result[i] = population[j];
        }
    }

    return result;
}
_

Lib/random.py から移植された実装。

ノート:

  • setsizeは、効率のためにPythonの特性に基づいて設定されます。JavaScript用に調整されていませんが、アルゴリズムは期待どおりに機能します。
  • _Array.prototype.sort_の誤用により、このページで説明されている他のいくつかの回答はECMAScript仕様に従って安全ではありません。ただし、このアルゴリズムは有限時間で終了することが保証されています。
  • Setが実装されていない古いブラウザの場合、セットはArrayに置き換えられ、.has(j).indexOf(j) > -1に置き換えられます。

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

5

ES6構文

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

繰り返しのないループで配列からアイテムをランダムに取得する場合は、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);
}

fiddle の例

3
Rory McCrossan
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
Archy Will He

この種の問題を解決する関数が必要だったので、ここで共有します。

    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
Storage Lenovo

うまく型付けされたバージョンがあります。失敗しません。サンプルサイズが元の配列の長さより大きい場合、シャッフルされた配列を返します。

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

[〜#〜] edit [〜#〜]:取得したい場合、このソリューションはここに示されている他のソリューション(ソース配列を接合する)よりも遅い少数の要素のみ。このソリューションの速度は、元の配列の要素数のみに依存しますが、スプライシングソリューションの速度は、出力配列に必要な要素数に依存します。

繰り返しのないランダム要素が必要な場合は、配列をシャッフルして、必要な数だけ取得できます。

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

1
Tibos

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

2019

これはLaurynasMališauskas answerと同じですが、要素が一意である(重複しない)だけです。

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
evilReiko

以下は、置換の有無にかかわらず配列を簡単にサンプリングできるようにする関数です。

_  // 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
Jared Wilber