¿Cómo obtener varios elementos aleatorios de una matriz?


103

Estoy trabajando en 'cómo acceder a elementos aleatoriamente desde una matriz en javascript'. Encontré muchos enlaces al respecto. Me gusta: obtener un elemento aleatorio de la matriz de JavaScript

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

Pero en esto, podemos elegir solo un elemento de la matriz. Si queremos más de un elemento, ¿cómo podemos lograrlo? ¿Cómo podemos obtener más de un elemento de una matriz?


5
¿Simplemente ejecutarlo varias veces?
Bergi

2
A partir de esta declaración, ¿podemos hacer esto? Bucle que genera duplicados.
Shyam Dixit

1
De esa declaración exacta no se puede obtener más de un elemento.
Sébastien


1
Hice un JsPerf para probar algunas de las soluciones aquí. @ Bergi parece ser el mejor en general, mientras que el mío funciona mejor si necesita muchos elementos de la matriz. jsperf.com/k-random-elements-from-array
Tibos

Respuestas:


97

Pruebe esta función no destructiva (y rápida ):

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

10
Hola hombre, solo quería decir que pasé unos diez minutos apreciando la belleza de este algoritmo.
Prajeeth Emanuel


@Derek 朕 會 功夫 Ah, inteligente, eso funciona mucho mejor para muestras pequeñas de grandes rangos. Especialmente con el uso de un ES6 Set(que no estaba disponible en '13: - /)
Bergi

@AlexWhite Gracias por los comentarios, no puedo creer que este error haya evadido a todos durante años. Fijo. Sin embargo, debería haber publicado un comentario, no sugerido una edición.
Bergi

2
@ cbdev420 Sí, es solo una mezcla (parcial) de Fisher-Yates
Bergi

187

Solo dos líneas:

// 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 :


22
¡Muy agradable! Una línea también, por supuesto, posible:let random = array.sort(() => .5 - Math.random()).slice(0,n)
unitario

1
¡Genio! Elegante, breve y simple, rápido, con funcionalidad incorporada.
Vlad

35
Es agradable, pero está lejos de ser aleatorio. El primer artículo tiene muchas más posibilidades de ser elegido que el último. Vea aquí por qué: stackoverflow.com/a/18650169/1325646
pomber

Esto no conserva el tipo de la matriz original
almathie

3
¡Asombroso! si desea mantener la matriz intacta, puede modificar la primera línea de esta manera: const shuffled = [... array] .sort (() => 0.5 - Math.random ());
Yair Levy


12

Portando .sampledesde la biblioteca estándar de 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;
}

Implementación portada de Lib / random.py .

Notas:

  • setsizese establece en función de las características de Python para la eficiencia. Aunque no se ha ajustado para JavaScript, el algoritmo seguirá funcionando como se esperaba.
  • Algunas otras respuestas descritas en esta página no son seguras de acuerdo con la especificación ECMAScript debido al mal uso de Array.prototype.sort. Sin embargo, se garantiza que este algoritmo terminará en un tiempo finito.
  • Para los navegadores más antiguos que no se han Setimplementado, el conjunto se puede reemplazar con Arrayy .has(j)reemplazar con .indexOf(j) > -1.

Desempeño contra la respuesta aceptada:


He publicado una versión optimizada de este código a continuación. También se corrigió el parámetro aleatorio incorrecto en el segundo algoritmo de su publicación. Me pregunto cuántas personas están usando la versión sesgada anterior en producción, espero que no haya nada crítico.
usuario

11

Obteniendo 5 elementos aleatorios sin cambiar la matriz original:

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

(No use esto para listas grandes)


¿Podríamos tener una mejor explicación de cómo funciona esto?
Qasim

10

crea una función que hace eso:

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

También debe verificar si sourceArray tiene suficientes elementos para ser devueltos. y si desea que se devuelvan elementos únicos, debe eliminar el elemento seleccionado de sourceArray.


¡Buena respuesta! Eche un vistazo a mi respuesta, copió su código y agregó la funcionalidad de "solo elementos únicos".
evilReiko

1
Esta función puede devolver el mismo elemento sourceArrayvarias veces.
Sampo

6

Sintaxis de ES6

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

4

