Get dense rank and gapped rank for all items in array

Viewed 68

I want to calculate and store the dense rank and gapped rank for all entries in an array using PHP.

I want to do this in PHP (not MySQL because I am dealing with dynamic combinations 100,000 to 900 combinations per week, that’s why I cannot use MySQL to make that many tables.

My code to find the dense ranks is working, but the gapped ranks are not correct.

PHP code

$members = [
    ['num' => 2, 'rank' => 0, 'dense_rank' => 0],
    ['num' => 2, 'rank' => 0, 'dense_rank' => 0],
    ['num' => 3, 'rank' => 0, 'dense_rank' => 0],
    ['num' => 3, 'rank' => 0, 'dense_rank' => 0],
    ['num' => 3, 'rank' => 0, 'dense_rank' => 0],
    ['num' => 3, 'rank' => 0, 'dense_rank' => 0],
    ['num' => 3, 'rank' => 0, 'dense_rank' => 0],
    ['num' => 5, 'rank' => 0, 'dense_rank' => 0],
    ['num' => 9, 'rank' => 0, 'dense_rank' => 0],
    ['num' => 9, 'rank' => 0, 'dense_rank' => 0],
    ['num' => 9, 'rank' => 0, 'dense_rank' => 0]    
];

$rank=0;
$previous_rank=0;
$dense_rank=0;
$previous_dense_rank=0;
foreach($members as &$var){
    //star of rank
    if($var['num']==$previous_rank){
        $var['rank']=$rank;
    }else{
        $var['rank']=++$rank;
        $previous_rank=$var['num'];   
    }//end of rank
    
    //star of rank_dense
    if($var['num']===$previous_dense_rank){
        $var['dense_rank']=$dense_rank;
        ++$dense_rank;
    }else{
        $var['dense_rank']=++$dense_rank;
        $previous_dense_rank=$var['num'];
    }   
    //end of rank_dense
    
    echo $var['num'].' - '.$var['rank'].' - '.$var['dense_rank'].'<br>';
}
?>

My flawed output is:

num rank dynamic rank
2 1 1
2 1 1
3 2 3
3 2 3
3 2 4
3 2 5
3 2 6
5 3 8
9 4 9
9 4 9
9 4 10

Notice when the error happens and there is a higher number in the number column it corrects the error in that row. See that when the number goes from 3 to 5.

2 Answers

It seems like you want the dynamic rank to be sequential?

Your sample data appears to be sorted, if this remains true for your real data then you can remove the conditional and just increment the variable as you assign it:

//start of rank_dense
$var['dense_rank']=++$dense_rank;
//end of rank_dense

It sounds like you're saying you won't be implementing a database. Databases like MySQL can easily handle the workload numbers you outlined and they can sort your data as well. You may want to reconsider.

Given that your results are already sorted in an ascending fashion...

  • For dense ranking, you need to only increment your counter when a new score is encountered.

  • For gapped ranking, you need to unconditionally increment your counter and use the counter value for all members with the same score.

??= is the "null coalescing assignment" operator (a breed of "combined operator"). It only allows the right side operand to be executed/used if the left side operand is not declared or is null. This is a technique of performing conditional assignments without needing to write a classic if condition.

Code: (Demo)

$denseRank = 0;
$gappedRank = 0;
foreach ($members as &$row) {
    $denseRanks[$row['num']] ??= ++$denseRank;
    $row['dense_rank'] = $denseRanks[$row['num']];
    
    ++$gappedRank;
    $gappedRanks[$row['num']] ??= $gappedRank;
    $row['rank'] = $gappedRanks[$row['num']];
    
    // for better presentation:
    echo json_encode($row) . "\n";
}

Output:

{"num":2,"rank":1,"dense_rank":1}
{"num":2,"rank":1,"dense_rank":1}
{"num":3,"rank":3,"dense_rank":2}
{"num":3,"rank":3,"dense_rank":2}
{"num":3,"rank":3,"dense_rank":2}
{"num":3,"rank":3,"dense_rank":2}
{"num":3,"rank":3,"dense_rank":2}
{"num":5,"rank":8,"dense_rank":3}
{"num":9,"rank":9,"dense_rank":4}
{"num":9,"rank":9,"dense_rank":4}
{"num":9,"rank":9,"dense_rank":4}

For the record, if you are dealing with huge volumes of data, I would be using SQL instead of PHP for this task.

Related