使用usort排序的通用方法

一、问题描述
先抛出问题,有一个二维数组,有多个维度的字段,需要计算出各个维度单独的排名, 降序排列?
数据简化后如下:

      // 示例数据生成
      for ($i = 0; $i < 100; $i++) {
            $data = [
                'id' => $i,
                'read_count' => mt_rand(100000, 999999),
                'interactive_count' => mt_rand(10000, 99999),
                'search_count' => mt_rand(1000, 9999),
                'video_count' => mt_rand(100, 999),
                'checkin_count' => mt_rand(100, 999),
                'watch_count' => mt_rand(100, 999),
                'score' => mt_rand(60, 90),
                'play_status' => mt_rand(1, 2),
            ];

            $datas[] = $data;
       }

      // 演示例数据
      function getDemoData()
      {
           $datas = [
	        ['id' => 1,'score' => 10, 'read_count' => 1000, 'search_count' => 90],
	        ['id' => 2,'score' => 80, 'read_count' => 300, 'search_count' => 490],
	        ['id' => 3,'score' => 10, 'read_count' => 500, 'search_count' => 590],
           ];
           return $datas;
      }

二、直接的解发
简单粗暴的办法,你必然能想到,如下:
算法:
0、定义一个排序类,每个维度定义一个排序方法
1、对每个维度分别排一次
2、分别记录每个维度的排名

<?php
/**
 * 排序工具
 *
 * @author:   salmonl <salmonl@niliu.me> 
 * @date:     2019-04-26
 */
class Tool
{
    /**
     * 各个维度排序方法
     * @param $data      array  数据
     * @param $dimension string 维度
     * 
     * 
     * @return array
     */
    public static function customSort(&$data, $dimension) {
        switch($dimension) {
            // 实际有6个case, 这里只列出2个
            case 'read':
                // 第一种方式, 把比较函数放在类外面,因为usort中的比较函数是在全局查找,框架中的类是自动加载,故而可以找到
                usort($data, 'read');
                break;
            case 'search':
                // 第二种方式,把比较函数放在类中,需要传入数组[类名,方法名],
                // 数组中第一个元素是类名,写Tool和self都有效, 如果customSort不用静态方法,还可以用$this;
                // 这里有一个思考,比较方法search不是静态方法,为啥可以直接使用类名
                // usort($data, [self, 'search']);
                usort($data, ['Tool', 'search']);
                break;
        }
    }

    public function search($a, $b) {
        if ($a['search_count'] == $b['search_count']) {
            return 0;
        }
        return ($a['search_count'] < $b['search_count']) ? 1 : -1;
    }
}

// 在类外,属全局函数
function read($a, $b) {
    if ($a['read_count'] == $b['read_count']) {
        return 0;
    }
    return ($a['read_count'] < $b['read_count']) ? 1 : -1;
}

$datas = getDemoData();

// read rank
Tool::customSort($datas, 'read');
print_r($datas);

Tool::customSort($datas, 'search');
print_r($datas);

$tool = new Tool();
$tool->customSort($datas, 'search');
print_r($datas);

三、通用方法
当你把6个方法都写了,你就会发现很长,你可能会想,能不能写一个通用的方法呢?
参考手册,写了如下测试脚本,满足要求

function build_sorter($key) {
    return function ($a, $b) use ($key) {
        if ($a[$key] == $b[$key]) {
            return 0;
        }
	    return ($a[$key] < $b[$key]) ? 1 : -1;
    };
}

$datas = getDemoData();

$res = usort($datas, build_sorter('read_count'));
print_r($datas);

但是放到类中发现,无法传递参数,
提示:Warning: usort() expects parameter 2 to be a valid callback, class ‘Tool’ does not have a method ‘build_sorter(‘read’)’

<?php
class Tool
{
    public static function customSort(&$data, $dimension) {
        usort($data, ['Tool', "build_sorter('$dimension')"]);
    }

    public static function build_sorter($key) {
        return function ($a, $b) use ($key) {
            if ($a[$key] == $b[$key]) {
                    return 0;
            }
            return ($a[$key] < $b[$key]) ? 1 : -1;
        };
    }
}

$datas = getDemoData();
// read rank
Tool::customSort($datas, 'read');
print_r($datas);

通过探索,usort的比较函数直接使用匿名函数的use传递参数即可

<?php
class Tool
{
    public static function customSort(&$datas, $key) {
        usort($datas, function ($a, $b) use ($key) {
            if ($a[$key] == $b[$key]) {
                    return 0;
            }
            return ($a[$key] < $b[$key]) ? 1 : -1;
        });
    }
}

$datas = getDemoData();
// read rank
Tool::customSort($datas, 'read');
print_r($datas);

另外还可以通过类属性传递,参考这里

四、总结
usort自定义排序常用用法汇总如下:

$datas = getDemoData();

// 不使用匿名函数
function score($a, $b) {
    if ($a['score'] == $b['score']) {
        return 0;
    }
    // 按照score降序,返回1表示需要交换
    return ($a['score'] < $b['score']) ? 1 : -1;
}
$res = usort($datas, 'score');
print_r($datas);

// 使用匿名函数
usort($datas, function ($a, $b) {
    if ($a['score'] == $b['score']) {
            return 0;
    }
    return ($a['score'] < $b['score']) ? 1 : -1;
});
print_r($datas);


// 使用宇宙飞船表达式(PHP7+)
usort($datas, function ($a, $b) use ($order) {
    // 左右相同返回0, 左 > 右 返回1, 否则返回-1。
    if ('desc' == $order) {
         return -($a['score'] <=> $b['score']);
    } elseif ('asc' == $order) {
         return ($a['score'] <=> $b['score']);
    }
});

echo 3<=>3; // 0
echo 3<=>2; // 1
echo 2<=>3; // -1

print_r($datas);

// 使用类中的方法
class Tool
{
    public function cmpCommon($a, $b) {
        return -($a['score'] <=> $b['score']);
    }

    public static function cmpStatic($a, $b) {
        return -($a['score'] <=> $b['score']);
    }
}

// 使用类名必须调用静态方法, 调用非静态方法报错
// Warning: usort() expects parameter 2 to be a valid callback, non-static method Tool::cmpCommon() should not be called statically
usort($datas, ['Tool', 'cmpStatic']);
print_r($datas);

// 使用对象,静态方法和非静态方法都可以调用。因为静态方法通过类和对象都可以访问
$obj = new Tool();
usort($datas, [$obj, 'cmpCommon']);
usort($datas, [$obj, 'cmpStatic']);
print_r($datas);

注意:usort()中的比较函数,如果不存在或者调用不到,PHP会有Warning提示,但是数据依然会返回,只是没有排序。

参考:Pass extra parameters to usort callback

发表评论

电子邮件地址不会被公开。 必填项已用*标注