私は「javascriptの配列からランダムに要素にアクセスする方法」に取り組んでいます。私はこれに関する多くのリンクを見つけました。のように: JavaScript配列からランダムなアイテムを取得します
var item = items[Math.floor(Math.random()*items.length)];
ただし、この場合、配列から選択できる項目は1つだけです。複数の要素が必要な場合、どうすればこれを実現できますか?配列から複数の要素を取得するにはどうすればよいですか?
私は「javascriptの配列からランダムに要素にアクセスする方法」に取り組んでいます。私はこれに関する多くのリンクを見つけました。のように: JavaScript配列からランダムなアイテムを取得します
var item = items[Math.floor(Math.random()*items.length)];
ただし、この場合、配列から選択できる項目は1つだけです。複数の要素が必要な場合、どうすればこれを実現できますか?配列から複数の要素を取得するにはどうすればよいですか?
回答:
この非破壊(かつ高速)機能を試してください:
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;
}
Set
('13では利用できませんでした:-/)
たった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);
let random = array.sort(() => .5 - Math.random()).slice(0,n)
ここにワンライナーのユニークなソリューションがあります
array.sort(() => Math.random() - Math.random()).slice(0, n)
.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, 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用に調整されていませんが、アルゴリズムは引き続き期待どおりに機能します。Array.prototype.sort
。ただし、このアルゴリズムは有限時間で終了することが保証されています。Set
実装されていない古いブラウザの場合、セットをに置き換えて、Array
に.has(j)
置き換えることができます.indexOf(j) > -1
。受け入れられた回答に対するパフォーマンス:
それを行う関数を作成します。
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から削除する必要があります。
sourceArray
複数回返すことができます。
繰り返しなしでループ内の配列からランダムにアイテムを取得したい場合は、次のようにして選択したアイテムを配列から削除できます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
でdeleteCount
除去するために、古い配列要素の数を示します。(ちなみに、最後の2行をに減らしましたnewItems.push(items.splice(idx, 1)[0])
)。
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();
これがうまくタイプされたバージョンです。失敗しません。サンプルサイズが元の配列の長さより大きい場合、シャッフルされた配列を返します。
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);
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オブジェクトではありません。
編集:このソリューションは、少数の要素のみを取得する場合、ここに示されている他のソリューション(ソース配列をスプライスする)よりも低速です。このソリューションの速度は元の配列の要素数にのみ依存しますが、スプライシングソリューションの速度は出力配列に必要な要素の数に依存します。
繰り返さないランダム要素が必要な場合は、配列をシャッフルしてから、必要な数だけ取得できます。
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
O(n+k)
(配列内のn個の要素、そのうちのk個が必要です)O(k)
可能ですが(最適です)。
O(2n)
削除したO(n+k)
場合に減らすことができるようなものがあります。確かにありませんが、解決策はまだ可能です(私の答えを参照してください)。ただし、スペースの複雑さは残念ながらそのままですが、スパース配列の実装によっては複雑になる可能性があります。while (counter-- > len-k)
k
splice(i, 1)
O(1)
O(k)
O(n+k)
O(2k)
このような問題を解決する関数が必要だったので、ここで共有します。
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
返します。
この回答では、すべての要素がランダムなサブ配列を持つ可能性を等しくするための最良の方法を知っている必要があるというテストをあなたと共有したいと思います。
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)
この方法を使用すると、要素の確率は同じになります。
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
これは、@ 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の実装はプールをシャッフルするだけなので、再作成せずにサンプリングに完全に再利用できます。そして、これが最も頻繁な使用例であると私は信じています。
不変のコンテキストで機能する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));
_shuffle
機能はどこにありますか?
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;
}
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');
これが私が使用する関数で、置換の有無にかかわらず配列を簡単にサンプリングできます。
// 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]
これが最も正しい答えであり、ランダム+ユニークな要素を提供します。
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
items.sort(()=>(Math.random()> 0.5?1:-1))。slice(0、count);