Si desea obtener elementos de la matriz al azar en un bucle sin repeticiones, puede eliminar el elemento seleccionado de la matriz con 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
En la declaración items.splice (idx, 1), ¿por qué usa este '1'? ¿¿empalme??
Shyam Dixit

2
Shyam Dixit , de acuerdo con la documentación MDN el 1es el deleteCountque indica el número de elementos de matriz viejos eliminar. (Por cierto, reduje las dos últimas líneas a newItems.push(items.splice(idx, 1)[0])).
Kurt Peek

2
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

Aquí hay una versión muy bien escrita. No falla. Devuelve una matriz mezclada si el tamaño de la muestra es mayor que la longitud de la matriz original.

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

2

lodash ( https://lodash.com/ ) _.sampley _.sampleSize.

Obtiene uno o n elementos aleatorios en claves únicas desde la colección hasta el tamaño de la colección.

_.sample([1, 2, 3, 4]);
// => 2

_.sampleSize([1, 2, 3], 2);
// => [3, 1]
 
_.sampleSize([1, 2, 3], 4);
// => [2, 3, 1]

¿Qué es _? No es un objeto estándar de JavaScript.
vanowm

Hola @vanowm, es lodash lodash.com
nodejh

1

EDITAR : Esta solución es más lenta que otras presentadas aquí (que empalman la matriz de origen) si desea obtener solo unos pocos elementos. La velocidad de esta solución depende solo del número de elementos de la matriz original, mientras que la velocidad de la solución de empalme depende de la cantidad de elementos necesarios en la matriz de salida.

Si desea elementos aleatorios que no se repitan, puede mezclar su matriz y luego obtener solo tantos como desee:

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

DEMO: http://jsbin.com/UHUHuqi/1/edit

Función aleatoria tomada de aquí: https://stackoverflow.com/a/6274398/1669279


Eso depende del porcentaje de elementos aleatorios requeridos de la matriz. Si desea 9 elementos aleatorios de una matriz de 10 elementos, seguramente será más rápido mezclarlos que extraer 9 elementos aleatorios uno tras otro. Si el porcentaje para el que es útil es inferior al 50%, existen casos de uso en los que esta solución es la más rápida. De lo contrario, concedo que es inútil :).
Tibos

Quise decir que mezclar 9 elementos es más rápido que mezclar 10 elementos. Por cierto, estoy seguro de que el OP no quiere destruir su matriz de entrada ...
Bergi

No creo que entiendo cómo el barajar 9 elementos ayuda en este problema. Soy consciente de que si desea más de la mitad de la matriz, simplemente puede cortar elementos aleatorios hasta que se quede con la cantidad que desea y luego mezclar para obtener un orden aleatorio. ¿Hay algo que me perdí? PD: Destrucción de matriz fija, gracias.
Tibos

No tiene que ver nada con "la mitad de". Solo necesita hacer tanto trabajo como los elementos que desee recuperar, no es necesario que trate toda la matriz en ningún momento. Su código actual tiene una complejidad de O(n+k)(n elementos en la matriz, quiere k de ellos) mientras O(k)que sería posible (y óptimo).
Bergi

1
De acuerdo, su código tiene más cosas similares a las O(2n)que podría reducirse O(n+k)si cambiara el bucle ay while (counter-- > len-k)eliminara los últimos elementos (en lugar de los primeros) k. De hecho splice(i, 1)no lo tiene O(1), pero O(k)aún es posible una solución (vea mi respuesta). Sin embargo, la complejidad del espacio se mantiene O(n+k)desafortunadamente, pero podría O(2k)depender de la implementación del arreglo disperso.
Bergi

1

Necesitaba una función para resolver este tipo de problema, así que la comparto aquí.

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

Nota: si n = arr.lengthentonces básicamente estás barajando la matriz arry randomItemsdevuelve esa matriz barajada.

Manifestación


1

En esta respuesta, quiero compartir con ustedes la prueba de que tengo que conocer el mejor método que brinda las mismas posibilidades de que todos los elementos tengan un subarreglo aleatorio.

Método 01

array.sort(() => Math.random() - Math.random()).slice(0, n)

Al utilizar este método, algunos elementos tienen mayores posibilidades en comparación con otros.

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)

Método 2

Usando este método, los elementos tienen la misma probabilidad:

 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)

La respuesta correcta se publica en el siguiente enlace: https://stackoverflow.com/a/46545530/3811640


1

