user765368
user765368

Reputation: 20346

get all array keys by value

Let's say that I have an array like this:

Array
(
[Start] => Array
    (
        [Item 1] => Array
            (
                [0] => Item 1_1
                [Item 2_1] => Array
                    (
                        [Item 2_1_1] => x
                    )

                [1] => Item 3_1
            )

        [0] => Item 2
        [1] => Item 3
    )

)

Is there a php function that I can use to get the path that leads to the value x in my array, meaning, in this case the result would be:

Start, Item 1, Item 2_1, Item 2_1_1, x

Upvotes: 4

Views: 2671

Answers (2)

hakre
hakre

Reputation: 197684

The problem you have involves recursion and/or tree traversal. PHP supports tree traversal of an array with the RecursiveArrayIterator and RecursiveIteratorIterator.

To get all keys of all parent arrays, you need to got from the first level up to the current depth and get the keys. This is supported by RecursiveIteratorIterator as well with the getSubIterator() method. It's not really well documented in the manual, so here is one example:

$it = new RecursiveIteratorIterator(
    new RecursiveArrayIterator($array)
);

foreach ($it as $value) {
    if ($value !== 'x') continue;

    $keys  = array();
    $depth = $it->getDepth();
    for ($i = 0; $keys[] = $it->getSubIterator($i)->key(), $depth--; $i++);

    echo implode(', ', $keys), ', ', $value, "\n";
}

In this example, first of all the RecursiveArrayIterator with your $array is created. To enable the tree-traversal, it is wrapped into the RecursiveIteratorIterator. This is necessary to use the $it-iterator with the foreach in a recursive manner.

Inside the foreach then, the array value is checked against your search value. If it does not match it continues with the next value.

But if it does match the getDepth() and getSubIterator() methods on the recursive iterator are used to create the array of keys.

The example does the following output:

 Start, Item 1, Item 2_1, Item 2_1_1, x

Which matches your description in the question.

Because those are iterators you can also implement that into a class of it's own. The following Iterator class allows not only to do the tree-traversal over the array provided in the constructor but also has a method named getKeys() which returns an array containing all the keys from the lowest level to the current depth:

/**
 * Class ArrayRecursiveKeysIterator
 */
class ArrayRecursiveKeysIterator extends RecursiveIteratorIterator
{
    /**
     * @param array $array
     */
    public function __construct(array $array)
    {
        parent::__construct(new RecursiveArrayIterator($array));
    }

    /**
     * @return array keys
     */
    public function getKeys()
    {
        for ($k = [], $i = 0, $m = $this->getDepth(); $i <= $m; $i++)
            $k[] = $this->getSubIterator($i)->key();
        return $k;
    }
}

It is then more easy to use (and probably also for other scenarios). So first some basic usage example. Go through the array show all the keys for each value. Instantiate the iterator for your array and output the keys per each value:

$it = new ArrayRecursiveKeysIterator($array);
foreach ($it as $value) {
    echo implode(', ', $it->getKeys()), ', ', $value, "\n";
}

This creates the following output:

Start, Item 1, 0, Item 1_1
Start, Item 1, Item 2_1, Item 2_1_1, x
Start, Item 1, 1, Item 3_1
Start, 0, Item 2
Start, 1, Item 3

In your scenario you also want to filter the iterator based on a specific value (here the string "x") which you can easily do by making use of the RegexIterator which is a FilterIterator. This then is your scenario:

$it     = new ArrayRecursiveKeysIterator($array);
$filter = new RegexIterator($it, '~^x$~');
foreach ($filter as $value) {
    echo implode(', ', $it->getKeys()), ', ', $value, "\n";
}

And here the output:

Start, Item 1, Item 2_1, Item 2_1_1, x

As you can see, it is filtered for the value you're interested in.

Other related questions you're probably interested in are:

Upvotes: 2

Anh Nhan Nguyen
Anh Nhan Nguyen

Reputation: 318

The only method I can currently think of would be plenty of nested foreach ($array as $key => $value) loops together with array_search().

It would be better design to make it a recursive one though, so using a function would be wise.

function recursiveSearch($key, $array)
{
    foreach ($array as $k => $ar) {
        if (is_array('x', $ar)) {
            return $k . ', ' . array_search('x', $ar);
        } else {
            if ($ar === 'x') {
                return $k
            } else {
                return recursiveSearch($key, $ar);
            }
        }
    }
}

Just a take on it, not necessarily working or something like that.

Upvotes: 2

Related Questions