Nesting an array based on parent IDs - php

I've been stuck trying to figure this out for a week now. I have an array in the following format:
[
1 => [
'name' => 'Maths',
'parent_category_id' => NULL
],
2 => [
'name' => 'Algebra',
'parent_category_id' => 1
],
3 => [
'name' => 'Expanding brackets',
'parent_category_id' => 2
],
4 => [
'name' => 'Factorising brackets',
'parent_category_id' => 2
],
5 => [
'name' => 'English',
'parent_category_id' => NULL
],
6 => [
'name' => 'Shakespeare',
'parent_category_id' => 5
]
]
and I want to transform it into an array in this format:
[
'Maths' => [
'category_id' => 1,
'questions' => [], //This array will then be filled with questions regarding each of the categories
'children_categories' => [
'Algebra' => [
'category_id' => 2,
'questions' => [],
'children_categories' => [
'Expanding brackets' => [
'category_id' => 3,
'questions' => [],
'children_categories' => []
],
'Factorising brackets' => [
'category_id' => 4,
'questions' => [],
'children_categories' => []
]
]
]
]
],
'English' => [
'category_id' => 5,
'questions' => [],
'children_categories' => [
'Shakespeare' => [
'category_id' => 6,
'questions' => [],
'children_categories' => []
]
]
]
]
So far I've been able to format categories that have no parents, but I for the life of me cannot figure out how to insert a category that has a parent into that parent's children_categories[] array. Here's the code I'm using, I need help figuring out what I should put in the "else" part of the foreach()
foreach($ids_as_keys as $category_id => $info){ //$info contains the name of the category, and the parent's ID (NULL if there is no parent)
if(is_null($info['parent_category_id'])){ //There is no parent, so put it at the root of $nested
$nested[$info['name']] = [
'category_id' => $category_id,
'questions' => [],
'children_categories' => []
];
}else{ //There is a parent, so search through all items (including sub-arrays, sub-sub-arrays etc.) until we find a match for the parent_category_id, and then add it into the children_categories[] array
}
}
return $nested;

I tested this, absolutely works :
$arr = array(
1 => array(
'name' => 'Maths',
'parent_category_id' => NULL
),
2 => array(
'name' => 'Algebra',
'parent_category_id' => 1
),
3 => array(
'name' => 'Expanding brackets',
'parent_category_id' => 2
),
4 => array(
'name' => 'Factorising brackets',
'parent_category_id' => 2
),
5 => array(
'name' => 'English',
'parent_category_id' => NULL
),
6 => array(
'name' => 'Shakespeare',
'parent_category_id' => 5
)
);
foreach ($arr as $key => &$value) {
if ($value['parent_category_id']) {
$arr[$value['parent_category_id']]['children_categories'][] = &$value;
}
else{
$parents[]=$key;
}
}
$result = array();
foreach ($parents as $val) {
$result[$val] = $arr[$val];
}
print_r($result);

This answer is too close may helpful for you
<?php
$array = [
1 => [
'name' => 'Maths',
'parent_category_id' => NULL
],
2 => [
'name' => 'Algebra',
'parent_category_id' => 1
],
3 => [
'name' => 'Expanding brackets',
'parent_category_id' => 2
],
4 => [
'name' => 'Factorising brackets',
'parent_category_id' => 2
],
5 => [
'name' => 'English',
'parent_category_id' => NULL
],
6 => [
'name' => 'Shakespeare',
'parent_category_id' => 5
]
];
//data array
$data = array();
$i = 0;
//gothrough one by one
foreach($array as $key=>$value)
{
//set the parent array
if(is_null($value['parent_category_id']))
{
$data[$value['name']]= array();
$data[$value['name']]['category_id'] = $key;
$data[$value['name']]['questions'] = array();
$data[$value['name']]['children_categories'] = array();
//add the childrens according to the parent
}elseif(array_key_exists($value['parent_category_id'], $array)){
//find the parent
$parent = $array[$value['parent_category_id']];
$data[$parent['name']]['children_categories'][$value['name']] = array();
$data[$parent['name']]['children_categories'][$value['name']]['category_id'] = $key;
$data[$parent['name']]['children_categories'][$value['name']]['questions'] = array();
}
}
//display the result
print_r($data);

