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];
}
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 have a complex multi-dimensional array that looks something like
[name] => Marko Polo
[description] => New application
[number] => ABCD1234
[loans] => Array
(
[0] => Array
(
[id] => 123
[application_id] => 456
[loan_fees] => Array
(
)
[loan_parts] => Array
(
[0] => Array
(
[id] => 987
[loan_id] => 123
[product_id] => 49788
[product] => Array
(
[id] => 49788
[lender] => MAC
...
I need to create an efficient way of traversing this array and, for example having a set of rules to filter/modify the data.
For example, in the array there is [lender] => MAC, I want to have something like
loans.loan_parts.product.lender.MAC = 'Macquarie'
This would be in a config of sorts such that if the data array changed, it would be simply a matter of changing that dot notation to point to the new location of the lender value.
Using this, I need to filter the lender and modify it to be Macquarie instead of Mac.
I know that a big no-no these days is using too many foreach loops and I've looked into Collections, but because the inner arrays are not named, I don't believe Collections is possible.
As I say, I'd like to avoid the situation of
foreach
foreach
if (is_array())
foreach
eeewww!
How can I execute this in the most efficient manner due to the possible large size of the array and its complexity.
You can use array_walk_recursive with callback that will change behavior according to key of array.
<?php
//can pass variable by reference to change array in function
function handleMAC(&$item, $key)
{
if($key == 'lender'){
$item['MAC'] = 'your value';
}
}
array_walk_recursive($array, 'handleMAC');
?>
I seem to be having some issues looping through an array of data it works one way but not the way I want it to.
I should preface that the array is characterized by two variables $departments and $_UNIQUECONSTANT['departments']. Any help getting the constant working would be great.
The Array
Array (
[departments] => Array (
[0] => Array (
[id] => 2
[name] => Support
[description] => Support Department
)
[1] => Array (
[id] => 3
[name] => Accounting
[description] => Accounting Department
)
)
)
The working function.
foreach($departments as $department){
$department['id'];
}
The function I need to be working.
foreach($_UNIQUECONSTANT['departments'] as $department){
$department['id'];
}
Here are 2 methods that should work:
Note: I didn't test them
define('_UNIQUECONSTANT', serialize(array()));
$d = unserialize(_UNIQUECONSTANT);
foreach($d["departments"] as $department){
$department['id'];
}
// Newer PHP versions
foreach(unserialize(_UNIQUECONSTANT)["departments"] as $department){
$department['id'];
}
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;
}
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.