Ha pasado un tiempo desde mi última publicación de programación Java. Recientemente me he centrado en los tutoriales de Python y Machine Learning, pero Java sigue siendo mi lenguaje de programación favorito.
La selección de números aleatorios con probabilidades ponderadas es una tarea común en programación, especialmente cuando se trabaja con datos que tienen una distribución de probabilidad específica. Por ejemplo, puede tener una lista de letras con diferentes probabilidades de ser seleccionadas o una lista de elementos con diferentes probabilidades de ser elegidos de un menú.
En esta publicación, exploraremos cómo implementar una solución para este problema en Java utilizando un enfoque simple e intuitivo.
Generación de números aleatorios
Un número aleatorio es un número generado por un programa de computadora o un generador de números aleatorios de hardware de una manera que es impredecible y no sigue un patrón determinista. El propósito de generar números aleatorios es proporcionar un medio para introducir un elemento de azar en un programa o sistema de computadora.
Hay muchas formas diferentes de generar números aleatorios, y la calidad y las características de los números aleatorios generados pueden variar significativamente según el método utilizado. Sin embargo, en general, un buen generador de números aleatorios debe producir números que se distribuyan uniformemente en un amplio rango, sean estadísticamente independientes entre sí y no sean fácilmente predecibles.
Para generar un número aleatorio en Java, puede usar la clase java.util.Random. Por ejemplo, el siguiente código genera un número aleatorio entre dos números:
1 2 3 4 |
Random random = new Random(); int min = 0; int max = 10; int randomNumber = random.nextInt(max - min + 1) + min; |
Este código genera un número entero aleatorio entre 0 y 10, inclusive. Cada número tiene la misma probabilidad de ser elegido. Para probar este algoritmo, vamos a crear una prueba de concepto.
Generación de números aleatorios con igual probabilidad
Para probar el código anterior, voy a crear un script de prueba. El código que voy a presentar aquí se puede encontrar en nuestro repositorio de Github.
Lo primero que voy a hacer es declarar una clase con los siguientes atributos:
1 2 3 4 5 6 7 8 9 10 11 12 |
private static class Letters { private String letter; private int count; private double probability; public Letters(String letter, int count, double probability) { this.letter = letter; this.count = count; this.probability = probability; } } |
Esta clase permite crear objetos con letras, un contador y una probabilidad específica. Ahora voy a crear una lista de 10 letras:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// Create a list of letters List<Letters> letters = new ArrayList<>(); // Add 10 letters to the list with their corresponding count and probability letters.add(new Letters("A", 0, 0.2)); letters.add(new Letters("B", 0, 0.2)); letters.add(new Letters("C", 0, 0.1)); letters.add(new Letters("D", 0, 0.1)); letters.add(new Letters("E", 0, 0.1)); letters.add(new Letters("F", 0, 0.1)); letters.add(new Letters("G", 0, 0.05)); letters.add(new Letters("H", 0, 0.05)); letters.add(new Letters("I", 0, 0.05)); letters.add(new Letters("J", 0, 0.05)); |
Para seleccionar aleatoriamente una letra, creé este método:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
/** * Method for performing selection tests with non-weighted probability. * * @param letters list of letters to perform tests on * @param testCount number of tests to perform */ private static void nonWeightedProbability(List<Letters> letters, int testCount) { for (int i = 0; i < testCount; i++) { // Generate random number between 0 and size of letters list minus 1 Random random = new Random(); int min = 0; int max = letters.size() - 1; int randomNumber = random.nextInt(max - min + 1) + min; // Select letter at index of random number and increment its count by 1 String selectedLetter = letters.get(randomNumber).getLetter(); letters.stream() .filter(a -> a.getLetter().equals(selectedLetter)) .findFirst() .ifPresent(l -> l.setCount(l.getCount() + 1)); } // Print count and percentage of each letter for (Letters letter : letters) { System.out.println(letter.getLetter() + " -> " + letter.getCount() + "(" + String.format("%.2f", (letter.getCount() * Math.pow(testCount, -1)) * 100) + "%)"); } // Reset count of each letter to 0 letters.forEach(a -> a.setCount(0)); } |
l método realiza un bucle que se repetirá determinada cantidad de veces. En cada iteración del bucle se genera un número entero aleatorio entre 0 y el tamaño de la lista de «letras». Luego, selecciona el objeto «Letra» en el índice del número aleatorio e incrementa su cuenta en 1.
Una vez que el bucle termina de ejecutarse, el método imprime el recuento y el porcentaje de cada objeto «Letra». Finalmente, restablece el recuento de cada objeto «Letra» a 0.
Para probar este código, necesitas hacer algo como esto:
1 2 |
int testCount = 100000; nonWeightedProbability(letters, testCount); |
El siguiente resultado se produce después de ejecutar la selección de letras aleatorias 100,000 veces:
Como podemos ver, cada letra ha sido seleccionada aproximadamente un 10% de las veces, fluctuando los resultados ligeramente por encima o por debajo de este valor. Esto demuestra que el proceso de selección de letras al azar está generando una salida con la misma probabilidad para cada letra.
Generación de números aleatorios con probabilidad ponderada en Java
A veces podemos querer generar números aleatorios con probabilidades desiguales. Por ejemplo, podemos querer que una letra se seleccione con más frecuencia que las demás. En estos casos, podemos utilizar probabilidades ponderadas. Esto nos permite especificar la probabilidad de seleccionar un elemento en particular, en lugar de que todos los elementos tengan la misma probabilidad de ser seleccionados.
En la lista de letras que creé antes asigné una probabilidad para cada letra:
Ahora crearemos un método similar al anterior, pero los porcentajes resultantes para cada letra deben estar cerca de las probabilidades que se establecieron. Aquí está el método:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
/** * * Method for performing selection tests with weighted probability. * * @param letters list of letters to perform tests on * @param testCount number of tests to perform */ private static void weightedProbability(List<Letters> letters, int testCount) { //Repeat the selection process 'testCount' times for (int i = 0; i < testCount; i++) { //List to hold the cumulative probabilities of each letter List<Double> cumulativeProbabilities = new ArrayList<>(); double sum = 0; //Calculate cumulative probabilities for each letter for (Letters l : letters) { sum += l.getProbability(); cumulativeProbabilities.add(sum); } //Generate a random number between 0 and 1 double r = Math.random() * sum; //Select a letter based on the cumulative probabilities String selectedLetter = letters.stream() //Find the first letter whose cumulative probability is greater than the random number .filter(l -> r < cumulativeProbabilities.get(letters.indexOf(l))) .findFirst().get().getLetter(); //Increment the count for the selected letter letters.stream() .filter(a -> a.getLetter().equals(selectedLetter)) .findFirst() .ifPresent(l -> l.setCount(l.getCount() + 1)); } //After 'testCount' loops, print out the number of times each letter was selected and the percentage it represents for (Letters letter : letters) { System.out.println(letter.getLetter() + " -> " + letter.getCount() + "(" + String.format("%.2f", (letter.getCount() * Math.pow(testCount, -1)) * 100) + "%)"); } //Reset the count for each letter to 0 letters.forEach(a -> a.setCount(0)); } |
El resultado de ejecutar este código es:
Como podemos ver en el resultado, la frecuencia de selección de cada letra se alinea con la probabilidad que se estableció previamente. Esto demuestra que nuestro algoritmo funciona efectivamente como un generador de números aleatorios ponderados.
Conclusion
El código completo con ambos ejemplos se puede descargar desde nuestro repositorio de Github.
En conclusión, hemos aprendido a generar números aleatorios con igual probabilidad y con probabilidad ponderada en Java usando la clase java.util.Random. También hemos visto cómo implementar un algoritmo de selección aleatoria con probabilidades ponderadas utilizando una lista de objetos con una letra, un contador y un atributo de probabilidad.
Esperamos que esta publicación le haya sido útil y si tiene alguna pregunta o comentario, no dude en dejarlo a continuación. ¡Gracias por leer!