Tengo, por ejemplo, esta tabla
+ ----------------- + El | fruta | peso | + ----------------- + El | manzana | 4 | El | naranja | 2 | El | limon | 1 | + ----------------- +
Necesito devolver una fruta al azar. Pero la manzana se debe recoger 4 veces más frecuente que el limón y 2 veces más frecuente que la naranja .
En el caso más general, debería ser f(weight)
veces con frecuencia.
¿Cuál es un buen algoritmo general para implementar este comportamiento?
¿O tal vez hay algunas gemas listas en Ruby? :)
PD
: he implementado el algoritmo actual en Ruby https://github.com/fl00r/pickup
algorithms
ruby
math
random
fl00r
fuente
fuente
Respuestas:
La solución conceptualmente más simple sería crear una lista donde cada elemento ocurra tantas veces como su peso, por lo que
Luego, use las funciones que tenga a su disposición para elegir un elemento aleatorio de esa lista (por ejemplo, generar un índice aleatorio dentro del rango adecuado). Por supuesto, esto no es muy eficiente con la memoria y requiere pesos enteros.
Otro enfoque un poco más complicado se vería así:
Calcule las sumas acumuladas de pesos:
Cuando un índice inferior a 4 representa una manzana , 4 a inferior a 6 una naranja y 6 a inferior a 7 un limón .
Genere un número aleatorio
n
en el rango de0
asum(weights)
.n
. El fruto correspondiente es tu resultado.Este enfoque requiere un código más complicado que el primero, pero menos memoria y cómputo, y admite pesos de punto flotante.
Para cualquiera de los algoritmos, el paso de configuración se puede hacer una vez para un número arbitrario de selecciones aleatorias.
fuente
Aquí hay un algoritmo (en C #) que puede seleccionar un elemento aleatorio ponderado de cualquier secuencia, solo iterando a través de él una vez:
Esto se basa en el siguiente razonamiento: seleccionemos el primer elemento de nuestra secuencia como "resultado actual"; luego, en cada iteración, guárdelo o descarte y elija un nuevo elemento como actual. Podemos calcular la probabilidad de que cualquier elemento dado sea seleccionado al final como un producto de todas las probabilidades de que no se descarte en los pasos posteriores, multiplicado por la probabilidad de que se seleccione en primer lugar. Si hace los cálculos, verá que este producto se simplifica a (peso del elemento) / (suma de todos los pesos), ¡que es exactamente lo que necesitamos!
Dado que este método solo itera sobre la secuencia de entrada una vez, funciona incluso con secuencias obscenamente grandes, siempre que la suma de pesos se ajuste a un
int
(o puede elegir un tipo más grande para este contador)fuente
Las respuestas ya presentes son buenas y las ampliaré un poco.
Como Benjamin sugirió, las sumas acumulativas se usan típicamente en este tipo de problema:
Para encontrar un elemento en esta estructura, puede usar algo como el código de Nevermind. Este fragmento de código C # que suelo usar:
Ahora a la parte interesante. ¿Qué tan eficiente es este enfoque y cuál es la solución más eficiente? Mi código requiere memoria O (n) y se ejecuta en tiempo O (n) . No creo que se pueda hacer con menos de O (n) espacio, pero la complejidad del tiempo puede ser mucho menor, de hecho O (log n) . El truco consiste en utilizar la búsqueda binaria en lugar del ciclo regular for.
También hay una historia sobre la actualización de pesos. En el peor de los casos, la actualización de peso para un elemento provoca la actualización de sumas acumulativas para todos los elementos, lo que aumenta la complejidad de la actualización a O (n) . Eso también se puede reducir a O (log n) usando un árbol indexado binario .
fuente
Esta es una implementación simple de Python:
y
En algoritmos genéticos, este procedimiento de selección se denomina Selección proporcional de aptitud física o Selección de rueda de ruleta, ya que:
Los algoritmos típicos tienen complejidad O (N) u O (log N), pero también puede hacer O (1) (por ejemplo , selección de rueda de ruleta mediante aceptación estocástica ).
fuente
Esta esencia está haciendo exactamente lo que estás pidiendo.
puedes usarlo así:
El código anterior probablemente devolverá (% 98) 0, que es el índice de 'manzana' para la matriz dada.
Además, este código prueba el método proporcionado anteriormente:
Da una salida algo así:
fuente