PHP中的数组函数学习(五)

总算来到了数组相关函数学习的最后一篇文章,今天的重点在于数组排序相关函数的使用。对于数组的操作来说,排序可以说是非常常用的一种能力,所以,今天的内容也是非常重点的内容之一,大家学习完了不要忘了自己写写代码测试测试哦。

根据范围创建数组

首先我们来看一个根据指定范围创建数组的函数。

print_r(range(-2,10));
// Array
// (
//     [0] => -2
//     [1] => -1
//     [2] => 0
//     [3] => 1
//     [4] => 2
//     [5] => 3
//     [6] => 4
//     [7] => 5
//     [8] => 6
//     [9] => 7
//     [10] => 8
//     [11] => 9
//     [12] => 10
// )
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

这个函数很好理解吧,指定一个最小数再指定一个最大数,就可以生成在这两个数中间范围内的全部数字的数组。不仅是对于数字,对于字符来说,它也可以实现英文字符的范围生成。

print_r(range('A', 'Z'));
// Array
// (
//     [0] => A
//     [1] => B
//     [2] => C
//     [3] => D
//     [4] => E
//     [5] => F
//     [6] => G
//     [7] => H
//     [8] => I
//     [9] => J
//     [10] => K
//     [11] => L
//     [12] => M
//     [13] => N
//     [14] => O
//     [15] => P
//     [16] => Q
//     [17] => R
//     [18] => S
//     [19] => T
//     [20] => U
//     [21] => V
//     [22] => W
//     [23] => X
//     [24] => Y
//     [25] => Z
// )
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.

这个函数还有第三个参数,指定生成数据的步长,比如我们指定为 2 的话,那么就是间隔一个生成一个。

print_r(range('A', 'Z', 2));
// Array
// (
//     [0] => A
//     [1] => C
//     [2] => E
//     [3] => G
//     [4] => I
//     [5] => K
//     [6] => M
//     [7] => O
//     [8] => Q
//     [9] => S
//     [10] => U
//     [11] => W
//     [12] => Y
// )
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

在实际的应用中,比如要生成偶数,或者生成指定间隔序列的数组数据来说,这个 range() 函数都非常有用。而对于字符的范围生成,也可以应用在我们需要生成 Excel 的时候,Excel 的列都是大写字母的列号。相信做过这方面开发的同学一定会接触过这个函数在生成 Excel 时的应用场景。

随机排列数组

在正式的排序函数学习之前,我们先看一个随机排序的函数。

$arr = range('A', 'Z', 2);
shuffle($arr);
print_r($arr);
// Array
// (
//     [0] => S
//     [1] => W
//     [2] => I
//     [3] => E
//     [4] => M
//     [5] => U
//     [6] => G
//     [7] => C
//     [8] => O
//     [9] => K
//     [10] => A
//     [11] => Q
//     [12] => Y
// )
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

上面的测试中,正常情况下应该从 A 开始到 Y 的数组,使用 shuffle() 之后,数组的顺序就被随机打乱了。当然,这个函数除了随机打乱数组顺序之外,还有一个重要的特点就是它能够重建索引。

$arr = ['A'=>1, 'B'=>2, 'C'=>3];
shuffle($arr);
print_r($arr);
// Array
// (
//     [0] => 1
//     [1] => 3
//     [2] => 2
// )
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

从这个测试代码中,我们可以看到返回的数据的索引变成了数字下标的,在某些场景下这个能力很有用,但在一些场景中,特别是这种使用 Hash 键值形式的数组,如果需要随机展示数据又想保留键的话,就需要特别注意了。

数组排序

排序能力在数组的应用中绝对是大家经常会接触到的功能之一。具体的应用场景也不用多说了,很多情况下都会使用到,不过这些函数很容易记混,今天我们就来好好地捋一捋每个函数的作用。

普通排序
$arr = range(1, 10);
shuffle($arr);

sort($arr);
print_r($arr);
// Array
// (
//     [0] => 1
//     [1] => 2
//     [2] => 3
//     [3] => 4
//     [4] => 5
//     [5] => 6
//     [6] => 7
//     [7] => 8
//     [8] => 9
//     [9] => 10
// )
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