Try using recursive function.
function insert_child($curArr,$childArray,&$parentArray){
foreach($parentArray as $key=>&$val){
if(is_array($val) && sizeof($val) > 0 ){
if($val['category_id']==$curArr['parent_category_id']){
$val['children_categories'][$curArr['name']] = $childArray;
return TRUE;
}else{
insert_child($curArr,$childArray,$val['children_categories']);
}
}
}
return FALSE;
}
Where $nest is your input array
$nest = array(
1=>array(
'name' => 'Maths',
'parent_category_id' => NULL
),
2=>array(
'name' => 'Algebra',
'parent_category_id' => 1
),
3=>array(
'name' => 'Expanding brackets',
'parent_category_id' => 2
),
4=>array(
'name' => 'Factorising brackets',
'parent_category_id' => 2
),
5=>array(
'name' => 'English',
'parent_category_id' => NULL
),
6=>array(
'name' => 'Shakespeare',
'parent_category_id' => 5
),
);
code
$result=array();
foreach($nest as $key=>$val){
$temp = array(
'category_id'=>$key,
'questions'=>array(),
'children_categories'=>array(),
);
if(!in_array($val['name'],$result) && $val['parent_category_id']==NULL){
$result[$val['name']] = $temp;
}else{
insert_child($val,$temp,$result);
}
}
echo "<pre>";
print_r($result);
exit();
PHPFIddle here

Related

How I can flatten a tree in an orderly way?

I have PHP array (tree), something like this:
$categoryTree = [
0 => [
'id' => 1360,
'parent' => 0,
'name' => 'main A',
'children' => [
0 => [
'id' => 1361,
'parent' => 1360,
'name' => 'sub a1'
],
1 => [
'id' => 57,
'parent' => 1360,
'name' => 'sub a2'
]
]
],
1 => [
'id' => 10,
'parent' => 0,
'name' => 'Main B'
]
];
I want to convert it into:
$categoryTree = [
0 => [
'id' => 1360,
'parent' => 0,
'name' => 'main A'
],
1 => [
'id' => 1361,
'parent' => 1360,
'name' => 'sub a1'
],
2 => [
'id' => 57,
'parent' => 1360,
'name' => 'sub a2'
],
3 => [
'id' => 10,
'parent' => 0,
'name' => 'Main B'
]
];
It is rather simple. You walk recursively and only make a recursive call if the node has the key children. During the iteration in the foreach loop, keep collecting results in an result array.
<?php
function flatten($tree, &$results){
foreach($tree as $kid){
$kid_copy = $kid;
unset($kid_copy['children']);
$results[] = $kid_copy;
if(isset($kid['children'])) flatten($kid['children'], $results);
}
}
Online Demo
Alternatively, using a generator, which then doesn't need the second argument:
function recurIter($arr) {
foreach ($arr as $item) {
$orig = $item;
unset($item["children"]);
yield $item;
if (isset($orig["children"])) yield from recurIter($orig["children"]);
}
}
$result = iterator_to_array(recurIter($categoryTree), false);

How can run recursive function on multiple Level array in php

