一、问题描述
先抛出问题,有一个二维数组,有多个维度的字段,需要计算出各个维度单独的排名, 降序排列?
数据简化后如下:
// 示例数据生成 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提示,但是数据依然会返回,只是没有排序。