2009-01-15 15 views
47

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.Generando resultados aleatorios por peso en PHP?

Respuesta

11

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

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

Y a continuación, seleccione al azar de eso.

+1

Exactamente. Cree una lista con los números ponderados en la proporción que desea ponderar (puede usar otra función para crear la lista, si es posible), y luego seleccione aleatoriamente un elemento de la lista. – Ross

+9

El único inconveniente es que si quieres que el número 1 sea 1 billón de veces más probable que los otros, necesitarás una matriz de 1 billón de elementos. –

+1

@Allain Lalonde Oh bastante. No escala, pero es muy simple y podría ser suficiente para las necesidades del OP. –

21

Hay un pretty good tutorial for you.

Básicamente:

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

Además, esto no tiene la sobrecarga de memoria de la respuesta anterior (crea otra matriz con la distribución deseada y se selecciona al azar de ella) –

+0

Este es el truco no ingenuo. :-) –

27

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

  • elegir un número aleatorio continuo entre 0..1
  • Raise a una γ de potencia, al sesgo de ella. 1 es ponderado, inferior da más de los números más altos y viceversa
  • Escala de intervalo deseado y vueltas a entero

por ejemplo. 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)); 
+0

Me encanta tu respuesta. Por favor, eche un vistazo a mi pregunta (enlace a continuación). Me encantaría saber de usted sobre la expansión de esto. http://stackoverflow.com/questions/4030427/generate-random-weight-value –

+0

Sé que esta pregunta es antigua, pero ¿qué hace '$ gamma' en este fragmento? – OptimusCrime

+2

@Optimus: es un factor de ponderación: la salida de la función es su entrada a la potencia de gamma, donde la entrada está entre 0 y 1. Entonces, por ejemplo, para gamma = 0.5 obtienes una curva de raíz cuadrada, que se curva hacia arriba desde 0 más rápido que la línea recta, por lo que obtienes números más altos. Ver, por ejemplo, [wiki] (http://en.wikipedia.org/wiki/Gamma_correction) para obtener información sobre curvas gamma (tradicionalmente aplicado para la corrección de imágenes) – bobince

4

This tutorial le guía 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 tomado del mensaje:

/** 
* 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]; 
} 
+0

Esta respuesta y el tutorial del que se copió tienen fallas porque el mínimo 'mt_rand()' no debería ser '0', debería ser' 1'. Esto significa que el peso en el primer elemento se verá favorecido más de lo previsto. [http://sandbox.onlinephpfunctions.com/code/454ecce15a148b0934164afb8297076640bf228b](Demo de edición) Edite su respuesta y elimine el hipervínculo del tutorial dudoso. – mickmackusa

+0

@mickmackusa: ya no uso PHP, pero haré que cambie ... ¡gracias! –

1

Desde que utiliza la solución de IainMH, que puede así compartir mi código PHP:

<pre><?php 

// Set total number of iterations 
$total = 1716; 

// Set array of random number 
$arr = array(1, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5); 
$arr2 = array(0, 0, 1, 1, 2, 2, 2, 3, 3, 4, 5); 

// Print out random numbers 
for ($i=0; $i<$total; $i++){ 

    // Pick random array index 
    $rand = array_rand($arr); 
    $rand2 = array_rand($arr2); 

    // Print array values 
    print $arr[$rand] . "\t" . $arr2[$rand2] . "\r\n"; 

} 

?></pre> 
2

normal y justo. Simplemente 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).'<br />'; 
} 

Editar: Se ha añadido el soporte faltante al final.

+0

Lo siento, veo que el alg es defectuoso a veces. –

+7

Solo ten cuidado de no tener las mismas llaves. –

+0

Buena respuesta, vars anotados .. –

81

Basado en @ Allain answer/link, 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; 
     } 
    } 
    } 
+0

Muchas gracias por este brad. Lo utilicé en un proyecto y es mucho más eficiente y flexible que la otra función que había escrito. – Cloudkiller

+1

acercamiento inteligente, +1 – Asciiom

+0

cosas geniales, muchas gracias +1 –

2

Puede utilizar weightedChoice de Non-standard PHP library. 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 pairs para convertir 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.

0

Acabo de lanzar un class to perform weighted sorting fácilmente.

Se basa en el mismo algoritmo mencionado en Brad's y Allain's respuestas, 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. Instanciarlo:

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

A continuación, añadir elementos como un conjunto de valores ponderados (sólo si sus elementos son cadenas o enteros):

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

O utilizar las llamadas individuales a addElement(). Este método es compatible con cualquier tipo de valores de PHP como elementos (cadenas, números, objetos, ...), en comparación con el enfoque de matriz:

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

A continuación, obtener un elemento aleatorio:

$element = $picker->getRandomElement(); 

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

1
/** 
* @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 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;).

0

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; 

}

I uf modificados esto desde una respuesta aquí Picking random element by user defined weights

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

0

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 simple, fácil de leer/modificar ...

Por 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 sólo se va a hacer una selección al azar y/o la matriz es relativamente pequeña * (hacer su propia evaluación comparativa para estar seguro), esta es probablemente la 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 se va a realizar más de una selección al azar en la matriz y/o la matriz es suficientemente grande * (hacer su propia evaluación comparativa para estar seguro), la reestructuración de la matriz puede ser mejor.

El costo en la memoria para generar una nueva matriz será cada vez justificado como:

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

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

A continuación, voltee la matriz para que los límites sean las teclas de 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 es> = $ 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 azar $pick y seleccione 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 brillante porque isset() es muy rápido y el número máximo de isset() llamadas en el ciclo while solo pueden ser tantas como el mayor peso (no debe confundirse con el límite) en la matriz. Para este caso, iteraciones máximas = 2!

ESTE ENFOQUE nunca necesita para recorrer toda la matriz

Cuestiones relacionadas