如何在PHP的多维数组中按key=>value进行搜索

168

有没有一种快速的方法可以获取多维数组中找到键值对的所有子数组?我无法确定数组的深度。

简单的示例数组:

$arr = array(0 => array(id=>1,name=>"cat 1"),
             1 => array(id=>2,name=>"cat 2"),
             2 => array(id=>3,name=>"cat 1")
);

当我搜索键为"name"且值为"cat 1"时,函数应返回:

array(0 => array(id=>1,name=>"cat 1"),
      1 => array(id=>3,name=>"cat 1")
);

我猜这个函数必须是递归的才能到达最深层。

17个回答

236

代码:

function search($array, $key, $value)
{
    $results = array();

    if (is_array($array)) {
        if (isset($array[$key]) && $array[$key] == $value) {
            $results[] = $array;
        }

        foreach ($array as $subarray) {
            $results = array_merge($results, search($subarray, $key, $value));
        }
    }

    return $results;
}

$arr = array(0 => array(id=>1,name=>"cat 1"),
             1 => array(id=>2,name=>"cat 2"),
             2 => array(id=>3,name=>"cat 1"));

print_r(search($arr, 'name', 'cat 1'));

输出:

Array
(
    [0] => Array
        (
            [id] => 1
            [name] => cat 1
        )

    [1] => Array
        (
            [id] => 3
            [name] => cat 1
        )

)

如果效率很重要,你可以这样编写代码,让所有递归调用都将其结果存储在同一个临时$results数组中,而不是将多个数组合并在一起,代码如下:

function search($array, $key, $value)
{
    $results = array();
    search_r($array, $key, $value, $results);
    return $results;
}

function search_r($array, $key, $value, &$results)
{
    if (!is_array($array)) {
        return;
    }

    if (isset($array[$key]) && $array[$key] == $value) {
        $results[] = $array;
    }

    foreach ($array as $subarray) {
        search_r($subarray, $key, $value, $results);
    }
}

关键在于 search_r 将其第四个参数按引用传递,而不是按值传递;& 符号至关重要。

顺便提一下:如果你使用的是旧版本的 PHP,则必须在调用 search_r 时指定按引用传递的部分,在其声明中不能这样做。也就是说,最后一行变成了 search_r($subarray, $key, $value, &$results)


77

你可以考虑使用SPL版本代替,这样可以省去一些打字:

// I changed your input example to make it harder and
// to show it works at lower depths:

$arr = array(0 => array('id'=>1,'name'=>"cat 1"),
             1 => array(array('id'=>3,'name'=>"cat 1")),
             2 => array('id'=>2,'name'=>"cat 2")
);

//here's the code:

    $arrIt = new RecursiveIteratorIterator(new RecursiveArrayIterator($arr));

 foreach ($arrIt as $sub) {
    $subArray = $arrIt->getSubIterator();
    if ($subArray['name'] === 'cat 1') {
        $outputArray[] = iterator_to_array($subArray);
    }
}

很棒的是,基本相同的代码通过使用RecursiveDirectoryIterator而不是RecursiveArrayIterator即可迭代遍历目录。SPL太强了。

SPL唯一的遗憾是其在网上文档化得很糟糕。但是几本PHP书籍详细介绍了一些有用的内容,特别是 Pro PHP;你还可以通过谷歌等搜索引擎获取更多信息。


这个方法非常好用,我计划在类似的问题上再次使用它:D 唯一奇怪的部分是在foreach循环中,使用RecursiveIteratorIterator的getSubIterator函数而不是$sub变量。起初我以为这是一个打字错误,但这是正确的方式!谢谢Jared。 - bchhun
谢谢您的解决方案。我们从哪里获取“id”?从$outputArray吗? - trante
谢谢,解决方案很简单,但不知道性能如何? - Mahesh.D
如何从原始数组中取消设置找到的元素(可以是子数组)? - Fr0zenFyr

62
<?php
$arr = array(0 => array("id"=>1,"name"=>"cat 1"),
             1 => array("id"=>2,"name"=>"cat 2"),
             2 => array("id"=>3,"name"=>"cat 1")
);
$arr = array_filter($arr, function($ar) {
   return ($ar['name'] == 'cat 1');
   //return ($ar['name'] == 'cat 1' AND $ar['id'] == '3');// you can add multiple conditions
});

echo "<pre>";
print_r($arr);

?>

参考: http://php.net/manual/zh/function.array-filter.php


4
如果你想搜索只有一层深度的数组,这是一个好的解决方案,但这个问题特别要求递归地搜索深层数组("函数必须递归到最深层级")。 - orrd

17

针对这些答案,特别是John Kugelman的优秀答案,我回来发表此更新,为需要优化提示的人提供帮助。

他发布的函数可以正常工作,但我必须为处理包含12,000行结果集的情况进行优化。该函数花费了长达8秒的时间去遍历所有记录,时间太长了。

我只需要函数在找到匹配项时停止搜索并返回即可。例如,如果正在搜索客户端ID,我们知道结果集中只有一个客户端ID,一旦在多维数组中找到客户端ID,我们就希望返回。

以下是这个函数的速度优化版(并且更加简化),供需要的人使用。与其他版本不同,它仅能处理一个数组深度,并且不进行递归处理,也不合并多个结果。

// search array for specific key = value
public function searchSubArray(Array $array, $key, $value) {   
    foreach ($array as $subarray){  
        if (isset($subarray[$key]) && $subarray[$key] == $value)
          return $subarray;       
    } 
}

这将任务的匹配时间压缩到了1.5秒,仍然非常昂贵,但更加合理。


