PHP: Recursive array function - php

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

Related

How to stop a recursive loop when a certain condition is met in PHP

I have an array of the below stucture and I want get the element from the root level to the 3rd level
Array
(
[0] => Array
(
[id] => 1
[parent_id] =>
[name] => try
[sub_categories] => Array
(
[0] => Array
(
[id] => 2
[parent_id] => 1
[name] => try1
[sub_categories] => Array
(
[0] => Array
(
[id] => 3
[parent_id] => 2
[name] => try2
[sub_categories] => Array
(
[0] => Array
(
[id] => 4
[parent_id] => 3
[name] => try3
[sub_categories] => Array
(
[0] => Array
(
[id] => 5
[parent_id] => 4
[name] => try4
)
)
)
)
)
)
)
)
(
[1] => Array
(
[id] => 6
[parent_id] => 1
[name] => try1
[sub_categories] => Array
(
[0] => Array
(
[id] => 7
[parent_id] => 6
[name] => try2
[sub_categories] => Array
(
[0] => Array
(
[id] => 8
[parent_id] => 7
[name] => try3
[sub_categories] => Array
(
[0] => Array
(
[id] => 9
[parent_id] => 8
[name] => try4
)
)
)
)
)
)
)
)
)
)
public function getElements( $parent_id = NULL, $level = 0 ) {
$data = models\Categories::find()->where( ['parent_id'=>$parent_id] )->all();
$arr = array();
foreach( $data as $data ) {
if( $level == 3 ) {
break;
}
//do something
$countChilds = models\Categories::find()->where( ['parent_id'=>$data->id] )->count();
if( $countChilds > 0 ){
$catData['sub_categories'] = $this->getElements( $parent_id = $data->id, $level++ );
}
$arr[] = $catData;
}
return $arr;
}
I tried to add a counter and break out of the loop when the counter gets to 3 but it's not working, so i think since the counter can only work on the immediate sub-categories that is next to the root category and since i want to go down the tree 3 level so the counter isn't working what is the best way to achieve this I'd like my resulting array to out put an array with ID 1,2,3,4 , 6,7,8. element with ID 5,9 should not be printed since they belong to level 4
Any help on this would be appreciated
<?php
public function getElements( $parent_id = NULL, $level = 0 ) {
if( $level == 3 ) return []; // or return $arr;
$data = models\Categories::find()->where( ['parent_id'=>$parent_id] )->all();
$arr = array();
foreach( $data as $data ) {
//do something
$countChilds = models\Categories::find()->where( ['parent_id'=>$data->id] )->count();
if( $countChilds > 0 ){
$catData['sub_categories'] = $this->getElements( $parent_id = $data->id, $level + 1);
}
$arr[] = $catData;
}
return $arr;
}
In the above code, I made 2 changes.
If $level == 3, then return it instantly. No need to put that in the foreach.
It should be $catData['sub_categories'] = $this->getElements( $parent_id = $data->id, $level + 1); instead of $level++, since the child will have parent level + 1 and post increment operator doesn't do that. Also, even ++$level(pre-increment operation) would be a bad idea since you are modifying the variable even for the next upcoming entries in the foreach loop. So, $level + 1 is the correct way.

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?

Assign parent ids to children of multidimensional arrays

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

How to count specific key values from a multi-dimensional array?

I have a vendor data array listed as a tree structure and each vendor have a type.
These are types of vendor and its id:
Agency = 1
Branch Agency = 2
Wholsaler = 3
Smartshop = 4
Example: ['type']=>2 (here this vendor is a branch agency).
My question is: How can I get the count of Branch agencies are in this array, same count of wholesaler and smart shop?
Desired result:
[2 => 2, 3 => 2, 4 => 1]
Here is my dynamic generated array:
Array
(
[2] => Array
(
[id] => 2
[type] => 2
[name] => R-1 Agency
[parent] => 1
[children] => Array
(
[3] => Array
(
[id] => 3
[type] => 3
[name] => R-1-W-1
[parent] => 2
[children] => Array
(
[11] => Array
(
[id] => 11
[type] => 4
[name] => mdf,lk
[parent] => 3
[children] => Array
(
)
)
)
)
)
)
[38] => Array
(
[id] => 38
[type] => 2
[name] => sndflk
[parent] => 1
[children] => Array
(
[40] => Array
(
[id] => 40
[type] => 3
[name] => new one
[parent] => 38
[children] => Array
(
)
)
)
)
)
I used this function :
function take_types($array){
foreach ($array as $key => $value) {
$types[] = $value['type'];
if(!empty($value['children'])){
$this->take_types($value['children']);
}
}
return $types;
}
When I use the above function the output is like this:
Array
(
[0] => 2
[1] => 2
)
I only get two values, I need to get the count of each vendor type.
There will be many techniques to recursively process your tree data. I'll offer a native function style and a custom recursive style.
array_walk_recursive() visits all of the "leaf nodes", so you only need to check the key and push the value into a variable which can be accessed outside of that function's scope -- this is why "modifying by reference" is vital.
Code: (Demo)
// I removed the chunky $tree declaration from my post, see the demo
$result = [];
array_walk_recursive(
$tree,
function($v, $k) use (&$result) {
if ($k === 'type') {
$result[] = $v;
}
}
);
var_export(array_count_values($result));
Or
function recursiveTypeCount($array, $output = []) {
foreach($array as $item) {
if (!isset($output[$item['type']])) {
$output[$item['type']] = 1;
} else {
++$output[$item['type']];
}
if ($item['children']) {
$output = recursiveTypeCount($item['children'], $output);
}
}
return $output;
}
var_export(recursiveTypeCount($tree));
Both will display:
array (
2 => 2,
3 => 2,
4 => 1,
)

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.

Categories