I have two arrays one is $apiRes and second is $mappData i want to match fields exist in mappData array and assign value of apiRes to match field.
Note: response of api may have different and mapdata array will change according to api response My two array and output format :
<?php
$apiRes = [
[
'firstname' => 'first name des',
'title' => "title des",
'category' => 1,
'result' =>
[
0 => [
'name' => 'Masterpass',
'skill' => 'low level one'
],
1 => [
'name' => 'Visa',
'skill' => 'low level two'
],
2 => [
'name' => 'Pocketpos',
'skill' => 'low level three'
],
],
'list' => [
'product_name'=>'product name',
'amount' => [
'currency'=>'$',
'kind' => 'kind'
]
],
'priority' => 'Low',
'visible_to' => 'Everyone',
]
];
$mappData = [
0 => [
"src_field" => "firstname",
"target_field" => "new1519110449758",
"src_field_data_type" => "string"
],
1 => [
"src_field" => "result.name",
"target_field" => "new1519110811942",
"src_field_data_type" => "string"
],
2 => [
"src_field" => "list.product_name",
"target_field" => "new1519110451708",
"src_field_data_type" => "string"
],
3 => [
"src_field" => "list.amount.currency",
"target_field" => "new1517556165360",
"src_field_data_type" => "string"
]
];
My final output should be:
$output = [
"new1519110449758" => "first name des",
"new1519110451708" => "product name",
"new1517556165360" => "$",
"new1519110811942" => [
0 => "Masterpass",
1 => "Visa",
2 => "Pocketpos"
]
];
Please help
Thanks
I've changed your mappData slightly as it's difficult to know how to deal with the result.name element as it's repeated. What I've done is change it so that it's *result.name and it uses the fact there is an * in it to mean there are multiple values.
I've tried to comment the code with enough to explain each bit, the main principle is using a recursive routine to go through each level of the array one step at a time.
<?php
ini_set('display_errors', 'On');
error_reporting(E_ALL);
$apiRes = [
[
'firstname' => 'first name des',
'title' => "title des",
'category' => 1,
'result' =>
[
0 => [
'name' => 'Masterpass',
'skill' => 'low level one'
],
1 => [
'name' => 'Visa',
'skill' => 'low level two'
],
2 => [
'name' => 'Pocketpos',
'skill' => 'low level three'
],
],
'list' => [
'product_name'=>'product name',
'amount' => [
'currency'=>'$',
'kind' => 'kind'
]
],
'priority' => 'Low',
'visible_to' => 'Everyone',
]
];
$mappData = [
0 => [
"src_field" => "firstname",
"target_field" => "new1519110449758",
"src_field_data_type" => "string"
]
,
1 => [
"src_field" => "*result.name",
"target_field" => "new1519110811942",
"src_field_data_type" => "string"
],
2 => [
"src_field" => "list.product_name",
"target_field" => "new1519110451708",
"src_field_data_type" => "string"
],
3 => [
"src_field" => "list.amount.currency",
"target_field" => "new1517556165360",
"src_field_data_type" => "string"
]
];
$result = [];
// Process next element of the array
function getArrayElement ( $next, $data, $array = false ) {
// Extract key for this level
$key = array_shift($next);
// If starts with * then this means there are multiple of them
if ( $key[0] == "*" ){
$nextArray = true;
// remove from current key
$key = substr($key,1);
}
else {
$nextArray = false;
}
if ( $array ){
$res = [];
// extract the data from each element at this level
foreach ( $data as $read ) {
$res[] = $read[$key];
}
$data = $res;
}
else {
// Fetch the element for the key for this level
$data = $data [ $key ];
}
// If there are more levels to deal with then repeat this method
if ( count($next) > 0 ) {
$data = getArrayElement ( $next, $data, $nextArray );
}
return $data;
}
// Flatten out original array if necessary
if ( count($apiRes) == 1 ){
$apiRes = $apiRes[0];
}
// Process each part of lookup
foreach ( $mappData as $element ) {
// Create an array of the elments broken down into each level
$map = explode( '.', $element['src_field']);
$result[$element['target_field']] = getArrayElement($map, $apiRes);
}
print_r($result);

PHP push a value to an array during recursive search