首先,我们定义一个从 1 到 10 的数组,然后使用 shuffle() 打乱顺序,再使用 sort() 进行排序。可以看到,sort() 函数是进行正序排序的一个数组函数。它针对的是数组的值。

shuffle($arr);

rsort($arr);
print_r($arr);
// Array
// (
//     [0] => 10
//     [1] => 9
//     [2] => 8
//     [3] => 7
//     [4] => 6
//     [5] => 5
//     [6] => 4
//     [7] => 3
//     [8] => 2
//     [9] => 1
// )
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

再次使用 shuffle() 打乱数组顺序之后,这回我们使用的是 rsort() 函数。这个函数返回的就是倒序排序的数组。这里可以看到,sort() 和 rsort() 是一对相反的排序函数。

使用 sort() 和 rsort() 排序之后的数组,键都会重建,如果是 Hash 数组的话,那么就和 shuffle() 函数一样会出现键丢失的情况。

$fruits = ["d" => "lemon", "a" => "orange", "b" => "banana", "c" => "apple"];
sort($fruits);
print_r($fruits);
// Array
// (
//     [0] => apple
//     [1] => banana
//     [2] => lemon
//     [3] => orange
// )
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

这时,我们可以使用另外一个保留原键名的排序函数。

$fruits = ["d" => "lemon", "a" => "orange", "b" => "banana", "c" => "apple"];
asort($fruits);
print_r($fruits);
// Array
// (
//     [c] => apple
//     [b] => banana
//     [d] => lemon
//     [a] => orange
// )

$fruits = ["d" => "lemon", "a" => "orange", "b" => "banana", "c" => "apple"];
arsort($fruits);
print_r($fruits);
// Array
// (
//     [a] => orange
//     [d] => lemon
//     [b] => banana
//     [c] => apple
// )
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

asort() 和 arsrot() 就是用于保留原键名并进行排序的函数。可以看到,它们的键不会发生变化,但数组元素的位置产生了变化。其实,在数组实现的底层,Hash 数组的 key 最后也会被 Hash 成一个数字下标,这样就可以保证它们在数组内容中保存的数据是有序的,也可以进行正常的排序实现。

根据键排序

上面两个函数都是根据数组的值进行排序的,同样地,针对键来说,我们也有函数可以进行排序。

$keys = range(0, 9);
shuffle($keys);
$arr = array_combine($keys, range(1, 10));
print_r($arr);
// Array
// (
//     [6] => 1
//     [3] => 2
//     [1] => 3
//     [2] => 4
//     [9] => 5
//     [5] => 6
//     [8] => 7
//     [0] => 8
//     [4] => 9
//     [7] => 10
// )

ksort($arr);
print_r($arr);
// Array
// (
//     [0] => 8
//     [1] => 3
//     [2] => 4
//     [3] => 2
//     [4] => 9
//     [5] => 6
//     [6] => 1
//     [7] => 10
//     [8] => 7
//     [9] => 5
// )

$arr = array_combine($keys, range(1, 10));
print_r($arr);
// Array
// (
//     [6] => 1
//     [3] => 2
//     [1] => 3
//     [2] => 4
//     [9] => 5
//     [5] => 6
//     [8] => 7
//     [0] => 8
//     [4] => 9
//     [7] => 10
// )

krsort($arr);
print_r($arr);
// Array
// (
//     [9] => 5
//     [8] => 7
//     [7] => 10
//     [6] => 1
//     [5] => 6
//     [4] => 9
//     [3] => 2
//     [2] => 4
//     [1] => 3
//     [0] => 8
// )
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.

在上面的测试代码中,我们使用 array_combie() 生成指定键值的数组,然后使用 ksrot() 和 krsort() 进行针对键的排序。结果很清晰,这里也就不多解释了。需要注意的是,这两个键排序的函数也是保留键值的,不会重建索引。

自然排序

还记得我们在 学习PHP中的字符串操作函数(一) https://mp.weixin.qq.com/s/hihnkTQ74ZTgRBnUYKX-CA 中讲过的 strnatcmp() 函数吗?在数组操作中也有类似的函数,用于实现自然语言的排序。下面我们先看看普通排序在一些场景下会出现什么问题。

$arr = ['img11', 'img21', 'img1', 'img2', 'img3', 'img4'];
sort($arr);
print_r($arr);
// Array
// (
//     [0] => img1
//     [1] => img11
//     [2] => img2
//     [3] => img21
//     [4] => img3
//     [5] => img4
// )
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

