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

继续我们关于 PHP 中数组函数的学习之旅。之前就说过,数组相关的操作函数非常多,功能也非常的丰富,所以大家不要着急,还是跟着我一步一步地学习了解。毕竟有些函数确实是不太常用,但在许多业务场景下,却又会无比的方便。在这其中,最主要的是你在这个业务场景下,会不会想到系统已经默认提供的这些函数。所以,不要求全部掌握,但心里有个印象就可以了。

数组搜索

首先是数组搜索的功能,其实就是在数据中搜索我们需要查找的值。

$array = [0 => 'blue', 1 => 'red', 2 => 'green', 3 => 'red'];

echo array_search('green', $array), PHP_EOL; // 2;
echo array_search('red', $array), PHP_EOL;   // 1;

array_search() 函数在搜索成功之后返回的是这个值所在的 key ,也就是说,如果在数字下标的情况下查找到的数据是第一个元素,那么它返回的就是 0 。这里就和 strpos() 一样也是需要使用 全等 符号来进行判断。只有查找不到的情况下才会返回 false 。可以将它看成是数组版的 strpos() 函数。

这个函数还有第三个参数,用于确定当前的判断是否遵循严格模式。什么是严格模式呢?也就是要搜索的值的内容、格式、类型必须是完全一样的。

$array = [0 => 'blue', 1 => 'red', 2 => 'green', 3 => 'red', 4=>"7"];

echo array_search(7, $array), PHP_EOL; // 4
var_dump(array_search(7, $array, true)); // bool(false)

在这段测试代码中,我们搜索的 7 在函数中给的是一个数字,而在数组中是一个字符串。如果将第三个参数设置为 true 的话,那么这个 7 就是无法被查询到的,因为它们的类型不同。

说到查找数组中是否存在某个值,还有一个函数大家肯定不会陌生,那就是 in_array() 函数。

var_dump(in_array(7, $array)); // bool(true)
var_dump(in_array(7, $array, true)); // bool(false)

可以看到,这个函数只是返回布尔值,也就是说,它只会确定给定的值在数组中是否存在,并不返回该值的下标。同时,in_array() 函数也是支持第三个严格参数的。

$array = [['a', 'b'], ['c', 'd'], 'e'];

var_dump(array_search(['c', 'd'], $array)); // int(1)
var_dump(array_search(['d', 'c'], $array)); // bool(false)

var_dump(in_array(['c', 'd'], $array)); // bool(true)
var_dump(in_array(['d', 'c'], $array)); // bool(false)

不管是 array_search() 还是 in_array() ,都是支持元素为数组或者对象内容的数据查找的。

数组反转

这里说的数组反转可不是之前讲过的反转键和值,而是真的将数组元素的顺序进行反转。

$input  = ["php", "a" => 6, 4.0, ["green", "red"]];
$reversed = array_reverse($input);
$preserved = array_reverse($input, true);

print_r($input);
print_r($reversed);
print_r($preserved);
// Array
// (
//     [0] => php
//     [a] => 6
//     [1] => 4
//     [2] => Array
//         (
//             [0] => green
//             [1] => red
//         )

// )
// Array
// (
//     [0] => Array
//         (
//             [0] => green
//             [1] => red
//         )

//     [1] => 4
//     [a] => 6
//     [2] => php
// )
// Array
// (
//     [2] => Array
//         (
//             [0] => green
//             [1] => red
//         )

//     [1] => 4
//     [a] => 6
//     [0] => php
// )

从上面的测试代码可以看出,数组中的元素位置都发生了反转,原来在第一位的 php 这个元素现在到了最后一个。注意它们的下标。array_reverse() 的第二个参数用于指定是否保留原来的数组中的下标。但如果原来数组中是有 a => 6 这样的 hash 键值对的话,它的下标不会变化,但它的位置会移动。在没有将这个参数设置为 true 的情况下,也就是默认不保留原始下标的情况下,除了 a => 6 之外的数据的下标都被重排了。