$menus = [
0 => [
'id' => 'home',
'title' => 'Home',
'url' => '/display/home',
'children' => [],
'parent' => null
],
1 => [
'id' => 'nodes',
'title' => 'Nodes',
'url' => 'nodes/index',
'children' => [
0 => [
'id' => 'addNode',
'title' => 'Add Node',
'url' => '/nodes/add',
'children' => [],
'parent' => "nodes"
],
1 => [
'id' => 'editNode',
'title' => 'Edit Node',
'url' => '/nodes/edit',
'children' => [],
'parent' => 'nodes'
],
2 => [
'id' => 'deleteNode',
'title' => 'Delete Node',
'url' => '/nodes/delete',
'children' => [
0 => [
'id' => 'deleteMultipleNodes',
'title' => 'Delete Multiple Nodes',
'url' => '/nodes/deleteall',
'children' => [
0 => [
'id' => 'deleteMultipleSelectedNodes',
'title' => 'Delete Multiple Selected Nodes',
'url' => '/nodes/deleteallselected',
'children' => [],
'parent' => 'deleteMultipleNodes'
]
],
'parent' => 'deleteNode'
]
],
'parent' => 'nodes'
]
],
'parent' => null
]
];
Assuming I have this array. What i want is to recursively search this array for an "id" and if found push a new children to the children array of that element.
I've tried it via different ways, I've also tried to use RecursiveArrayIterator to traverse the array, but the problem is how can i push value to that index of the array when found while traversing.
For Example here is a code from one of my tries:
private function traverseArray($array)
{
$child = [
'id' => 'deleteMultipleNotSelectedNodes',
'title' => 'Delete Multiple Not Selected Nodes',
'url' => '/nodes/deletenotselected',
'children' => [],
'parent' => 'deleteMultipleNodes'
];
foreach($array as $key=>$value)
{
if(is_array($value))
{
$this->traverseArray($value);
}
if($key == "id" && $value == "deleteMultipleNodes")
{
array_push($array["children"], $child); // This part is confusing me, How to add the child on this index where the id is found.
}
}
}
Any help on how to do such thing in an efficient way would save my days.
Here it's how it would work without using $this and fixing bugs in comparing $value instead assigning anything to value.
Please note the difference with &$array and &$value, which are references, so it would change the original data instead of copying it into new variables.
<?php
$menus = [
0 => [
'id' => 'home',
'title' => 'Home',
'url' => '/display/home',
'children' => [],
'parent' => null
],
1 => [
'id' => 'nodes',
'title' => 'Nodes',
'url' => 'nodes/index',
'children' => [
0 => [
'id' => 'addNode',
'title' => 'Add Node',
'url' => '/nodes/add',
'children' => [],
'parent' => "nodes"
],
1 => [
'id' => 'editNode',
'title' => 'Edit Node',
'url' => '/nodes/edit',
'children' => [],
'parent' => 'nodes'
],
2 => [
'id' => 'deleteNode',
'title' => 'Delete Node',
'url' => '/nodes/delete',
'children' => [
0 => [
'id' => 'deleteMultipleNodes',
'title' => 'Delete Multiple Nodes',
'url' => '/nodes/deleteall',
'children' => [
0 => [
'id' => 'deleteMultipleSelectedNodes',
'title' => 'Delete Multiple Selected Nodes',
'url' => '/nodes/deleteallselected',
'children' => [],
'parent' => 'deleteMultipleNodes'
]
],
'parent' => 'deleteNode'
]
],
'parent' => 'nodes'
]
],
'parent' => null
]
];
function traverseArray(&$array)
{
$child = [
'id' => 'deleteMultipleNotSelectedNodes',
'title' => 'Delete Multiple Not Selected Nodes',
'url' => '/nodes/deletenotselected',
'children' => [],
'parent' => 'deleteMultipleNodes'
];
foreach($array as $key=>&$value)
{
if(is_array($value))
{
traverseArray($value);
}
if($key == "id" && $value == "deleteMultipleNodes")
{
array_push($array["children"], $child);
}
}
}
echo "=== before \n";
var_export($menus);
echo "\n\n";
traverseArray($menus);
echo "=== after \n";
var_export($menus);

Recursively Create an Array from another Array

