Convert a series of parent-child relationships into a tree? - php

I am terribly stuck. I think I need to write a recursive method but I can't work out how...!
I am trying to convert an array of parent-child relationships into a hierarchical tree which I will later display to a user.
This is an example of the input data I might have:
$input = array(
array(
'itemGroupID' => 1,
'childItemGroupID' => 2
),
array(
'itemGroupID' => 1,
'childItemGroupID' => 3
),
array(
'itemGroupID' => 1,
'childItemGroupID' => 4
),
array(
'itemGroupID' => 1,
'childItemGroupID' => 212
),
array(
'itemGroupID' => 1,
'childItemGroupID' => 339
),
array(
'itemGroupID' => 1,
'childItemGroupID' => 336
),
array(
'itemGroupID' => 1,
'childItemGroupID' => 6
),
array(
'itemGroupID' => 1,
'childItemGroupID' => 5
),
array(
'itemGroupID' => 6,
'childItemGroupID' => 8
),
array(
'itemGroupID' => 6,
'childItemGroupID' => 9
),
array(
'itemGroupID' => 6,
'childItemGroupID' => 10
),
array(
'itemGroupID' => 6,
'childItemGroupID' => 11
),
array(
'itemGroupID' => 6,
'childItemGroupID' => 12
),
array(
'itemGroupID' => 6,
'childItemGroupID' => 13
),
array(
'itemGroupID' => 6,
'childItemGroupID' => 74
),
array(
'itemGroupID' => 9,
'childItemGroupID' => 15
),
array(
'itemGroupID' => 10,
'childItemGroupID' => 16
),
array(
'itemGroupID' => 11,
'childItemGroupID' => 17
),
array(
'itemGroupID' => 12,
'childItemGroupID' => 18
),
array(
'itemGroupID' => 13,
'childItemGroupID' => 19
),
array(
'itemGroupID' => 74,
'childItemGroupID' => 75
)
);
I wish to get back data in a format like:
$output = array(
array(
'itemGroupID' => 1,
'children' => array(
array(
'itemGroupID' => 2
),
array(
'itemGroupID' => 3
),
array(
'itemGroupID' => 4
),
array(
'itemGroupID' => 212
),
array(
'itemGroupID' => 339
),
array(
'itemGroupID' => 336
),
array(
'itemGroupID' => 6,
'children' => array(
array(
'itemGroupID' => 8
),
array(
'itemGroupID' => 9,
'children' => array(
array(
'itemGroupID' => 15
)
)
),
array(
'itemGroupID' => 10,
'children' => array(
array(
'itemGroupID' => 16
)
)
),
array(
'itemGroupID' => 11,
'children' => array(
array(
'itemGroupID' => 17
)
)
),
array(
'itemGroupID' => 12,
'children' => array(
array(
'itemGroupID' => 18
)
)
),
array(
'itemGroupID' => 13,
'children' => array(
array(
'itemGroupID' => 19
)
)
),
array(
'itemGroupID' => 74,
'children' => array(
array(
'itemGroupID' => 75
)
)
)
)
),
array(
'itemGroupID' => 5
)
)
)
);
I managed to write some code which would tell me the root(s) to start from. (As I was worried that if you were recursing from the first node it might turn out that this is from half-way down the chain already...)
Btw, the element I output can be used to get the starting index, for each hierarchical chain.
private function _arraySearch($arr, $callback)
{
foreach ($arr as $key => $item) {
if ($callback($item)) {
return $key;
}
}
return false;
}
private function _findRootsOfItemGroupTree($activeItemGroupChildren)
{
$searchArray = $activeItemGroupChildren;
$roots = array();
foreach ($activeItemGroupChildren as $itemGroupChild) {
$parentItemGroupID = $itemGroupChild['itemGroupID'];
$found = array_filter($searchArray, function ($element) use ($parentItemGroupID) {
return $element['childItemGroupID'] == $parentItemGroupID;
});
$rootItemGroupID = $parentItemGroupID;
if (count($found) == 0
&& $this->_arraySearch($roots,
function ($element) use ($rootItemGroupID) {
return $element['itemGroupID'] == $rootItemGroupID;
}) === false) {
$roots[] = $itemGroupChild;
}
}
return $roots;
}
However, I now need to use this information to create a new associative array. I can't work out how. (I will post some workings in a couple of minutes..)
Ideas?
note: assume there are no recursive loops in this structure even though they could technically exist.

