Assign parent ids to children of multidimensional arrays - php

I need to assign parent ids to all the children of a multidimensional array in PHP.
Array
(
[expanded] => 1
[key] => root_1
[title] => root
[children] => Array
(
[0] => Array
(
[folder] => 1
[key] => 34
[title] => YAY PROJECTS
)
[1] => Array
(
[expanded] => 1
[folder] => 1
[key] => 6
[title] => Grand Designs Episodes
[children] => Array
(
[0] => Array
(
[folder] => 1
[key] => 8
[title] => AU Episodes
)
[1] => Array
(
[expanded] => 1
[folder] => 1
[key] => 7
[title] => UK Episodes
[children] => Array
(
[0] => Array
(
[folder] =>
[key] => 9
[title] => Start something
)
[1] => Array
(
[folder] =>
[key] => 2
[title] => Grand Designs Season 10
)
)
)
)
)
[2] => Array
(
[expanded] => 1
[folder] => 1
[key] => 5
[title] => Animations
[children] => Array
(
[0] => Array
(
[folder] =>
[key] => 4
[title] => Futurama Episode 191
)
[1] => Array
(
[folder] =>
[key] => 3
[title] => Miniscule Series 5 Ep 1
)
[2] => Array
(
[folder] =>
[key] => 1
[title] => The Simpsons Episode 459
)
)
)
[3] => Array
(
[folder] => 1
[key] => 11
[title] => Test Folder
)
[4] => Array
(
[folder] => 1
[key] => 10
[title] => Testing
)
)
)
At first I thought this would be fairly trivial, however my solution quickly falls apart assigning the wrong parent_ids.
public function generateParentIds(array $input, $parentId = 0)
{
$return = [];
foreach ($input as $key => $value) {
if (is_array($value)) {
$value = $this->generateParentIds($value, $parentId);
if (isset($value['children'])) {
$parentId = $value['key'];
}
if (!is_int($key)) {
$return['parent_id'] = $parentId;
}
}
$return[$key] = $value;
}
return $return;
}
I'm not sure whats going on, I did a lot of research but couldn't find any examples, so I'd be very grateful for some help.

Assuming that each child should get the immediate parent's key value as its parent_id, this should do what you want. Note that it modifies the array in place ($input is passed by reference to the function, and the foreach loop uses a reference to $child), rather than attempting to merge returned values.
function generateParentIds(&$input, $parentId = 0) {
$input['parent_id'] = $parentId;
if (isset($input['children'])) {
foreach ($input['children'] as &$child) {
generateParentIds($child, $input['key']);
}
}
}
generateParentIds($input);
Output is too long to show here but there's a demo at 3v4l.org

Related

PHP break Nested Multi-dimensional array into single multi-dimensional array

I want to break the below nested array in simple associative array.
Input
Array
(
[0] => Array
(
[id] => 1
[name] => Gadgets
[code] => gadget
[parent_id] =>
[children] => Array
(
[0] => Array
(
[id] => 2
[name] => Mobile
[code] => mobile
[parent_id] => 1
[children] => Array
(
)
)
[1] => Array
(
[id] => 3
[name] => Laptops
[code] => laptop
[parent_id] => 1
[children] => Array
(
[0] => Array
(
[id] => 4
[name] => Dell
[code] => dell
[parent_id] => 3
[children] => Array
(
)
)
[1] => Array
(
[id] => 5
[name] => Lenovo
[code] => lenovo
[parent_id] => 3
[children] => Array
(
)
)
)
)
)
)
)
Output
Array
(
[0] => Array
(
[id] => 1
[name] => Gadgets
[code] => gadget
[parent_id] =>
)
[1] => Array
(
[id] => 2
[name] => Mobile
[code] => mobile
[parent_id] => 1
)
[2] => Array
(
[id] => 3
[name] => Laptops
[code] => laptop
[parent_id] => 1
)
[3] => Array
(
[id] => 4
[name] => Dell
[code] => dell
[parent_id] => 3
)
[4] => Array
(
[id] => 5
[name] => Lenovo
[code] => lenovo
[parent_id] => 3
)
)
Need help in making this type of array from the given array. I tried many things with for loops, but get stuck when in case there are many nested array and that solution does not fit correctly to my requirement.
There is one root node and others are child nodes and many parent nodes can have child nodes.
There are a ton of ways to do this, here are a couple of simple examples. If you don;t care about maintaining order, the recursive function is pretty simple. If you do need to maintain the order of the elements as they are encountered while traversing the tree (to render them as tables for example), it's just a bit more of a faff.
<?php
function flattenTree($array)
{
$output = [];
foreach($array as $currBranch)
{
if(!empty($currBranch['children']))
{
$children = flattenTree($currBranch['children']);
$output = array_merge($output, $children);
}
unset($currBranch['children']);
$output[] = $currBranch;
}
return $output;
}
function flattenTreeMaintainingOrder($array)
{
$output = [];
foreach($array as $currBranch)
{
$children = (array_key_exists('children', $currBranch)) ? $currBranch['children']:[];
unset($currBranch['children']);
$output[] = $currBranch;
if(!empty($children))
{
$children = flattenTreeMaintainingOrder($children);
$output = array_merge($output, $children);
}
}
return $output;
}
$flat = flattenTree($array);
$flatOrdered = flattenTreeMaintainingOrder($array);
print_r($flat) . PHP_EOL;
print_r($flatOrdered) . PHP_EOL;
A recursive function is one option...
function extractChildren($parent, $farr) {
$children = $parent['children'];
if (!$children || count($children)==0) return $farr;
unset($parent['children']);
$farr[]= $parent;
return extractChildren($children, $farr);
}
$finalarray=array();
// $array is the array you have in your question
foreach ($array as $parent) {
$finalarray = extractChildren($parent, $finalarray);
}
As #El_Vanya mentioned above, there are scads of other ways to accomplish this here: How to Flatten a Multidimensional Array?