这个比Jhon/Jared的答案快(0.0009999275207519 vs 0.0020008087158203)。好吧,这个测试是特定于我的情况和环境的。我会坚持使用这个,谢谢stefgosselin。 - Awena

16
if (isset($array[$key]) && $array[$key] == $value)

对快速版本的一项次要改进。


2
实际上,这可以防止在键未设置时抛出警告。这并不是小问题!-> +1。 - Stephane Gosselin
2
我认为,同意能够实际浏览 PHP 错误日志以查找重大错误而不受警告干扰是正确的方法。 - codercake
这不是一个完整的解决方案,更像是“尝试回复另一篇帖子”和“不是答案”。 - mickmackusa

11

这是解决方案:

<?php
$students['e1003']['birthplace'] = ("Mandaluyong <br>");
$students['ter1003']['birthplace'] = ("San Juan <br>");
$students['fgg1003']['birthplace'] = ("Quezon City <br>");
$students['bdf1003']['birthplace'] = ("Manila <br>");

$key = array_search('Delata Jona', array_column($students, 'name'));
echo $key;  

?>

7

在多维数组中,要小心使用线性搜索算法(上述算法是线性的),因为随着深度的增加,遍历整个数组所需的迭代次数会呈指数级增长。例如:

array(
    [0] => array ([0] => something, [1] => something_else))
    ...
    [100] => array ([0] => something100, [1] => something_else100))
)

如果使用适当的算法,最多需要200次迭代就可以找到您要查找的内容(如果针在[100] [1]处)。

在这种情况下,线性算法的执行效率为O(n)(整个数组中元素的数量),这很差,例如一百万条记录(例如一个1000x100x10数组)平均需要500,000次迭代才能找到针。此外,如果您决定更改多维数组的结构会发生什么?如果您的深度超过100,则PHP将退出递归算法。计算机科学可以做得更好:

在可能的情况下,始终使用对象而不是多维数组:

ArrayObject(
   MyObject(something, something_else))
   ...
   MyObject(something100, something_else100))
)

并应用自定义比较器接口和函数进行排序和查找:

interface Comparable {
   public function compareTo(Comparable $o);
}

class MyObject implements Comparable {
   public function compareTo(Comparable $o){
      ...
   }
}

function myComp(Comparable $a, Comparable $b){
    return $a->compareTo($b);
}

如果你想要自定义排序规则,可以使用uasort()。如果你感到有冒险精神,你可以为你的对象实现自己的集合来进行排序和管理(我通常至少扩展ArrayObject以包含搜索函数)。

$arrayObj->uasort("myComp");

一旦它们被排序(uasort的时间复杂度为O(n log n),这已经是对任意数据的最佳表现),二分查找就可以在O(log n)的时间内完成操作,即一百万条记录只需要约20次迭代即可搜索。据我所知,在PHP中没有实现自定义比较器二分查找( array_search()使用自然排序,其基于对象引用而非属性),您需要像我一样自己实现。
这种方法更有效率(不再有深度),更重要的是通用性强(只要你使用接口强制实现比较),因为对象定义了它们的排序方式,所以您可以无限地重复利用代码。这很棒 =)

这个答案应该是正确的。虽然暴力搜索方法可以做到,但这样会消耗更少的资源。 - Drew
需要注意的是,如果您要多次搜索相同的数组,则您提出的建议才有意义。排序需要更长的时间(O(n log n)),而仅仅进行线性搜索所需的时间(O(n))则要少得多。但是,一旦它被排序,那么二分搜索就会更快。 - orrd
我还应该补充一点,使用对象代替数组可能是一种有用的抽象,但如果数组已经排序,你也可以在数组上执行二分查找。你不需要使用对象来对数组进行排序或在其上执行二分查找。 - orrd
如果有人使用了正确实现的多线程,那么只需要一个操作就能在 needle @ [100][1] 中找到目标。可以从 [0][0] 启动一个向前的线程,从 [100][1] 启动一个向后的线程,从 [50][1] 启动一个向后的线程,以及从 [50][0] 启动一个向前的线程。只要您的处理器没有崩溃,基本上可以通过增加更多的线程来缩短搜索时间^^ - clockw0rk

6
$result = array_filter($arr, function ($var) {   
  $found = false;
  array_walk_recursive($var, function ($item, $key) use (&$found) {  
    $found = $found || $key == "name" && $item == "cat 1";
  });
  return $found;
});

3
function in_multi_array($needle, $key, $haystack) 
{
    $in_multi_array = false;
    if (in_array($needle, $haystack))
    {
        $in_multi_array = true; 
    }else 
    {
       foreach( $haystack as $key1 => $val )
       {
           if(is_array($val)) 
           {
               if($this->in_multi_array($needle, $key, $val)) 
               {
                   $in_multi_array = true;
                   break;
               }
           }
        }
    }

    return $in_multi_array;
} 

我的情况不同,但从你的答案中得到了提示。 - shyammakwana.me

3

http://snipplr.com/view/51108/nested-array-search-by-value-or-key/

<?php

//PHP 5.3

function searchNestedArray(array $array, $search, $mode = 'value') {

    foreach (new RecursiveIteratorIterator(new RecursiveArrayIterator($array)) as $key => $value) {
        if ($search === ${${"mode"}})
            return true;
    }
    return false;
}

$data = array(
    array('abc', 'ddd'),
    'ccc',
    'bbb',
    array('aaa', array('yyy', 'mp' => 555))
);

var_dump(searchNestedArray($data, 555));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接