Elegir un valor aleatorio de un array de forma ponderada con php

Con php podemos elegir el valor de un array de forma aleatoria utilizando directamente la función array_rand() para ello diseñada. Sin embargo, utilizando esta función cada uno de los elementos del array tienen el mismo peso, esto es, la misma probabilidad de ser elegidos.

En ciertos casos podemos necesitar tomar de forma aleatoria un valor de un array pero con un determinado peso, que cada valor del array tenga diferentes probabilidades de ser elegido. En otras palabras, queremos elegir un valor de un array de forma ponderada.

Por ejemplo, tenemos el siguiente array:

<?php
  $miarray = array('dato1','dato2','dato3');
?>

Y que queremos que el dato1 tenga un peso del 30%, el dato2 un peso del 20% y el dato3 un peso del 50%. Esto significa que el dato1 tendrá una probabilidad de ser elegido de forma aleatoria del 30%, el dato2 del 20% y el datos3 del 50%. Para hacer esto tenemos varias posibilidades:

1.- Crear un nuevo array en el que cada elemento se repita tanta veces como su peso indique y utilizar array_rand()

Con esto se persigue que en el nuevo array cada valor quede representado en la proporción que indique su peso. Para nuestro ejemplo podríamos crear un nuevo array de 10 elementos en el que el dato1 se repitiese 3 veces, el dato2 aparezca 2 veces y el dato3 5 veces:

<?php

$miarray = array('dato1','dato1','dato1','dato2','dato2','datos3','dato3','dato3','dato3','dato3'); $valor_ponderado = $miarray[array_rand($miarray)]; ?>

Para hacer esto de forma automática con cualquier array, dicho array debe llevar el peso otorgado a cada valor y podemos utilizar la siguiente función:

<?php
  function valor_ponderado(array $items) {

    $weighted = array();
    foreach($items as $item) {
       array_merge($weighted, array_fill(0, $item['weight'], $item['value']);
    }
    return $weighted[array_rand($weighted)];

  }

  $miarray = array(array('value'=>'dato1', 'weight'=>3),array('value'=>'dato2', 'weight'=>2),array('value'=>'dato3', 'weight'=>5));
  echo valor_ponderado($miarray);
}

?>

Con la función anterior se creará un array en el que cada elemento se repite tantas veces como el valor indicado en 'weight'.

2.- Método de suma de los pesos

Si tenemos un array muy grande el método anterior puede ser un poco lento (según varias fuentes, no lo he comprobado personalmente) y este segundo método sería mejor.

La siguiente función suma los pesos dados a cada elemento del array, después elige un número aleatorio entre 0 y la suma de los pesos. En el momento en el que el número aleatorio escogido es mayor o igual a un índice aleatorio, elige este índice del array y devuelve su valor:

<?php

  function valor_ponderado(array $items) {
     $weight = 0;

     foreach($items as $item) {
        $weight += $item['weight'];
     }

     $index = rand(1, $weight);
     foreach($items as $item) {
        $index -= $item['weight'];
        if($index <= 0) { return $item['value']; }
     }

     return null;
  }

  $miarray = array(array('value'=>'dato1', 'weight'=>3),array('value'=>'dato2', 'weight'=>2),array('value'=>'dato3', 'weight'=>5)); echo valor_ponderado($miarray); } ?>

Y aún se puede mejorar más la función anterior ejecutando sólo un loop foreach, lo que hará el código más rápido, perceptible sobretodo si tenemos muchos datos:

<?php

 function valor_ponderado($items) {
    $r = mt_rand(1,10000);
    $offset = 0;
    foreach ($items as $k => $w) {
	$offset += $w*10000;
	if ($r <= $offset) {
		return $k;
	}
 }

 $miarray = array('dato1'=>0.30,'dato2'=>0.20,'dato3'=>0.50);
  echo valor_ponderado($miarray);
}

?>

Para elegir un valor aleatorio de un array de forma ponderada yo me decanto por la última opción por resultarme más cómoda y ser el código más rápido si tenemos muchos datos.

Vea también Obtener valores aleatorios de un array con php.



Comentarios (0)

Smileys

:confused::cool::cry::laugh::lol::normal::blush::rolleyes::sad::shocked::sick::sleeping::smile::surprised::tongue::unsure::whistle::wink: