convert normal array in nested array with php - php

I have a hierarchical database table like below
ID Name Subcategory ParentID
1 ABC 0
2 DEF QFE 0
3 QFE XYZ 2
4 XYZ MNJ 3
From Thant I have got PHP array like below
$array_name = array(
array('ID' => '1', 'Name' => 'ABC', 'Subcategory' => '', 'ParentID' => '0'),
array('ID' => '2', 'Name' => 'DEF', 'Subcategory' => 'QFE', 'ParentID' => '0'),
array('ID' => '3', 'Name' => 'QFE', 'Subcategory' => 'XYZ', 'ParentID' => '2'),
array('ID' => '4', 'Name' => 'XYZ', 'Subcategory' => 'MNJ', 'ParentID' => '3')
);
but I want array like below
$array_name = array(
array('ID' => '1', 'Name' => 'ABC', 'Subcategory' => '', 'ParentID' => '0'),
array('ID' => '2', 'Name' => 'DEF', 'Subcategory' => array('ID' => '3', 'Name' => 'QFE', 'Subcategory' => array('ID' => '4', 'Name' => 'XYZ', 'Subcategory' => 'MNJ', 'ParentID' => '3'), 'ParentID' => '2'), 'ParentID' => '0'),
);
I want a function which checks is that row have some Subcategory or not and if a row has Subcategory then get that subcategory row as an array and make one array with all category and Subcategory
for that, I have tried to make a function which is given below
function find_subcategory($ID,$con){
$table_name ="SELECT * FROM `table_name` WHERE `parent_id` = '$ID'";
$table_name_result = mysqli_query($con,$table_name);
$category_array = array();
if(mysqli_num_rows($table_name_result)) {
while($row = mysqli_fetch_assoc($table_name_result)) {
$Subcategory= $row['Subcategory'];
$ID = $row['ID'];
if ($Subcategory== '') {
$find_subcategory = find_subcategory($ID,$con);
$row['Subcategory'] = $find_subcategory;
$category_array[] = $row;
}else{
$category_array[] = $row;
}
}
}
return json_encode(array('tbl_category'=>$category_array));
}
but this function is not working to get all the subcategories of one category.
can anybody help me with this

Rather than create a recursive routine, which executes the SQL for each level, this instead reads all of the categories in and them assembles them into the hierarchy.
Note that it reads them in reverse order so that when it assembles them, each subcategory is always read before the parent (More details in code comments)...
$table_name ="SELECT * FROM `category` ORDER BY parent_id DESC, id desc";
$table_name_result = mysqli_query($con,$table_name);
$categories = mysqli_fetch_all($table_name_result, MYSQLI_ASSOC);
$output= [];
foreach ( $categories as $category) {
// If there is a parent for this item
if ( !empty ($category['parent_id']) ) {
// Set basic details
$output[$category['parent_id']]['Subcategory'][$category['id']] = $category;
// If there is already some data (subcategories)
if ( isset($output[$category['id']]) ){
// Copy subcategories
$output[$category['parent_id']]['Subcategory'][$category['id']] +=
$output[$category['id']];
// Remove old node
unset ( $output[$category['id']] );
}
}
else {
// Add in category data (allow for existing data to be added
$output[$category['id']] = $category + ($output[$category['id']]??[]);
}
}

I successfully implemented and tested a recursive routine to solve this. However, for performance reasons I had to decouple the access to the database from the recursive call.
First, you fetch your query into an array as you already have and then recursively rearrange the elements so that the keys are nested in the proper order.
Pretty much of the explanations I'd like to put here are put as comments in the code.
$array_name = array(
array('ID' => '1', 'Name' => 'ABC', 'Subcategory' => '', 'ParentID' => '0'),
array('ID' => '2', 'Name' => 'DEF', 'Subcategory' => 'QFE', 'ParentID' => '0'),
array('ID' => '3', 'Name' => 'QFE', 'Subcategory' => 'XYZ', 'ParentID' => '2'),
array('ID' => '4', 'Name' => 'XYZ', 'Subcategory' => 'MNJ', 'ParentID' => '3'),
array('ID' => '5', 'Name' => 'XYY', 'Subcategory' => 'MNJ', 'ParentID' => '1')
);
// recreate nested array
function get_nested_array($arr) {
$new_arr = array(); // new array to collect each top level element with nesting
foreach ($arr as $key => $value) {
// only top level elements would appear here as they would not be nested
if($value['ParentID'] == '0') {
array_push($new_arr, get_nested_item($value, $arr));
}
}
return $new_arr;
}
// recursive function to perform nesting on each element
function get_nested_item($hay, $stack) {
foreach ($stack as $key => $value) {
if ($hay['ID'] == $value['ParentID']) {
$index = get_key($hay, $stack);
// reduce $stack size by removing the HAY from the STACK
// recursion terminates when $stack size is 0
$stack = $index >= 0 ? array_splice($stack, $index) : [];
// update subcategory of the found nesting
$hay['Subcategory'] = get_nested_item($value, $stack);
}
}
return $hay;
}
// get the position of $hay in a $stack using the ID
function get_key($hay, $stack) {
foreach ($stack as $key => $value) {
if($hay['ID'] == $value['ID']) return $key;
}
return -1;
}
// print array so that it understandable
function print_array($arr) {
foreach ($arr as $key => $value) {
print_r($value);
echo "<br>";
}
}
// Test case
// print array before nesting
print_array($array_name);
echo"<br>";
// print array after nesting
$new_array = get_nested_array($array_name);
print_array($new_array);

Related

how to check whether 4 exist or not in array at 'id' key position

how to check whether 4 exist or not in array at id key position
$arr = array(
array(
'id' => 1,
'other_data' => 'ganesh'
),
array(
'id' => 2,
'other_data' => 'ramesh'
),
array(
'id' => 3,
'other_data' => '4'
),
)
The array you provided is not a valid multi-dimensional array. You need to add commas after each array in the array. Below i use array_column and in_array without using foreach
<?php
$arr = array(
array(
'id' => 1,
'other_data' => 'ganesh'
),//add comma
array(
'id' => 2,
'other_data' => 'ramesh'
),
array(
'id' => 3,
'other_data' => '4'
),
);
$filtered = array_column($arr, 'id');//return the id column in this array
if(in_array(4, $filtered)){//check if 4 exists
echo '4 exists';
} else {
echo '4 does not exist';
}
?>
Quite simple, loop through the array and check if the value exists:
$value = 4;
$exists = false;
foreach($arr as $innerArr){
if($innerArr['id'] == $value){
$exists = true;
break;
}
}
If $exists is now true, the value exists within the array.
Try this one, and let me know if you face any problem.
<?php
$arr = array(
array(
'id' => 1,
'other_data' => 'ganesh'
),
array(
'id' => 2,
'other_data' => 'ramesh'
),
array(
'id' => 3,
'other_data' => '4'
)
);
foreach ($arr as $key => $value) {
if (in_array("4", $value))
{
$sub_index = $value['id'];
echo "Match found at $sub_index";
break;
}
}
Just gotta loop through your array and check existence through in_array() function.
you need something like this
$arr = array(
array(
'id' => 1,
'other_data' => 'ganesh'
),
array(
'id' => 2,
'other_data' => 'ramesh'
),
array(
'id' => 3,
'other_data' => '4'
)
);
$exists_flag = false;
foreach($arr as $inside_arr)
{
if($inside_arr['other_data'] == 4) {
$exists_flag = true;
break;
}
}
print($exists_flag);
Hope this helps!
As it stand, your array is wrong, you need to separate multi array with commas, you need to not that in_array() will not work with multi array, however you may create a recursive function that will check a provided key does exists or not in an array try code below, hope it helps ,
<?php
$arr = array(
array(
'id' => 1,
'other_data' => 'ganesh'
),
array(
'id' => 2,
'other_data' => 'ramesh'
),
array(
'id' => 3,
'other_data' => '4'
)
);
function search_items($search_value, $array)
{
foreach ($array as $item) {
if (($item == $search_value) || (is_array($item) && search_items($search_value, $item))) {
return true;
}
}
return false;
}
echo search_items("4", $arr) ? 'item found' : 'item not found';
?>
$result = array_search(4, array_column($arr, 'id'));
If we split this into steps then it would be the following:
$allColumnsNamedId = array_column($arr, 'id'); // find all columns with key 'id'
$resultBoolean = array_search(4, $allColumnsNamedId); //search the array for value 4
if($resultBoolean) {
echo 'Exists';
} else {
echo 'Does not exist';
}

