Creando números aleatorios sin duplicados

Resuelto Woong-Sup Jung asked hace 13 años • 20 respuestas

En este caso el MAX es solo 5, por lo que podría comprobar los duplicados uno por uno, pero ¿cómo podría hacer esto de una forma más sencilla? Por ejemplo, ¿qué pasa si MAX tiene un valor de 20? Gracias.

int MAX = 5;

for (i = 1 , i <= MAX; i++)
{
        drawNum[1] = (int)(Math.random()*MAX)+1;

        while (drawNum[2] == drawNum[1])
        {
             drawNum[2] = (int)(Math.random()*MAX)+1;
        }
        while ((drawNum[3] == drawNum[1]) || (drawNum[3] == drawNum[2]) )
        {
             drawNum[3] = (int)(Math.random()*MAX)+1;
        }
        while ((drawNum[4] == drawNum[1]) || (drawNum[4] == drawNum[2]) || (drawNum[4] == drawNum[3]) )
        {
             drawNum[4] = (int)(Math.random()*MAX)+1;
        }
        while ((drawNum[5] == drawNum[1]) ||
               (drawNum[5] == drawNum[2]) ||
               (drawNum[5] == drawNum[3]) ||
               (drawNum[5] == drawNum[4]) )
        {
             drawNum[5] = (int)(Math.random()*MAX)+1;
        }

}
Woong-Sup Jung avatar Oct 28 '10 12:10 Woong-Sup Jung
Aceptado

La forma más sencilla sería crear una lista de los números posibles (1..20 o lo que sea) y luego mezclarlos con Collections.shuffle. Luego simplemente toma todos los elementos que quieras. Esto es genial si tu rango es igual al número de elementos que necesitas al final (por ejemplo, para barajar una baraja de cartas).

Eso no funciona tan bien si quieres (digamos) 10 elementos aleatorios en el rango de 1 a 10 000; terminarías haciendo mucho trabajo innecesariamente. En ese punto, probablemente sea mejor mantener un conjunto de valores que ha generado hasta ahora y seguir generando números en un bucle hasta que el siguiente no esté presente:

if (max < numbersNeeded)
{
    throw new IllegalArgumentException("Can't ask for more numbers than are available");
}
Random rng = new Random(); // Ideally just create one instance globally
// Note: use LinkedHashSet to maintain insertion order
Set<Integer> generated = new LinkedHashSet<Integer>();
while (generated.size() < numbersNeeded)
{
    Integer next = rng.nextInt(max) + 1;
    // As we're adding to a set, this will automatically do a containment check
    generated.add(next);
}

Sin embargo, tenga cuidado con la elección del conjunto: la he usado de manera muy deliberada LinkedHashSetya que mantiene el orden de inserción, que es lo que nos importa aquí.

Otra opción más es progresar siempre , reduciendo el rango cada vez y compensando los valores existentes. Entonces, por ejemplo, supongamos que desea 3 valores en el rango 0...9. En la primera iteración, generarías cualquier número en el rango de 0 a 9; digamos que generas un 4.

En la segunda iteración, generarías un número en el rango de 0 a 8. Si el número generado es menor que 4, lo mantendrás como está... de lo contrario, le agregarás uno. Eso te da un rango de resultados de 0 a 9 sin 4. Supongamos que obtenemos 7 de esa manera.

En la tercera iteración generarías un número en el rango de 0 a 7. Si el número generado es menor que 4, lo mantendrás como está. Si son 4 o 5, agregarías uno. Si son 6 o 7, sumarías dos. De esa forma, el rango de resultados es 0..9 sin 4 o 6.

Jon Skeet avatar Oct 28 '2010 05:10 Jon Skeet

Así es como lo haría

import java.util.ArrayList;
import java.util.Random;

public class Test {
    public static void main(String[] args) {
        int size = 20;

        ArrayList<Integer> list = new ArrayList<Integer>(size);
        for(int i = 1; i <= size; i++) {
            list.add(i);
        }

        Random rand = new Random();
        while(list.size() > 0) {
            int index = rand.nextInt(list.size());
            System.out.println("Selected: "+list.remove(index));
        }
    }
}