I am trying to make a multi-dimensional array build an array path adding the hr field so it looks like this:
I just can't figure out how to add the totals, nor create a sub-array so the dot notation in an option too. My goal is to get something like this:
[1] => [1][2][1][5][0][6] = 35 (the second child path "1")
[1] => [1][2][1][5][0][7] = 25
or Something like this:
array (
[children.0.children.0.children.0.total] = 20
[children.0.children.1.children.1.total] = 35
// etc
)
The complicated part is that it goes in different directions and I want to know what is the highest and lowest total based on the path:
==> Run Code Here or Copy/Paste
// -------------
// The Flattener
// -------------
function doit($myArray) {
$iter = new RecursiveIteratorIterator(new RecursiveArrayIterator($myArray));
$result = array();
foreach ($iter as $leafKey => $leafValue) {
$keys = array();
foreach (range(0, $iter->getDepth()) as $depth) {
$keys[] = $iter->getSubIterator($depth)->key();
}
$result[ join('.', $keys) ] = $leafValue;
}
return $result;
}
// -------------
// Example Tree
// -------------
$tree = [
'id' => 1,
'type' => 'note',
'data' => [],
'children' => [
[
'id' => 2,
'type' => 'wait',
'data' => [
'hr' => 10,
],
'children' => [
[
'id' => 3,
'type' => 'wait',
'data' => [
'hr' => 10,
],
'children' => [
'id' => 4,
'type' => 'exit',
'data' => [],
'children' => []
]
],
[
'id' => 5,
'type' => 'note',
'data' => [
'hr' => 10,
],
'children' => [
[
'id' => 6,
'type' => 'wait',
'data' => [
'hr' => 10,
],
'children' => []
],
[
'id' => 7,
'type' => 'exit',
'data' => [],
'children' => []
],
]
]
],
]
]
];
$result = doit($tree);
print_r($result);
This seems to work, I found it somewhere googling all day.
array_reduce(array_reverse($keys), function($parent_array, $key) {
return $parent_array ? [$key => $parent_array] : [$key];
}, null);

PHP Merge by values in same array

So I have this array in PHP.
$arr = [
[ 'sections' => [1], 'id' => 1 ],
[ 'sections' => [2], 'id' => 1 ],
[ 'sections' => [3], 'id' => NULL ],
[ 'sections' => [4], 'id' => 4 ],
[ 'sections' => [5], 'id' => 4 ],
[ 'sections' => [6], 'id' => 4 ]
];
I want to merge on 'id' and get something like
$arr = [
[ 'sections' => [1, 2], 'id' => 1 ],
[ 'sections' => [3], 'id' => NULL ],
[ 'sections' => [4, 5, 6], 'id' => 4 ]
];
Just struggling to get my head around this one. Any Ideas
I've created this quick function that might work for you
<?php
// Your array
$arr = array(
array( 'elem1' => 1, 'elem2' => 1 ),
array( 'elem1' => 2, 'elem2' => 1 ),
array( 'elem1' => 3, 'elem2' => NULL ),
array( 'elem1' => 4, 'elem2' => 4 ),
array( 'elem1' => 5, 'elem2' => 4 ),
array( 'elem1' => 6, 'elem2' => 4 )
);
print_r($arr);
function mergeBy($arr, $elem2 = 'elem2') {
$result = array();
foreach ($arr as $item) {
if (empty($result[$item[$elem2]])) {
// for new items (elem2), just add it in with index of elem2's value to start
$result[$item[$elem2]] = $item;
} else {
// for non-new items (elem2) merge any other values (elem1)
foreach ($item as $key => $val) {
if ($key != $elem2) {
// cast elem1's as arrays, just incase you were lazy like me in the declaration of the array
$result[$item[$elem2]][$key] = $result[$item[$elem2]][$key] = array_merge((array)$result[$item[$elem2]][$key],(array)$val);
}
}
}
}
// strip out the keys so that you dont have the elem2's values all over the place
return array_values($result);
}
print_r(mergeBy($arr));
?>
Hopefully it'll work for more than 2 elements, and you can choose what to sort on also....

Categories