Copying a multi dimensional array of nodes into another array

I am looking to convert a multi dimensional array into another multidimensional array using a recursive function.
Source array :
Array
(
[1] => Array
(
[id] => 1
[source_name] => kk56ca1d0f2378f
[company_id] => 1
[lft] => 1
[rgt] => 18
[parent_id] => 0
[children] => Array
(
[2] => Array
(
[id] => 2
[source_name] => kk56ca1d17f3f63
[company_id] => 1
[lft] => 2
[rgt] => 3
[parent_id] => 1
[children] => Array
(
)
)
[3] => Array
(
[id] => 3
[source_name] => kk56ca1d1ebe975
[company_id] => 1
[lft] => 4
[rgt] => 13
[parent_id] => 1
[children] => Array
(
[6] => Array
(
[id] => 6
[source_name] => kk56ca1fc882ac0
[company_id] => 1
[lft] => 5
[rgt] => 10
[parent_id] => 3
[children] => Array
(
)
)
)
)
)
)
)
which I need to get into the format of
Array
(
[0] => Array
(
[id] => 1
[text] => kk56ca1d0f2378f
[parent_id] => 0
[nodes] => Array
(
[0] => Array
(
[id] => 2
[text] => kk56ca1d17f3f63
[parent_id] => 1
[nodes] => Array
(
)
)
[1] => Array
(
[id] => 3
[text] => kk56ca1d1ebe975
[parent_id] => 1
[nodes] => Array
(
[0] => Array
(
[id] => 6
[text] => kk56ca1fc882ac0
[parent_id] => 3
[nodes] => Array
(
)
)
[1] => Array
(
[id] => 15
[text] => kk
[parent_id] => 3
[nodes] => Array
(
)
)
)
)
)
)
)
I have been trying for hours and getting nowhere with this. Any help would be really appreciated.
The source array has associative indexes (though they are numbers) and the destination array has numerical indexes. Besides this, just need to remove a few indexes and change names of a few.
EDIT :
Specific changes :
change index name source_name to text
change index name children to nodes
unset indexes lft, rgt, company_id
I do not have much experience with recursion so I have trying fruitlessly.
This is what I could come up with :
// pass array of nodes
function convert_array($from){
// this is a node
if(isset($from['source_name']))
{
$temp = array();
$temp['id'] = $from['id'];
convert_array($from['children']);
}
// this is an array of nodes
else
{
foreach($from as $arr)
{
$ret = convert_array($arr);
print_r($ret);
}
}
}
But I am not able to understand what data to be returned and how the new array builds up from the return values.
Here the working function:
function convert_array( $array )
{
$retval = array();
foreach( $array as $row )
{
$child = array();
$child['id'] = $row['id'];
$child['text'] = $row['source_name'];
$child['parent_id'] = $row['parent_id'];
if( count( $row['children'] ) )
{ $child['nodes'] = convert_array( $row['children'] ); }
else
{ $child['nodes'] = array(); }
$retval[] = $child;
}
return $retval;
}
3v4l demo
I think it is self-explanatory, BTW: we init an empty array ($retval), then we perform a foreach loop through all array argument: for each element, we init a new array and we add it id, source_name as text and parent_id; if the children index has elements, we perform a recursive call to fill nodes array index, otherwise we set it to empty array.