unable to delete array key from multidimensional array in cakephp

I want to delete array index which contain rating 0 here is my array
array(
(int) 0 => array(
'Gig' => array(
'id' => '1',
'rating' => (int) 5
)
),
(int) 1 => array(
'Gig' => array(
'id' => '3',
'rating' => (int) 9
)
),
(int) 2 => array(
'Gig' => array(
'id' => '4',
'rating' => '0'
)
)
)
and what I did
for($i = 0; $i<count($agetGigsItem); $i++)
{
if($agetGigsItem[$i]['Gig']['rating']==0)
{
unset($agetGigsItem[$i]);
}
$this->set('agetGigsItem', $agetGigsItem);
}
i also try foreach loop but unable to resolve this issue.
foreach ($agetGigsItem as $key => $value) {
if ($value["Gig"]["rating"] == 0) { unset($agetGigsItem[$key]); }
}
I think you need to reupdate your array.
foreach ($agetGigsItem as $key => $value) {
if ($value["Gig"]["rating"] != 0)
{
unset($agetGigsItem[$key]);
}
$this->set('agetGigsItem', $agetGigsItem);
}
I hope you are missing $this and so you cannot access the array in CakePHP.
So try this:
foreach ($this->$agetGigsItem as $key => $value) {
if ($value["Gig"]["rating"] == 0) {
unset($this->$agetGigsItem[$key]);
}
}
This code will unset arrey index with value 0.
<?php
$array=array(
array(
'Gig' => array(
'id' => '1',
'rating' =>5
)
),
array(
'Gig' => array(
'id' => '3',
'rating' =>9
)
),
array(
'Gig' => array(
'id' => '4',
'rating' =>0
)
)
);
foreach($array as $a){
if($a['Gig']['rating']==0){
unset($a['Gig']['rating']);
}
$array1[]=$a;
}
var_dump($array1);
Destroying occurances within an array you are actually processing over with a for or a foreach is always a bad idea. Each time you destroy an occurance the loop can easily get corrupted and get in a terrible mess.
If you want to remove items from an array it is better to create a copy of the array and process over that new array in the loop but remove the items from the original array.
So try this instead
$tmparray = $this->agetGigsItem; // will copy agetGigsItem into new array
foreach ($tmparray as $key => $value) {
if ($value["Gig"]["rating"] == 0) {
unset($this->agetGigsItem[$key]);
}
}
unset($tmparray);

How to loop through array with indeterminate amount of children arrays

