#javascript #arrays #random #sample
#javascript #массивы #Случайный #пример
Вопрос:
Учитывая массив, как я могу извлечь из него n
неперекрывающиеся случайные выборки размера m
?
Например, учитывая массив:
const arr = [1, 2, 3, 4, 5, 6, 7, 8];
вызов sample(arr, 3, 2)
, например, вернул бы [[7, 8], [4, 5], [2, 3]]
, вызов sample(arr, 2, 4)
обязательно вернул бы [[1, 2, 3, 4], [5, 6, 7, 8]
, а вызов sample(arr, 5, 2)
выдал бы ошибку.
РЕДАКТИРОВАТЬ — Возможно, это было неясно в первоначальном вопросе: выборки должны быть списками смежных элементов. Вот почему sample(arr, 2, 4)
может возвращать только [[1, 2, 3, 4], [5, 6, 7, 8]
, а не [[2, 3, 1, 6], [5, 4, 7, 8]
, например.
Комментарии:
1. Что произойдет, если вы сделаете это
sample(arr, 20, 40)
или дажеsample(arr, 5, 2)
etc?2. @Dominik Вероятно, должна быть выдана ошибка, отредактирую.
3. Можно
sample(arr, 2, 4)
также вернуть[[5,6,7,8], [1,2,3,4]]
? Я предположил, что это будет в моем ответе, потому чтоsample(arr, 3, 2)
возвращает[[7,8], [4,5], [2,3]]
, который, казалось бы, является случайным порядком выборки.4. @3limin4t0r Да, но это необязательный шаг. Конечный массив не нужно перетасовывать.
Ответ №1:
Вы могли бы начать с создания списка с форматом возвращаемого значения:
[ 1, 2, 3, 4, 5, 6, 7, 8]
[<---->, <---->, <---->, <>, <>] // sample(array, 3, 2)
[<------------>, <------------>] // sample(array, 2, 4)
Массивы этого формата могут быть записаны с использованием длин:
[1, 2, 3, 4, 5, 6, 7, 8]
[ 2, 2, 2, 1, 1] // sample(array, 3, 2)
[ 4, 4] // sample(array, 2, 4)
Затем перетасуйте массивы формата, чтобы получить случайную выборку:
[1, 2, 3, 4, 5, 6, 7, 8]
[ 2, 1, 2, 2, 1] // sample(array, 3, 2)
[ 4, 4] // sample(array, 2, 4)
Затем для каждого элемента массива format удалите первые n
элементы из входного массива. Затем сохраните их, если это не было заполнителем (фрагменты одного размера, которые вставляются для достижения длины массива).
[1, 2, 3, 4, 5, 6, 7, 8]
[[1,2], [4,5], [6,7]] // sample(array, 3, 2)
[[1,2,3,4], [5,6,7,8]] // sample(array, 2, 4)
Наконец, перетасуйте полученные выборки.
[1, 2, 3, 4, 5, 6, 7, 8]
[[4,5], [1,2], [6,7]] // sample(array, 3, 2)
[[5,6,7,8], [1,2,3,4]] // sample(array, 2, 4)
const arr = [1, 2, 3, 4, 5, 6, 7, 8];
console.log(sample(arr, 3, 2));
console.log(sample(arr, 2, 4));
console.log(sample(arr, 5, 2));
function randomInt(limit) {
return Math.floor(Math.random() * limit);
}
function shuffle(array) {
for (let limit = array.length; limit > 0; --limit)
array.push(...array.splice(randomInt(limit), 1));
}
function sample(array, sampleCount, sampleLength) {
let elementCount = sampleCount * sampleLength;
if (elementCount > array.length)
throw "invalid sampleCount/sampleLength arguments";
const filler = {valueOf: () => 1};
const fillerCount = array.length - elementCount;
const lengths = Array.from(
{length: sampleCount fillerCount},
(_, i) => i < sampleCount ? sampleLength : filler
);
shuffle(lengths);
const samples = Array.from(array);
for (const length of lengths) {
const sample = samples.splice(0, length);
if (length === filler) continue;
samples.push(sample);
}
shuffle(samples);
return samples;
}
Обратите внимание, что ===
это важно в length === filler
. Если вы используете ==
, filler
также будет равно 1
. Тогда это привело бы к конфликту с вызовом типа, sample(array, 5, 1)
где длина каждой выборки равна 1
.
const filler = {valueOf: () => 1};
console.log("1 == filler //=>", 1 == filler);
console.log("2 == filler //=>", 2 == filler);
console.log("filler == filler //=>", filler == filler);
console.log("1 === filler //=>", 1 === filler);
console.log("2 === filler //=>", 2 === filler);
console.log("filler === filler //=>", filler == filler);
Комментарии:
1. Действительно понравился этот подход. Также изучил
{valueOf: () => 1}
полезный трюк. Спасибо за ваш ответ.2.
valueOf
полезно, если вы хотите создать собственное «комплексное» число. Он вызывается автоматически, когда ожидается число. Например,4 complexObject
можно вызватьvalueOf
функцию для извлечения значения.
Ответ №2:
вы можете использовать жадный алгоритм и взять n кортежей размером m из перетасованного массива:
const arr = [2, 1, 3, 4, 5, 6, 7, 8];
function sample(arr, length, size){
if(arr.length < length*size)
throw new Error("too short");
arr.sort(() => Math.random() - 0.5);
let res = [];
for(let i = 0; i < length; i ) res.push(arr.slice(i*size, i*size size));
return res;
}
console.log(sample(arr, 2, 4));
Комментарии:
1. Где случайная выборка?
2. @cabralpinto добавил строку для перетасовки массива
3. @cabralpinto также с «неперекрывающимися» вы имеете в виду, что вы не принимаете одно и то же значение более одного раза?
4. Это решение просто берет первые n неперекрывающихся выборок m-размера, а не случайные.
5. да, ни одна выборка не должна содержать то же значение, что и другая. За исключением, конечно, случая, когда массив имеет повторяющиеся значения.
Ответ №3:
Я думаю, что лучшей реализацией было бы сначала перемешать. Вот мои два цента:
function shuffle(array){
let a = array.slice(), i = a.length, n, h;
while(i){
n = Math.floor(Math.random()*i--); h = a[i]; a[i] = a[n]; a[n] = h;
}
return a;
}
function sample(array, chunks, count){
const r = [], a = shuffle(array);
for(let n=0; n<chunks; n ){
r.push(a.splice(0, count));
}
return r;
}
const arr = [1, 2, 3, 4, 5, 6, 7, 8];
console.log(sample(arr, 3, 2)); console.log(sample(arr, 2, 4));
Ответ №4:
Вы можете сделать это с помощью Rando.js (что криптографически безопасно), сопоставить и склеить довольно легко. Просто используйте randoSequence
функцию randojs, чтобы перетасовать предоставленный массив и склеить из этого перетасованного массива массивы n
размера m
, чтобы получить все, что нам нужно для возврата. Если в предоставленном массиве слишком мало значений, более поздние массивы, которые мы вернем, будут просто короче.
function sample(arr, n, m){
arr = randoSequence(arr).map(i => i.value), sample = [];
for(var i = 0; i < n; i ) sample[i] = arr.splice(-m);
return sample;
}
console.log(sample([1, 2, 3, 4, 5, 6, 7, 8], 3, 2));
<script src="https://randojs.com/2.0.0.js"></script>