5

What is a good algorithm that shuffles an array or arrays using weights from the nested arrays?

Example:

$array = array(
  array("name"=>"John", "rank"=>3),
  array("name"=>"Bob", "rank"=>1),
  array("name"=>"Todd", "rank"=>8),
  array("name"=>"Todd", "rank"=>14),
  array("name"=>"Todd", "rank"=>4)
);

I want the array randomly shuffled but I want the rank value to be a weight. So those with a low number rank are more likely to be at the top of the list.

I've experimented with a few things, like iterating through the array and pulling out arrays chosen using mt_rand(mt_rand(0,$value),$value) but I don't think I'm on the right track...

Jake Wilson
  • 88,616
  • 93
  • 252
  • 370

3 Answers3

5

I was able to solve this problem like so:

function compare($a, $b)
{
  $share_of_a = $a['rank'];
  $share_of_b = $b['rank'];
  return mt_rand(0, ($share_of_a+$share_of_b)) > $share_of_a ? 1 : -1;
}

usort($array, "compare"); // Sort the array using the above compare function when comparing
$array = array_reverse($array);
Jake Wilson
  • 88,616
  • 93
  • 252
  • 370
1

Since this question is first on google while searching php array weighted shuffle and accepted answer doesn't solve it - here is the solution, based on some algorithm i found.

Pretty fast (for PHP), probability distribution is also tested and correct

<?php

/**
 * Input can be specified as basic array or array of arrays:
 * - array = [key1 => weight1, key2 => weight2, ...]
 * - array = [[..., weightKey => weight], [..., weightKey2 => weight2], ...]
 *
 * Usage:
 *
 * $arr = ['key1' => 1, 'key2' => 2, 'key3' => 3];
 * weighted_shuffle($arr);
 *
 * On average key3 is gonna be the first 50% of the time
 *
 * @param array         $array Array to shuffle
 * @param string|null   $weight_key Optional weight key if input is array of arrays 
 */
function weighted_shuffle(array &$array, $weight_key = null)
{
    if($weight_key === null) {
        $arr = $array;
    } else {
        $arr = array_combine(array_keys($array), array_column($array, $weight_key));
    }
    $max = 1.0 / getrandmax();
    array_walk($arr, function (&$v, $k) use($max) {
        $v = pow(rand()*$max, 1.0/$v);
    });
    arsort($arr);
    array_walk($arr, function (&$v, $k) use($array) {
       $v = $array[$k];
    });
    $array = $arr;
}
savvot
  • 21
  • 3
1

You can try something like this:

function weightedshuffle ($a, $b) {
    return rand(0, $a['rank'] + $b['rank']) <= $a['rank'];
}

usort($data, 'weightedshuffle');
gintas
  • 2,118
  • 1
  • 18
  • 28
  • 1
    I don't know the php specifics, but you certainly SHOULDN'T be able to sort using that function - it's not an equivalence relation. – Alexander Corwin Feb 22 '12 at 21:20
  • Interesting. Well, I'm not really sure what it means to sort on something that isn't an equivalence relation, but if it works it works I guess. – Alexander Corwin Feb 22 '12 at 21:31
  • On the same note, yeah - you are right, this is quite an abuse of sort function. – gintas Feb 22 '12 at 21:31
  • 1
    @AlexanderCorwin `usort` uses some kind of Quick Sort algorithm and the passed function as comparison function. – Gumbo Feb 22 '12 at 21:34