Generar números aleatorios en Java

De ChuWiki
Saltar a: navegación, buscar

Para generar números aleatorios en Java tenemos dos opciones. Por un lado podemos usar Math.random(), por otro la clase java.util.Random. La primera es de uso más sencillo y rápido. La segunda nos da más opciones.

Veremos también algunos casos interesantes, por ejemplo, generar números aleatorios sin repetición o generar una cadena de caracteres (un String) aleatorio.


Math.random()

La llamada a Math.random() devuelve un número aleatorio entre 0.0 y 1.0, excluido este último valor, es decir, puede devolver 0.346442, 0.2344234, 0.98345,....

En muchas de nuestras aplicaciones no nos servirá este rango de valores. Por ejemplo, si queremos simular una tirada de dado, queremos números entre 1 y 6 sin decimales. Debemos echar unas cuentas para obtener lo deseado.

En primer lugar, miramos cuántos valores queremos. En nuestro caso del dado son 6 valores, del 1 al 6 ambos incluido. Debemos entonces multiplicar Math.random() por 6. Si quisieramos valores entre dos números cualquiera ambos incluídos, por ejemplo, 5 y 10, la cuenta sería (maximo-minimimo)+1, es decir, (10-5)+1 = 6, también multiplicaríamos por 6.

 Math.random()*6   // Esto da valores de 0.0 a 6.0, excluido el 6.0

Como nuestro primer valor es 1, le sumamos 1 al resultado. En el caso de que quisieramos entre 5 y 10, habría que sumar 5, es decir, el valor mínimo.

 Math.random()*6 + 1   // Esto da valores entre 1.0 y 7.0 excluido el 7.0

Finalmente, para conseguir un entero, quitamos los decimales usando la clase Math.floor()

 int valorDado = Math.floor(Math.random()*6+1);

En general, para conseguir un número entero entre M y N con M menor que N y ambos incluídos, debemos usar esta fórmula

  int valorEntero = Math.floor(Math.random()*(N-M+1)+M);  // Valor entre M y N, ambos incluidos.

Si no queremos un valor entero sino double, la fórmula es sin el +1

  double valorAleatorio = Math.random()*(N-M)+M;

eso sí, recuerda que el valor N queda excluido y no saldrá nunca.

Clase java.util.Random

La clase java.util.Random debemos instanciarla, a diferencia del método Math.random(). A cambio, tendremos bastantes más posibilidades.

Podemos usar un constructor sin parámetros o bien pasarle una semilla. Si instanciamos varias veces la clase con la misma semilla, tendremos siempre la misma secuencia de números aleatorios.

Random r1 = new Random();
Random r2 = new Random(4234);
Random r3 = new Random(4234); // r2 y r3 darán la misma secuencia.

Lo más fácil es usar el constructor sin parámetros, que normalmente dará secuencias distintas en cada instancia. De todas formas, una manera de obtener una semilla que sea distinta cada vez que ejecutemos nuestro programa puede ser obtener el tiempo actual en milisegundos con System.currentTimeMillis(), que dará números distintos salvo que hagamos la instacia justo en el mismo instante de tiempo.

Con esta clase, una vez instanciada, nuestro problema del dado sería bastante más sencillo, usando el método nextInt(int n), que devuelve un valor entre 0 y n, excluido n

Random r = new Random();
int valorDado = r.nextInt(6)+1;  // Entre 0 y 5, más 1.

También tenemos funciones que nos dan un valor aleatorio siguiendo una curva de Gauss o que nos rellenan un array de bytes de forma aleatoria. Y por supuesto, el nextDouble() que devuelve un valor aleatorio entre 0.0 y 1.0, excluido este último.

Números aleatorios sin repetición

Si queremos generar una serie de números aleatorios sin que se repita ninguno, según nuestro problema concreto, tenemos dos formas de hacerlo.

Elegir y eliminar aleatoriamente de un conjunto

