Как искать по ключу => значение в многомерном массиве в PHP

147

Есть ли какой-нибудь быстрый способ получить все подмассивы, в которых пара ключ-значение была найдена в многомерном массиве? Я не могу сказать, насколько глубоким будет массив.

Простой пример массива:

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

Когда я ищу key = name и value = "cat 1", функция должна вернуть:

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

Я предполагаю, что функция должна быть рекурсивной, чтобы опуститься до самого глубокого уровня.


источник

Ответы:

217

Код:

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четвертый параметр принимает его по ссылке, а не по значению; амперсанд &имеет решающее значение.

FYI: Если у вас есть старая версия PHP , то вы должны указать проход по ссылке участие в вызове к , search_rа не в его заявлении. То есть последняя строка становится search_r($subarray, $key, $value, &$results).

Джон Кугельман
источник
2
@JohnKugelman Разве ошибка "эффективного" ответа, если $keyне существует в массиве? Не лучше ли это сделать if (array_key_exists($key, $array) && $array[$key] == $value) {?
Погоня
1
@JohnKugelman Эта функция работает хорошо, но иногда у меня есть мой, $valueкоторый есть, nullи функция не работает ... array empty... Как получить массив, даже если $value= null? как search($array, 'id', null)?
Заглу
71

Как насчет версии 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; и вы можете, вероятно, Google для получения дополнительной информации, тоже.

Jared
источник
Это работает как талисман, и я планирую использовать его снова для подобных проблем: D Единственная странная часть в foreach и использование функции getSubIterator в RecursiveIteratorIterator вместо переменной $ sub. Сначала я думал, что это опечатка, но это правильный путь! спасибо Джаред.
bchhun
2
Отличное решение. Довольно быстро тоже!
TaylorOtwell
Спасибо за решение. Где мы можем получить «идентификатор»? Из $ outputArray?
trante
Спасибо, очень простое решение, но не знаю о производительности ??
Mahesh.D
Как сбросить найденный элемент (может быть под-массив) из исходного массива?
Fr0zenFyr
49
<?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/en/function.array-filter.php

Прасант Бендра
источник
4
Это хорошее решение, если вы хотите найти массив, который имеет глубину только на один уровень, но этот конкретный вопрос был о рекурсивном поиске в глубоком массиве («функция должна быть рекурсивной, чтобы перейти на самый глубокий уровень»).
orrd
16

Вернулся, чтобы опубликовать это обновление для тех, кому нужна подсказка по оптимизации этих ответов, особенно замечательный ответ Джона Кугельмана выше.

Его опубликованная функция работает нормально, но мне пришлось оптимизировать этот сценарий для обработки 12 000 строк результатов. Функция занимала вечные 8 секунд, чтобы просмотреть все записи, оооочень долго.

Мне просто нужна была функция, чтобы остановить поиск и вернуться, когда совпадение было найдено. То есть, если мы ищем customer_id, мы знаем, что в результирующем наборе есть только один, и как только мы находим customer_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;       
    } 
}

Это сбило задачу, чтобы сопоставить 12 000 записей с 1,5 сек. Все еще очень дорого, но гораздо разумнее.

stefgosselin
источник
этот ответ быстрее, чем ответ Джона / Джареда (0.0009999275207519) против (0.0020008087158203) .. Ну, этот тест специфичен для моего случая и окружающей среды. Я придерживаюсь этого, спасибо stefgosselin
Авена
14
if (isset($array[$key]) && $array[$key] == $value)

Незначительное улучшение быстрой версии.

blackmogu
источник
2
На самом деле это предотвращает выдачу предупреждений, когда ключ не установлен. Не так незначительно! -> +1
stefgosselin
2
согласился, на мой взгляд, возможность просмотреть журнал ошибок php на наличие серьезных ошибок и не загрязнять его предупреждениями.
кодеркейк
Это не полное решение, а скорее «Попытка ответить на другой пост» и «Не ответ».
mickmackusa
7

Будьте осторожны с алгоритмами линейного поиска (вышеупомянутые являются линейными) в многомерных массивах, поскольку они имеют сложную сложность, поскольку его глубина увеличивает количество итераций, необходимых для обхода всего массива. Например:

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

потребуется не более 200 итераций, чтобы найти то, что вы ищете (если стрелка была в [100] [1]), с подходящим алгоритмом.

Линейные алгоритмы в этом случае работают при O (n) (порядок общего числа элементов во всем массиве), это плохо, миллион записей (например, массив 1000x100x10) потребует в среднем 500 000 итераций, чтобы найти стрелку. И что будет, если вы решите изменить структуру многомерного массива? И PHP запускает рекурсивный алгоритм, если ваша глубина превышает 100. Информатика может сделать лучше:

