前言
PHP 內建 in_array()、array_search() 僅支援一維陣列。本文整理多維陣列搜尋的多種方案:從簡單的鍵名檢查到完整的遞歸搜尋鍵值/鍵名,並提供可直接使用的通用函式。
一維陣列基礎搜尋回顧
| 函式 | 用途 | 回傳值 | 範例 |
in_array($needle, $haystack, $strict) | 檢查值是否存在 | bool | in_array('Glenn', $people) |
array_key_exists($key, $array) | 檢查鍵名是否存在 | bool | array_key_exists('apple', $fruit) |
array_search($needle, $haystack, $strict) | 搜尋值,回傳對應鍵名 | key|false | array_search('green', $fruits) |
array_keys($array, $search_value) | 取得所有鍵名 (可依值過濾) | array | array_keys($fruits, 'red') |
array_values($array) | 取得所有值,重設數字索引 | array | array_values($fruits) |
多維陣列搜尋鍵值 (遞歸版) - 回傳所有匹配路徑
/**
* 遞歸搜尋多維陣列中所有符合 $needle 的值
* @param mixed $needle 要搜尋的值
* @param array $haystack 目標陣列
* @return array 符合的鍵名路徑陣列,每個元素為 [層級1=>鍵1, 層級2=>鍵2, ...]
*/
function array_search_recursive($needle, $haystack) {
$results = [];
function _search($needle, $haystack, $path, &$results) {
foreach ($haystack as $key => $value) {
$current_path = $path + [$key => $key]; // 保持鍵名類型
if (is_array($value)) {
_search($needle, $value, $current_path, $results);
} elseif ($value === $needle) { // 嚴格比較
$results[] = $current_path;
}
}
}
_search($needle, $haystack, [], $results);
return $results;
}
// 測試資料
$foo = [
1 => ['a' => ['xx' => 'bar 1'], 'b' => ['xx' => 'bar 2']],
2 => ['a' => ['bb' => 'bar 3', 'yy' => 'bar 4']],
3 => ['c' => ['dd' => 'bar 3'], 'f' => ['gg' => 'bar 3']],
'info' => [1 => 'bar 5']
];
$result = array_search_recursive('bar 3', $foo);
print_r($result);
/* 輸出:
Array (
[0] => Array ( [1] => 2 [2] => a [3] => bb )
[1] => Array ( [1] => 3 [2] => c [3] => dd )
[2] => Array ( [1] => 3 [2] => f [3] => gg )
)
*/
多維陣列搜尋鍵名 - 回傳對應的子陣列
/**
* 遞歸搜尋多維陣列中所有符合 $key 的鍵名
* @return array 符合鍵名對應的值 (子陣列或純量)
*/
function array_search_key_recursive($key, $haystack) {
$results = [];
function _search_key($key, $haystack, &$results) {
foreach ($haystack as $k => $value) {
if ($k === $key) {
$results[] = $value;
}
if (is_array($value)) {
_search_key($key, $value, $results);
}
}
}
_search_key($key, $haystack, $results);
return $results;
}
// 同樣測試資料
$result = array_search_key_recursive('a', $foo);
print_r($result);
/* 輸出:
Array (
[0] => Array ( [xx] => bar 1 )
[1] => Array ( [bb] => bar 3, [yy] => bar 4 )
)
*/
進階版:支援多條件搜尋 (鍵名+鍵值同時匹配)
/**
* 多維陣列多條件搜尋
* @param array $conditions ['key' => 'keyname', 'value' => 'val', 'strict' => true]
* @return array 滿足所有條件的節點路徑
*/
function array_search_multi($haystack, $conditions) {
$results = [];
function _search($haystack, $conditions, $path, &$results) {
foreach ($haystack as $key => $value) {
$match = true;
if (isset($conditions['key']) && $key !== $conditions['key']) $match = false;
if (isset($conditions['value'])) {
$cmp = $conditions['strict'] ?? false;
if ($cmp) { if ($value !== $conditions['value']) $match = false; }
else { if ($value != $conditions['value']) $match = false; }
}
if ($match && !isset($conditions['key']) && !isset($conditions['value'])) {
$match = false; // 至少要有一個條件
}
if ($match) {
$results[] = $path + [$key => $key];
}
if (is_array($value)) {
_search($value, $conditions, $path + [$key => $key], $results);
}
}
}
_search($haystack, $conditions, [], $results);
return $results;
}
// 找鍵名為 'a' 且值包含 'bar 3' 的節點
$result = array_search_multi($foo, ['key' => 'a', 'value' => 'bar 3']);
效能優化建議
| 場景 | 建議方案 | 時間複雜度 |
| 頻繁搜尋固定結構陣列 | 建立索引陣列 (鍵名=>路徑映射) | O(1) 查詢 |
| 單次搜尋、資料量 < 10000 | 遞歸函式即可 | O(n) |
| 大型資料、需高效能 | 改用資料庫 (SQL WHERE) 或 Elasticsearch | O(log n) 或更好 |
| 只需判斷存在性 | 使用 array_walk_recursive + 旗標變數 | O(n) 但可提早中斷 |
array_walk_recursive 簡易版 (僅判斷存在)
function array_exists_recursive($needle, $haystack, $strict = true) {
$found = false;
array_walk_recursive($haystack, function($value, $key) use ($needle, $strict, &$found) {
if ($found) return; // 已找到,雖無法真正中斷但可跳過後續比對
if ($strict ? $value === $needle : $value == $needle) {
$found = true;
}
});
return $found;
}
常見陷阱提醒
- 參考傳遞:遞歸函式中陣列參考傳遞 (
&$results) 避免大量複製
- 嚴格比較:預設使用
=== 避免 0 == 'string' 等隱性轉換
- 循環引用:陣列包含自我引用會導致無限遞歸,需加入
spl_object_id() 追蹤已訪問節點
- 記憶體爆炸:超大陣列遞歸可能耗盡記憶體,考慮生成器 (
yield) 分批處理