Como ha señalado el estimado Sr. Skeet:
Si n es el número de números seleccionados aleatoriamente que desea elegir y N es el espacio muestral total de números disponibles para su selección:

  1. Si n << N , simplemente debe almacenar los números que ha elegido y verificar una lista para ver si el número seleccionado está en ella.
  2. Si n ~= N , probablemente deberías usar mi método, completando una lista que contenga todo el espacio muestral y luego eliminando números a medida que los seleccionas.
Catchwa avatar Oct 28 '2010 05:10 Catchwa
//random numbers are 0,1,2,3 
ArrayList<Integer> numbers = new ArrayList<Integer>();   
Random randomGenerator = new Random();
while (numbers.size() < 4) {

    int random = randomGenerator .nextInt(4);
    if (!numbers.contains(random)) {
        numbers.add(random);
    }
}
Satheesh Guduri avatar Apr 13 '2012 07:04 Satheesh Guduri

Esto sería mucho más sencillo en java-8:

Stream.generate(new Random()::ints)
            .flatMap(IntStream::boxed)
            .distinct()
            .limit(16) // whatever limit you might need
            .toArray(Integer[]::new);
Eugene avatar Sep 09 '2018 20:09 Eugene

Hay otra forma de hacer números ordenados "aleatorios" con LFSR, eche un vistazo a:

http://en.wikipedia.org/wiki/Linear_feedback_shift_register

con esta técnica puedes lograr el número aleatorio ordenado por índice y asegurándote de que los valores no estén duplicados.

Pero estos no son números aleatorios VERDADEROS porque la generación aleatoria es determinista.

Pero dependiendo de su caso, puede utilizar esta técnica reduciendo la cantidad de procesamiento en la generación de números aleatorios cuando se utiliza la mezcla aleatoria.

Aquí un algoritmo LFSR en Java (lo llevé a algún lugar que no recuerdo):

public final class LFSR {
    private static final int M = 15;

    // hard-coded for 15-bits
    private static final int[] TAPS = {14, 15};

    private final boolean[] bits = new boolean[M + 1];

    public LFSR() {
        this((int)System.currentTimeMillis());
    }

    public LFSR(int seed) {
        for(int i = 0; i < M; i++) {
            bits[i] = (((1 << i) & seed) >>> i) == 1;
        }
    }

    /* generate a random int uniformly on the interval [-2^31 + 1, 2^31 - 1] */
    public short nextShort() {
        //printBits();

        // calculate the integer value from the registers
        short next = 0;
        for(int i = 0; i < M; i++) {
            next |= (bits[i] ? 1 : 0) << i;
        }

        // allow for zero without allowing for -2^31
        if (next < 0) next++;

        // calculate the last register from all the preceding
        bits[M] = false;
        for(int i = 0; i < TAPS.length; i++) {
            bits[M] ^= bits[M - TAPS[i]];
        }

        // shift all the registers
        for(int i = 0; i < M; i++) {
            bits[i] = bits[i + 1];
        }

        return next;
    }

    /** returns random double uniformly over [0, 1) */
    public double nextDouble() {
        return ((nextShort() / (Integer.MAX_VALUE + 1.0)) + 1.0) / 2.0;
    }

    /** returns random boolean */
    public boolean nextBoolean() {
        return nextShort() >= 0;
    }

    public void printBits() {
        System.out.print(bits[M] ? 1 : 0);
        System.out.print(" -> ");
        for(int i = M - 1; i >= 0; i--) {
            System.out.print(bits[i] ? 1 : 0);
        }
        System.out.println();
    }


    public static void main(String[] args) {
        LFSR rng = new LFSR();
        Vector<Short> vec = new Vector<Short>();
        for(int i = 0; i <= 32766; i++) {
            short next = rng.nextShort();
            // just testing/asserting to make 
            // sure the number doesn't repeat on a given list
            if (vec.contains(next))
                throw new RuntimeException("Index repeat: " + i);
            vec.add(next);
            System.out.println(next);
        }
    }
}
felipe avatar Sep 24 '2012 14:09 felipe