If I understand your problem correctly, this should work.
Notice how I call the orderMe function inside the function to make it recursive.
function orderMe($input, $parentId)
{
$return = array($parentId => array('itemGroupID' => $parentId));
$childs = array();
foreach ($input as $i)
{
if ($i['itemGroupID'] == $parentId)
{
$return[$i['itemGroupID']]['children'][$i['childItemGroupID']] = array('itemGroupID' => $i['childItemGroupID']);
$childs[] = $i['childItemGroupID'];
}
if (in_array($i['childItemGroupID'], $childs))
{
$allChilds = orderMe($input, $i['childItemGroupID']);
if (!empty($allChilds[$i['childItemGroupID']]['children']))
$return[$i['itemGroupID']]['children'][$i['childItemGroupID']] = $allChilds;
}
}
return $return;
}
print_r(orderMe($input, 1));
Outputs:
array (
1 =>
array (
'itemGroupID' => 1,
'children' =>
array (
2 =>
array (
'itemGroupID' => 2,
),
3 =>
array (
'itemGroupID' => 3,
),
4 =>
array (
'itemGroupID' => 4,
),
212 =>
array (
'itemGroupID' => 212,
),
339 =>
array (
'itemGroupID' => 339,
),
336 =>
array (
'itemGroupID' => 336,
),
6 =>
array (
6 =>
array (
'itemGroupID' => 6,
'children' =>
array (
8 =>
array (
'itemGroupID' => 8,
),
9 =>
array (
9 =>
array (
'itemGroupID' => 9,
'children' =>
array (
15 =>
array (
'itemGroupID' => 15,
),
),
),
),
10 =>
array (
10 =>
array (
'itemGroupID' => 10,
'children' =>
array (
16 =>
array (
'itemGroupID' => 16,
),
),
),
),
11 =>
array (
11 =>
array (
'itemGroupID' => 11,
'children' =>
array (
17 =>
array (
'itemGroupID' => 17,
),
),
),
),
12 =>
array (
12 =>
array (
'itemGroupID' => 12,
'children' =>
array (
18 =>
array (
'itemGroupID' => 18,
),
),
),
),
13 =>
array (
13 =>
array (
'itemGroupID' => 13,
'children' =>
array (
19 =>
array (
'itemGroupID' => 19,
),
),
),
),
74 =>
array (
74 =>
array (
'itemGroupID' => 74,
'children' =>
array (
75 =>
array (
'itemGroupID' => 75,
),
),
),
),
),
),
),
5 =>
array (
'itemGroupID' => 5,
),
),
),
)

Related

Creating multidimensional array and removing empty categories