Где возможно, всегда используйте объекты вместо многомерных массивов:

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()использует естественное упорядочение, которое работает со ссылками на объекты, а не с их свойствами), вам придется реализовать это самостоятельно, как я.

Этот подход более эффективен (больше нет глубины) и, что более важно, универсален (предполагается, что вы применяете сопоставимость с помощью интерфейсов), поскольку объекты определяют способ их сортировки, поэтому вы можете бесконечно перерабатывать код. Гораздо лучше =)

mbdxgdb2
источник
Этот ответ должен быть правильным. Хотя метод поиска методом грубой силы сделает это, он намного менее ресурсоемок.
Дрю
Следует отметить, что то, что вы предлагаете, имеет смысл, только если вы просматриваете один и тот же массив много раз. Требуется гораздо больше времени, чтобы пройти через сортировку (O (n log n)), чем просто выполнить линейный поиск значения (O (n)). Но как только он отсортирован, конечно, бинарный поиск будет быстрее.
Орд
Я также должен добавить, что использование объектов вместо массивов может быть полезной абстракцией, но вы также можете выполнить двоичный поиск по массиву, если массив отсортирован. Вам не нужно использовать объекты для сортировки массива или выполнения бинарного поиска по нему.
Орд
6

Вот решение:

<?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;  

?>
Тристан
источник
5
$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

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));
Прамендра Гупта
источник
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
2

Мне нужно было что-то подобное, но для поиска многомерного массива по значению ... Я взял пример Джона и написал

function _search_array_by_value($array, $value) {
        $results = array();
        if (is_array($array)) {
            $found = array_search($value,$array);
            if ($found) {
                $results[] = $found;
            }
            foreach ($array as $subarray)
                $results = array_merge($results, $this->_search_array_by_value($subarray, $value));
        }
        return $results;
    }

Надеюсь, это кому-нибудь поможет :)

confiq
источник
2

Это пересмотренная функция от той, которую написал Джон К. ... Мне нужно взять только определенный ключ в массиве и ничего над ним.

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

    if ( is_array($array) )
    {
        if ( $array[$key] == $value )
        {
            $results[] = $array;
        } else {
            foreach ($array as $subarray) 
                $results = array_merge( $results, $this->search_array($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_array($arr, 'name', 'cat 1'));
Тревор Леттман
источник
1

И еще одна версия, которая возвращает значение ключа из элемента массива, в котором это значение найдено (без рекурсии, оптимизировано для скорости):

// if the array is 
$arr['apples'] = array('id' => 1);
$arr['oranges'] = array('id' => 2);

//then 
print_r(search_array($arr, 'id', 2);
// returns Array ( [oranges] => Array ( [id] => 2 ) ) 
// instead of Array ( [0] => Array ( [id] => 2 ) )

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

Спасибо всем, кто разместил здесь.

Дарко Хргович
источник
1
function findKey($tab, $key){
    foreach($tab as $k => $value){ 
        if($k==$key) return $value; 
        if(is_array($value)){ 
            $find = findKey($value, $key);
            if($find) return $find;
        }
    }
    return null;
}
Monaem AMINA
источник
2
Не могли бы вы расширить этот ответ? Ответы только кода не объясняют, что вы на самом деле делаете.
Рич Беннер,
Пожалуйста, обновите ваш вопрос с намерением обучить.
mickmackusa
Это функционально только для поиска ключа, это работает для меня.
Джованни Гонсалес
0

Если вы хотите найти массив ключей, это хорошо

function searchKeysInMultiDimensionalArray($array, $keys)
{
    $results = array();

    if (is_array($array)) {
        $resultArray = array_intersect_key($array, array_flip($keys));
        if (!empty($resultArray)) {
            $results[] = $resultArray;
        }

        foreach ($array as $subarray) {
            $results = array_merge($results, searchKeysInMultiDimensionalArray($subarray, $keys));
        }
    }

    return $results;
}

Ключи не будут перезаписываться, потому что каждый набор значений key => будет находиться в отдельном массиве в результирующем массиве.
Если вы не хотите дублировать ключи, используйте этот

function searchKeysInMultiDimensionalArray($array, $keys)
{
    $results = array();

    if (is_array($array)) {
        $resultArray = array_intersect_key($array, array_flip($keys));
        if (!empty($resultArray)) {
            foreach($resultArray as $key => $single) {

                $results[$key] = $single;
            }
        }

        foreach ($array as $subarray) {
            $results = array_merge($results, searchKeysInMultiDimensionalArray($subarray, $keys));
        }
    }

    return $results;
}
Панкай
источник