Im trying to list categories with sub categories in my app - I can use either PHP or Javascript / Jquery for the following:
I have an array of categories with sub categories appended as additional arrays
the trick is that it can go as deep as there are sub categories.
and sub categories can also have sub categories.
Therefore for each category it can have as many children each of whom can have many children arrays.
What would be the best way to loop through them to create a dropdown list?
Here is the structure when dumping the main array:
array (size=2)
0 =>
array (size=4)
'id' => int 1
'name' => string 'Stationery' (length=10)
'parent_id' => int 0
'childs' =>
array (size=1)
0 =>
array (size=4)
...
1 =>
array (size=3)
'id' => int 4
'name' => string 'boots' (length=5)
'parent_id' => int 0
notice sub zero has a "childs" array
when dumping this array i get:
array (size=1)
0 =>
array (size=4)
'id' => int 2
'name' => string 'pens' (length=4)
'parent_id' => int 1
'childs' =>
array (size=1)
0 =>
array (size=4)
...
Notice this too has a child attached which when dumped looks like:
array (size=1)
0 =>
array (size=4)
'id' => int 3
'name' => string 'penfillers' (length=10)
'parent_id' => int 2
'childs' =>
array (size=1)
0 =>
array (size=3)
...
Sneaky - this one also has another child!
This can go as deep as there are sub categories
How would i loop through them and have the output in a dropdown list?
Im stumped as to how to loop infinitely until the chain ends.
Thanks
Jason
You should recursively yield all the options in the array. There are 2 ways to implement it. Depends on your PHP version.
To make the core logic cleaner, let's say we'd render the output with these utilities:
//
// some function to tidy up outputs
//
// simply make the recursive level visible
function pad_level($string, $level) {
// no pad for 0 level
if ($level == 0) return $string;
// pad some '-' to show levels
$pad = str_repeat('-', $level);
return $pad . ' ' . $string;
}
// render a leaf into standardized info array for an option
function option_from($item, $level) {
return array(
'value' => $item['id'],
'display_value' => pad_level($item['name'], $level),
);
}
// render options into HTML select
function render_select($name, $options) {
$output = '';
foreach ($options as $option) {
$output .= ' '.
'<option value="'.htmlspecialchars($option["value"]).'">'.
htmlspecialchars($option["display_value"]).
'</option>'."\n";
}
return '<select name="'.htmlspecialchars($name).'">'."\n".
$output."</select>\n";
}
// render options into plain text display
function render_plain_text($name, $options) {
$output = '';
foreach ($options as $option) {
$output .= $option["value"].' => '.$option["display_value"]."\n";
}
return $output;
}
These are the 2 methods:
//
// core logic
//
// Method 1: Generator. Available with PHP 5 >= 5.5.0
function options_in($array, $level=0) {
foreach ($array as $leaf) {
yield option_from($leaf, $level);
// yield the children options, if any
if (isset($leaf['childs']) && is_array($leaf['childs'])) {
foreach (options_in($leaf['childs'], $level+1) as $option) {
yield $option;
}
}
}
}
// Method 2: Normal recursion then merge arrays. For PHP 4 or after
function get_options($array, $level=0) {
$output = array();
// yield for the option array
foreach ($array as $leaf) {
$output[] = option_from($leaf, $level);
// yield the children
if (isset($leaf['childs']) && is_array($leaf['childs'])) {
$childs = get_options($leaf['childs'], $level+1);
$output = array_merge($output, $childs); // this could be slow
}
}
return $output;
}
And this is how you actually render some HTML from it:
// dummy input
$input = array(
array(
'id' => 1,
'name' => 'Stationery',
'parent_id' => 0,
'childs' => array(
array(
'id' => 2,
'name' => 'Pencil',
'parent_id' => 1,
),
array(
'id' => 3,
'name' => 'Pen',
'parent_id' => 1,
),
array(
'id' => 5,
'name' => 'Notepad',
'parent_id' => 1,
'childs' => array(
array(
'id' => 8,
'name' => 'Blue Pad',
'parent_id' => 3,
),
array(
'id' => 9,
'name' => 'Red Pad',
'parent_id' => 3,
),
array(
'id' => 10,
'name' => 'iPad',
'parent_id' => 3,
),
),
),
),
),
array(
'id' => 4,
'name' => 'boots',
'parent_id' => 0,
),
);
// method 1, preferred
echo "\nMethod 1\n";
echo render_select('mySelect', options_in($input));
echo render_plain_text('mySelect', options_in($input));
// method 2
echo "\nMethod 2\n";
echo render_select('mySelect', get_options($input));
echo render_plain_text('mySelect', get_options($input));
Here is a really simple example of how you could do it using recursion. I'm sure there is better ways but this is a very simple function so you can see the concept. The function calls itself until the job is done (I'm using the square bracket array notation here so make sure your PHP version supports it)
<?php
function getItems(array $items)
{
$return = [];
foreach ($items as $item) {
$return[] = $item;
if (isset($item['childs']) && count($item['childs']) > 0) {
$return = array_merge(
$return,
getItems($item['childs'])
);
}
}
return $return;
}
$array = [
0 => [
'id' => 1,
'childs' => []
],
1 => [
'id' => 2,
'childs' => [
0 => [
'id' => 3,
'childs' => []
]
]
]
];
print_r(getItems($array));
Then just loop over the results to create your select options. Hope this helps
create a sub function, input of the sub function would be object ,this function will check if it is an array or a simple element, if it is an array then it will call the function again on that element else return the element.
just elaborate "Gerald Schneider"'s answer.

