3

Okay I've been beating my head over this one for a while and google searching trying to find anything to lead me in the right direction to no avail.

So I have a ranked leaderboard array like so:

[1] = array('points' => '99', 'rank' => '1');
[2] = array('points' => '90', 'rank' => '2');
[3] = array('points' => '90', 'rank' => '2');
[4] = array('points' => '80', 'rank' => '4');
[5] = array('points' => '70', 'rank' => '5');
[6] = array('points' => '70', 'rank' => '5');

Then I have a reward point system with a predefined array to award points based on rank:

[1]=10;
[2]=9;
[3]=8;
[4]=7;
[5]=6;
[6]=5;

If it weren't for ties I'd just match the two arrays up and award points as so based off of the array keys. But, since I'm allowing for ties I'm trying to split the tied reward points between all tied users. So with the references above players 2 and 3 who are tied at 2nd would split the rewards point 9 and 8... so both would be awarded 8.5 points.

So the end result I'm looking for when merging both arrays would be:

[1] = array('points' => '99', 'rank' => '1', 'reward' => '10');
[2] = array('points' => '90', 'rank' => '2', 'reward' => '8.5');
[3] = array('points' => '90', 'rank' => '2', 'reward' => '8.5');
[4] = array('points' => '80', 'rank' => '4', 'reward' => '7');
[5] = array('points' => '70', 'rank' => '5', 'reward' => '5.5');
[6] = array('points' => '70', 'rank' => '5', 'reward' => '5.5');

The leaderboard can constantly change until the end of the day so I'm trying not to make it too complicated. I'm just wondering if anyone can point me to something they've seen that would help me out or if they had any implementation ideas.

I the part I'm struggling with the most in my attempt is looping through the leaderboard array and continuously looking forward for the 'rank' to be the same to then know what I need to sum up and split out, while also knowing I can skip the array ahead instead of performing this same test on the next item that I just went through in my lookahead. Sorry If I'm confusing, but I'm obviously confused.

Al Bundy
  • 43
  • 4
  • 2
    Change it around so it is indexed by rank in the first dimension. Will make it easier for you. – ficuscr Mar 26 '13 at 18:36
  • But wouldn't I then have repetitive keys which would override the values – Al Bundy Mar 26 '13 at 18:38
  • No, you can do it... Also, what are your keys now? There any userID? Just look like numeric offsets now. $scores[*rank*][] = array('points' => 90, 'reward' => x); $scores[*rank*][] = array('points' => 90, 'reward' => x); Just easier to use in that format. eg. `$distribution = $totalReward / count($scores[1]);`. You can do it with the array in any format - I just suggest this since you are having trouble wrapping your head around it in current format. – ficuscr Mar 26 '13 at 18:41
  • Right now the Rewards array is keyed by the position first place, 2nd place. I actually haven't defined a key in the leaderboard array in the sense that it's just being defined by it's generic iteration. What I show is dwindled down to make it easy to read for the question. But, I would probably key it off the userId. But, I think I'm grasping what you're suggesting... to combine the array with a key of the rank and add each tied user as an offshoot of each rank I have. Would make it easier looking at it and figuring it out. – Al Bundy Mar 26 '13 at 18:52
  • Exactly! You can iterate over any array, use buffers/ temp variables etc. to accomplish your goal. But why jump through hoops and make it less readable? Start instead with your array in the best structure possible for how you will actually use it. As you get better thinking about multi-dim arrays you'll look back on this as being trivial. That said I still have to `var_dump` all the time to think through these types of things - I'm better visually, var_dump is my best friend. – ficuscr Mar 26 '13 at 18:57
  • Yeah once you can get yourself going it's like jump starting a car. Get yourself brain fried and can't think outside of the box sometimes to even attempt to make it more readable and work from there. Thanks you. – Al Bundy Mar 26 '13 at 19:00

2 Answers2

3

This is probably what you want:

Code

$persons = array(
    array('points' => '99', 'rank' => '1'),
    array('points' => '90', 'rank' => '2'),
    array('points' => '90', 'rank' => '2'),
    array('points' => '80', 'rank' => '4'),
    array('points' => '70', 'rank' => '5'),
    array('points' => '70', 'rank' => '5')
);

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

foreach($persons as $person => $prop) {
    $reward = $ranks[$prop['rank']];

    if (isset($persons[$person+1])) {
        if ($persons[$person+1]['rank'] == $prop['rank']) {
            $reward = $reward - 0.5;
        }
    }

    if (isset($persons[$person-1])) {
        if ($persons[$person-1]['rank'] == $prop['rank']) {
            $reward = $reward - 0.5;
        }
    }

    $persons[$person]['reward'] = $reward;
}