这个函数还可以做一个很取巧的事,那就是一些面试题中的 不使用原始的字符串函数来反转字符串 。

$str = 'abc';
echo implode("",array_reverse(str_split($str, 1))), PHP_EOL; // cba

当然,正式情况下肯定还是 strrev() 来得方便直接,不过真的不排除有的面试官就会出这种诡异的题目。

数组替换

数组的替换操作其实和字符串的替换操作也是比较类似的,不过它都是以下标为配置规则的,所以需要特别注意键的信息。

$base = ["orange", "banana", "apple", "raspberry"];
$replacements = [0 => "pineapple", 4 => "cherry"];
$replacements2 = [0 => "grape", 6 => "watermelon"];

$basket = array_replace($base, $replacements, $replacements2);
print_r($basket);
// Array
// (
//     [0] => grape
//     [1] => banana
//     [2] => apple
//     [3] => raspberry
//     [4] => cherry
//     [6] => watermelon
// )

array_replace() 函数后面可以有多个替换数组,所有的替换数组针对的都是第一个数组。如果有下标相同的,那么以最后一个数组的值为准,如果有第一个数组中没有的下标数据,那么就会追加到这个数组中。

它还有一个深度迭代的函数 array_replace_recursive() 通过下面的代码可以看出它和 array_replace() 的区别。

$base = ['citrus' => ["orange"], 'berries' => ["blackberry", "raspberry"], ];
$replacements = ['citrus' => ['pineapple'], 'berries' => ['blueberry']];

$basket = array_replace_recursive($base, $replacements);
print_r($basket);
// Array
// (
//     [citrus] => Array
//         (
//             [0] => pineapple
//         )

//     [berries] => Array
//         (
//             [0] => blueberry
//             [1] => raspberry
//         )

// )

$basket = array_replace($base, $replacements);
print_r($basket);
// Array
// (
//     [citrus] => Array
//         (
//             [0] => pineapple
//         )

//     [berries] => Array
//         (
//             [0] => blueberry
//         )

// )

神奇函数 array_reduce

为什么说这个函数很神奇呢?因为它在回调函数中返回的是这一次和上一次两个元素的数据。我们先来看看它的用法。

function sum($carry, $item)
{
    $carry += $item;
    return $carry;
}

function product($carry, $item)
{
    $carry *= $item;
    return $carry;
}

$a = [1, 2, 3, 4, 5];
$x = [];

var_dump(array_reduce($a, "sum")); // int(15)
var_dump(array_reduce($a, "product", 10)); // int(1200), because: 10*1*2*3*4*5
var_dump(array_reduce($x, "sum", "No data to reduce")); // string(17) "No data to reduce"

在回调函数中会有两个参数,carry 是上次迭代返回的值,而 item 是这一次迭代的值。如果是第一个元素的话,上一次返回的值就是 initial ,也就是 array_reduce() 函数可以选择定义的第三个参数的内容。如果不定义的话,就是一个 null 值。

从测试代码中,我们可以看到,第一个函数调用了 sum ,返回的是数组 a 中累加的结果。由于是加法,第一个 carry 会转化为 0,这对加法没有什么影响。但是第二段乘法我们就必须要给一个初始值了,这里我们直接给了一个 10 作为初始值,最后就是 10*1*2*3*4*5 的结果了。

可能看得会比较晕,大家可以在回调函数中直接打印 carry 和 item 就可以看出每次的数据变化情况了。这个函数也可以直接使用 匿名函数 的方式来使用。效果是一样的。

var_dump(array_reduce($a, function ($carry, $item) {
    echo $carry, '----', $item, PHP_EOL;
    $carry *= $item;
    return $carry;
}, 10));exit;
// 10----1
// 10----2
// 20----3
// 60----4
// 240----5
// int(1200)