I have a single-dimensional array of categories; some of which are children of other categories, and some of which contain 'elements'. I need to turn it into a multidimensional array, and remove any categories which have no elements in them or any of their children (or children's children...).
I have the following array:
$category_array = array(
1 => array(
'elementcount' => 3,
'parentcat' => 0,
'depth' => 1
),
4 => array(
'elementcount' => 0,
'parentcat' => 1,
'depth' => 2
),
8 => array(
'elementcount' => 0,
'parentcat' => 4,
'depth' => 3
),
9 => array(
'elementcount' => 2,
'parentcat' => 4,
'depth' => 3
),
11 => array(
'elementcount' => 3,
'parentcat' => 0,
'depth' => 1
),
12 => array(
'elementcount' => 0,
'parentcat' => 11,
'depth' => 2
),
21 => array(
'elementcount' => 3,
'parentcat' => 0,
'depth' => 1
)
);
and I need the following array:
$multidimensional_array = array(
1 => array(
'elementcount' => 3,
'children' => array(
4 => array(
'elementcount' => 0,
'children' => array(
9 => array(
'elementcount' => 2
)
)
)
)
),
11 => array(
'elementcount' => 3,
),
21 => array(
'elementcount' => 3,
)
);
How can this be achieved?
This would be an approach:
<?php
$input = [
1 => [
'elementcount' => 3,
'parentcat' => 0,
'depth' => 1
],
4 => [
'elementcount' => 0,
'parentcat' => 1,
'depth' => 2
],
8 => [
'elementcount' => 0,
'parentcat' => 4,
'depth' => 3
],
9 => [
'elementcount' => 2,
'parentcat' => 4,
'depth' => 3
],
11 => [
'elementcount' => 3,
'parentcat' => 0,
'depth' => 1
],
12 => [
'elementcount' => 0,
'parentcat' => 11,
'depth' => 2
],
21 => [
'elementcount' => 3,
'parentcat' => 0,
'depth' => 1
]
];
$maxDepth = max(array_column($input, 'depth'));
// handle elements from higher to lower depth
for ($d = $maxDepth; $d >= 0; $d--) {
array_walk($input, function(&$entry, $index) use (&$input, $d) {
if (isset($entry['depth']) && $entry['depth'] == $d) {
// omit entries without elements or elements in children
if ($entry['elementcount'] < 1 && empty($entry['children'])) {
unset($input[$index]);
// handle as child entry of a parent entry
} else if (array_key_exists($entry['parentcat'], $input)) {
$input[$entry['parentcat']]['children'][$index] = [
'elementcount' => $entry['elementcount'],
'children' => isset($entry['children']) ? $entry['children'] : []
];
unset($input[$index]);
// handle as ordinary entry
} else {
$input[$index] = [
'elementcount' => $entry['elementcount'],
'children' => isset($entry['children']) ? $entry['children'] : []
];
}
}
});
}
print_r($input);
The strategy:
handle higher depths first so that the order of input elements does not matter
for each element check if the parent exists, if so stuff it in there
redefine all handled elements
The obvious output is:
(
[1] => Array
(
[elementcount] => 3
[children] => Array
(
[4] => Array
(
[elementcount] => 0
[children] => Array
(
[9] => Array
(
[elementcount] => 2
[children] => Array
(
)
)
)
)
)
)
[11] => Array
(
[elementcount] => 3
[children] => Array
(
)
)
[21] => Array
(
[elementcount] => 3
[children] => Array
(
)
)
)
I took the liberty to create a slightly modified result compared to your suggestion:
The 'children' property always exists as an array. That makes the usage of the result easier and more robust later on. I'd say that in general all elements inside a structure should have identical structure themselves if possible ...
function isParent($id, $list) : bool {
foreach($list as $item) {
if ($item['parentcat'] === $id) {
return true;
}
}
return false;
}
function buildLevel($parent, $catsByParent) : array {
$result = $catsByParent[$parent] ?? [];
foreach($result as $id => $cat) {
if (isset($catsByParent[$id])) {
$result[$id]['children'] = buildLevel($id, $catsByParent);
unset($catsByParent[$id]);
}
}
return $result;
}
// Filter out empty categories
$cats = array_filter(
$category_array,
function($cat, $id) use($category_array) {
return $cat['elementcount']>0 || isParent($id, $category_array);
},
ARRAY_FILTER_USE_BOTH
);
$catsByParent = [];
// Build cats list keyed by parentcat
foreach($cats as $id => $cat) {
$parent = $cat['parentcat'];
unset($cat['parentcat'], $cat['depth']);
$catsByParent[$parent] = ($catsByParent[$parent] ?? []) + [$id => $cat];
}
// Build result
$multidimensional_array = buildLevel(0, $catsByParent);
print_r($multidimensional_array);
First one filters out empty elements, i.e "categories which have no elements in them or any of their children". (children's children requirement sounds strange, wouldn't that be "any of their children" one level further down?)
Then the remaining categories are grouped/sorted by parentcat, aka "level id", to make the list workable :).
Then that list is traversed, starting with level id 0 at the top, and recursively processed(the children) as deep down as needed.

Sort array by mutiple fields (closest to number)