Imagina que estamos haciendo un juego de cartas y queremos repartir las cartas de forma aleatoria entre los jugadores. Para evitar repeticiones, el algoritmo que podemos usar puede ser como el siguiente

  • Suponiendo una baraja de 40 cartas (baraja española), metemos en un array/lista los números del 1 al 40. Cada número representa una de las cartas, por ejemplo, el 1 el As de Oros, el 2 el dos de Oros, ... el 10 el Rey de Oros, el 11 el As de Copas, ... y así sucesivamente.
  • Elegir aleatoriamente un índice válido del array o lista para extraer la carta y eliminarla del array/lista. El tamaño del array/lista quedará reducido en 1. Pasa, por ejemplo, de 40 cartas a 39 cartas una vez que hayamos extraído la primera.

De esta forma, simulamos el mazo de cartas del que vamos extrayendo cartas. El código java para esto podría asemejar a lo siguiente

// Metemos en una lista los números del 1 al 40.
List<Integer> numbers = new ArrayList<>(40);
for (int i=1;i<41;i++){
   numbers.add(i);
}

// Instanciamos la clase Random
Random random = new Random();

// Mientras queden cartas en el mazo (en la lista de numbers)
while (numbers.size()>1){
   // Elegimos un índice al azar, entre 0 y el número de cartas que quedan por sacar
   int randomIndex = random.nextInt(numbers.size());

   // Damos la carta al jugador (sacamos el número por pantalla)
   System.out.println("Not Repeated Random Number "+numbers.get(randomIndex));

   // Y eliminamos la carta del mazo (la borramos de la lista)
   numbers.remove(randomIndex);
}

Por supuesto, si no son cartas, sino números, simplemente mete en la lista los números que quieras que puedan salir.

Este algoritmo vale siempre y cuando el posible conjunto de número a sacar no ocupe demasiada memoria. Si necesitas todos los posibles enteros sin repetición, este algoritmo quizás no te valga.

Almacenar los números aleatorios generados

La otra alternativa es ir generando números aleatorios y guardándolos en un conjunto Set de java. Generamos un nuevo número aleatorio y antes de sacar el número por pantalla (o hacer lo que tengamos que hacer con él), debemos verificar si ya existe en el conjunto. Si existe, lo descartamos y generamos otro. Si no existe, lo usamos y lo añadimos al conjunto.

El código java puede ser similar al siguiente

// Conjunto de números ya usados
Set<Integer> alreadyUsedNumbers = new HashSet<>();

// Vamos a generar 10 números aleatorios sin repetición
while (alreadyUsedNumbers.size()<10) {

   // Número aleatorio entre 0 y 40, excluido el 40.  
   int randomNumber = random.nextInt(40);

   // Si no lo hemos usado ya, lo usamos y lo metemos en el conjunto de usados.
   if (!alreadyUsedNumbers.contains(randomNumber)){
      System.out.println("Not Repeated Random Number "+randomNumber);
      alreadyUsedNumbers.add(randomNumber);
   }
}