echo '<pre>';
print_r($persons);
echo '<pre>';

What happens here is you create the reward key and fill it with the person's rank based on the index in the $ranks array. This example is not safe tho. You might want to do a check first if the key even exists in the $ranks array.

This example is very "raw" and probably isn't flawed with different rankings, so you have to do the final tests yourself.

Output

Array
(
    [0] => Array
        (
            [points] => 99
            [rank] => 1
            [reward] => 10
        )

    [1] => Array
        (
            [points] => 90
            [rank] => 2
            [reward] => 8.5
        )

    [2] => Array
        (
            [points] => 90
            [rank] => 2
            [reward] => 8.5
        )

    [3] => Array
        (
            [points] => 80
            [rank] => 4
            [reward] => 7
        )

    [4] => Array
        (
            [points] => 70
            [rank] => 5
            [reward] => 5.5
        )

    [5] => Array
        (
            [points] => 70
            [rank] => 5
            [reward] => 5.5
        )

)
  • +1 for the effort put into providing a working answer with data as is. – ficuscr Mar 26 '13 at 19:00
  • Oh wow, man you make me feel so awful for taking how little I provided and making it into how I should have provided it to you. I really appreciate you doing this. – Al Bundy Mar 26 '13 at 19:02
  • You not only pointed me in the right direction you solved the entire thing basically... I cannot thank you enough. – Al Bundy Mar 26 '13 at 19:04
0

if more then 2 person can have the same rank like

$persons = array(
    array('points' => '99', 'rank' => '1'),
    array('points' => '90', 'rank' => '2'), // <---
    array('points' => '90', 'rank' => '2'), // <---
    array('points' => '90', 'rank' => '2'), // <---
    array('points' => '70', 'rank' => '5'),
    array('points' => '70', 'rank' => '5')
);

then this @Allendar algorithm will give you wrong results (will give reward of 8.5 than 8 and 8.5 points to persons with rank 2 in input above).

if there is always the same number of persons and number of defined ranking (like 6 in this case) you could use something like this:

$persons = array(
    array('points' => '99', 'rank' => '1'),
    array('points' => '90', 'rank' => '2'),
    array('points' => '90', 'rank' => '2'),
    array('points' => '90', 'rank' => '2'),
    array('points' => '70', 'rank' => '5'),
    array('points' => '70', 'rank' => '5')
);

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

$current_ranks = $ranks;
$rank_pool = array();
$rank_counter = array();

$i = 1; // person counter, start with 1 because $ranks start with 1

// distribute reword equally for all ranks
foreach($persons as $person => $prop) {
  if(!isset($ranks[$i])){
    exit("no reword for this much persons [$i]");
  }

  $rank = $prop['rank'];
  if(!isset($rank_pool[$rank])){
    $rank_pool[$rank] = 0;
  }
  $rank_pool[$rank] += $ranks[$i];
  if(!isset($rank_counter[$rank])){
    $rank_counter[$rank] = 0;
  }
  $rank_counter[$rank]++;

  $i++;
}

// set reword according to equally distributed reword
// points for all ranks
foreach($persons as $person => $prop) {
  $rank = $prop['rank'];
  if(!isset($rank_pool[$rank]) || !isset($rank_counter[$rank])){
    exit('something is wrong!');
  }
  $persons[$person]['reword'] = $rank_pool[$rank]/$rank_counter[$rank];
}


echo '<pre>';
print_r($rank_counter);
print_r($rank_pool);
print_r($persons);
echo '<pre>';

and with input as above you'll get:

$rank_counter=
Array
(
  [1] => 1// --- 1 with rank 1
  [2] => 3// --- 3 with rank 2
  [5] => 2// --- 2 with rank 5
)

$rank_pool
Array
(
  [1] => 10// --- 10 points for persons with rank 1
  [2] => 24// --- 24 points for persons with rank 2
  [5] => 11// --- 11 points for persons with rank 5
)

and you get reword points per person by simple division of total points for each rank and count of each rank

10/1 = 10 points for each person with rank 1
24/3 = 8 points for each person with rank 2
and
11/2 = 5.5 points for each person with rank 5
panos
  • 321
  • 2
  • 4
  • Yeah, I noticed the previous code wouldn't work for ties larger than 2. You hit on what I expected I was going to have to do with totalling up and then distributing award points. The award points will always be set beforehand so I'd think your route would work. I'm gonna try to implement it tonight and see if it works with most scenarios I'm throwing at it. Thanks for your help. – Al Bundy Mar 26 '13 at 21:08