一、问题描述
先抛出问题,有一个二维数组,有多个维度的字段,需要计算出各个维度单独的排名, 降序排列?
数据简化后如下:
// 示例数据生成
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、分别记录每个维度的排名
/**
* 排序工具
*
* @author: salmonl
* @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’)’
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传递参数即可
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提示,但是数据依然会返回,只是没有排序。