Este algoritmo también tiene sus pegas y es que podemos tener la mala suerte de que se generen muchos números aleatorios que no nos valgan y tengamos que iterar muchas veces hasta obtener un número que no hayamos usado ya. En el código anterior existe la posibilidad, posible pero no probable, de que el bucle no termine nunca, o eche media hora sólo en sacar 10 números. Esto será más probable cuantos más números queramos sacar y menos números haya donde elegir. En el ejemplo, sacamos 10 números sin repetición (bucle de 10 números) de un conjunto posible de 40 números (nextInt(40). Así que al final tenemos una probabilidad entre 4 de que nos salga un número ya usado.

Cadena de texto aleatoria

Veamos un par de posibilidades para generar una cadena de texto aleatoria.

Elegir caracteres aleatoriamente de un array

Generar una cadena de texto aleatoria es una variante de lo que acabamos de ver de elegir números de una lista. Metemos en un array de caracteres los caracteres que nos interesen para nuestra cadena aleatoria. Luego sólo tenemos que elegir índices al azar dentro de ese array. El código java se puede parecer al siguiente

// Los caracteres de interés en un array de char.
char [] chars = "0123456789abcdefghijklmnopqrstuvwxyz".toCharArray();

// Longitud del array de char.
int charsLength = chars.length;

// Instanciamos la clase Random
Random random = new Random();

// Un StringBuffer para componer la cadena aleatoria de forma eficiente
StringBuffer buffer = new StringBuffer();

// Bucle para elegir una cadena de 10 caracteres al azar
for (int i=0;i<10;i++){

   // Añadimos al buffer un caracter al azar del array
   buffer.append(chars[random.nextInt(charsLength)]);
}

// Y solo nos queda hacer algo con la cadena
System.out.println("Random String " + buffer.toString());

Por supuesto, podemos poner en el array de caracteres los que no interesen (mayúsculas, minúsculas, caracteres especiales o lo que queramos). Si el array es demasiado largo para hacerlo a mano, podemos hacer bucles o algo para ir rellenándolo.

O también podemos usar los valores ascii de los caracteres. Por ejemplo, las letras mayúsculas de la A a la Z llevan los código ascii del 65 al 90, por lo que elegir letras mayúsculas al azar se reduce a elegir número al azar entre 65 y 90 ambos incluidos.

Para frikis

Un mecanismo algo más rebuscado, pero que si no el mismo, uno parecido se usa para generar los id de sesión de ciertas aplicaciones, puede ser algo parecido a lo siguiente

  • La clase BigInteger de java tiene un método para generar un número aleatorio con el número de bits que le digamos.
  • Sacamos ese número aleatorio en base 32 (en base 32, cada 5 bits se convierten en un dígito/letra de 0 a 9 y de 'a' a 'v' (no saldrá w,x,y ni z).
  • Así que nuestro BigInteger tiene que tener tantos bits como letras queramos por 5.

Por ejemplo, el siguiente código java saca una cadena aleatoria de 10 caracteres

// El famoso Random
Random random = new Random();

// Un BigInteger, de 50 bits (10 caracteres * 5 bits por caracter)
System.out.println("Random String " + new BigInteger(50, random).toString(32));

Usamos el constructor de BigInteger al que se le pasa el número de bits (50 bits = 10 caracteres deseados en nuestra cadena * 5 bits por caracter) y una instancia de Random. Usamos el método BigInteger.toString(32) para convertirlo en un String base 32.

Para id de sesión, se necesita algo más fuerte. Habitualmente se suele pedir un mínimo de 128 bits (que igual debemos redondear a 130 para que sea múltiplo de 5) y se usa un generador de números aleatorios más aleatorio, la clase SecureRandom, que es más costosa de instanciar.

Una pega de este método es que puede salir un cero o más como primer caracter ... y en los números los cero de delante no se imprimen, por lo que en este caso nos saldrían menos de 10 caracteres.

Números aleatorios al estilo Java 8

Desde Java 8, la clase Random tiene varios métodos ints() que nos devuelven un IntStream o flujo de enteros aleatorios. El código para generar números aleatorios usando este método puede ser como el siguiente:

// Instanciar clase Random
Random random = new Random();

// Obtener IntStream. El IntStream tendrá 10 números aleatorios
// entre 1 y 7, excluido el 7. Vaya, la típica tirada de dados del 1 al 6.
IntStream intStream = random.ints(10, 1, 7);

// Iterador para ir obteniendo los números
Iterator iterator = intStream.iterator();

// Sacamos los números aleatorios por pantalla, en un bucle.
while (iterator.hasNext()){
   System.out.println("Random Number "+iterator.next());
}

El método ints() tiene varias variantes según el número de parámetros que le pasemos. En el ejemplo hemos elegido la más completa, en el que damos cuántos números aleatorios queremos, el número más bajo (el 1) y el número más alto (el 7, excluido). Si no ponemos el primer parámetro, el flujo de enteros será infinito, podemos pedir todos los que queramos. Si no ponemos mínimo y máximo cualquier número entero puede salir, por grande que sea.

La clase IntStream tiene métodos interesantes para generar número aleatorios o flujos de enteros de distintas formas.

Si queremos recorrer nuestro IntStream más a lo Java 8, podemos usar el método IntStream.forEach() de la siguiente forma

Random random = new Random();
intStream = random.ints(10, 1, 7);

intStream.forEach(value ->
   System.out.println("Random Number "+value)
};

Por supuesto, el IntStream debe estar limitado, o no acabaremos nunca de escribir números aleatorios por pantalla.

Enlaces