MENU

PHP多维数组排序之usort

August 18, 2017 • PHP

在最近对接一个艺龙的接口的项目中需要对数据进行去重操作,保留最新的数据。第一想到的就是先对数组根据时间字段来进行升序排列。但是在这篇文章中,为了演示和实验我先使用了TotalPrice来排序,效果都是一样的。所以开始下面的代码

//艺龙返回的原始数据,已经整理了格式
$hotelList => [
    [
        "LastId" => 32714,
        "Time" => "2017-07-24T10:36:33+08:00",
        "OrderId" => 171868350,
        "TotalPrice" => 200,
    ],
    [
        "LastId" => 32731,
        "Time" => "2017-07-24T00:38:00+08:00",
        "OrderId" => 171868367,
        "TotalPrice" => 200,
    ],
    [
        "LastId" => 32733,
        "Time" => "2017-07-24T09:20:00+08:00",
        "OrderId" => 171868366,
        "TotalPrice" => 200,
    ],
    [
        "LastId" => 32749,
        "Time" => "2017-07-24T01:35:25+08:00",
        "OrderId" => 171868375,
        "TotalPrice" => 400,
    ],
    [
        "LastId" => 32757,
        "Time" => "2017-07-24T01:36:00+08:00",
        "OrderId" => 171868383,
        "TotalPrice" => 160,
    ],
    [
        "LastId" => 32769,
        "Time" => "2017-07-24T01:36:59+08:00",
        "OrderId" => 171868395,
        "TotalPrice" => 150,
    ],
    [
        "LastId" => 32778,
        "Time" => "2017-07-24T01:38:09+08:00",
        "OrderId" => 171868405,
        "TotalPrice" => 700,
    ],
];
usort($incrOrderList, function($a, $b) {
    if ($a['TotalPrice'] == $b['TotalPrice']) {
        return 0;
    }
    return ($a['TotalPrice'] < $b['TotalPrice']) ? -1 : 1; //正序
});
dd($incrOrderList)

那么输出的结果为

array:7 [▼
  0 => array:5 [▼
    "LastId" => 32769
    "Time" => "2017-07-24T01:36:59+08:00"
    "OrderId" => 171868395
    "TotalPrice" => 150
  ]
  1 => array:5 [▼
    "LastId" => 32757
    "Time" => "2017-07-24T01:36:00+08:00"
    "OrderId" => 171868383
    "TotalPrice" => 160
  ]
  2 => array:5 [▼
    "LastId" => 32714
    "Time" => "2017-07-24T10:36:33+08:00"
    "OrderId" => 171868350
    "TotalPrice" => 200
  ]
  3 => array:5 [▼
    "LastId" => 32731
    "Time" => "2017-07-24T00:38:00+08:00"
    "OrderId" => 171868367
    "TotalPrice" => 200
  ]
  4 => array:5 [▼
    "LastId" => 32733
    "Time" => "2017-07-24T09:20:00+08:00"
    "OrderId" => 171868366
    "TotalPrice" => 200
  ]
  5 => array:5 [▼
    "LastId" => 32749
    "Time" => "2017-07-24T01:35:25+08:00"
    "OrderId" => 171868375
    "TotalPrice" => 400
  ]
  6 => array:5 [▼
    "LastId" => 32778
    "Time" => "2017-07-24T01:38:09+08:00"
    "OrderId" => 171868405
    "TotalPrice" => 700
  ]
]

可以看到所有的元素都以TotalPrice字段进行正序排列。如果想要倒序的话只需要在上面的匿名函数里将-11调换位置即可。如果是php7那么下面的写法将更加的简洁。

usort($hotelList, function ($a, $b) {
    return $a['TotalPrice'] <=> $b['TotalPrice']; //太空船运算符(php7+)
});

到此已经满足了我的排序需求。


可以看出这和MySQL的ORDER BY很像。那么这个时候MySQL的ORDER BY可以同时对多个字段进行排序,如:ORDER BY TotalPrice ASC,Time DESC先对TotalPrice进行正序然后再对TotalPrice值相同的字段进行Time的倒序排列。

