Getting Started
I was researching into the idea of randomly selecting array elements based on varying weights, i was looking for something like;
<?php
$arr = ['RED' => 50, 'BLUE' => 25, 'GREEN' => 25];
return_me_a_true_random($arr);
From this I would expect red to be returned around 50% of the time, blue around a quarter, and green around a quarter also.
I came across this on stack overflow and wanted to test how true the outcome is.
Implementation
The Code
function getRandomWeightedElement(array $weightedValues) {
$rand = mt_rand(1, (int) array_sum($weightedValues));
foreach ($weightedValues as $key => $value) {
$rand -= $value;
if ($rand <= 0) {
return $key;
}
}
}
The Test
I decided to test this PHPUnit, so I created the following test.
require './vendor/autoload.php';
require './random-weighted.php';
class RandomTest extends PHPUnit_Framework_TestCase
{
protected $iterations;
public function setUp()
{
parent::setUp();
$this->iterations = 1000;
}
/**
* Helper method to see if result is roughly in the correct percentage outcome
*/
public function isInbetween($number, $min, $max)
{
if($number > $min && $number < $max) {
return true;
}
return false;
}
/**
* @test
*/
public function testWeightedArray()
{
// these are the weighted results
$arr = ['RED' => 50, 'GREEN' => 25, 'BLUE' => 25];
// results array
$results = ['RED' => 0, 'GREEN' => 0, 'BLUE' => 0];
// loop through 1000 times to get a true repesentation of results
for($i = 0; $i < $this->iterations; $i++){
//execute random
$color = getRandomWeightedElement($arr);
// store results
$results[$color]++;
}
// Test for the outcome of red, somewhere around 45% - 55%
$redMin = ($this->iterations / 2) - ($this->iterations * 0.10);
//var_dump($redMin);exit;
$redMax = ($this->iterations / 2) + ($this->iterations * 0.10);
//var_dump($redMax);exit;
$redResult = $this->isInbetween($results['RED'], $redMin, $redMax);
$this->assertTrue($redResult);
// Test for the outcome of green, somewhere around 20% - 30%
$greenMin = ($this->iterations / 4) - ($this->iterations * 0.10);
//var_dump($redMin);exit;
$greenMax = ($this->iterations / 4) + ($this->iterations * 0.10);
//var_dump($redMax);exit;
$greenResult = $this->isInbetween($results['GREEN'], $greenMin, $greenMax);
$this->assertTrue($greenResult);
// Test for the outcome of blue, somewhere around 20% - 30%
$blueMin = ($this->iterations / 4) - ($this->iterations * 0.10);
//var_dump($redMin);exit;
$blueMax = ($this->iterations / 4) + ($this->iterations * 0.10);
//var_dump($redMax);exit;
$blueResult = $this->isInbetween($results['BLUE'], $blueMin, $blueMax);
$this->assertTrue($blueResult);
}
}
In essence I set the weights associated to each of the array items and ran the function 10,000 times. I then examined the results.
The Results My results were the following;
- Red: 497
- Green: 272
- Blue: 231
So this does work as expected, and the more you run this function the more accurate it gets.