2

I have a deep multidimensional array that I am needing to extract the value of a specific key. I have found that the array_walk_recursive function will be my best option. I only need the first occurrence.

My array looks like this - (except much more complicated)

Array (
    [vehicle info] => Array (
        [one] => Array (
            [submodel] => LX
            [engine] => 2.3
        )
        [two] => Array (
            [color] => blue
            [year] => 2007
            [wheels] => 4
        )
        [three] => Array (
            [submodel] => LX
            [make] => Ford
            [model] => F-150
            [offroad] => No
        )
    )
)

The issue here is, submodel is in both one and three. Additionally, the array is not consistent, so I must use array_walk_recursive to search through it for the matching key, then return the value for that key.

Here is my current code -

array_walk_recursive ($array, (function ($item, $key) {
    $wanted = "submodel";
    if ($key === $wanted) {
        echo ("$key is $item");
    }
}));

The above returns submodel is LXsubmodel is LX.

Bonus Question!! How can I search for multiple keys and return the first corresponding value for each of those? I was thinking putting all wanted keys in an array, then do a foreach loop, but don't quite know how to structure this. I am new to php.

codyfraley
  • 99
  • 3
  • 12
  • Interesting question, although one might wonder what the value means when is returned, since there might be multiple values with the same index? – Code4R7 Jun 05 '17 at 20:01
  • @Code4R7, the way I saw it is that there are multiple of the same key per `vehicle_info`, but they are have duplicate values so it doesn't matter which one specifically is saved, as long as one is returned. e.g. `[submodel] => LX` exists in `[one]` and `[three]` – Hollings Jun 05 '17 at 20:16
  • The guy did say *'I am new to php.'*, I guess this holds true for databases as well. I mean, this is usually the point where MySQL/MariaDB/PostgreSQL enters the picture. – Code4R7 Jun 05 '17 at 20:25
  • @CFraley can you explain more about `(except much more complicated)` ? I mean, are you saying the actual array is "longer" but not "deeper" or is it both longer and deeper? If there aren't any deeper levels than what you have posted and the values are all on the same "level", then `array_walk_recursive()` can/should be replaced as JoeBlack says. – mickmackusa Jun 05 '17 at 21:27
  • @mickmackusa The array is much longer and deeper than what is written here. It is an XML response from a cURL post. Unfortunately, the first 5 "steps" of the array are not always consistent, so I have to search the entire array and dig down until I find what I'm looking for. Does that make sense? – codyfraley Jun 06 '17 at 12:45

4 Answers4

4

array_walk_recursive() is the appropriate native function to call for this task. Keep track of which keys have already been declared in the result array and ensure that they are never overwritten.

Code: (Demo)

$needles = ['submodel', 'offroad'];
$result = [];
array_walk_recursive(
    $array,
    function($value, $key) use ($needles, &$result) {
        if (
            in_array($key, $needles)
            && !key_exists($key, $result)
        ) {
            $result[$key] = "$key is $value";
        }
    }
);
var_export($result);

Output:

array (
  'submodel' => 'submodel is LX',
  'offroad' => 'offroad is No',
)

For improved performance when you only want the first qualifying match, write return; as the last line inside of the if block.


Alternatively, you can design your own recursive function which will return when all sought keys are found.

Code: (Demo)

$soughtKeys = array_flip(['submodel', 'offroad']);

function earlyReturningRecursion(array $array, array $soughtKeys, array &$result = []): array
{
    foreach ($array as $key => $value) {
        if (!array_diff_key($soughtKeys, $result)) {  // check if result is complete
            return $result;
        } elseif (is_array($value)) {
            earlyReturningRecursion($value, $soughtKeys, $result);
        } elseif (isset($soughtKeys[$key]) && !key_exists($key, $result)) {
            $result[$key] = "$key is $value";
        }
    }
    return $result;
}
var_export(earlyReturningRecursion($array, $soughtKeys));
// same output as the first snippet
mickmackusa
  • 43,625
  • 12
  • 83
  • 136
2

I would start by setting the values you want to null, and then only saving them if they haven't been found yet, by checking is_null(). I haven't tested this code, but it should look something like this:

$submodel = null;
array_walk_recursive ($array, (function ($item, $key) {
    $wanted = "submodel";
    if ($key === $wanted && is_null($submodel)) {
        echo ("$key is $item");
        $submodel = $item;
    }
}));
Hollings
  • 534
  • 5
  • 14
2

array_walk_recursive() has the defect of not allowing to return matching results however in PHP 7 you could use an anonymous function and a variable to store the matching value.

$matching = null;
$wanted = "submodel";

array_walk_recursive ($array, function ($item, $key) use ($wanted, $matching) {
    if (($key === $wanted) && is_null($matching)) {
        $matching = $item;
    }
});
Youmy001
  • 515
  • 3
  • 11
2

As far as there is no way to return early from array_walk_recursive(), I'd suggest to create a function to find the first occurrence of $wanted:

$arr = [
  'vehicle info' => [
     'one' => ['submodel' => 'LX', 'engine' => '2.3'],
     'two' => ['color' => 'blue', 'year' => '2007', 'wheels' => '4'],
     'three' => ['submodel' => 'LX', 'make' => 'Ford', 'model' => 'F-150', 'offroad' => 'No'],
    ],
];

function find($needle, $haystack, $found = '')
{
    foreach ($haystack as $key => $value) {
        if ($found) {
            break;
        }
        if ($key === $needle) {
            $found = "{$needle} is {$value}";
            break;
        }
        if (is_array($value)) {
            $found = find($needle, $value, $found);
        }
    }
    return $found;
}

$wanted = 'submodel';
$result = find($wanted, $arr);

var_dump($result); // string(14) "submodel is LX"

Live demo


Update: to search for multiple keys you'll need to do it in a loop:

$multiple_keys = array('submodel', 'year');

foreach ($multiple_keys as $wanted) {
    var_dump(find($wanted, $arr));
}

// Output:
//    string(14) "submodel is LX"
//    string(12) "year is 2007"

Live demo

Joe Black
  • 867
  • 1
  • 9
  • 10