How can I sort a array by two(one) different values?
So I have a array like this:
array(
array(
'id' => 10,
'total' => 38,
'entry' => 400
),
array(
'id' => 4,
'total' => 34,
'entry' => 3100
),
array(
'id' => 2,
'total' => 34,
'entry' => 3150
),
array(
'id' => 8,
'total' => 34,
'entry' => 2980
),
);
The array is already sorted by the key total, but they all have the same value in total. So I need to sort by who is closest to 3000 by entry.
Edit
The array should be first sorted by total and then entry, since entry is only there so I can differentiate who is the best.
So the array should look like this:
array(
array(
'id' => 10,
'total' => 38,
'entry' => 400
),
array(
'id' => 8,
'total' => 34,
'entry' => 2980
),
array(
'id' => 4,
'total' => 34,
'entry' => 3100
),
array(
'id' => 2,
'total' => 34,
'entry' => 3150
)
);
Try this:
usort($arr, function ($a, $b) {
if ($a['total'] == $b['total']) { // Only compare on entry when the totals are the same.
return abs($a['entry'] - 3000) > abs($b['entry'] - 3000);
}
return $a['total'] < $b['total'];
});
print_r($arr);
Output:
Array
(
[0] => Array
(
[id] => 2
[total] => 35
[entry] => 3150
)
[1] => Array
(
[id] => 8
[total] => 34
[entry] => 2980
)
[2] => Array
(
[id] => 4
[total] => 34
[entry] => 3100
)
[3] => Array
(
[id] => 6
[total] => 34
[entry] => 3250
)
[4] => Array
(
[id] => 3
[total] => 32
[entry] => 3400
)
)
Here's how it works: it compares the totals, but if they're the same, it compares the absolute value of the difference between entry and 3000 of both entrys.
eval.in demo

PHP - generate multi-dimentional array

This is my array:
$arr = array(
0 => array(
'title' => 'test1',
'count' => 4,
'month' => 'jan-2015'
),
1 => array(
'title' => 'test2',
'count' => 10,
'month' => 'jan-2015'
),
2 => array(
'title' => 'test3',
'count' => 14,
'month' => 'jun-2015'
),
3 => array(
'title' => 'test4',
'count' => 45,
'month' => 'july-2015'
),
);
I've to convert this array into multi-dimentional array as below:
$arr = array(
'jan-2015' => array(
0 => array(
'title' => 'test1',
'count' => 4,
),
1 => array(
'title' => 'test2',
'count' => 10,
),
),
'jun-2015' => array(
0 => array(
'title' => 'test3',
'count' => 14,
),
),
'july-2015' => array(
0 => array(
'title' => 'test4',
'count' => 45,
),
),
);
I've tried to make it as expected but unfortunately i can't make this.
Is any other solutions for this?
According to your data structure :
$arr = array(
0 => array(
'title' => 'test1',
'count' => 4,
'month' => 'jan-2015'
),
1 => array(
'title' => 'test2',
'count' => 10,
'month' => 'jan-2015'
),
2 => array(
'title' => 'test3',
'count' => 14,
'month' => 'jun-2015'
),
3 => array(
'title' => 'test4',
'count' => 45,
'month' => 'july-2015'
),
);
try this:
$newArray = array();
foreach($arr as $key => $val) {
$newArray[$val['month']][] = $val;
}
echo '<pre>'.print_r($newArray,1).'</pre>';
Output:
Array
(
[jan-2015] => Array
(
[0] => Array
(
[title] => test1
[count] => 4
[month] => jan-2015
)
[1] => Array
(
[title] => test2
[count] => 10
[month] => jan-2015
)
)
[jun-2015] => Array
(
[0] => Array
(
[title] => test3
[count] => 14
[month] => jun-2015
)
)
[july-2015] => Array
(
[0] => Array
(
[title] => test4
[count] => 45
[month] => july-2015
)
)
)
You could use this function:
function transform($input) {
// Extract months, and use them as keys, with value set to empty array
// The array_fill_keys also removes duilicates
$output = array_fill_keys(array_column($input, 'month'), array());
foreach ($input as $element) {
$copy = $element;
// remove the month key
unset($copy["month"]);
// assign this to the month key in the output
$output[$element["month"]][] = $copy;
}
return $output;
}
Call it like this:
$arr = array(
0 => array(
'title' => 'test1',
'count' => 4,
'month' => 'jan-2015'
),
1 => array(
'title' => 'test2',
'count' => 10,
'month' => 'jan-2015'
),
2 => array(
'title' => 'test3',
'count' => 14,
'month' => 'jun-2015'
),
3 => array(
'title' => 'test4',
'count' => 45,
'month' => 'july-2015'
),
);
print_r (transform($arr));
Output:
Array
(
[jan-2015] => Array
(
[0] => Array
(
[title] => test1
[count] => 4
)
[1] => Array
(
[title] => test2
[count] => 10
)
)
[jun-2015] => Array
(
[0] => Array
(
[title] => test3
[count] => 14
)
)
[july-2015] => Array
(
[0] => Array
(
[title] => test4
[count] => 45
)
)
)
By using answer of #Girish Patidar, You can achieve this by:
$outputArr = array();
$to_skip = array();
foreach($arr as $row){
$to_skip = $row;
unset($to_skip['month']);
$outputArr[$row['month']][] = $to_skip;
}
echo "<pre>";
print_r($outputArr);
die;
There could many way to do this. Please try this one if it works for you
<?php
$newArr=NULL;
foreach($arr as $array)
{
$temp=NULL;
$temp['title']=$array['title'];
$temp['count']=$array['count'];
$newArr[$array['month']][]=$temp;
}
var_dump($newArr);
?>

