Question

Essentially, I want to use the

$foo = new RecursiveIteratorIterator(new RecursiveArrayIterator($haystack));

Methodology, but instead of returning a flat array for foreach()ing through, keep the structure but only return a single great-grand-child node and it's parent nodes. Is this possible in PHP?


I've been tasked with optimising some of my company's (horrific) codebase. I found a function that recurses through an array, searching for a key. I can't replace this with a simple array_search() or array_key_exists() because the custom function also returns the parent nodes of the matched (found) key, instead of just a true or false.

How can I use RecursiveArrayIterator, RecursiveIteratorIterator or failing that, other built-in functions (i.e. as little custom code as possible) to return a matching node with it's parent tree from a search array? I want to get the fastest function possible, as currently this function spends 8 seconds executing 14 thousand times, hence my requirement to use built-in functions.

My existing attempt (below) is incredibly slow.

function search_in_array($needle, $haystack) {
    $path = array();

    $it = new RecursiveArrayIterator($haystack);
    iterator_apply($it, 'traverse', array($it, $needle, &$path));

    return $path;
}

function traverse($it, $needle, &$path) {
    while($it->valid()) {
        $key = $it->key();
        $value = $it->current();

        if(strcasecmp($value['id'], $needle) === 0) {
            $path[] = $key;
            return;
        } else if($it->hasChildren()) {
            $sub = null;
            traverse($it->getChildren(), $needle, &$sub);
            if($sub) {
                $path[$key] = $sub;
            }
        }

        $it->next();
    }
}

Example output for $needle = TVALL would look like this:

Array (
    [HOMECINEMA] => Array (
        [children] => Array (
            [HC-VISION] => Array (
                [children] => Array (
                    [0] => TVALL
                )
            )
        )
    )
)

The search array looks something like this (sorry for the vast-ness). There are more than two top-level nodes, but I've truncated it for brevity:

