对具有潜在重复值的数组值进行排序,如果出现平局,则跳过某些位置


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

我正在处理操纵大学生考试成绩的数据库数据。基本上,我从MySQL数据库中提取记录,并在任何给定的时间提取一个类。我想给表现最好的学生排个1
这是一幅插图;

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

我想将MySQL数据捕获为单个数组。一旦我把数据放在一个数组中,我就应该给每个学生分配一个班级中的位置,比如1/10(数字1,92分)、4/10等。现在的问题是,如果有平局,下一个分数跳过一个位置,如果一个位置有3分,那么下一个得分跳过2个位置。因此,以上分数将按如下方式排列;

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

分级系统要求保持职位数量(如果你愿意的话,排名),所以我们最终在这个级别上有10个职位,因为第3、4、5和9个职位没有任何人员。(填写每个数字的替代方案只会给我们提供8个职位!)

是否有可能(从人的角度/程序的角度)使用PHP对上面的分数进行排名,这样它就可以处理可能的平局,比如在一个位置上有4分?遗憾的是,我无法想出一个函数来做这件事。我需要一个PHP函数(或者PHP中的一些东西),它将接受一个数组并产生如上所述的排名。

如果可以在不将MySQL查询数据放在数组中的情况下对其进行处理,那么这也会很有帮助!

我假设成绩已经由数据库排序,否则使用sort($grades);

代码:

$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];
}

结果:

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

编辑(对以下讨论的回应)

显然,如果平局出现在最低分数,
所有最低分数的排名应等于分数的总数

代码:

$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];
}

结果:

92 - 1
84 - 2
84 - 2
84 - 2
83 - 5
65 - 6
41 - 7
38 - 8
37 - 10
37 - 10
$scores = array(92, 84, 84, 84, 83, 65, 41, 38, 38, 37);
$ranks = array(1);
for ($i = 1; $i < count($scores); $i++)
{
    if ($scores[$i] != $scores[$i-1])
        $ranks[$i] = $i + 1;
    else
        $ranks[$i] = $ranks[$i-1];
}
print_r($ranks);

我需要最终得到一个要排序的值映射。这种方法可能对原始问题也更有效。

public static function getGrades($grades)
{
    $occurrences = array_count_values($grades);
    krsort($occurrences);
    $position = 1;
    foreach ($occurrences as $score => $count) {
        $occurrences[$score] = $position;
        $position += $count;
    }
    return $occurrences;
}

如果你在$次打印,你会得到

Array
(
    [92] => 1
    [84] => 2
    [83] => 5
    [65] => 6
    [41] => 7
    [38] => 8
    [37] => 10
)

基于原始答案,谢谢!

使用array_count_values()foreach()在输入数组上执行两个循环,但此任务可以在一个循环中完成(最小化/优化时间复杂性)。

代码:(演示)

// assumed already rsort()ed.
$scores = [92, 84, 84, 84, 83, 65, 41, 38, 38, 37];
$gappedRank = 0;
$result = [];
foreach ($scores as $score) {
    ++$gappedRank;
    $gappedRanks[$score] ??= $gappedRank;
    $result[] = [$score => $gappedRanks[$score]];
}
var_export($result);

对于分数及其排名的平面关联查找数组,无条件地递增计数器,并且只有当关键字是新的时才将新元素推入查找数组。(演示)

$gappedRank = 0;
$lookup = [];
foreach ($scores as $score) {
    ++$gappedRank;
    $lookup[$score] ??= $gappedRank;
}
var_export($lookup);

第一个片段提供了";差距排名";。我有另一个答案,它实现了类似的方法,但使用了不同的输入数据结构,目的是在循环时修改行数据。

  • 获取数组中所有项目的密集等级和间隔等级

在排名领域,还有";密集排序";。查看我的时间复杂性优化答案:

  • 用密集秩数填充多维数组的秩列
  • 将顺序列添加到数组中,以指示从最老到最年轻的排名