在这个匿名函数中,我们打印了 carry 和 item 每一步的变化。说这个函数的神奇,其实主要是在 Laravel 框架中的中间件,也就是 Pipe 管道操作的整个系统中,最核心的代码就是这个 array_reduce() ,大家可以在 src/Illuminate/Pipeline/Pipeline.php 找到 then() 方法,里面使用到的就是 array_reduce() 。如果你从头跟踪整个框架代码中中间件相关代码的执行的话,最后也会找到这里。

关于 array_reduce() 在 Laravel 中具体的用法,在学习 Laravel 框架的时候已经深入的了解过了,在这里,我们只需要先知道这个函数的作用以及它被用在了 Laravel 的什么地方就可以了,相信如果你面试的岗位是使用 Laravel 框架的话,那么能说出这个函数用在中间件中,也能够让面试官刮目相看了。

并集运算

最后还是交、并、差运算,这里我们也是学习最后一个也就是并集的运算了。其实并集运算比较简单,函数也比较少,并就是合并的意思嘛,相信不少同学已经想到了 array_merge() 这个函数了。

$arr1 = [0 => 'zero', 1 => 'one'];
$arr2 = [1 => 'one', 2 => 'two', 3 => 'three'];

print_r($arr1 + $arr2);
// Array
// (
//     [0] => zero
//     [1] => one
//     [2] => two
//     [3] => three
// )

print_r(array_merge($arr1, $arr2));
// Array
// (
//     [0] => zero
//     [1] => one
//     [2] => one
//     [3] => two
//     [4] => three
// )

注意,这里我们除了 array_merge() 之外,还使用了 + 号运算符这种形式的并集操作。大家能看出它们的不同吗?很明显,array_merge 是忽略相同下标数据的,它真的就是把两个数组合起来,后面的数组就依次按下标位置继续添加到第一个数组的后面。而 + 号运算符这种,则是会排除相同下标的数据的。

当然,对于非数字下标的数组,也就是使用 字符串 作键的 Hash 型数据来说,array_merge() 会替换相同下标的元素,使用后面的数组的数据也就是最新的数据来替换相同键的值。

$arr1 = ["color" => "red", 2, 4];
$arr2 = ["a", "b", "color" => "green", "shape" => "trapezoid", 4];

print_r(array_merge($arr1, $arr2));
// Array
// (
//     [color] => green
//     [0] => 2
//     [1] => 4
//     [2] => a
//     [3] => b
//     [shape] => trapezoid
//     [4] => 4
// )

注意上面 array_merge() 中的 color 这个元素,它的数据在合并后的结果就是 arr2 中的 green 了。想想上面讲过的 + 号运算符和 array_merge() 的区别,大家觉得如果使用 + 号运行符来操作这两个 arr1 和 arr2 的话,结果会是什么呢?自己试试吧!

当然,array_merge() 也有它的迭代递归版本的函数,也就是 array_merge_recursive() 函数。

$ar1 = ["color" => ["favorite" => "red"], 5];
$ar2 = [10, "color" => ["favorite" => "green", "blue"]];
print_r(array_merge_recursive($ar1, $ar2));
// Array
// (
//     [color] => Array
//         (
//             [favorite] => Array
//                 (
//                     [0] => red
//                     [1] => green
//                 )

//             [0] => blue
//         )

//     [0] => 5
//     [1] => 10
// )

总结

今天学习到的函数其实并不多,但 array_reduce() 是一个很重要的函数。要完全的理解并掌握它的应用还是挺难的,特别是如果你去翻看 Laravel 的源码的话,会很惊讶这一个函数就完成了那么重要的一个功能。而 array_merge() 和 + 号运算符的差别也是一个重点,估计不少新学的小同学们还不知道数组是可以使用这个运行符来进行合并的,这也是一个重点的地方,也是面试中容易出题的地方哦。

测试代码:

https://github.com/zhangyue0503/dev-blog/blob/master/php/2021/04/source/2.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%B8%89%EF%BC%89.php

参考文档:

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