PHP: merge multidimensional, associative arrays (LEFT JOIN simulation - keep duplicates, keep non-existent elements)

I got two associative, multidimensional arrays $arrayOffered and $arraySold. I would like to merge them under certain conditions:
if value of key 'item' from $arrayOffered exists in $arraySold, both elements should be included in array $result. If for 1 element from $arrayOffered there are 3 elements in $arraySold, I should get also 3 elements in $result.
otherwise, element from $arrayOffered should be added into $result.
One element from $arrayOffered can have >1 equivalents in $arraySold. They should be joined in the way shown below.
Input data:
$arrayOffered = array(
0 => array('item' => 'product_1', 'Category' => 'ABC'),
1 => array('item' => 'product_2', 'Category' => 'DEF')
);
$arraySold = array(
0 => array('item' => 'product_1', 'ItemsSold' => '2', 'ItemsReturned' => 1), //arrays in this array can contain up to 30 elements
1 => array('item' => 'product_1', 'ItemsSold' => '1')
);
Desired result:
$desiredResult = array(
0 => array('item' => 'product_1', 'Category' => 'ABC', 'ItemsSold' => '2', 'ItemsReturned' => 1),
1 => array('item' => 'product_1', 'Category' => 'ABC', 'ItemsSold' => '1'),
2 => array('item' => 'product_2', 'Category' => 'DEF')
);
I got stuck on something like:
$result = array();
foreach ($arrayOffered as $keyOffered => $offeredSubArr)
{
$item = $offeredSubArr['item'];
foreach($arraySold as $keySold => $soldSubArr)
{
if(isset($soldSubArr['item']) && $soldSubArr['item'] == $item)
{
$i = 0;
$test = array_merge($offeredSubArr, $soldSubArr);
$result[$i][] = $test;
$i++;
}
else
{
$result[$i][] = $offeredSubArr;
$i++;
}
}
}
Problem:
- output array isn't formatted the way I wanted
- I know I'm not going in the right direction. Can you please give me a hint?
This is an option, since you have this $arrayOffered as a kind of master file I suggest to build a hash with this array and use later on the foreach look for sold array.
$arrayOffered = array(
0 => array('item' => 'product_1', 'Category' => 'ABC'),
1 => array('item' => 'product_2', 'Category' => 'DEF')
);
$arraySold = array(
0 => array('item' => 'product_1', 'ItemsSold' => '2', 'ItemsReturned' => 1), //arrays in this array can contain up to 30 elements
1 => array('item' => 'product_1', 'ItemsSold' => '1')
);
//Build a hash to get the extra properties
$hashArray = array();
foreach ($arrayOffered as $offered) {
$hashArray[$offered['item']]=$offered;
}
$resultArray = array();
foreach ($arraySold as $sold) {
$hashItem = $hashArray[$sold['item']];
// you dont want this sold flag on your final result
unset($hashItem['sold']);
$resultArray[]=array_merge($hashItem,$sold);
$hashArray[$sold['item']]['sold']= true;
}
//Add all the missing hash items
foreach($hashArray as $hashItem){
if(!isset($hashItem['sold'])){
$resultArray[]=$hashItem;
}
}
print_r($resultArray);
Test sample
http://sandbox.onlinephpfunctions.com/code/f48ceb3deb328088209fbaef4f01d8d4430478db
$result = array();
foreach ($arrayOffered as $keyOffered => $offeredSubArr)
{
$item = $offeredSubArr['item'];
foreach($arraySold as $keySold => $soldSubArr)
{ $i = 0;
if(isset($soldSubArr['item']) && $soldSubArr['item'] == $item)
{
$test = array_merge($offeredSubArr, $soldSubArr);
$result[$i][] = $test;
}
else
{
$result[$i][] = $offeredSubArr;
}
$i++;
}
}
$result = $result[0];
echo '<pre>'; print_r($result); die();
Well i will try to follow your logic although there is simpler solutions.
First of all we will need to search in a multidimentional array thats why we will need the followed function from this so thread
function in_array_r($needle, $haystack, $strict = false) {
foreach ($haystack as $item) {
if (($strict ? $item === $needle : $item == $needle) || (is_array($item) && in_array_r($needle, $item, $strict))) {
return true;
}
}
return false;
}
Next after small changes:
$i you don't need to make it zero on every loop just once so place it outside
unnecessary [] ($result[$i][]) you don't need the empty brackets no reason to create an extra table in the $i row since what you add there, the $test is already table itself
Adding the last loop coz when sth is not in the second table it will be added in your new table in every loop and as far as i get you don't want that kind of duplicates
We have the following code:
$arrayOffered = array(
0 => array('item' => 'product_1', 'Category' => 'ABC'),
1 => array('item' => 'product_2', 'Category' => 'DEF')
);
$arraySold = array(
0 => array('item' => 'product_1', 'ItemsSold' => '2', 'ItemsReturned' => 1), //arrays in this array can contain up to 30 elements
1 => array('item' => 'product_1', 'ItemsSold' => '1')
);
$i = 0;
$result = array();
foreach ($arrayOffered as $keyOffered => $offeredSubArr)
{
$item = $offeredSubArr['item'];
foreach($arraySold as $keySold => $soldSubArr)
{
if(isset($soldSubArr['item']) && $soldSubArr['item'] == $item)
{
$test = array_merge($offeredSubArr, $soldSubArr);
$result[$i] = $test;
$i++;
}
}
}
foreach ($arrayOffered as $value)
{
if (!in_array_r($value['item'], $result))
{
$result[$i] = $value;
$i++;
}
}
print_r($result);
Which as far as i tested gives the wanted result.

