巧用 PHP 陣列函式

一隻賤熊貓發表於2018-08-06

0x00 前言

PHP 的陣列是一種很強大的資料型別,與此同時 PHP 內建了一系列與陣列相關的函式可以很輕易的實現日常開發的功能。但是我發現好像很多小夥伴都忽略了內建函式的作用(比如我自己就編寫過一些有關陣列操作的程式碼然後發現PHP自帶了/(ㄒoㄒ)/~~),善用 PHP 內建函式能極大的提高開發效率和執行效率(內建函式都是用 C 寫的效率比用 PHP 寫的高很多),所以本文便總結了一些在常見場景中利用 PHP 內建函式的實現方法。此外如果想更深入的學習有關 PHP 陣列函式最好還是去查 PHP 手冊!點我看官方陣列函式手冊

0x01 取指定鍵名

對於某些關聯陣列,有時候我們只想取指定鍵名的那部分,比如陣列為 ['id' => 1, 'name' => 'zane', 'password' => '123456'] 此時若只想取包含 id 和 name 的部分該怎麼實現呢?下面直接貼程式碼。

<?php
$raw = ['id' => 1, 'name' => 'zane', 'password' => '123456'];
// 自己用 PHP 實現
function onlyKeys($raw, $keys) {
    $new = [];
    foreach ($raw as $key => $val) {
        if (in_array($key, $keys)) {
            $new[$key] = $val;
        }
    }
    
    return $new;
}
// 用 PHP 內建函式實現
function newOnlyKeys($array, $keys) {
    return array_intersect_key($array, array_flip($keys));
}
var_dump(onlyKeys($raw, ['id', 'name']));
// 結果 ['id' => 1, 'name' => 'zane']
var_dump(newOnlyKeys($raw, ['id', 'name']));
// 結果 ['id' => 1, 'name' => 'zane']
複製程式碼

很明顯簡潔很多有木有!不過 array_intersect_keyarray_flip 是什麼鬼?這裡簡單的介紹一下這兩個函式的作用,首先是 array_flip 函式,這個函式的功能是「將陣列的鍵和值對調」,也就是鍵名變成值,值變成鍵名。我們傳遞的 $keys 引數經過這個函式便從 [0 => 'id', 1 => 'name'] 轉變為了 ['id' => 0, 'name' => 1]。這樣做的目的是為了向 array_intersect_key 函式服務,array_intersect_key 函式的功能是「使用鍵名比較計算陣列的交集」,也就是返回第一個引數陣列中與其他引數陣列相同鍵名的值。這樣便實現了取指定鍵名的功能 ~(≧▽≦)/~啦!當然要詳細瞭解這兩個函式的功能還是要查 PHP 官方手冊:array_flip array_intersect_key

0x02 移除指定鍵名

有了上一個例子做鋪墊,這個就簡單講講啦,道理是大同小異滴。

<?php
$raw = ['id' => 1, 'name' => 'zane', 'password' => '123456'];
// 用 PHP 內建函式實現
function removeKeys($array, $keys) {
    return array_diff_key($array, array_flip($keys));
}
// 移除 id 鍵
var_dump(removeKeys($raw, ['id', 'password']));
// 結果 ['name' => 'zane']
複製程式碼

和上一個例子相比本例只是將 array_intersect_key 函式改為 array_diff_key,嗯……相信大家能猜出來這個函式的功能「使用鍵名比較計算陣列的差集」,剛好和 array_intersect_key 的功能相反而已。官方手冊:array_diff_key

0x03 陣列去重

這個相信大家都有這個需求,當然 PHP 也內建了 array_unique 函式供給大家使用,如下例:

<?php
$input = ['you are' => 666, 'i am' => 233, 'he is' => 233, 'she is' => 666];
$result = array_unique($input);
var_dump($result);
// 結果 ['you are' => 666, 'i am' => 233]
複製程式碼

嘿,用這個函式就能解決大部分問題了,但是有時候你可能會覺得它不夠快,原因如下:

array_unique() 先將值作為字串排序,然後對每個值只保留第一個遇到的鍵名,接著忽略所有後面的鍵名。

因為這個函式會先將陣列進行排序,所以速度可能在某些場景達不到預期的要求。

現在我們可以祭出我們的黑科技 array_flip 函式,眾所周知 PHP 裡陣列的鍵名是唯一的,所以在鍵名和值對調後重復的值便被忽略了。試想一下我們連續呼叫兩次 array_flip 函式是不是就相當於實現了 array_unique 函式的功能呢?示例程式碼如下:

<?php
$input = ['you are' => 666, 'i am' => 233, 'he is' => 233, 'she is' => 666];
$result = array_flip(array_flip($input));
var_dump($result);
// 結果 ['she is' => 666, 'he is' => 233]
複製程式碼