Array(2) (
    [HOMECINEMA] => Array (
        [id] => HOMECINEMA
        [position] => 2
        [title] => TV & Home Cinema
        [children] => Array (
            [HC-VISION] => Array (
                [id] => HC-VISION
                [title] => LCD & Plasma
                [children] => Array (
                    [TVALL] => Array (
                        [id] => TVALL
                        [title] => All TVs
                    )
                    [LCD2] => Array (
                        [id] => LCD2
                        [title] => LCD/LED TVs
                    )
                    [PLASMA] => Array (
                        [id] => PLASMA
                        [title] => Plasma TVs
                    )
                    [3DTV] => Array (
                        [id] => 3DTV
                        [title] => 3D TV
                    )
                    [LED] => Array (
                        [id] => LED
                        [title] => SMART TVs
                    )
                    [PROJECTORS] => Array (
                        [id] => PROJECTORS
                        [title] => Projectors
                    )
                    [SYS-HOMECINEMATV] => Array (
                        [id] => SYS-HOMECINEMATV
                        [title] => TV Home Cinema Systems
                    )
                )
            )
            [HC-SEPARATES] => Array (
                [id] => HC-SEPARATES
                [title] => Home Cinema Separates
                [children] => Array (
                    [DVDRECORDERS] => Array (
                        [id] => DVDRECORDERS
                        [title] => DVD Recorders
                    )
                    [HDDVD] => Array (
                        [id] => HDDVD
                        [title] => Blu-ray
                    )
                    [AVRECEIVERS] => Array (
                        [id] => AVRECEIVERS
                        [title] => AV Receivers
                    )
                    [DVDPLAYERS] => Array (
                        [id] => DVDPLAYERS
                        [title] => DVD Players
                    )
                    [FREEVIEW] => Array (
                        [id] => FREEVIEW
                        [title] => Digital Set Top Boxes
                    )
                    [DVDPACKAGESYSTEMS-3] => Array (
                        [id] => DVDPACKAGESYSTEMS-3
                        [title] => 1 Box Home Cinema Systems
                    )
                    [HOMECINEMADEALS] => Array (
                        [id] => HOMECINEMADEALS
                        [title] => Home Cinema System Deals
                    )
                )
            )
            [SPEAKER2] => Array (
                [id] => SPEAKER2
                [title] => Speakers
                [children] => Array (
                    [SPEAKERPACKAGES] => Array (
                        [id] => SPEAKERPACKAGES
                        [title] => Speaker packages
                    )
                    [SOUNDBARS] => Array (
                        [id] => SOUNDBARS
                        [title] => Soundbars
                    )
                    [CENTRES] => Array (
                        [id] => CENTRES
                        [title] => Centres
                    )
                    [SUBWOOFERS] => Array (
                        [id] => SUBWOOFERS
                        [title] => Subwoofers
                    )
                    [FLOORSTANDING] => Array (
                        [id] => FLOORSTANDING
                        [title] => Floorstanders
                    )
                    [INSTALLATIONSPEAKERS] => Array (
                        [id] => INSTALLATIONSPEAKERS
                        [title] => Installation Speakers
                    )
                    [STAND-MOUNT] => Array (
                        [id] => STAND-MOUNT
                        [title] => Bookshelf Speakers
                    )
                )
            )
            [HC-ACCYS] => Array (
                [id] => HC-ACCYS
                [title] => Accessories
                [children] => Array (
                    [AVESSENTIALS] => Array (
                        [id] => AVESSENTIALS
                        [title] => AV Interconnects
                    )
                    [PLASMALCDSTANDSBRACKETS1] => Array (
                        [id] => PLASMALCDSTANDSBRACKETS1
                        [title] => TV Accessories
                    )
                    [RACKS] => Array (
                        [id] => RACKS
                        [title] => TV Racks
                    )
                    [HIFIRACKS] => Array (
                        [id] => HIFIRACKS
                        [title] => HiFi Racks
                    )
                    [PROJECTORACCYS] => Array (
                        [id] => PROJECTORACCYS
                        [title] => Projector Screens/Accessories
                    )
                )
            )
        )
    )
    [SPEAKERS] => Array (
        [id] => SPEAKERS
        [position] => 3
        [title] => Speakers
        [children] => Array (
            [SPK-HIFI] => Array (
                [id] => SPK-HIFI
                [title] => Hi-Fi
                [children] => Array (
                    [STAND-MOUNT] => Array (
                        [id] => STAND-MOUNT
                        [title] => Bookshelf Speakers
                    )
                    [FLOORSTANDING] => Array (
                        [id] => FLOORSTANDING
                        [title] => Floorstanders
                    )
                    [INSTALLATIONSPEAKERS] => Array (
                        [id] => INSTALLATIONSPEAKERS
                        [title] => Installation Speakers
                    )
                )
            )
            [SPK-HOMECINEMA] => Array (
                [id] => SPK-HOMECINEMA
                [title] => Home Cinema
                [children] => Array (
                    [SPEAKERPACKAGES] => Array (
                        [id] => SPEAKERPACKAGES
                        [title] => Speaker Packages
                    )
                    [SOUNDBARS] => Array (
                        [id] => SOUNDBARS
                        [title] => Soundbars
                    )
                    [CENTRES] => Array (
                        [id] => CENTRES
                        [title] => Centres
                    )
                    [SUBWOOFERS] => Array (
                        [id] => SUBWOOFERS
                        [title] => Subwoofers
                    )
                )
            )
            [SPK-ACCYS] => Array (
                [id] => SPK-ACCYS
                [title] => Accessories
                [children] => Array (
                    [SPEAKERESSENTIALS1] => Array (
                        [id] => SPEAKERESSENTIALS
                        [title] => Speaker Cables
                    )
                    [SPEAKERSTANDS] => Array (
                        [id] => SPEAKERSTANDS
                        [title] => Speaker Stands
                    )
                    [SPEAKERBRACKETS] => Array (
                        [id] => SPEAKERBRACKETS
                        [title] => Speaker Wall Brackets
                    )
                )
            )
        )
    )
)
Was it helpful?

Solution

The example below will not necessarily be more performant (in time or memory requirements) but avoids manually recursing through the structure and shows an easier (IMHO) way to build your desired output array.

function search_in_array($needle, $haystack) {
    $path = array();

    $it = new RecursiveIteratorIterator(
        new ParentIterator(new RecursiveArrayIterator($haystack)),
        RecursiveIteratorIterator::SELF_FIRST
    );

    foreach ($it as $key => $value) {
        if (array_key_exists('id', $value) && strcasecmp($value['id'], $needle) === 0) {
            $path = array($needle);
            for ($i = $it->getDepth() - 1; $i >= 0; $i--) {
                $path = array($it->getSubIterator($i)->key() => $path);
            }
            break;
        }
    }

    return $path;
}

Reference

Bonus

You could also use the RecursiveIteratorIterator::setMaxDepth() method to limit the recursion to n levels deep, if your array also goes much deeper.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top