Combining two arrays from two tables into a single array using a common value

I'm trying to combine arrays generated from 2 different tables - 'articles' and 'article_categories'.
The output of 'article_categories' is:
Array
(
[0] => Array
(
[id] => 0
[title] => local
)
[1] => Array
(
[id] => 1
[title] => politics
)
[2] => Array
(
[id] => 2
[title] => economics
)
)
The function code is:
public function newsGetCategoryList() {
$result = $this->db->select('SELECT * FROM article_categories ORDER BY id');
return $result;
}
The output of 'articles' is:
Array
(
[0] => Array
(
[id] => 0
[title] => Article 1
[content] => content
[category] => local
)
[1] => Array
(
[id] => 1
[title] => Article 2
[content] => content
[category] => local
)
[2] => Array
(
[id] => 2
[title] => Article 2
[content] => content
[category] => local
)
)
The function code is:
public function newsGetCategoryArticles($category) {
$result = $this->db->select('SELECT * FROM articles WHERE category_id = :category', array('category' => $category));
return $result;
}
I need to insert 'articles' array into 'article_categories' so the output becomes:
Array
(
[0] => Array
(
[id] => 0
[title] => local
[articles] => Array
(
[0] => Array
(
[id] => 0
[title] => Article 1
[content] => content
[category] => local
)
[1] => Array
(
[id] => 1
[title] => Article 2
[content] => content
[category] => local
)
[2] => Array
(
[id] => 2
[title] => Article 2
[content] => content
[category] => local
)
)
)
[1] => Array
(
[id] => 1
[title] => politics
)
[2] => Array
(
[id] => 2
[title] => economics
)
)
In other words, articles should be inside the category array if articles' category value matches to category's title.
I have tried applying array_push in foreach loop on 'article_categories' but I was only getting a separate array for each category entry, rather than the whole array. I have no ideas how to approach this.
Thanks
Found the solution myself. Had to change newsGetCategoryList()
public function newsGetCategoryList() {
$array = $this->db->select('SELECT * FROM article_categories ORDER BY id');
$result = array();
foreach ($array as $value) {
$articles = $this->newsGetCategoryArticles($value['title']);
array_push($value, $articles);
$result[] = $value;
}
return $result;
}

Get all parent nodes with RecursiveArrayIterator

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
)
)
)
)
)
)
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
RecursiveIteratorIterator::SELF_FIRST iteration mode - required to see non-"leaf" items
ParentIterator - easy way to filter out the "leaf" items
RecursiveIteratorIterator::getDepth()
RecursiveIteratorIterator::getSubIterator()
Bonus
You could also use the RecursiveIteratorIterator::setMaxDepth() method to limit the recursion to n levels deep, if your array also goes much deeper.

PHP: Recursive array function

I want to create a function that returns the full path from a set node, back to the root value. I tried to make a recursive function, but ran out of luck totally. What would be an appropriate way to do this? I assume that a recursive function is the only way?
Here's the array:
Array
(
[0] => Array
(
[id] => 1
[name] => Root category
[_parent] =>
)
[1] => Array
(
[id] => 2
[name] => Category 2
[_parent] => 1
)
[2] => Array
(
[id] => 3
[name] => Category 3
[_parent] => 1
)
[3] => Array
(
[id] => 4
[name] => Category 4
[_parent] => 3
)
)
The result I want my function to output when getting full path of node id#4:
Array
(
[0] => Array
(
[id] => 1
[name] => Root category
[_parent] =>
)
[1] => Array
(
[id] => 3
[name] => Category 3
[_parent] => 1
)
[2] => Array
(
[id] => 4
[name] => Category 4
[_parent] => 3
)
)
The notoriously bad example of my recursive skills:
function recursive ($id, $array) {
$innerarray = array();
foreach ($array as $k => $v) {
if ($v['id'] === $id) {
if ($v['_parent'] !== '') {
$innerarray[] = $v;
recursive($v['id'], $array);
}
}
}
return $innerarray;
}
assuming "id" in your sub array is that sub arrays index + 1 inside the parent array (otherwise you would need to do a search in the array each time), you could do this:
$searchNode = 4;
while ($searchNode)
{
$result[] = $nodes[$searchNode - 1];
$searchNode = $nodes[$searchNode - 1]["id"];
}
$result = array_reverse($result);

Categories