Summing The Contents Of A Three Tiered Array PHP

This is the array I am using (3 tiers), and I am trying to add the number of sales (nosales) from the first month in the first array to the first month in the second array (Should be 150) and so on through all the variables (months shouldn't be added) so I am left with a two level array with the nosales, salevalue,salecost and saleprofit totals for each month.
Array (
[0] => Array
(
[1] => Array
(
[month] => 1
[nosales] => 100
[salevalue] => 1200
[salecost] => 360
[saleprofit] => 840
)
[2] => Array
(
[month] => 2
[nosales] => 110
[salevalue] => 1320
[salecost] => 396
[saleprofit] => 924
)
)
[1] => Array
(
[1] => Array
(
[month] => 1
[nosales] => 50
[salevalue] => 350
[salecost] => 70
[saleprofit] => 280
)
[2] => Array
(
[month] => 2
[nosales] => 55
[salevalue] => 385
[salecost] => 77
[saleprofit] => 308
)
)
)
Now, I have tried looping through them to get them to add together but I am getting a number of errors. Could someone please help?
Here is the script I am using at the moment:
$acc = array_shift($results_array);
foreach ($results_array as $val) {
foreach ($val as $v) {
foreach ($v as $key => $v){
$acc[$key] += $v;
}
}
}
Thanks for your help in advance!
Is this what you wanted to do?
$aResultsArray = array(
0 => array(
1 => array(
'month' => 1,
'nosales' => 100,
'salevalue' => 1200,
'saleconst' => 360,
'saleprofit' => 840,
),
2 => array(
'month' => 2,
'nosales' => 110,
'salevalue' => 1320,
'saleconst' => 396,
'saleprofit' => 924,
),
),
1 => array(
1 => array(
'month' => 1,
'nosales' => 50,
'salevalue' => 350,
'saleconst' => 70,
'saleprofit' => 280,
),
2 => array(
'month' => 2,
'nosales' => 55,
'salevalue' => 385,
'saleconst' => 77,
'saleprofit' => 308,
),
),
);
$aSum = array();
foreach ($aResultsArray as $mYear => $aMonths) {
foreach ($aMonths as $mMonth => $aMonth) {
if (!isset($aSum[$aMonth['month']])) {
$aSum[$aMonth['month']] = array(
'month' => $aMonth['month'],
'nosales' => 0,
'salevalue' => 0,
'saleconst' => 0,
'saleprofit' => 0,
);
}
$aSum[$aMonth['month']]['nosales'] += $aMonth['nosales'];
$aSum[$aMonth['month']]['salevalue'] += $aMonth['salevalue'];
$aSum[$aMonth['month']]['saleconst'] += $aMonth['saleconst'];
$aSum[$aMonth['month']]['saleprofit'] += $aMonth['saleprofit'];
}
}
var_dump($aSum);

Elegant way to convert associative array into an indexed one [duplicate]

This question already has answers here:
Transposing multidimensional arrays in PHP
(12 answers)
Closed 1 year ago.
Having an associative array like
$myarray = array (
'user' => array (
2 => 'john',
5 => 'done',
21 => 'foo'
),
'city' => array (
2 => 'london',
5 => 'buenos aires',
21 => 'paris',
),
'age' => array (
2 => 24,
5 => 38,
21 => 16
)
...
);
EDITED
Assuming I don't know how many keys this array has, nor the keys themselves (they could be whatever) The above is just an example
Is there any elegant way (using built-in functions and not loops) to convert it into
$result = array(
array(
'user',
'city',
'age'
...
),
array(
'john',
'london',
24
...
),
array(
'done',
'buenos aires',
38
...
),
array(
'foo',
'paris',
16
...
)
);
As a side question: how to obtain this too (in a similar elegant way)
$result = array(
array(
'row',
'user',
'city',
'age'
...
),
array(
2,
'john',
'london',
24
...
),
array(
5,
'done',
'buenos aires',
38
...
),
array(
21,
'foo',
'paris',
16
...
)
);
Note that both of these operations are basically the transpose of your original array.
All of this is strongly adapted from this answer (consider upvoting it!).
helper
First we need a helper function, which keeps the row headers as the keys in the associative array:
function flipArrayKeys($arr) {
$out = array('row' => array_keys($arr));
foreach ($arr as $key => $subarr) {
foreach ($subarr as $subkey => $subvalue) {
$out[$subkey][] = $subvalue;
}
}
return $out;
}
flipArrayKeys($myarray) gives:
array (
'row' =>
array (
0 => 'user',
1 => 'city',
2 => 'age',
),
2 =>
array (
0 => 'john',
1 => 'london',
2 => 24,
),
5 =>
array (
0 => 'done',
1 => 'buenos aires',
2 => 38,
),
21 =>
array (
0 => 'foo',
1 => 'paris',
2 => 16,
),
)
part 1
$result = array_values(flipArrayKeys($myarray));
and now result looks like:
array (
0 =>
array (
0 => 'user',
1 => 'city',
2 => 'age',
),
1 =>
array (
0 => 'john',
1 => 'london',
2 => 24,
),
2 =>
array (
0 => 'done',
1 => 'buenos aires',
2 => 38,
),
3 =>
array (
0 => 'foo',
1 => 'paris',
2 => 16,
),
)
This part can also be done using transpose from this answer:
$result = transpose($myarray);
array_unshift($result, array_keys($myarray));
part 2
function flipArrayWithHeadings($arr) {
$out = flipArrayKeys($arr);
foreach (array_keys($out) as $key) {
array_unshift($out[$key],$key);
}
return array_values($out);
}
So flipArrayWithHeadings($myarray) looks like:
array (
0 =>
array (
0 => 'row',
1 => 'user',
2 => 'city',
3 => 'age',
),
1 =>
array (
0 => 2,
1 => 'john',
2 => 'london',
3 => 24,
),
2 =>
array (
0 => 5,
1 => 'done',
2 => 'buenos aires',
3 => 38,
),
3 =>
array (
0 => 21,
1 => 'foo',
2 => 'paris',
3 => 16,
),
)
Use the array_values function as:
$result = array(array_values($myarray['user']),
array_values($myarray['city']),
array_values($myarray['age']));
For the 2nd part of the question you can do:
$result2 = array_keys($myarray);
foreach(array_keys($myarray['user']) as $k) {
$result2[] = array($k, $myarray['user'][$k], $myarray['city'][$k], $myarray['age'][$k]);
}

Categories