Aquí hay una versión optimizada del código portado desde Python por @Derek, con la opción destructiva agregada (en el lugar) que lo convierte en el algoritmo más rápido posible si puede seguirlo. De lo contrario, realiza una copia completa o, para una pequeña cantidad de elementos solicitados de una matriz grande, cambia a un algoritmo basado en selección.

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

En comparación con la implementación de Derek, el primer algoritmo es mucho más rápido en Firefox y un poco más lento en Chrome, aunque ahora tiene la opción destructiva, la de mayor rendimiento. El segundo algoritmo es simplemente un 5-15% más rápido. Intento no dar números concretos, ya que varían según kyn y probablemente no signifiquen nada en el futuro con las nuevas versiones del navegador.

La heurística que hace la elección entre algoritmos se origina en el código Python. Lo dejé como está, aunque a veces selecciona el más lento. Debería estar optimizado para JS, pero es una tarea compleja ya que el rendimiento de los casos de esquina depende del navegador y de su versión. Por ejemplo, cuando intente seleccionar 20 de 1000 o 1050, cambiará al primer o segundo algoritmo en consecuencia. En este caso, el primero se ejecuta 2 veces más rápido que el segundo en Chrome 80, pero 3 veces más lento en Firefox 74.


Hay un error log(k*3, 4)ya que JS no tiene el baseargumento. En caso de serlog(k*3)/log(4)
disfated

Además, veo una desventaja en la parte en la que se reutiliza poolcomo result. Como lo trunca pool, ya no se puede usar como fuente para muestrear y la próxima vez que lo use sampletendrá que volver a crear a poolpartir de alguna fuente. La implementación de Derek solo baraja el grupo, por lo que se puede reutilizar perfectamente para muestrear sin volver a crear. Y creo que este es el caso de uso más frecuente.
desaparecido el

1


Estilo de programación funcional 2020 no destructivo, trabajando en un contexto inmutable.

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


Me doy cuenta de que la función no genera toda la matriz aleatoria posible a partir de una matriz de origen. En otro mundo, el resultado no es tan aleatorio como debería ... ¿alguna idea de mejora?
MAMY Sébastien

donde esta la _shufflefuncion?
vanowm

0

Extrae elementos aleatorios de srcArray uno por uno mientras obtiene suficiente o no quedan más elementos en srcArray para extraer. Rápido y confiable.

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


¡Bienvenido a SO! Al publicar respuestas, es importante mencionar cómo funciona su código y / o cómo resuelve el problema de OP :)
Joel

0

2019

Esto es lo mismo que la respuesta de Laurynas Mališauskas , solo que los elementos son únicos (sin duplicados).

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

Ahora, para responder a la pregunta original "Cómo obtener varios elementos aleatorios con jQuery", aquí tienes:

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

Aquí hay una función que uso que le permite muestrear fácilmente una matriz con o sin reemplazo:

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

Usarlo es simple:

Sin reemplazo (comportamiento predeterminado)

randomSample([1, 2, 3], 2) puede volver [2, 1]

Con reemplazo

randomSample([1, 2, 3, 4, 5, 6], 4) puede volver [2, 3, 3, 2]


0
var getRandomElements = function(sourceArray, requiredLength) {
    var result = [];
    while(result.length<requiredLength){
        random = Math.floor(Math.random()*sourceArray.length);
        if(result.indexOf(sourceArray[random])==-1){
            result.push(sourceArray[random]);
        }
    }
    return result;
}

0

No puedo creer que nadie no haya mencionado este método, bastante limpio y sencillo.

const getRnd = (a, n) => new Array(n).fill(null).map(() => a[Math.floor(Math.random() * a.length)]);

No se está asegurando de que dos elementos no se repitan.
Valery

-2

Aquí está la respuesta más correcta y le dará elementos aleatorios + únicos.

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

Algo extraño está sucediendo con la aleatorización en este: obtuve el mismo resultado en 6 de 9 intentos (con una n de 8 y un tamaño de matriz de 148). Podría pensar en cambiar a un método de Fisher-Yates ; es lo que hice y ahora funciona mucho mejor.
asetniop

Esto lleva un tiempo cuadrático porque realiza una mala comprobación de unicidad y no tiene las mismas posibilidades de seleccionar todos los elementos porque se ordena con una comparación aleatoria.
Ry-

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.