PHP Print indefinite categories tree - php

I'm trying to print list of categories with indefinite subcategories.
Example:
[
[
'categoryName' => 'Category1',
'categoryUrl' => 'category-1',
'subcategories' => [
[
'categoryName' => 'Subcategory 1',
'categoryUrl' => 'sucbategory-1',
'subcategories' => [
[
'categoryName' => 'Subcategory subcategory 1',
'categoryUrl' => 'sucbategory-subcategory-1',
'subcategories' => [
[
'....'
]
]
]
],
[
'categoryName' => 'Subcategory 2',
'categoryUrl' => 'sucbategory-12',
]
]
]
]
]
I was trying it with foreach inside foreach, etc...
Then I realised I don't know how many levels category tree will have.
Category1->Subcategory1->Subcategory Subcategory1-> Subcategory ... 1-> ??

This is called a recursion. The idea is like this:
function printLeafs($node){
echo $node->title;
$leafs = getLeafs($node);
foreach ($leafs as $leaf){
printLeafs($leaf);
}
}

This will print all using recursion.
function recurse($array) {
foreach( $array as $one ) {
echo $one['categoryName'] . '->' ;
echo $one['categoryUrl'] . '->' ;
if( isset($one['subcategories']) ) {
if( is_array($one['subcategories'])) {
recurse($one['subcategories']) ;
}
}
}
};
recurse($array);
But in your code I've noted a problem, the following looks misplaced. In this case you are having categoryName not having a parent subcategories. If that a typo the above will work. Otherwise it wont.
'categoryName' => 'Subcategory 2',
'categoryUrl' => 'sucbategory-12',

Related

Create a tree from a flat array

