¿Generar resultados aleatorios por peso en PHP?

Sé cómo generar un número aleatorio en PHP, pero digamos que quiero un número aleatorio entre 1 y 10, pero quiero más 3,4,5 y luego 8,9,10. ¿Cómo es esto posible? Publicaría lo que intenté pero, sinceramente, ni siquiera sé por dónde empezar.

Basado en la respuesta / enlace de @ Allain, desarrollé esta función rápida en PHP. Tendrá que modificarlo si desea usar un peso no entero.

/** * getRandomWeightedElement() * Utility function for getting random values with weighting. * Pass in an associative array, such as array('A'=>5, 'B'=>45, 'C'=>50) * An array like this means that "A" has a 5% chance of being selected, "B" 45%, and "C" 50%. * The return value is the array key, A, B, or C in this case. Note that the values assigned * do not have to be percentages. The values are simply relative to each other. If one value * weight was 2, and the other weight of 1, the value with the weight of 2 has about a 66% * chance of being selected. Also note that weights should be integers. * * @param array $weightedValues */ function getRandomWeightedElement(array $weightedValues) { $rand = mt_rand(1, (int) array_sum($weightedValues)); foreach ($weightedValues as $key => $value) { $rand -= $value; if ($rand <= 0) { return $key; } } } 

Para un número aleatorio eficiente sesgado consistentemente hacia un extremo de la escala:

  • Elija un número aleatorio continuo entre 0..1
  • Elevar a un poder γ, para sesgarlo. 1 es no ponderado, más bajo da más de los números más altos y viceversa
  • Escala al rango deseado y redondo a entero

p.ej. en PHP (no probado):

 function weightedrand($min, $max, $gamma) { $offset= $max-$min+1; return floor($min+pow(lcg_value(), $gamma)*$offset); } echo(weightedrand(1, 10, 1.5)); 

Hay un buen tutorial para ti .

Básicamente:

  1. Suma los pesos de todos los números.
  2. Elija un número aleatorio menor que ese
  3. reste los pesos en orden hasta que el resultado sea negativo y devuelva ese número si lo es.

El truco ingenuo para esto sería construir una lista o una matriz como

1, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7, 7, 7, 8, 8, 9, 9, 10, 10

Y luego selecciona al azar de eso.

Este tutorial lo guiará a través de él, en PHP, con múltiples soluciones de cortar y pegar. Tenga en cuenta que esta rutina se modifica ligeramente de lo que encontrará en esa página, como resultado del comentario a continuación.

Una función tomada de la publicación:

 /** * weighted_random_simple() * Pick a random item based on weights. * * @param array $values Array of elements to choose from * @param array $weights An array of weights. Weight must be a positive number. * @return mixed Selected element. */ function weighted_random_simple($values, $weights){ $count = count($values); $i = 0; $n = 0; $num = mt_rand(1, array_sum($weights)); while($i < $count){ $n += $weights[$i]; if($n >= $num){ break; } $i++; } return $values[$i]; } 

Llano y justo. Solo copie / pegue y pruébelo.

 /** * Return weighted probability * @param (array) prob=>item * @return key */ function weightedRand($stream) { $pos = mt_rand(1,array_sum(array_keys($stream))); $em = 0; foreach ($stream as $k => $v) { $em += $k; if ($em >= $pos) return $v; } } $item['30'] = 'I have more chances than everybody :]'; $item['10'] = 'I have good chances'; $item['1'] = 'I\'m difficult to appear...'; for ($i = 1; $i <= 10; $i++) { echo weightedRand($item).'
'; }

Editar: agregado soporte faltante al final.

Puede usar weightedChoice desde una biblioteca PHP no estándar . Acepta una lista de pares (elemento, peso) para tener la posibilidad de trabajar con elementos que no pueden ser claves de matriz. Puede usar la función de pares para convertir la array(item => weight) al formato necesario.

 use function \nspl\a\pairs; use function \nspl\rnd\weightedChoice; $weights = pairs(array( 1 => 10, 2 => 15, 3 => 15, 4 => 15, 5 => 15, 6 => 10, 7 => 5, 8 => 5, 9 => 5, 10 => 5 )); $number = weightedChoice($weights); 

En este ejemplo, 2-5 aparecerá 3 veces más a menudo que 7-10.

Como utilicé la solución de IainMH, también puedo compartir mi código PHP:

 

 /** * @param array $weightedValues * @return string */ function getRandomWeightedElement(array $weightedValues) { $array = array(); foreach ($weightedValues as $key => $weight) { $array = array_merge(array_fill(0, $weight, $key), $array); } return $array[array_rand($array)]; } 

getRandomWeightedElement(array('A'=>10, 'B'=>90));

Este es un método muy fácil. Cómo obtener elemento ponderado al azar. Lleno la variable variable $ clave. Obtengo $ key para array $ weight x. Después de eso, usa array_rand to array. Y tengo un valor aleatorio;).

