Search code examples
phpmysqlarraysrankingranking-functions

Rank array values with potential duplicate values and skipping some positions if there is a tie


I am working with database data that manipulates college students exam results. Basically, I am pulling the records from a MySQL database and pulling one class at any given time. I want to rank the students with the highest performer given the rank of 1.
Here is an illustration;

Marks: 37, 92, 84, 83, 84, 65, 41, 38, 38, 84.  

I want to capture MySQL data as a single array. Once I have the data in an array, I should then assign each student a position in the class such as 1/10 (number 1, the 92 score), 4/10 etc. Now the problem is that if there is a tie, then the next score skips a position and if there are 3 scores at one position then the next score skips 2 positions. So the scores above would be ranked as follows;

92 - 1
84 - 2,
84 - 2,
84 - 2,
83 - 5,
65 - 6,
41 - 7,
38 - 8,
38 - 8 ,
37 - 10

The grading system requires that the number of positions (ranks, if you will) will be maintained, so we ended up with 10 positions in this class since positions 3, 4, 5 and 9 did not have any occupants. (The alternative of filling every number will have given us only 8 positions!)

Is it possible (humanly/programmatically possible) to use PHP to rank the scores above in such a way that it can handle possible ties such as 4 scores at one position? Sadly, I could not come up with a function to do this. I need a PHP function (or something in PHP) that will take an array and produce a ranking as above.

If it's possible to do this with MySQL query data without having it in an array, then that will also be helpful!


Solution

  • I assume the grades are already sorted by the database, otherwise use sort($grades);.

    Code:

    $grades = array(92, 84, 84, 84, 83, 65, 41, 38, 38, 37);
    $occurrences = array_count_values($grades);
    $grades = array_unique($grades);
    foreach($grades as $grade) {
        echo str_repeat($grade .' - '.($i+1).'<br>',$occurrences[$grade]);
        $i += $occurrences[$grade];
    }
    

    Result:

    92 - 1
    84 - 2
    84 - 2
    84 - 2
    83 - 5
    65 - 6
    41 - 7
    38 - 8
    38 - 8
    37 - 10
    

    EDIT (Response to discussion below)

    Apparently, in case the tie occurs at the lowest score,
    the rank of all lowest scores should be equal to the total count of scores.

    Code:

    $grades = array(92, 84, 84, 84, 83, 65, 41, 38, 37, 37);
    $occurrences = array_count_values($grades);
    $grades = array_unique($grades);
    foreach($grades as $grade) {
        if($grade == end($grades))$i += $occurrences[$grade]-1;
        echo str_repeat($grade .' - '.($i+1).'<br>',$occurrences[$grade]);
        $i += $occurrences[$grade];
    }
    

    Result:

    92 - 1
    84 - 2
    84 - 2
    84 - 2
    83 - 5
    65 - 6
    41 - 7
    38 - 8
    37 - 10
    37 - 10