嗯哼?!結果和 array_unique 的不一樣!為什麼,我們可以從 PHP 官方手冊得到答案:

如果同一個值出現多次,則最後一個鍵名將作為它的值,其它鍵會被丟棄。

總的來說就是 array_unique 保留第一個出現的鍵名,array_flip 保留最後一個出現的鍵名。

注意:使用 array_flip 作為陣列去重時陣列的值必須能夠作為鍵名(即為 string 型別或 integer 型別),否則這個值將被忽略。

此外,若不需要保留鍵名我們可以直接這樣使用 array_values(array_flip($input))

0x04 重置索引

當我們想要對一個索引並不連續的陣列進行重置時,比如陣列:[0 => 233, 99 => 666],對於這種陣列我們只需要呼叫 array_values 函式即可實現。如下例:

<?php
$input = [0 => 233, 99 => 666];
var_dump(array_values($input));
// 結果 [0 => 233, 1 => 66]
複製程式碼

需要注意的是 array_values 函式並不止重置數字索引還會將字串鍵名也同樣刪除並重置。那如何在保留字串鍵名的同時重置數字索引呢?答案就是 array_slice 函式,程式碼示例如下:

<?php
$input = ['hello' => 'world', 0 => 233, 99 => 666];
var_dump(array_slice($input, 0));
// 結果 ['hello' => 'world', 0 => 233, 1 => 66]
複製程式碼

array_slice 函式的功能是取出陣列的中的一段,但它預設會重新排序並重置陣列的數字索引,所以可以利用它重置陣列中的數字索引。

0x05 清除空值

嘿,有時候我們想清除某個陣列中的空值比如:nullfalse00.0[]空陣列''空字串'0'字串0 ,這時 array_filter 函式便能幫上大忙。程式碼如下:

<?php
$input = ['foo', false, -1, null, '', []];
var_dump(array_filter($input));
// 結果 [0 => 'foo', 2 => -1]
複製程式碼

為什麼會出現這樣的結果捏?array_filter 的作用其實是「用回撥函式過濾陣列中的單元」,它的第二個引數其實是個回撥函式,向陣列的每個成員都執行這個回撥函式,若回撥函式的返回值為 true 便保留這個成員,為 false 則忽略。這個函式還有一個特性就是:

如果沒有提供 callback 函式, 將刪除 array 中所有等值為 FALSE 的條目。

等值為 false 就是轉換為 bool 型別後值為 false 的意思,詳細看文件:轉換為布林型別

注意:如果不填寫 callback 函式,00.0'0'字串0 這些可能有意義的值會被刪除。所以如果清除的規則有所不同還需要自行編寫 callback 函式。

0x06 確認陣列成員全部為真

有時候我們希望確認陣列中的的值全部為 true,比如:['read' => true, 'write' => true, 'execute' => true],這時我們需要用一個迴圈判定嗎?NO,NO,NO……只需要用 array_product 函式便可以實現了。程式碼如下:

<?php
$power = ['read' => true, 'write' => true, 'execute' => true];
var_dump((bool)array_product($power));
// 結果 true
$power = ['read' => true, 'write' => true, 'execute' => false];
var_dump((bool)array_product($power));
// 結果 false
複製程式碼

為什麼能實現這個功能呢? array_product 函式本來的功能是「計算陣列中所有值的乘積」,在累乘陣列中所有成員的時候會將成員的值轉為數值型別。當傳遞的引數為一個 bool 成員所組成的陣列時,眾所周知 true 會被轉為 1,false 會被轉為 0。然後只要陣列中出現一個 false 累乘的結果自然會變成 0,然後我們再將結果轉為 bool 型別不就是 false 了嘛!

注意:使用 array_product 函式將在計算過程中將陣列成員轉為數值型別進行計算,所以請確保你瞭解陣列成員轉為數值型別後的值,否則會產生意料之外的結果。比如:

<?php
$power = ['read' => true, 'write' => true, 'execute' => 'true'];
var_dump((bool)array_product($power));
// 結果 false
複製程式碼

上例是因為 'true' 在計算過程中被轉為 0。要想詳細瞭解請點選這裡

0x07 獲取指定鍵名之前 / 之後的陣列

如果我們只想要關聯陣列中指定鍵名值之前的部分該怎麼辦呢?又用一個迴圈?當然不用我們可以通過 array_keysarray_searcharray_slice 組合使用便能夠實現!下面貼程式碼:

