it-swarm.com.de

Stichprobe einer zufälligen Teilmenge aus einem Array

Was ist eine saubere Art, eine Zufallsstichprobe zu entnehmen, ohne sie durch ein Array in Javascript zu ersetzen? Angenommen, es gibt ein Array

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

und ich möchte zufällig 5 eindeutige Werte abtasten; d.h. eine zufällige Teilmenge der Länge 5 erzeugen. Um eine zufällige Stichprobe zu erzeugen, könnte man etwas tun wie:

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

Wenn dies jedoch mehrmals durchgeführt wird, besteht die Gefahr, dass derselbe Eintrag mehrmals abgerufen wird.

18
Jeroen

Ich schlage vor, eine Kopie des Arrays mit dem Fisher-Yates-Shuffle zu mischen und ein Stück zu nehmen:

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

Beachten Sie, dass dies nicht die effizienteste Methode ist, um eine kleine zufällige Teilmenge eines großen Arrays zu erhalten, da das gesamte Array unnötigerweise gemischt wird. Um eine bessere Leistung zu erzielen, können Sie stattdessen eine teilweise Zufallswiedergabe durchführen:

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);
}
38
Tim Down

Ein bisschen zu spät zur Party, aber dies könnte durch Unterstreichen der neuen sample Methode behoben werden (Unterstreichung 1.5.2 - Sep 2013):

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

var randomFiveNumbers = _.sample(x, 5);
11
alengel

Oder ... wenn Sie underscore.js verwenden ...

_und = require('underscore');

...

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

Einfach genug.

6
ntalbs

Sie können die Elemente aus einer Kopie des Arrays entfernen, wenn Sie sie auswählen. Die Leistung ist wahrscheinlich nicht ideal, aber für das, was Sie benötigen, ist sie möglicherweise in Ordnung:

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

Wenn Sie lodash verwenden, wurde die API in 4.x geändert:

const oneItem = _.sample(arr);
const nItems = _.sampleSize(arr, n);

https://lodash.com/docs#sampleSize

2
chovy

Obwohl ich die Verwendung des Fisher-Yates-Shuffle nachdrücklich unterstütze, wie von Tim Down vorgeschlagen , ist hier eine sehr kurze Methode, um eine zufällige Teilmenge wie gewünscht zu erhalten, die mathematisch korrekt ist, einschließlich der leeren Menge und der gegebenen Menge.

Hinweis Lösung hängt von lodash / Unterstrich ab:

function subset(arr) {
    return _.sample(arr, _.random(arr.length));
}
2
Selfish

Meiner Meinung nach halte ich es nicht für notwendig, das gesamte Deck zu mischen. Sie müssen nur sicherstellen, dass Ihre Stichprobe zufällig ist, nicht Ihr Deck. Sie können die size Menge von vorne auswählen und dann jede Position im Sampling-Array mit einer anderen Position darin tauschen. Wenn Sie also Ersatz zulassen, werden Sie immer mehr gemischt.

function getRandom(length) { return Math.floor(Math.random()*(length)); }

function getRandomSample(array, size) {
    var length = array.length;

    for(var i = size; i--;) {
        var index = getRandom(length);
        var temp = array[index];
        array[index] = array[i];
        array[i] = temp;
    }

    return array.slice(0, size);
}

Dieser Algorithmus besteht nur aus 2*size Schritten, wenn Sie die slice Methode einbeziehen, um die Zufallsstichprobe auszuwählen.


Mehr zufällig

Um die Stichprobe zufälliger zu machen, können wir den Startpunkt der Stichprobe zufällig auswählen. Aber es ist etwas teurer, die Probe zu bekommen.

function getRandomSample(array, size) {
    var length = array.length, start = getRandom(length);

    for(var i = size; i--;) {
        var index = (start + i)%length, rindex = getRandom(length);
        var temp = array[rindex];
        array[rindex] = array[index];
        array[index] = temp;
    }
    var end = start + size, sample = array.slice(start, end);
    if(end > length)
        sample = sample.concat(array.slice(0, end - length));
    return sample;
}