test> SELECT `TotalPrice`,`Time` from tmp order by `TotalPrice` asc;
+--------------+---------------------+
|   TotalPrice | Time                |
|--------------+---------------------|
|          150 | 2017-07-24 01:36:59 |
|          160 | 2017-07-24 01:36:00 |
|          200 | 2017-07-24 10:36:33 |
|          200 | 2017-07-24 00:38:00 |
|          200 | 2017-07-24 09:20:00 |
|          400 | 2017-07-24 01:35:25 |
|          700 | 2017-07-24 01:38:09 |
+--------------+---------------------+
test> SELECT `TotalPrice`,`Time` from tmp order by `TotalPrice` asc,`Time` desc;
+--------------+---------------------+
|   TotalPrice | Time                |
|--------------+---------------------|
|          150 | 2017-07-24 01:36:59 |
|          160 | 2017-07-24 01:36:00 |
|          200 | 2017-07-24 10:36:33 |
|          200 | 2017-07-24 09:20:00 |
|          200 | 2017-07-24 00:38:00 |
|          400 | 2017-07-24 01:35:25 |
|          700 | 2017-07-24 01:38:09 |
+--------------+---------------------+

那么我们通过 usort也可以实现类似的效果。直接上代码

usort($hotelList, function ($a, $b) {
    $criteria = [
        'TotalPrice' => 'asc',
        'Time'       => 'desc'  //这里还可以根据需要继续加条件 如:'x'=>'asc'等  
    ];
    foreach ($criteria as $field => $order) {
        if ($a[$field] == $b[$field]) {
            continue;
        }
        return (($order == 'desc') ? -1 : 1) * (($a[$field] < $b[$field]) ? -1 : 1);
    }
    return 0;
});

输出结果为

array:7 [▼
  0 => array:5 [▼
    "LastId" => 32769
    "Time" => "2017-07-24T01:36:59+08:00"
    "OrderId" => 171868395
    "TotalPrice" => 150
  ]
  1 => array:5 [▼
    "LastId" => 32757
    "Time" => "2017-07-24T01:36:00+08:00"
    "OrderId" => 171868383
    "TotalPrice" => 160
  ]
  2 => array:5 [▼
    "LastId" => 32714
    "Time" => "2017-07-24T10:36:33+08:00"
    "OrderId" => 171868350
    "TotalPrice" => 200
  ]
  3 => array:5 [▼
    "LastId" => 32733
    "Time" => "2017-07-24T09:20:00+08:00"
    "OrderId" => 171868366
    "TotalPrice" => 200
  ]
  4 => array:5 [▼
    "LastId" => 32731
    "Time" => "2017-07-24T00:38:00+08:00"
    "OrderId" => 171868367
    "TotalPrice" => 200
  ]
  5 => array:5 [▼
    "LastId" => 32749
    "Time" => "2017-07-24T01:35:25+08:00"
    "OrderId" => 171868375
    "TotalPrice" => 400
  ]
  6 => array:5 [▼
    "LastId" => 32778
    "Time" => "2017-07-24T01:38:09+08:00"
    "OrderId" => 171868405
    "TotalPrice" => 700
  ]
]

重点关注TotalPrice为200的元素,元素的顺序已经按照Time字段进行倒序排列了。和我们上面查询的MySQL结果是一致的。
后面的多个字段排序方法参考了CSDN博客。对于usort已经有了大概的了解,但是对于具体的排序原理还不是很清楚。如果是2个元素的排序很好理解,当时多于2个的时候就有些不理解。


2017-08-16更新
在上面对2个字段同时进行排序的时候,看了别人的代码写的有些难理解。今天在StackOverFlow看到另一种写法,很容易理解。代码如下:
usort($hotelList, function ($a, $b) {
    return
        ($a['TotalPrice'] - $b['TotalPrice']) ?: 
        (strtotime($b['Time']) - strtotime($a['Time'])); //Time倒序则$b放在前面
});

由于Time字段做减法运算会报一个不是数字类型的错误,所以我转了一下。得到的结果和上面是一样的。而这样的写法看起来更加的舒服。这个写法需要注意的一点是如果是正序则$a则放在前面,$b放在后面进行减法运算

Last Modified: November 10, 2019