Lo que es una buena forma de tomar una muestra aleatoria sin reemplazo de un array en javascript? Así que supongamos que hay una matriz

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

y quiero al azar muestras de 5 valores únicos; es decir, generar un subconjunto aleatorio de longitud 5. Para generar una muestra aleatoria se podría hacer algo como:

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

Pero si esto se hace varias veces, hay un riesgo de un acaparamiento de la misma entrada varias veces.

InformationsquelleAutor Jeroen | 2012-08-13

10 Comentarios

  1. 41

    Sugiero barajando una copia de la matriz de uso de la Fisher-Yates shuffle y tomar una rebanada:

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

    Tenga en cuenta que esto no va a ser el método más eficaz para conseguir un pequeño subconjunto aleatorio de una matriz de gran tamaño, ya que se baraja la totalidad de la matriz innecesariamente. Para un mejor rendimiento que usted podría hacer un parcial shuffle lugar:

    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);
    }
    • underscore.js utiliza un «versión moderna» de la aleatorización de Fisher-Yates
    • Debe ser i* Matemáticas.aleatorio() en lugar de (i+1) * Matemáticas.aleatorio(). De matemáticas.aleatorio() * (i+1) puede devolver yo después de Matemáticas.piso. Y arr[i] será el resultado en el índice de obligado cuando i==arr.longitud
    • No, eso es deliberado. i ya ha sido disminuye cuando index se calcula así, en la primera iteración i + 1 es igual a arr.length en la primera función, que es la correcta.
  2. 11

    Un poco tarde a la fiesta, pero esto se puede resolver con carácter de subrayado del nuevo la muestra método (subrayado 1.5.2 – Sept 2013):

    var x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
    
    var randomFiveNumbers = _.sample(x, 5);
    • Esto produce sólo 1 elemento para mí, no 5.
    • De subrayado del documentación: «Producir una muestra aleatoria de la lista. Pasa un número para devolver n aleatoria de los elementos de la lista. De otro modo un solo elemento aleatorio será devuelto.» – ¿Pasó usted en el segundo parámetro?
    • lodash tiene un _.sampleSize que funciona como se describió anteriormente: lodash.com/docs/4.17.4#sampleSize
  3. 6

    O… si se utiliza underscore.js…

    _und = require('underscore');
    
    ...
    
    function sample(a, n) {
        return _und.take(_und.shuffle(a), n);
    }

    Bastante Simple.

  4. 2

    Puede eliminar los elementos a partir de una copia de la matriz como de selección. El rendimiento es probablemente no es lo ideal, pero puede que esté bien para lo que usted necesita:

    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;
    }
  5. 2

    Aquí es otra aplicación basada en Fisher-Yater Shuffle. Pero esta es optimizado para el caso de que el tamaño de la muestra es significativamente menor que la longitud del arreglo. Esta aplicación no puede escanear la totalidad de la matriz ni asigna matrices tan grande como la de la matriz original. Utiliza matrices dispersas para reducir la asignación de memoria.

    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;
    }
    • Yo no se cómo funciona esto, pero lo hace-y es mucho más eficiente cuando el conde << matriz.longitud. Para que sea completamente genérico (es decir, cuando la cuenta es igual o mayor que la longitud de matriz) añadí: ` vamos a val = array[índices de[j] === undefined ? j : índices de[j]]; if (val === undefined) { resultado.longitud = i; break; } el resultado[i] = val; ` para forzar el resultado.longitud <= array.de longitud, de lo contrario obtendrá montón de undefineds en el resultado.
  6. 2

    En mi opinión, no creo que barajar el mazo entero necesario. Usted sólo tiene que asegurarse de que la muestra es aleatoria, no tu deck. Lo que puedes hacer, es seleccionar la size cantidad de la parte delantera, a continuación, intercambiar cada uno en el muestreo de la matriz con otra posición en el mismo. Por lo tanto, si usted permite que la sustitución se obtiene más y más revueltos.

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

    Este algoritmo es sólo 2*size pasos, si se incluyen las slice método para seleccionar la muestra aleatoria.


    Más Aleatorio

    Para hacer el ejemplo más aleatorio, podemos seleccionar al azar el punto de partida de la muestra. Pero es un poco más caro para obtener la muestra.

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

    Lo que hace que este más al azar es el hecho de que cuando usted siempre pasar a la parte delantera elementos que tienden a no llegar a ellos muy a menudo en el ejemplo, si el muestreo de la matriz es grande y la muestra es pequeña. Esto no sería un problema si la matriz no debía ser siempre el mismo. Así, lo que hace este método es cambiar esta posición, donde las revueltas de la región se inicia.


    Sin Reemplazo

    Para no tener que copiar el muestreo de la matriz y no te preocupes de reemplazo, usted puede hacer lo siguiente pero no se puede 3*size vs el 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;
    }

    Sin Reemplazo y Más Aleatoria

    Para aplicar el algoritmo que se dio un poco más muestras al azar sin reemplazo de la función:

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

    Más rápido…

    Como todos estos post, este utiliza la aleatorización de Fisher-Yates. Pero, he quitado la cabeza más de la copia de la matriz.

    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 = [];
  7. 2

    Usted puede obtener un 5 elementos de la muestra de esta forma:

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

    Puede definir como una función para utilizar en el código:

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

    O agregar a la Matriz objeto en sí mismo:

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

    si quieres, puedes separar el código para tener 2 funcionalidades (Shuffle y de la Muestra):

        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); };
  8. 1

    Mientras yo apoyo firmemente el uso de la aleatorización de Fisher-Yates, como sugerido por Tim Abajo, he aquí una muy breve método para lograr un subconjunto aleatorio de lo solicitado, matemáticamente correcta, incluyendo el conjunto vacío y el conjunto en sí.

    Nota de la solución depende de lodash /subrayado:

    function subset(arr) {
        return _.sample(arr, _.random(arr.length));
    }
  9. 0

    Tal vez me estoy perdiendo algo, pero parece que hay una solución que no requiera la complejidad o potencial de sobrecarga de un shuffle:

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

Dejar respuesta

Please enter your comment!
Please enter your name here