Як шукати за ключем => значення у багатовимірному масиві в 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")
);

Коли я шукаю ключ = ім'я та значення = "кішка 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 - це roxor.

Єдиний простір щодо SPL - це те, що він погано задокументований в Інтернеті. Але кілька PHP-книг детально описуються, зокрема Pro PHP; і ви, ймовірно, також можете отримати Google для отримання додаткової інформації.


Це працює як шарм, і я планую використовувати його ще раз для подібних проблем: D Єдина дивна частина - це передбачення та використання функції getSubIterator на RecursiveIteratorIterator замість змінної $ sub. Спочатку я думав, що це помилка друку, але це правильний шлях! дякую Джаред.
bchhun

2
Дивовижне рішення. Досить швидко теж!
ТейлорОтвелл

Дякую за рішення. Звідки ми беремо "id"? Від $ 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 секунди. Ще дуже дорого, але набагато розумніше.


ця швидша, ніж відповідь Джона / Джареда (0.0009999275207519) проти (0,0020008087158203) .. Ну цей тест характерний для мого випадку та оточення. Я дотримуюся цього, дякую stefgosselin
Awena

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

Невеликий імпорт до швидкої версії.


2
Насправді це не дозволяє йому кидати попередження, коли ключ не встановлений. Не так вже й незначно! -> + 1'ed.
stefgosselin

2
погодився, будучи можливим переглядати фактичний журнал помилок php для основних помилок і не забруднити його попередженнями, на мій погляд.
codercake

Це не повне рішення, і це скоріше "Спроба відповісти на інший пост" та "Не відповісти".
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()використовує природне впорядкування, яке працює на посиланнях на об'єкти, а не на їх властивості), вам доведеться реалізувати це самостійно, як я.

Такий підхід є більш ефективним (більше немає глибини) і важливішим універсальним (припускаючи, що ви застосовуєте порівнянність за допомогою інтерфейсів), оскільки об'єкти визначають спосіб їх сортування, тому ви можете безперервно переробити код. Набагато краще =)


Ця відповідь повинна бути правильною. Хоча метод пошуку грубої сили зробить це, це значно менше ресурсів.
Дрю

Слід зазначити, що те, що ви пропонуєте, має сенс, якщо ви шукаєте один і той же масив багато разів. Для того, щоб сортувати його (O (n log n)), потрібно набагато більше часу, ніж це просто зробити лінійний пошук значення (O (n)). Але як тільки це буде відсортовано, звичайно, тоді двійковий пошук був би швидшим.
orrd

Я також повинен додати, що використання об'єктів замість масивів може бути корисною абстракцією, але ви також можете виконати двійковий пошук на масиві, якщо масив буде відсортований. Вам не потрібно використовувати об’єкти для сортування масиву або для здійснення двійкового пошуку по ньому.
orrd

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

Я сподіваюся, що це комусь допоможе :)


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

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;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.