Tengo una caja de botín que quiero llenar con un elemento aleatorio. Pero quiero que cada elemento tenga una probabilidad diferente de ser elegido. Por ejemplo:
- 5% de probabilidad de 10 de oro
- 20% de probabilidad de espada
- 45% de probabilidad de escudo
- 20% de probabilidad de armadura
- 10% de probabilidad de poción
¿Cómo puedo lograr que seleccione exactamente uno de los elementos anteriores, donde esos porcentajes son las posibilidades respectivas de obtener el botín?
Respuestas:
La solución de probabilidades de código suave
La solución de probabilidad codificada tiene la desventaja de que necesita establecer las probabilidades en su código. No puedes determinarlos en tiempo de ejecución. También es difícil de mantener.
Aquí hay una versión dinámica del mismo algoritmo.
Aquí hay una implementación de muestra en Java en forma de una clase de plantilla que puedes instanciar para cualquier objeto que use tu juego. Luego puede agregar objetos con el método
.addEntry(object, relativeWeight)
y elegir una de las entradas que agregó anteriormente con.get()
Uso:
Aquí está la misma clase implementada en C # para su proyecto Unity, XNA o MonoGame:
Y aquí hay uno en JavaScript :
Pro:
Contra:
O(n)
complejidad de tiempo de ejecución). Entonces, cuando tiene un conjunto muy grande de elementos y dibuja con mucha frecuencia, puede volverse lento. Una optimización simple es poner los elementos más probables primero para que el algoritmo termine temprano en la mayoría de los casos. Una optimización más compleja que puede hacer es explotar el hecho de que la matriz está ordenada y hacer una búsqueda de bisección. Esto solo llevaO(log n)
tiempo.O(n)
peor tiempo de ejecución)fuente
Nota: creé una biblioteca de C # para este problema exacto
Las otras soluciones están bien si solo tiene una pequeña cantidad de elementos y sus probabilidades nunca cambian. Sin embargo, con muchos elementos o probabilidades cambiantes (por ejemplo, eliminar elementos después de seleccionarlos) , querrá algo más poderoso.
Estas son las dos soluciones más comunes (ambas incluidas en la biblioteca anterior)
Método de alias de Walker
Una solución inteligente que es extremadamente rápida (
O(1)
!) Si sus probabilidades son constantes. En esencia, el algoritmo crea un tablero de dardos 2D ("tabla de alias") a partir de sus probabilidades y le lanza un dardo.Hay muchos artículos en línea sobre cómo funciona si desea obtener más información.
El único problema es que si sus probabilidades cambian, necesita regenerar la tabla de alias, que es lenta. Por lo tanto, si necesita eliminar elementos después de haberlos elegido, esta no es la solución para usted.
Solución basada en árboles
La otra solución común es hacer una matriz donde cada elemento almacena la suma de su probabilidad y todos los elementos anteriores. Luego, simplemente genere un número aleatorio a partir de [0,1) y realice una búsqueda binaria para saber dónde cae ese número en la lista.
Esta solución es muy fácil de codificar / comprender, pero hacer una selección es más lento que el Método de alias de Walker, y cambiar las probabilidades sigue siendo
O(n)
. Podemos mejorarlo convirtiendo la matriz en un árbol de búsqueda binaria, donde cada nodo realiza un seguimiento de la suma de probabilidades en todos los elementos de su subárbol. Luego, cuando generamos el número desde [0,1), podemos simplemente caminar hacia abajo del árbol para encontrar el elemento que representa.¡Esto nos da
O(log n)
para elegir un artículo y cambiar las probabilidades! ¡Esto lo haceNextWithRemoval()
extremadamente rápido!Los resultados
Aquí hay algunos puntos de referencia rápidos de la biblioteca anterior, comparando estos dos enfoques
Como puede ver, para el caso especial de probabilidades estáticas (que no cambian), el método Alias de Walker es aproximadamente 50-100% más rápido. ¡Pero en los casos más dinámicos, el árbol es varios órdenes de magnitud más rápido !
fuente
nlog(n)
) cuando clasificamos artículos por peso.La solución de la Rueda de la Fortuna
Puede usar este método cuando las probabilidades en su grupo de elementos tienen un denominador común bastante grande y necesita recurrir a él con mucha frecuencia.
Crea una variedad de opciones. Pero coloque cada elemento en él varias veces, con el número de duplicados de cada elemento proporcional a su posibilidad de aparecer. Para el ejemplo anterior, todos los elementos tienen probabilidades que son multiplicadores del 5%, por lo que puede crear una matriz de 20 elementos como este:
Luego, simplemente elija un elemento aleatorio de esa lista generando un entero aleatorio entre 0 y la longitud de la matriz - 1.
Desventajas
Ventajas:
fuente
Epic Scepter of the Apocalypse
. Tal enfoque de dos niveles aprovecha las ventajas de ambos enfoques.[('gold', 1),('sword',4),...]
sumar todos los pesos, y luego rodar un número aleatorio de 0 a la suma, luego iterar la matriz y calcular dónde aterriza el número aleatorio (es decir, areduce
) Funciona bien para matrices que se actualizan con frecuencia, y no hay un gran problema de memoria.La solución de probabilidades codificada
La forma más simple de encontrar un elemento aleatorio de una colección ponderada es atravesar una cadena de instrucciones if-else, donde cada if-else aumenta probablemente, ya que la anterior no golpea.
La razón por la cual los condicionales son iguales a su probabilidad más todas las posibilidades condicionales anteriores es porque los condicionales anteriores ya han eliminado la posibilidad de que sean esos elementos. Entonces, para el condicional del escudo
else if(rand <= 70)
, 70 es igual al 45% de probabilidad del escudo, más el 5% de probabilidad del oro y el 20% de probabilidad de la espada.Ventajas:
Desventajas
fuente
En C #, podría usar un escaneo de Linq para ejecutar su acumulador para verificar un número aleatorio en el rango de 0 a 100.0f y .First () para obtener. Entonces, como una línea de código.
Entonces algo como:
sum
es un entero inicializado cero ya
es una lista de estructuras de prob / ítem / tuplas / instancias.rand
es un número aleatorio generado previamente en el rango.Esto simplemente acumula la suma sobre la lista de rangos hasta que excede el número aleatorio seleccionado previamente y devuelve el elemento o nulo, donde nulo se devolvería si el rango de números aleatorios (por ejemplo, 100) es menor que el rango de ponderación total por error , y el número aleatorio seleccionado está fuera del rango de ponderación total.
Sin embargo, notará que los pesos en OP coinciden estrechamente con una distribución normal (curva de campana). Creo que, en general, no querrá rangos específicos, tenderá a querer una distribución que se reduzca, ya sea alrededor de una curva de campana o simplemente en una curva exponencial decreciente (por ejemplo). En este caso, podría usar una fórmula matemática para generar un índice en una matriz de elementos, ordenados por orden de probabilidad preferida. Un buen ejemplo es CDF en distribución normal.
También un ejemplo aquí .
Otro ejemplo es que podría tomar un valor aleatorio de 90 grados a 180 grados para obtener el cuadrante inferior derecho de un círculo, tomar el componente x usando cos (r) y usarlo para indexar en una lista priorizada.
Con diferentes fórmulas, podría tener un enfoque general en el que simplemente ingrese una lista priorizada de cualquier longitud (por ejemplo, N) y asigne el resultado de la fórmula (por ejemplo: cos (x) es 0 a 1) por multiplicación (por ejemplo: Ncos (x ) = 0 a N) para obtener el índice.
fuente
Las probabilidades no necesitan estar codificadas. Los elementos y los umbrales pueden estar juntos en una matriz.
Aún debe acumular los umbrales, pero puede hacerlo al crear un archivo de parámetros en lugar de codificarlo.
fuente
random()
en el bucle?Hice esta función: https://github.com/thewheelmaker/GDscript_Weighted_Random Now! en tu caso puedes usarlo así:
Solo da un número entre 0 y 4, pero puede colocarlo en la matriz donde obtuvo los elementos.
O en función:
Aquí está el código. Lo hice en GDscript, puedes, pero puede alterar otro idioma, también verifica si hay errores lógicos:
Funciona así: on_normal_case ([50,50], 0) Esto da 0 o 1, tiene la misma probabilidad de ambos.
on_normal_case ([50,50], 1) Esto da 1 o 2, tiene la misma probabilidad de ambos.
on_normal_case ([20,80], 1) Esto da 1 o 2, tiene un cambio mayor para obtener dos.
on_normal_case ([20,80,20,20,30], 1) Esto da un rango de números aleatorios 1-5 y los números más grandes son más probables que los números más pequeños.
on_normal_case ([20,80,0,0,20,20,30,0,0,0,0,33], 45) Este lanzamiento corta entre los números 45,46,49,50,51,56 que ves cuando hay es cero, nunca ocurre.
Por lo tanto, la función devuelve solo un número aleatorio que depende de la longitud de esa matriz de matriz y el número de transformm, y las entradas en la matriz son pesos de probabilidad de que un número pueda ocurrir, donde ese número es la ubicación en la matriz, más el número de transformm.
fuente