I'm trying to make a parent child like array out of a previous array of things, some are duplicates, some are not.
I have an array that spits out like so:
[0] => Array
(
[parent] => dogs
[child_cat_1] => category one
[child_cat_2] => category two
[title] => Title
[price] => 9.49
[sku] => 3558505550
[old_post_id] => 110
)
[1] => Array
(
[parent] => cats
[child_cat_1] => category one
[child_cat_2] => category six
[title] => Title
[price] => 16.49
[sku] => 2251752419
[old_post_id] => 113
)
[2] => Array
(
[parent] => cats
[child_cat_1] => category three
[child_cat_2] => category nine
[title] => Title
[price] => 59.99
[sku] => 7944100467
[old_post_id] => 114
)
[3] => Array
(
[parent] => dogs
[child_cat_1] => category one
[child_cat_2] => category two
[title] => Title
[price] => 69.99
[sku] => 85932810243
[old_post_id] => 117
)
I'm having a real hard time creating a new array based off these arrays, and turning it into an array of parents and children.
I tried doing what this post said, but I couldn't get it to work as I'm not entirely sure of what the 'parent' is going to be.
I tried doing this, but I can't figure out how to factor in child_cat_2, and also remove the duplicates. I've also read pretty much every "Building Hierarchy out of array" on Stackoverflow, but I just can't figure this one out.
foreach($new_array as $k => $v){
$array[$v['parent']] = $v['child_cat_1'];
}
Ideally, what I'm trying to accomplish is arranging each child_cat_2 under child_cat_1, and then both under parent, but then also remove the duplicates. I think the parent only has 11 types, but the two children have up to 100 each.
Can someone point me in the right direction to get these arrays sorted out.
What you've described is a 3-level nested array. The code you've posted certainly won't accomplish that. You have nodes with four properties other than your array structure, so that is what you should build in a loop. Then you place them into your structure depending on the other three properties.
$results = array();
foreach ($data as $datum){
$node = array(
'title' => $datum['title']
'price' => $datum['price']
'sku' => $datum['sku']
'old_post_id' => $datum['old_post_id']
);
if (!is_array($results[$datum['parent']])) {
$results[$datum['parent']] = array();
}
if (!is_array($results[$datum['parent']][$datum['child_cat_1']])) {
$results[$datum['parent']]
[$datum['child_cat_1']] = array();
}
if (!is_array($results[$datum['parent']][$datum['child_cat_1']][$datum['child_cat_2']])) {
$results[$datum['parent']]
[$datum['child_cat_1']]
[$datum['child_cat_2']] = array();
}
$results[$datum['parent']]
[$datum['child_cat_1']]
[$datum['child_cat_2']][] = $node;
}
Related
I have a huge dynamically generated tree. The tree is generated from a flat array based on each elements "parent_id" attribute.
The final result would for example look like this:
Array
(
[0] => Array
(
[id] => 70
[name] => Top Corp
[parent_id] => 0
[children] => Array
(
[0] => Array
(
[id] => 43
[name] => Department
[parent_id] => 70
[children] => Array
(
[0] => Array
(
[id] => 45
[name] => Building
[parent_id] => 43
[children] => Array
(
[0] => Array
(
[id] => 75
[name] => Office
[parent_id] => 45
)
)
)
How do I extract just a part of the array tree? What functions or methods should I look at?
E.g. how do I say another sub-level (potentially 20-30 levels deep) is now the top.
For example, a pseudo function of sliceTree(45) should produce the following result, aka start the tree from id 45
[0] => Array
(
[id] => 45
[name] => Building
[parent_id] => 43
[children] => Array
(
[0] => Array
(
[id] => 75
[name] => Office
[parent_id] => 45
)
)
)
There is no way to know how deep the tree can go so it a solution needs to be recursive.
I have tried looping the array, looking for the starting id, but I am unsure of how to continue the execution after the point has been found.
My proposed solution is as follows
function sliceTree($tree, $id){
$ret = [];
foreach ($tree as $out) {
// if the top level matches
if($out["id"] == $id){
array_push($ret, $out);
}
else {
if(isset($out["children"])){
foreach ($out["children"] as $c) {
if($c["id"] == $id){
array_push($ret, $c);
}
// probably needs to call itself here
}
}
}
}
return $ret;
}
Which works, but only for top-level elements. How can I make recursive and account for the many levels of children?
The sliceTree() function basically looks for a certain id and returns it. Something like this:
function sliceTree($tree, $branchId)
{
// check all branches
foreach ($tree as $branch) {
// have we found the correct branch?
if ($branch['id'] == $branchId) return $branch;
// check the children
if (isset($branch['children'])) {
$slice = sliceTree($branch['children'], $branchId);
if (isset($slice)) return $slice;
}
}
// nothing was found
return null;
}
As you can see, this routine is recursive. Code is untested.
I'm sorry about the mixed metaphors: branches and children, but you started it.
This function is slightly more complex, than I would like it to be, because in your example the children key is absent when there are no children. I would normally expect it to be there and the value to be an empty array.
I've seen other questions about this but not quite like my situation. I have the following table in MySQL:
term_id name slug taxonomy parent
1 Entry Form entry-form format 0
2 Page page format 3
3 Facebook facebook format 0
4 Entry Form facebook-entry-form format 3
5 Twitter twitter format 0
6 Single single format 2
I have the following OBJECT query:
$formats = $wpdb->get_results($wpdb->prepare("
SELECT * FROM table t
WHERE t.taxonomy = 'format'
"));
I wind up with the following array:
Array ( [0] => stdClass Object ( [term_id] => 1 [name] => Entry Form [slug] => entry-form [taxonomy] => format [parent] => 0 ) [2] => stdClass Object ( [term_id] => 2 [name] => Page [slug] => page [taxonomy] => format [parent] => 3 ) [3] => stdClass Object ( [term_id] => 3 [name] => Facebook [slug] => facebook [taxonomy] => format [parent] => 0 ) [4] => stdClass Object ( [term_id] => 4 [name] => Entry Form [slug] => entry-form-facebook [taxonomy] => format [parent] => 3 ) [5] => stdClass Object ( [term_id] => 5 [name] => Twitter [slug] => twitter [taxonomy] => format [parent] => 0 ) [6] => stdClass Object ( [term_id] => 6 [name] => Single [slug] => single [taxonomy] => format [parent] => 2 ) ) 1
All of the above needs to be turned into a hierarchical list on output that looks like this:
Entry Form
Twitter
Facebook
- Entry Form
- Page
-- Single
As such, I need to turn array $formats into a hierarchical array based on the parent field. A parent of 0 means it is a top level item. As such, since Single has parent of 2 it is the child of Page which in turn has parent of 3 and is a child of Facebook.
Can anyone help me turn my array into a hierarchical array and then show me how I can loop through it for output?
If performance due to volume of queries is not going to be a problem, the simplest solution is that, instead of doing a single query that populates an array, you do one query per node in your hierarchical tree, adding a "AND parent = $id", where $id is the term_id of the current node. Something like:
Do a SELECT WHERE .... AND parent = 0;
for each result in 1, $id = term_id, do a select WHERE ... AND
parent = $id
Repeat recursively until no more results
If performance is a problem you can still dump the query to your array and apply the same algorithm to the array, but you most likely will have memory issues doing it that way if you really have that much volume.
You need to put the data into assoc array while fetching it from the database:
//$groups - result array
$groups = array();
//$record contains the assoc array of the current record
while($record = $result->fetchAssoc()) {
if (!isset($groups[$record["parent"]]))
{
$groups[$record["parent"]] = array();
}
array_push($groups[$record["parent"]], $record);
}
In the end you will get an assoc array of hierarchy with parent as a key. Then traverse through it recursively, and you'll get the result:
function print_recursively(&$groups, $parent_id, $dashes = '')
{
if(isset($groups[$parent_id]))
{
foreach($groups[$parent_id] as $key => $value)
{
print $dashes . ' ' . $value["name"];
print_recursively(&$groups, $value["term_id"], $dashes . '-');
}
}
}
I didn't test this code, but the algorithm is correct.
I am building an MLM software in PHP, one of its function is to count the downline performance.
It creates an array from parent-child relationships, which can be seen below.
How can I get the performance (array key: points) of my children, and their grandchildren as well through the fifth down level?
Array
(
[children] => Array
(
[0] => Array
(
[id] => 1
[name] => Medvedev
[email] =>
[points] => 7
[children] => Array
(
[0] => Array
(
[id] => 3
[name] => Putin
[email] =>
[points] => 4
[children] => Array
(
[0] => Array
(
[id] => 5
[name] => Nathan
[email] =>
[points] => 3
)
[1] => Array
(
[id] => 7
[name] => George
[email] =>
[points] => 666
)
)
)
[1] => Array
(
[id] => 4
[name] => Lucas
[email] =>
[points] => 43
)
)
)
)
[id] => 27
[name] => Boss
[email] =>
[points] => 99999
)
this should work with unlimited depth starting from a main array like
$array = array(
'children' => array( /* ADD HERE INFINITE COMBINATION OF CHILDREN ARRAY */ ),
'id' => #,
'name' => '',
'email' => '',
'points' => #
);
function recursive_children_points($arr) {
global $hold_points;
if (isset($arr['points'])) {
$hold_points[] = $arr['points'];
}
if (isset($arr['children'])) {
foreach ($arr['children'] as $children => $child) {
recursive_children_points($child);
}
}
return $hold_points;
}
$points = recursive_children_points($array);
print "<pre>";
print_r($points);
/**
// OUTPUT
Array
(
[0] => 99999
[1] => 7
[2] => 4
[3] => 3
[4] => 666
[5] => 43
)
**/
print "<pre>";
In my opinion this calls for recursion because you'll do the same thing over each flat level of the array: add the points.
so you'll need to
loop over each array
add points found
if we find any children, do it again but with children array
while keeping track of levels and jumping out if you reach your limit
With that in mind I thought of the following solution:
<?php
$values = array();
//first
$values[] = array('name'=>'andrej','points'=>1,'children'=>array());
$values[] = array('name'=>'peter','points'=>2,'children'=>array());
$values[] = array('name'=>'mark','points'=>3,'children'=>array());
//second
$values[0]['children'][] = array('name'=>'Sarah','points'=>4,'children'=>array());
$values[2]['children'][] = array('name'=>'Mike','points'=>5,'children'=>array());
//third
$values[0]['children'][0]['children'][] = array('name'=>'Ron','points'=>6,'children'=>array());
//fourth
$values[0]['children'][0]['children'][0]['children'][] = array('name'=>'Ronny','points'=>7,'children'=>array());
//fifth
$values[0]['children'][0]['children'][0]['children'][0]['children'][] = array('name'=>'Marina','points'=>10,'children'=>array());
//sixth
$values[0]['children'][0]['children'][0]['children'][0]['children'][0]['children'][] = array('name'=>'Pjotr','points'=>400,'children'=>array());
function collect_elements($base, $maxLevel,$child='children',$gather='points', &$catch = array(), $level = 0) {
/* I pass $catch by reference so that all recursive calls add to the same array
obviously you could use it straight away but I return the created array as well
because I find it to be cleaner in PHP (by reference is rare and can lead to confusion)
$base = array it works on
$maxLevel = how deep the recursion goes
$child = key of the element where you hold your possible childnodes
$gather = key of the element that has to be collected
*/
$level++;
if($level > $maxLevel) return; // we're too deep, bail out
foreach ($base as $key => $elem) {
// collect the element if available
if(isset($elem[$gather])) $catch[] = $elem[$gather];
/*
does this element's container have children?
[$child] needs to be set, [$child] needs to be an array, [$child] needs to have elements itself
*/
if (isset($elem[$child]) && is_array($elem[$child]) && count($elem[$child])){
// if we can find another array 1 level down, recurse that as well
collect_elements($elem[$child],$maxLevel,$child,$gather, $catch,$level);
}
}
return $catch;
}
print array_sum(collect_elements($values,5)) . PHP_EOL;
collect_elements will collect the element you're interested in (until maximum depth is reached) and append it to a flat array, so that you can act on it upon return. In this case we do an array_sum to get the total of poins collected
Only the first for parameters are interesting:
collect_elements($base, $maxLevel,$child='children',$gather='points'
not optional:
$base is the array to work on
$maxLevel is the maximum depth the function needs to descend into the arrays
optional:
$child defines the key of the element that contains the children of current element (array)
$gather defines the key of the element that contains what we want to gather
The remaining parameters are just ones used for recursion
I am (via a web service) receiving a flat XML file that contains a listing of categories, subcategories, subsubcategories, etc, all at the same level. (i.e. Subcategories are not nested under their parent categories.) This, unfortunately, cannot be changed. I have to work with the data provided.
I'm taking this XML and converting it into an object, then into an array using simplexml_load_string and array_map. This is all working as expected. What I'm left with is a master category array that looks something like this.
Array
(
[0] => Array
(
[CategoryID] => HARDWARE
[Description] => Hardware Issue
)
[1] => Array
(
[CategoryID] => MAC_OSX
[Description] => Mac OSX
[ParentCategoryID] => OS
)
[2] => Array
(
[CategoryID] => OFFICE
[Description] => Microsoft Office
[ParentCategoryID] => SOFTWARE
)
[3] => Array
(
[CategoryID] => OS
[Description] => Operating Systems
[ParentCategoryID] => SOFTWARE
)
[4] => Array
(
[CategoryID] => WIN_7
[Description] => Windows 7
[ParentCategoryID] => OS
)
[5] => Array
(
[CategoryID] => SOFTWARE
[Description] => Software Issue
)
)
As you can see, there are subcategories mixed in there, all keyed off of the ParentCategoryID. Parent Categories have that field omitted.
The CategoryID will always be unique, no matter what level it is on. And the array is sorted alphabetically by the Description. The real array is over 250 categories long, above is an abbreviated version for example sake.
I need to take that master array, loop through it and come up with a new array that looks something like this.
Array
(
[0] => Array
(
[CategoryID] => SOFTWARE
[Description] => Software Issue
[SubCategories] => Array
(
[0] => Array
(
[CategoryID] => OS
[Description] => Operating Systems
[SubCategories] => Array
(
[0] => Array
(
[CategoryID] => WIN_7
[Description] => Windows 7
)
[1] => Array
(
[CategoryID] => MAC_OSX
[Description] => Mac OSX
)
)
)
[1] => Array
(
[CategoryID] => OFFICE
[Description] => Microsoft Office
)
)
)
[1] => Array
(
[CategoryID] => HARDWARE
[Description] => Hardware Issue
)
)
Things I have to keep in mind is there may be infinitely many subcategories (so there isn't a set number of sublevels I can hard-code in). I've been trying to mess around with array_search and array_filter, but I'm just not having any luck getting something working. I obviously don't want to loop hundreds of times for this process to happen.
Does anyone with a little more experience with multidimensional arrays under their belt have some ideas, direction, or an example that may help me achieve my desired result?
OK, I figured out an algorithm (i think). The key is to build a linked list, and use placeholders for parent categories later in your list.
Create a category obect, which includes all the relevant data, a link to its parent and an array of links to its children. These will all go into a 1d array
Next loop through your inputs and:
If there is a parent category, check if we have an object for it already. If not, create a placeholder for the parent.
If we don't have an object for it yet, create an object for the category.
Set the parent/child relationships in both objects.
Finally, loop through your array again, and add any object without a parent to a new array.
This new array should be a list of all the parent categories, and usign the relationship you defined you should be able to browse it like a tree. You could also do another pass and build a native 2d array if you need.
I finally got it! It seems so simple now, I'm almost embarrassed to say it took so long to figure out. This was my final code to accomplish my goal.
if($cats['HasError'] == "false") {
$cats = $cats['Support_SubjectsList']['Support_Subjects'];
//Generate a hierarchy array of categories
$count = count($cats);
$i=0;
while($count > 0) {
foreach($cats as $k => $v) {
if($i==0) {
//Parents
if(is_array($v['ParentCategoryID'])) {
$categories[$i][$v['CategoryID']] = array("Description" => $v['Description']);
unset($cats[$k]);
}
} else {
//Children
if(array_key_exists($v['ParentCategoryID'], $categories[($i-1)])) {
$categories[$i][$v['CategoryID']] = array("Description" => $v['Description'], "ParentCategoryID" => $v['ParentCategoryID']);
unset($cats[$k]);
}
}
}
$count = count($cats);
$i++;
}
//Traverse the hierarchy array backwards to make a nested category array
$count = count($categories)-1;
for($i=$count;$i>0;$i--) {
foreach($categories[$i] as $k => $v) {
$categories[($i-1)][$v['ParentCategoryID']]['SubCategories'][$k] = array("Description" => $v['Description']);
if(is_array($v['SubCategories'])) {
$categories[($i-1)][$v['ParentCategoryID']]['SubCategories'][$k]['SubCategories'] = $v['SubCategories'];
}
}
unset($categories[$i]);
}
$categories = $categories[0];
}
Trying to create tree in multidimensional array with the following code
$source = array(
(array('id'=>406,'parent'=>0,'title'=>'level_0_406')),
(array('id'=>270,'parent'=>268,'title'=>'level_0_406_268_270')),
(array('id'=>271,'parent'=>268,'title'=>'level_0_406_268_271')),
(array('id'=>272,'parent'=>268,'title'=>'level_0_406_268_272')),
(array('id'=>273,'parent'=>268,'title'=>'level_0_406_268_273)')),
(array('id'=>269,'parent'=>268,'title'=>'level_0_406_268_269')),
(array('id'=>268,'parent'=>406,'title'=>'level_0_406_268')),
(array('id'=>407,'parent'=>406,'title'=>'level_0_406_407')),
(array('id'=>274,'parent'=>406,'title'=>'level_0_406_274')),
(array('id'=>500,'parent'=>407,'title'=>'level_0_406_407_500')),
);
$result = array();
$links = array(0=>&$result);
foreach ($source as &$element){
$links[$element['id']] = &$element;
$links[$element['parent']]['childs'][$element['id']] = &$element;
}
But result array does not include several nodes of source array, viz. nodes with id=269,270,271,272,273.
Array
(
[childs] => Array
(
[406] => Array
(
[id] => 406
[parent] => 0
[title] => level_0_406
[childs] => Array
(
[268] => Array
(
[id] => 268
[parent] => 406
[title] => level_0_406_268
)
[407] => Array
(
[id] => 407
[parent] => 406
[title] => level_0_406_407
[childs] => Array
(
[500] => Array
(
[id] => 500
[parent] => 407
[title] => level_0_406_407_500
)
)
)
[274] => Array
(
[id] => 274
[parent] => 406
[title] => level_0_406_274
)
)
)
)
)
I tried different code examples of tree generation but all of them have the same issue with source array like $source. Please help me understand such behavior.
Update
Now i understand what is wrong with array. But what if i have such data in DB, how to make selection properly? $source array should be specially sorted before using tree generation function.
The original $source array values are not properly created. It should be:-
$source = array(
(array('id'=>406,'parent'=>0,'title'=>'level_0_406')),
(array('id'=>268,'parent'=>406,'title'=>'level_0_406_268')),
(array('id'=>407,'parent'=>406,'title'=>'level_0_406_407')),
(array('id'=>274,'parent'=>406,'title'=>'level_0_406_274')),
(array('id'=>270,'parent'=>268,'title'=>'level_0_406_268_270')),
(array('id'=>271,'parent'=>268,'title'=>'level_0_406_268_271')),
(array('id'=>272,'parent'=>268,'title'=>'level_0_406_268_272')),
(array('id'=>273,'parent'=>268,'title'=>'level_0_406_268_273)')),
(array('id'=>269,'parent'=>268,'title'=>'level_0_406_268_269')),
(array('id'=>500,'parent'=>407,'title'=>'level_0_406_407_500')),
);
If you look carefully, you will see that previously, only the child element of the parent element ID 407 was available, since the element ID 407 has been defined before the occurrence of the child element.
It is the de-facto of your coding logic to have the parent elements defined first followed by the definitions of the child elements. Also the general practice & standard has always been the same.
In my answer, I have changed the occurrence of the elements properly. This should work.
Hope it helps.