如果是普通的排序函数,排序之后的结果 img11 会排在 img2 之前,img21 也会排在 img3 之前。就像之前在字符串函数中讲过的,正常情况下,在我们自然语言的认知中,img11 后面的数字应该表示的是 11 而不是两个 1 。但对于计算机来说,它们之前的字符比较是一个一个字符地进行比较的,所以 img11 在比较到第 1 个 1 的时候,就会认为它比后面的 img2 要大了。这时候,就需要我们自然语言排序函数的登场了。

$arr = ['img11', 'img21', 'img1', 'img2', 'img3', 'img4'];
natsort($arr);
print_r($arr);
// Array
// (
//     [2] => img1
//     [3] => img2
//     [4] => img3
//     [5] => img4
//     [0] => img11
//     [1] => img21
// )
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

这个函数排序出来的结果就比较符合我们的认知了吧。它还有一个扩展的函数是忽略大小写的,因为不同的大小写字符在 asc2 码表中的顺序也是不同的,但有的时候在数组中的数据可能是需要忽略掉这个大小写的差异的。

$arr = ['img11', 'Img21', 'Img1', 'Img2', 'img3', 'img4'];
natsort($arr);
print_r($arr);
// Array
// (
//     [2] => Img1
//     [3] => Img2
//     [1] => Img21
//     [4] => img3
//     [5] => img4
//     [0] => img11
// )

$arr = ['img11', 'Img21', 'Img1', 'Img2', 'img3', 'img4'];
natcasesort($arr);
print_r($arr);
// Array
// (
//     [2] => Img1
//     [3] => Img2
//     [4] => img3
//     [5] => img4
//     [0] => img11
//     [1] => Img21
// )
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.

natsort() 函数会把大写开头的 Img 都放到前面,因为大写字符在 asc2 中更靠前,而使用 natcasesort() 则会忽略这个问题,直接将所有的字符一视同仁。

自定义排序

自定义排序顾名思义,就是通过我们自定义的回调函数来确定数组的排序情况。

$arr = range(1, 10);
shuffle($arr);
usort($arr, function($a, $b){
    return $a < $b;
}); 
print_r($arr);
// Array
// (
//     [0] => 10
//     [1] => 9
//     [2] => 8
//     [3] => 7
//     [4] => 6
//     [5] => 5
//     [6] => 4
//     [7] => 3
//     [8] => 2
//     [9] => 1
// )
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

usort() 从名称猜测,就可以感觉到这个 u 应该表示的是 user 的意思。也就是用户来定义的排序。如果我们在回调函数中返回的是小于号,那么就是倒序排序,如果返回的是大于号,那么就是正序排序。

shuffle($arr);
usort($arr, function($a, $b){
    return $a > $b;
}); 
print_r($arr);
// Array
// (
//     [0] => 1
//     [1] => 2
//     [2] => 3
//     [3] => 4
//     [4] => 5
//     [5] => 6
//     [6] => 7
//     [7] => 8
//     [8] => 9
//     [9] => 10
// )
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

如果使用飞船操作符的话,那么它就是默认的和使用大于号一样的正序排序。我们将在下面的自定义键排序的函数中看到。

$arr = array_combine($keys, range(1, 10));
uksort($arr, function($a, $b){
    return $a <=> $b;
});
print_r($arr);
// Array
// (
//     [0] => 7
//     [1] => 4
//     [2] => 1
//     [3] => 10
//     [4] => 8
//     [5] => 2
//     [6] => 9
//     [7] => 3
//     [8] => 6
//     [9] => 5
// )
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

另外,还有一个和 asort() 一样的保留键值的自定义排序函数。

$fruits = ["d" => "lemon", "a" => "orange", "b" => "banana", "c" => "apple"];
uasort($fruits, function($a, $b){
    return $a <=> $b;
});
print_r($fruits);
// Array
// (
//     [c] => apple
//     [b] => banana
//     [d] => lemon
//     [a] => orange
// )
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
多个与多维数组排序