<?php
$data = ['first' => 1, 'second' => 2, 'third' => 3];
function beforeKey($array, $key) {
    $keys = array_keys($array);
  	// $keys = [0 => 'first', 1 => 'second', 2 => 'third']
    $len = array_search($key, $keys);
    return array_slice($array, 0, $len);
}
var_dump(beforeKey($data, 'first'));
// 結果 []
var_dump(beforeKey($data, 'second'));
// 結果 ['first' => 1]
var_dump(beforeKey($data, 'third'));
// 結果 ['first' => 1, 'second' => 2]
複製程式碼

思路解析,要實現這樣的功能大部分同學都應該能想到 array_slice 函式,但這個函式取出部分陣列是根據偏移量(可以理解為鍵名在陣列中的順序,從 0 開始)而不是根據鍵名的,而關聯陣列的鍵名卻是是字串或者是不按順序的數字,此時要解決的問題便是「如何取到鍵名對應的偏移量?」,這是 array_keys 函式便幫了我們大忙,它的功能是「返回陣列中部分的或所有的鍵名」預設返回全部鍵名,此外返回的鍵名陣列是以數字索引的,也就是說返回的鍵名陣列的索引就是偏移量!例子中的原陣列變為: [0 => 'first', 1 => 'second', 2 => 'third'] 。然後我們通過 array_search 便可以獲得指定鍵名的偏移量了,因為這個函式的功能是「在陣列中搜尋給定的值,如果成功則返回首個相應的鍵名」。有了偏移量我們直接呼叫 array_slice 函式便可以實現目的了。

上面的例子懂了,那獲取指定鍵名之後的陣列也就輕而易舉了,略微修改 array_slice 即可。直接貼程式碼:

<?php
$data = ['first' => 1, 'second' => 2, 'third' => 3];
function afterKey($array, $key) {
    $keys = array_keys($array);
    $offset = array_search($key, $keys);
    return array_slice($array, $offset + 1);
}
var_dump(afterKey($data, 'first'));
// 結果 ['second' => 2, 'third' => 3]
var_dump(afterKey($data, 'second'));
// 結果 ['third' => 3]
var_dump(afterKey($data, 'third'));
// 結果 []
複製程式碼

那如何獲取指定值之前或之後的陣列呢?嘿,記得 array_search 的作用吧,其實我們只需要這樣呼叫 beforeKey($data, array_search($value, $data)) 不就實現了嘛!

0x08 陣列中重複次數最多的值

敲黑板,劃重點!據說這是一道面試題喔。假設有這樣一個陣列 [6, 11, 11, 2, 4, 4, 11, 6, 7, 4, 2, 11, 8],請問如何獲取陣列中重複次數最多的值?關鍵就在於 array_count_values 函式。例項程式碼如下:

<?php
$data = [6, 11, 11, 2, 4, 4, 11, 6, 7, 4, 2, 11, 8];
$cv = array_count_values($data);
// $cv = [6 => 2, 11 => 4, 2 => 2, 4 => 3, 7 => 1, 8 => 1]
arsort($cv);
$max = key($cv);
var_dump($max);
// 結果 11
複製程式碼

array_count_values 函式的功能是「統計陣列中所有的值」,就是將原陣列中的值作為返回陣列的鍵名,值出現的次數作為返回陣列的值。這樣我們便可以通過 arsort 函式對出現的次數進行降序排序並且保持索引關聯。最後使用 key 獲得當前單元(當前單元預設為陣列第一個成員)的鍵名,此時的鍵名即是原陣列的值重複次數最多的值。

0x09 打廣告時間

雖然 PHP 提供了很多和陣列相關的函式,但使用起來還是不算太方便而且都是通過函式的呼叫方式而沒有物件導向相關的實現,所以我最近在寫一個開源的工具類專案 zane/utils,封裝了一些常用的方法並且支援鏈式呼叫,其中的 Ary 類實現 「獲取陣列中重複次數最多的值」只需一行,如下所示:

$data = [6, 11, 11, 2, 4, 4, 11, 6, 7, 4, 2, 11, 8];
$max = Ary::new($data)->countValues()->maxKey();
var_dump($max);
// 結果 11
複製程式碼

歡迎大家給我提 issue 和 pr,另外如果你喜歡這個專案希望動動小手點個 star :-D

專案地址:github.com/zanemmm/uti…

0x0A 結語

其實還有很多實用的函式沒有介紹,但是限於文章篇幅就講到這裡了吧。本文出現的很多例子都並非本人原創的,多數出於 PHP 官方手冊(每個函式功能下面的評論裡都有很多大神提出一些厲害的用法,部分示例就是出自評論)。在下只是拾人牙慧,將其總結了一下。另外文章中若出現錯誤,希望大家能夠指出,若有疑問可以互相討論:-D。

我的部落格原文

相關文章