I am having trouble in converting a flat array, e.g. from a DB query, into a tree structure.
I have something like this:
[
[
id => 11,
path => '/11',
label => 'AAA'
],
[
id => 12,
path => '/12',
label => 'BBB'
],
[
id => 21,
path => '/12/21',
label => 'BBBB'
],
[
id => 21,
path => '/12/22',
label => 'CCCCC'
],
]
path points to the hierarchical position inside the tree, defined by the id's. So in the end I have to get something like this:
$tree=[
[
'id' => '11',
'label' => 'AAA'
],
[
'id' => '12',
'label' => 'BBB',
'children' => [
[
'id' => '21',
'label' => 'BBBB'
],
[
'id' => '22',
'label' => 'CCCCC'
]
]
]
];
The depth can be infinite. I would appreciate any solution. Thank you :-)
Thank you all, but I am still stuck :-( Here's my way so far.
First I order the categories by depth & loop them in this order. This way I can be sure that the parent node exists.
public function buildCategoryTree() {
// order by depth
$categoryLevel = [];
foreach (Category::all() as $category) {
$catPathSplitted = array_filter(explode("/", $category->path));
$categoryLevel[count($catPathSplitted)][] = $category;
}
$categoryTree = [];
// now loop each level and create a tree node
foreach ($categoryLevel as $level => $categories) {
foreach ($categories as $category) {
$test = $category->path;
$catPathSplitted = array_filter(explode("/", $category->path));
$categoryTree = $this->addNode($category, $catPathSplitted, $categoryTree, $test);
}
}
}
Then I try to use this recursion, but it only works partially, means I get the child nodes in the hierarchical order,
but the child nodes are also created again on each level. So there's something wrong :-(
private function addNode($cat, $keys, &$tree) {
foreach ($keys as $counter => $id) {
if (!next($keys)) {
if (!isset($tree[$id]['category'])) {
$tree[$id]['category'] = $cat->toArray();
}
} else {
if (!isset($tree[$id]['children'])) {
$tree[$id]['children'] = [];
}
array_shift($keys);
$this->addNode($cat, $keys, $tree[$id]['children'], $test);
}
}
return $tree;
}
Anybody can spot the flaw?

Lower child array keys and combine items

I've one array
$arr = [
'parent' => [
'CHILD' => [
5,6
],
'child' => [
1,2,3,4
],
'Child' => [
5,6,7,8
],
...
]
];
I want to lower the child keys and combine each child having the same case insensitive keys like
$arr = [
'parent' => [
'child' => [
1,2,3,4,5,6,7,8
],
]
];
I've tried with array_change_key_case which always takes the last element and ignores the others.
An array may have multiple children with the same key (with different case)
Try the code below should work:
<?php
$arr = [
'parent' => [
'CHILD' => [
5,6
],
'child' => [
1,2,3,4
],
]
];
$arNew = [];
foreach ($arr as $sParent => $ar) {
foreach ($ar as $sChild => $ar1) {
$sChild = strtolower($sChild);
if (empty($arNew[$sParent][$sChild])) {
$arNew[$sParent][$sChild] = $ar1;
} else {
$arNew[$sParent][$sChild] = array_merge($arNew[$sParent][$sChild], $ar1);
}
}
}
print_r($arNew);

PHP Array function to compare and merge values

Appreciate your time!
After reviewing several 'Compare and Merge' threads, finally, I am going to request someone to help with this very specific scenario.
$input = array(
[ 2616 ] => array(
[ 9878767654 ] => array(
[ 987987987 ] => 987987987,
[ 987987986 ] => 987987986,
),
),
[ 2618 ] => array(
[ 9878767654 ] => array(
[ 987987987 ] => 987987987,
),
),
[ 'tmp-9878767654' ] => array(
[ 9878767654 ] => array(
[ 987987985 ] => 987987985,
[ 987987987 ] => 987987987,
),
),
[ 'tmp-9878767655' ] => array(
[ 9878767655 ] => array(
[ 987987975 ] => 987987975,
),
),
);
$desired_output = array(
[ 2616 ] => array(
[ 9878767654 ] => array(
[ 987987987 ] => 987987987,
[ 987987986 ] => 987987986,
[ 987987985 ] => 987987985,
),
),
[ 2618 ] => array(
[ 9878767654 ] => array(
[ 987987987 ] => 987987987,
[ 987987986 ] => 987987986,
[ 987987985 ] => 987987985,
),
),
[ 'tmp-9878767655' ] => array(
[ 9878767655 ] => array(
[ 987987975 ] => 987987975,
),
),
);
This is the inventory of products (listed by Product ID and Model ID) by Store ID. I want to merge the Model ID values WHERE the product id is the same FROM the array with store-ID starting with 'tmp-'. If product ID is not matched then I want that array to stay as it is. I hope I am making some sense.
Please help.
Here is a snippet to solve the specific problem posed by your example:
$temporaryStores = [];
$prefix = 'tmp-';
$prefixLength = strlen($prefix);
// extract the temporary store structures
foreach ($input as $storeId => $store) {
if (is_string($storeId) && strpos($storeId, $prefix) === 0) {
$productId = (int) substr($storeId, $prefixLength);
$temporaryStores[$productId] = $store;
unset($input[$storeId]);
}
}
// merge matching temporary store structures into the actual ones
$mergedProductIds = [];
foreach ($temporaryStores as $temporaryProductId => $temporaryModels) {
$temporaryModels = reset($temporaryModels); // Incompatible array structure
foreach ($input as $storeId => $store) {
foreach ($store as $productId => $models) {
if ($productId === $temporaryProductId) {
$modelsIds = array_merge($temporaryModels, $models);
$modelsIds = array_unique($modelsIds);
$input[$storeId][$productId] = $modelsIds;
$mergedProductIds[] = $temporaryProductId;
unset($temporaryStores[$temporaryProductId]);
}
}
}
}
// append leftover temporary store structures to the result
foreach ($temporaryStores as $temporaryProductId => $temporaryModels) {
if (!in_array($temporaryProductId, $mergedProductIds, true)) {
$input[$prefix . $temporaryProductId] = $temporaryModels;
}
}
var_dump($input);
This snippet might work for you or not. Either way, I strongly suggest you refactor this code into using a more object oriented design. Where it is made obvious what each value/structure represents, and validation can occur in isolation.
Now you are left having to deal with incompatible array structures that visually look like an incomprehensible mess.

Recursive array parse with nested nodes

I have blackhole in my mind. Im trying to parse array with multilevel nodes. Here's example array:
global $array;
$array = [
'0' => [
'id' => 1,
'parent' => 0,
'name' => 'root 0'
],
'1' => [
'id' => 2,
'parent' => 1,
'name' => 'root 1'
],
'2' => [
'id' => 3,
'parent' => 2,
'name' => 'root 2'
],
'3' => [
'id' => 4,
'parent' => 3,
'name' => 'root 3'
],
'4' => [
'id' => 5,
'parent' => 3,
'name' => 'root 4'
],
'5' => [
'id' => 6,
'parent' => 2,
'name' => 'root 2'
]
];
This should looks after parse like this. Element 3 with parent 3 should have parent 1, because element 2 has parent 2, and its first child.
I trying to get to this using foreach and function:
global $new_array;
$new_array = [];
foreach( $array as $item )
{
if( $item['parent'] == 0 ) {
$new_array[] = $item; // if parent 0 - clone into new array
continue;
}
//echo $item['name'] . PHP_EOL;
$new_array[] = check_parent( $item['parent'] );
}
print_r($new_array);
function check_parent( $parent )
{
//echo '- check for parent of ' . $parent . PHP_EOL;
global $array;
foreach( $array as $item ) {
if( $item['id'] == $parent && $item['parent'] == 0 ) {
//echo '[OK] found root parent id: ' . $item['id'] . PHP_EOL;
$item['parent'] = $item['id'];
return $item;
} else {
return check_parent( $item['id'] );
}
}
}
I'm so confused, but I didn't see where I make a mistake. Maybe someone, can help me to see - where's problem. I working on it few hours and for now, I had blackhole in my mind.
Fiddle:
https://implode.io/jHS8m1
Desired output:
$new_array = [
'0' => [
'id' => 1,
'parent' => 0,
'name' => 'root 0'
],
'1' => [
'id' => 2,
'parent' => 1,
'name' => 'root 1'
],
'2' => [
'id' => 3,
'parent' => 2, // this should have after parse parent 1
'name' => 'root 2'
],
'3' => [
'id' => 4,
'parent' => 3, // this should have after parse parent 1
'name' => 'root 3'
],
'4' => [
'id' => 5,
'parent' => 3, // this should have after parse parent 1
'name' => 'root 4'
],
'5' => [
'id' => 6,
'parent' => 2, // this should have after parse parent 1
'name' => 'root 2'
]
];
Thanks !
Replace the following line in your code
$new_array[] = check_parent( $item['parent'] ); // get child
with below lines of code.
$temp = check_parent( $item['parent'] ); // get child
$item['parent'] = $temp['id'];
$new_array[] = $item;
What is happening is that your check_parent is returning the $item, which happens to be the parent. However, we are only interested in the id of this. So we get the id and replace the parent it in the original $item.
Here is the working Demo
A bit tardy in my response, but I think it is valuable to provide a refined recursive solution.
My snippet:
Modifies by reference
Does not leverage a global variable declaration
Uses just one loop in the custom recursive function.
Code: (Demo)
function replaceParent(&$array, $parent = null) {
foreach ($array as &$item) {
if ($item['id'] == $parent) {
if ($item['parent']) {
return replaceParent($array, $item['parent']);
} else {
return $item['id'];
}
} elseif ($item['parent']) {
$item['parent'] = replaceParent($array, $item['parent']);
}
}
}
replaceParent($array);
var_export($array);
I'll try to explain...
id 1's parent value of 0 fails both primary conditions, so no recursion/processing is performed on that row of data.
id 2's parent value of 1 passes the elseif condition, so the recursive call goes in search of the row with an id of 1. Finding id 1 with a parent value of 0 means the else branch is satisfied and the id value of 1 is passed back through the recursive call to be assigned to $item['parent'] for the original id 2.
To process id 3 (or deeper), multiple recursive calls occur and all conditions play a role in the search and assignment process. First the elseif leads to the first recursion, then the if's if leads to the second recursion, finally the if's else passes the root id's value all the way back to the original grandchild.

Add grouping elements to 2d array and sum values in respective group

I need to add new elemets to my array when a new category value is encountered. When a category value is encountered after the first time, its value1 and value2 values should be added to the first encounter's respective values.
Also, in the result array, I no longer wish to keep the category column. The category-grouping rows should use the category value as its name value.
Sample input:
$datas = [
[
'category' => 'Solution',
'name' => 'Name1',
'value1' => 20,
'value2' => 21
],
[
'category' => 'Solution',
'name' => 'Name2',
'value1' => 30,
'value2' => 31
],
[
'category' => 'Solution1',
'name' => 'Name3',
'value1' => 40,
'value2' => 41
]
];
Desired result:
[
['name' => 'Solution', 'value1' => 50, 'value2' => 52],
['name' => 'Name1', 'value1' => 20, 'value2' => 21],
['name' => 'Name2', 'value1' => 30, 'value2' => 31],
['name' => 'Solution1', 'value1' => 40, 'value2' => 41],
['name' => 'Name3', 'value1' => 40, 'value2' => 41]
]
I tried like this:
private function groupByProductSuperCategory($datas)
{
$return = [];
foreach ($datas as $data) {
$return[$data['category']][$data['name']] = array_sum(array_column('category', $data);
}
return $return;
}
The idea is to calculate first all sum values for by category, and after that just put values from name like another array. Have you an idea of how to do that?
From the posted array... To end in the desired array, there is some tiny fixes to do first. But I assumed it was due to typos while copying here...
So here is the array I started with:
$result = [
0 => [
"category" => 'Solution',
"name" => 'Name1',
"value1" => 20,
"value2" => 21
],
1 => [
"category" => 'Solution',
"name" => 'Name2',
"value1" => 30,
"value2" => 31
],
2 => [
"category" => 'Solution1',
"name" => 'Name3',
"value1" => 40,
"value2" => 41
]
];
Now, that re-organization of the data is a bit more complex than it looks... You need to perform several loops to:
Find distinct "category" names
Perform the summations for each
Add the sum item and the single items
So here is the code I ended with:
function groupByProductSuperCategory($datas){
$category = [];
$return = [];
// Find distinct categories
foreach ($datas as $data) {
if(!in_array($data["category"],$category)){
array_push($category,$data["category"]);
}
}
// For each distinct category, add the sum item and the single items
foreach ($category as $cat) {
// Get the sums
if(!in_array($cat,$return)){
$sum1 = 0;
$sum2 = 0;
foreach ($datas as $data) {
if($data["category"] == $cat){
$sum1 += $data["value1"];
$sum2 += $data["value2"];
}
}
}
// Push the sums in the return array
array_push($return,[
"name" => $cat,
"value1" => $sum1,
"value2" => $sum2,
]);
// Push the single elements
foreach ($datas as $data) {
if($cat == $data["category"]){
array_push($return,[
"name" => $data["name"],
"value1" => $data["value1"],
"value2" => $data["value2"],
]);
}
}
}
return $return;
}
Here is a PHPFiddle to try it out... Hit [F9] to run.
It is much more direct, efficient, and readable to implement a single loop and push reference variables into the result array to allow summing based on shared categories without keeping track of the actual indexes of the category rows.
Code: (Demo)
$result = [];
foreach ($array as $row) {
if (!isset($ref[$row['category']])) {
$ref[$row['category']] = [
'name' => $row['category'],
'value1' => 0,
'value2' => 0
];
$result[] = &$ref[$row['category']];
}
$ref[$row['category']]['value1'] += $row['value1'];
$ref[$row['category']]['value2'] += $row['value2'];
unset($row['category']);
$result[] = $row;
}
var_export($result);

Categories