Acabo de lanzar una clase para realizar una clasificación ponderada fácilmente.

Se basa en el mismo algoritmo mencionado en las respuestas de Brad y Allain , y está optimizado para la velocidad, probado en unidades para una distribución uniforme y admite elementos de cualquier tipo de PHP.

Usarlo es simple. Crear una instancia:

 $picker = new Brick\Random\RandomPicker(); 

A continuación, agregue elementos como una matriz de valores ponderados (solo si sus elementos son cadenas o enteros):

 $picker->addElements([ 'foo' => 25, 'bar' => 50, 'baz' => 100 ]); 

O use llamadas individuales a addElement() . Este método admite cualquier tipo de valores de PHP como elementos (cadenas, números, objetos, …), a diferencia del enfoque de matriz:

 $picker->addElement($object1, $weight1); $picker->addElement($object2, $weight2); 

Luego obtén un elemento aleatorio:

 $element = $picker->getRandomElement(); 

La probabilidad de obtener uno de los elementos depende de su peso asociado. La única restricción es que los pesos deben ser enteros.

function getBucketFromWeights ($ values) {$ total = $ currentTotal = $ bucket = 0;

 foreach ($values as $amount) { $total += $amount; } $rand = mt_rand(0, $total-1); foreach ($values as $amount) { $currentTotal += $amount; if ($rand => $currentTotal) { $bucket++; } else { break; } } return $bucket; 

}

Modifiqué esto de una respuesta aquí Escogiendo elementos aleatorios por pesos definidos por el usuario

Después de escribir esto, vi que alguien tenía una respuesta aún más elegante. Él él el.

Muchas de las respuestas en esta página parecen usar distensión de matriz, iteración excesiva, una biblioteca o un proceso difícil de leer. Por supuesto, todos piensan que su propio bebé es el más lindo, pero honestamente creo que mi enfoque es delgado, simple y fácil de leer / modificar …

Según el OP, crearé una matriz de valores (declarados como claves) de 1 a 10, con 3, 4 y 5 con el doble del peso de los otros valores (declarados como valores).

 $values_and_weights=array( 1=>1, 2=>1, 3=>2, 4=>2, 5=>2, 6=>1, 7=>1, 8=>1, 9=>1, 10=>1 ); 

Si solo vas a hacer una selección al azar y / o tu matriz es relativamente pequeña * (haz tu propio benchmarking para estar seguro), esta es probablemente tu mejor opción:

 $pick=mt_rand(1,array_sum($values_and_weights)); $x=0; foreach($values_and_weights as $val=>$wgt){ if(($x+=$wgt)>=$pick){ echo "$val"; break; } } 

Este enfoque no implica modificación de matriz y probablemente no será necesario iterar toda la matriz (pero sí).


Por otro lado, si va a hacer más de una selección al azar en la matriz y / o su matriz es suficientemente grande * (haga su propia evaluación comparativa para estar seguro), la reestructuración de la matriz puede ser mejor.

El costo en memoria para generar una nueva matriz se justificará cada vez más como:

  1. el tamaño de la matriz aumenta y
  2. número de selecciones aleatorias aumenta.

La nueva matriz requiere el reemplazo de “peso” por un “límite” para cada valor al sumr el peso del elemento anterior al peso del elemento actual.

A continuación, voltee la matriz de modo que los límites sean las teclas de la matriz y los valores sean los valores de la matriz. La lógica es: el valor seleccionado tendrá el límite más bajo que sea> = $ pick.

 // Declare new array using array_walk one-liner: array_walk($values_and_weights,function($v,$k)use(&$limits_and_values,&$x){$limits_and_values[$x+=$v]=$k;}); //Alternative declaration method - 4-liner, foreach() loop: /*$x=0; foreach($values_and_weights as $val=>$wgt){ $limits_and_values[$x+=$wgt]=$val; }*/ var_export($limits_and_values); 

Crea esta matriz:

 array ( 1 => 1, 2 => 2, 4 => 3, 6 => 4, 8 => 5, 9 => 6, 10 => 7, 11 => 8, 12 => 9, 13 => 10, ) 

Ahora para generar el $pick aleatorio y seleccionar el valor:

 // $x (from walk/loop) is the same as writing: end($limits_and_values); $x=key($limits_and_values); $pick=mt_rand(1,$x); // pull random integer between 1 and highest limit/key while(!isset($limits_and_values[$pick])){++$pick;} // smallest possible loop to find key echo $limits_and_values[$pick]; // this is your random (weighted) value 

Este enfoque es shiny porque isset() es muy rápido y la cantidad máxima de llamadas isset() en el ciclo while solo puede ser el mayor peso (que no debe confundirse con el límite) en la matriz. Para este caso, iteraciones máximas = 2!

ESTE ENFOQUE NUNCA NECESITA PARA ITERAR TODO EL ARRAY