Was dies zufälliger macht, ist die Tatsache, dass, wenn Sie immer nur die vorderen Elemente mischen, Sie dazu neigen, sie nicht sehr oft in der Stichprobe zu erhalten, wenn das Stichprobenarray groß und die Stichprobe klein ist. Dies wäre kein Problem, wenn das Array nicht immer dasselbe sein sollte. Diese Methode ändert also die Position, an der der Bereich mit dem Mischen beginnt.


Kein Ersatz

Um das Sampling-Array nicht kopieren zu müssen und sich keine Sorgen um den Austausch zu machen, können Sie Folgendes tun, aber es gibt Ihnen 3*size gegenüber dem 2*size.

function getRandomSample(array, size) {
    var length = array.length, swaps = [], i = size, temp;

    while(i--) {
        var rindex = getRandom(length);
        temp = array[rindex];
        array[rindex] = array[i];
        array[i] = temp;
        swaps.Push({ from: i, to: rindex });
    }

    var sample = array.slice(0, size);

    // Put everything back.
    i = size;
    while(i--) {
         var pop = swaps.pop();
         temp = array[pop.from];
         array[pop.from] = array[pop.to];
         array[pop.to] = temp;
    }

    return sample;
}

Kein Ersatz und mehr zufällig

So wenden Sie den Algorithmus an, der der No-Replacement-Funktion etwas mehr Zufallsstichproben gab:

function getRandomSample(array, size) {
    var length = array.length, start = getRandom(length),
        swaps = [], i = size, temp;

    while(i--) {
        var index = (start + i)%length, rindex = getRandom(length);
        temp = array[rindex];
        array[rindex] = array[index];
        array[index] = temp;
        swaps.Push({ from: index, to: rindex });
    }

    var end = start + size, sample = array.slice(start, end);
    if(end > length)
        sample = sample.concat(array.slice(0, end - length));

    // Put everything back.
    i = size;
    while(i--) {
         var pop = swaps.pop();
         temp = array[pop.from];
         array[pop.from] = array[pop.to];
         array[pop.to] = temp;
    }

    return sample;
}

Schneller...

Wie bei allen diesen Beiträgen wird auch hier der Fisher-Yates-Shuffle verwendet. Aber ich habe den Aufwand für das Kopieren des Arrays beseitigt.

function getRandomSample(array, size) {
    var r, i = array.length, end = i - size, temp, swaps = getRandomSample.swaps;

    while (i-- > end) {
        r = getRandom(i + 1);
        temp = array[r];
        array[r] = array[i];
        array[i] = temp;
        swaps.Push(i);
        swaps.Push(r);
    }

    var sample = array.slice(end);

    while(size--) {
        i = swaps.pop();
        r = swaps.pop();
        temp = array[i];
        array[i] = array[r];
        array[r] = temp;
    }

    return sample;
}
getRandomSample.swaps = [];
2
tkellehe

Sie können auf diese Weise ein Beispiel mit 5 Elementen erhalten:

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]);

Sie können es als eine Funktion definieren, die in Ihrem Code verwendet werden soll:

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

Oder fügen Sie es dem Array-Objekt selbst hinzu:

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

wenn Sie möchten, können Sie den Code für zwei Funktionen (Shuffle und Sample) trennen:

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); };
1
Luis Marin

Hier ist eine weitere Implementierung, die auf Fisher-Yater Shuffle basiert. Dieser ist jedoch für den Fall optimiert, dass die Stichprobengröße erheblich kleiner als die Arraylänge ist. Diese Implementierung scannt nicht das gesamte Array und ordnet keine Arrays zu, die so groß sind wie das ursprüngliche Array. Es werden spärliche Arrays verwendet, um die Speicherzuordnung zu verringern.

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;
}
1
Jesús López

Vielleicht fehlt mir etwas, aber es scheint, dass es eine Lösung gibt, die nicht die Komplexität oder den potenziellen Overhead eines Shuffle erfordert:

function sample(array,size) {
  const results = [],
    sampled = {};
  while(results.length<size && results.length<array.length) {
    const index = Math.trunc(Math.random() * array.length);
    if(!sampled[index]) {
      results.Push(array[index]);
      sampled[index] = true;
    }
  }
  return results;
}
0
AnyWhichWay