多维数组排序也是一个非常常用的函数。相信不少同学都使用过,有的时候数据库中的数据排序代价很高,或者说一组数据需要多种情况使用不同字段排序,我们在减轻数据库查询压力的情况下就可以使用这个多维数组排序的能力来实现在 PHP 代码中对数据进行排序。

arr = [
    [
        'id'=>5,
        'name'=>'zhang'
    ],
    [
        'id'=>4,
        'name'=>'li'
    ],
    [
        'id'=>2,
        'name'=>'yang'
    ],
    [
        'id'=>6,
        'name'=>'liu'
    ],
    [
        'id'=>1,
        'name'=>'bai'
    ],
];

$ids = array_column($arr, 'id');

array_multisort($ids, SORT_DESC, $arr);
print_r($arr);
// Array
// (
//     [0] => Array
//         (
//             [id] => 6
//             [name] => liu
//         )

//     [1] => Array
//         (
//             [id] => 5
//             [name] => zhang
//         )

//     [2] => Array
//         (
//             [id] => 4
//             [name] => li
//         )

//     [3] => Array
//         (
//             [id] => 2
//             [name] => yang
//         )

//     [4] => Array
//         (
//             [id] => 1
//             [name] => bai
//         )

// )
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.

相信只要是做过类似功能的同学都不会对这段代码感觉到陌生。不过这里的学习还是有很多同学并不清楚这个函数执行的意义。其实,它可以有多个数组,前面的数组的顺序会影响后面的数组顺序。利用这个特性,我们先使用 array_column() 取出原始数据中所有的 id 字段的内容保存在一个数组变量中。然后在排序的时候将这个 ids 数组放在前面,并使用 SORT_DESC 进行倒排,这个排序的结果就会影响到后面那个原始数组 arr 的顺序,从而实现多维数组的排序。

这么说会有点晕,再来一个简单地例子看下这个函数是如何影响多个数组的。

$arr1 = [10, 100, 100, 0];
$arr2 = [1, 3, 2, 4];
array_multisort($arr1, $arr2);

print_r($arr1);
// Array
// (
//     [0] => 0
//     [1] => 10
//     [2] => 100
//     [3] => 100
// )

print_r($arr2);
// Array
// (
//     [0] => 4
//     [1] => 1
//     [2] => 2
//     [3] => 3
// )
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

从这个例子中看到端倪了吗?arr1 中的数据正序排序了,我们没有指定任何常量配置值的话就是正序排序的。arr2 会受到 arr1 的影响,两个数组的原始关系可以看到 arr1 的 10 对应的是 arr2 的 1,两个 100 分别对应 2 和 3 , 最后一个 0 对应的是 arr2 的 4 。arr1 正向排序成功后,结果变成 0 、10 、100 、100 ,arr2 的数组根据 arr1 的排序结果变成了 4(0)、1(10) 、2(100) 、3(100) 。这下看明白这个函数的作用了吧?如果还是明白的话,自己查查资料再深入地研究一下哦!

总结

今天的内容还是比较有意思的吧。前面那两个函数不多说了,主要还是总结一下排序相关的函数。

  • sort() 和 rsort() 是一对根据值排序的函数,会重建索引
  • asrot() 和 arsort() 也是根据值排序的函数,但不会重建索引
  • ksort() 和 krsort() 是根据键进行排序的
  • 上面函数带 r 的都是倒序的,usort()、uasort()、uksort() 三个分别是它们对应的自定义排序函数
  • natsort()、natcasesort() 是根据自然语言排序的函数,natcasesort() 忽略大小写
  • array_multisort() 可以根据多个数组进行排序,可以实现多维数组排序,前面的数组排序结果会影响后面的数组顺序

至此,数组相关的函数学习就结束了,其实还有一些函数我们没有学习,主要就是游标操作数组相关的函数。这些函数的使用场景非常少,大家有兴趣的就自己研究一下吧。一个阶段的结束又是一个新的阶段的开始,做好准备迎接新的挑战吧!

测试代码:

 https://github.com/zhangyue0503/dev-blog/blob/master/php/2021/04/source/4.PHP%E4%B8%AD%E7%9A%84%E6%95%B0%E7%BB%84%E5%87%BD%E6%95%B0%E5%AD%A6%E4%B9%A0%EF%BC%88%E4%BA%94%EF%BC%89.php

参考文档:

 https://www.php.net/manual/zh/ref.array.php