Convert PHP array to JSON tree

I have got a array in this format:
array(
array('id' => 1, 'parent_id' => null, 'name' => 'lorem ipsum'),
array('id' => 2, 'parent_id' => 1, 'name' => 'lorem ipsum1'),
array('id' => 3, 'parent_id' => 1, 'name' => 'lorem ipsum2'),
array('id' => 4, 'parent_id' => 2, 'name' => 'lorem ipsum3'),
array('id' => 5, 'parent_id' => 3, 'name' => 'lorem ipsum4'),
array('id' => 6, 'parent_id' => null, 'name' => 'lorem ipsum5'),
);
I have to convert this array to json object with this style:
var json = {
id: "1",
name: "loreim ipsum",
data: {},
children: [{
id: "2",
name: "lorem ipsum1",
data: {},
children: [{
id: "3",
name: "lorem ipsum2",
data: {},
children: [{
..............
How can i do this? Thanks.
My solution:
$data = array(
array('id' => 1, 'parent_id' => null, 'name' => 'lorem ipsum'),
array('id' => 2, 'parent_id' => 1, 'name' => 'lorem ipsum1'),
array('id' => 3, 'parent_id' => 1, 'name' => 'lorem ipsum2'),
array('id' => 4, 'parent_id' => 2, 'name' => 'lorem ipsum3'),
array('id' => 5, 'parent_id' => 3, 'name' => 'lorem ipsum4'),
array('id' => 6, 'parent_id' => null, 'name' => 'lorem ipsum5'),
);
$itemsByReference = array();
// Build array of item references:
foreach($data as $key => &$item) {
$itemsByReference[$item['id']] = &$item;
// Children array:
$itemsByReference[$item['id']]['children'] = array();
// Empty data class (so that json_encode adds "data: {}" )
$itemsByReference[$item['id']]['data'] = new StdClass();
}
// Set items as children of the relevant parent item.
foreach($data as $key => &$item)
if($item['parent_id'] && isset($itemsByReference[$item['parent_id']]))
$itemsByReference [$item['parent_id']]['children'][] = &$item;
// Remove items that were added to parents elsewhere:
foreach($data as $key => &$item) {
if($item['parent_id'] && isset($itemsByReference[$item['parent_id']]))
unset($data[$key]);
}
// Encode:
$json = json_encode($data);
Here's code to do what you need. It does not need the items to be in parent-children order in the array, but will finish faster if they are.
Please study the comments to understand what the code is doing and why; and if you still have questions, ask them too!
// Assume your array is $data
$root = new stdClass; // this is your root item
$objectMap = array(); // this holds objects indexed by their id
// Since we need to iterate over the array, but there may be no guarantee
// that an item's parent will be always encountered before the item itself,
// we loop as many times as needed, skipping items whose parent we have not
// yet seen. Hopefully we will see it later and be able to process these
// items in the next iteration.
while (!empty($data)) {
// Remember how many items there are when starting the loop
$count = count($data);
// Do the actual work!
foreach ($data as $key => $row) {
$parentId = $row['parent_id'];
if ($parentId === null) {
// We just met the root element
$current = $root;
}
else if (isset($objectMap[$parentId])) {
// We met an element with a parent that we have already seen
$current = new stdClass;
}
else {
// We met an element with an unknown parent; ignore it for now
continue;
}
// Put the current element on the map so that its children will
// be able to find it when we meet them
$objectMap[$row['id']] = $current;
// Add the item to its parent's children array
$objectMap[$parentId]->children[] = $current;
// Set the item's properties
$current->id = $row['id'];
$current->name = $row['name'];
$current->data = new stdClass; // always empty
$current->children = array();
// We successfully processed this, remove it (see why below!)
unset($data[$key]);
}
// OK, we looped over the array once. If the number of items has
// not been reduced at all, it means that the array contains only
// items whose parents do not exist. Instead of looping forever,
// let's just take what we are given and stop here.
if ($count == count($data)) {
break;
}
// If there are still items in $data, we will now iterate again
// in the hope of being able to process them in the next iteration
}
// All set! If $data is not empty now, it means there were items
// with invalid parent_ids to begin with.
$output = json_encode($root);
My take (I know an answer has been accepted, but I worked on this so I'm gonna post id =P)
// Test data
$data = array(
array('id' => 1, 'parent_id' => null, 'name' => 'lorem ipsum'),
array('id' => 2, 'parent_id' => 1, 'name' => 'lorem ipsum1'),
array('id' => 3, 'parent_id' => 1, 'name' => 'lorem ipsum2'),
array('id' => 4, 'parent_id' => 2, 'name' => 'lorem ipsum3'),
array('id' => 5, 'parent_id' => 3, 'name' => 'lorem ipsum4'),
array('id' => 6, 'parent_id' => null, 'name' => 'lorem ipsum5'),
);
// Randomize, because the data may not be in a top-down order
shuffle( $data );
// Parse and inspect the result
$builder = new TreeBuilder( $data );
echo '<pre>', print_r( $builder->getTree() ), '</pre>';
class TreeBuilder
{
protected $leafIndex = array();
protected $tree = array();
protected $stack;
function __construct( $data )
{
$this->stack = $data;
while( count( $this->stack ) )
{
$this->branchify( array_shift( $this->stack ) );
}
}
protected function branchify( &$leaf )
{
// Root-level leaf?
if ( null === $leaf['parent_id'] )
{
$this->addLeaf( $this->tree, $leaf );
}
// Have we found this leaf's parent yet?
else if ( isset( $this->leafIndex[$leaf['parent_id']] ) )
{
$this->addLeaf( $this->leafIndex[$leaf['parent_id']]['children'], $leaf );
} else {
// Nope, put it back on the stack
$this->stack[] = $leaf;
}
}
protected function addLeaf( &$branch, $leaf )
{
// Add the leaf to the branch
$branch[] = array(
'id' => $leaf['id']
, 'name' => $leaf['name']
, 'data' => new stdClass
, 'children' => array()
);
// Store a reference so we can do an O(1) lookup later
$this->leafIndex[$leaf['id']] = &$branch[count($branch)-1];
}
protected function addChild( $branch, $leaf )
{
$this->leafIndex[$leaf['id']] &= $branch['children'][] = $leaf;
}
public function getTree()
{
return $this->tree;
}
}

Categories