PHP multi-dimensional associative array conversion (URL folder structure as hierarchical array) - php

I'm twisting my brain trying to convert a multi-dimensional associative array into a multi-dimensional non-associative one and got to the point where I decided to seek help..
The whole story:
A. I've got a set of URLs and their KPIs (with an arbitrary folder depth):
URL, Visits
www.example.com
www.example.com/resource1, 100
www.example.com/folderA/resource2, 200
www.example.com/folderA/resource3, 300
www.example.com/folderB/resource4, 400
B. With help of StackOverflow (and surprisingly few lines of PHP -> see here) I was able to transform this into a hierarchical array, representing the URL-structure as folders:
Array
(
[www.example.com] => Array
(
[folderA] => Array
(
[resource2] => Array
(
[visits] => 200
)
[resource3] => Array
(
[visits] => 300
)
)
[folderB] => Array
(
[resource4] => Array
(
[visits] => 400
)
)
[resource1] => Array
(
[visits] => 100
)
)
)
C. Now, however, I need to get this array into the following structure (children must be non-associative arrays) which is a real brain twister to me...
Array
(
[name] => www.example.com
[isFolder] => 1
[children] => Array
(
[0] => Array
(
[name] => folderA
[isFolder] => 1
[children] => Array
(
[0] => Array
(
[name] => resource2
[isFolder] => 0
[kpis] => Array
(
[visits] => 200
)
[children] => Array
(
)
)
[1] => Array
(
[name] => resource3
[isFolder] => 0
[kpis] => Array
(
[visits] => 300
)
[children] => Array
(
)
)
)
)
[1] => Array
(
[name] => folderB
[isFolder] => 1
[children] => Array
(
[0] => Array
(
[name] => resource4
[isFolder] => 0
[kpis] => Array
(
[visits] => 400
)
[children] => Array
(
)
)
)
)
[2] => Array
(
[name] => resource1
[isFolder] => 0
[kpis] => Array
(
[visits] => 100
)
[children] => Array
(
)
)
)
)
Can anyone help with an approach how to achieve this? Either by transforming the array from step B. or starting from scratch with the original URLs from step A...
Any help is greatly appreciated!
Thanks a lot!

Below is a little function that will do just what you want. It uses a reference that is moved through the tree and adds data to the array referenced by that reference where necessary.
I hope the comments give enough explanation of what the code does.
$sourceArray = [
'www.example.com' => 0,
'www.example.com/resource1' => 100,
'www.example.com/folderA/resource2' => 200,
'www.example.com/folderA/resource3' => 300,
'www.example.com/folderB/resource4' => 400,
];
function convertToStructure($sourceArray)
{
// Initialize the tree
$tree = [
'name' => 'root',
'isFolder' => 1,
'children' => [],
];
// Do nothing if sourceArray is not an array
if(!is_array($sourceArray)){
return $tree;
}
// Loop through the source array
foreach($sourceArray as $path => $visits){
// Get a reference to the target tree
$treeReference = &$tree;
// Split the path into pieces
$path = explode('/', $path);
// For each piece:
foreach($path as $key => $name){
// Get the childKey if the child already exists
$childKey = findChild($treeReference, $name);
// Create a new child otherwise
if ($childKey === false) {
$treeReference['children'][] = [
'name' => $name,
'isFolder' => 0,
'children' => [],
];
// Get the key of the new child item
end($treeReference['children']);
$childKey = key($treeReference['children']);
}
// Change the reference to the correct child item.
$treeReference = &$treeReference['children'][$childKey];
// Set isFolder if necessary
$isFolder = ($key == count($path) - 1 ? 0 : 1);
if(!empty($isFolder)){
$treeReference['isFolder'] = $isFolder;
}
// Set visits if necessary
if(!empty($visits)){
$treeReference['isFolder'] = ['kpis' => ['visits' => $visits]];
}
}
}
return $tree;
}
function findChild($tree, $name)
{
if(empty($tree['children'])){
return false;
}
foreach($tree['children'] as $key => $child){
if($child['name'] == $name){
return $key;
}
}
return false;
}
$treeStructure = convertToStructure($sourceArray);

Related

Tree depth recursion PHP array [duplicate]

This question already has answers here:
Determine number of Dimensions in a PHP Array
(8 answers)
Closed 6 years ago.
I have the following array and want to find the max. depth of its tree structure. But my code returns 12, when it should be 4...I am not very good at recursion, so this is kind of making me crazy!
Array Declaration:
Array (
[relation] => Array (
[parent.item] => Array (
[0] => cs
[1] => ls
)
[cs.item] => Array (
[0] => business
[1] => sporting_cultural
[2] => tourism
[3] => family
[4] => friend
[5] => student_family
[6] => transit
[7] => other_cases
)
[business.item] => Array (
[0] => short_stay_business
[1] => short_stay_business_tourism
[2] => short_stay_german_company
[3] => short_stay_german_company_tourism
[4] => short_stay_work_training
[5] => short_stay_work
[6] => short_stay_student_internship
[7] => exhibition
[8] => scientific_research_all
)
[exhibition.item] => Array (
[0] => short_stay_visitor_fair
[1] => short_stay_visitor_fair_tourism
[2] => short_stay_exhibitor
[3] => short_stay_exhibitor_tourism
)
[scientific_research_all.item] => Array (
[0] => short_stay_scientific_research
[1] => short_stay_scientific_research_spouse
[2] => short_stay_scientific_research_child
)
[sporting_cultural.item] => Array (
[0] => short_stay_sporting_or_cultural
)
[tourism.item] => Array (
[0] => short_stay_tourism
[1] => medical_treatment
)
[medical_treatment.item] => Array (
[0] => short_stay_medical_treatment
[1] => short_stay_medical_treatment_tourism_friend_family_visit
[2] => short_stay_accompanying_person_of_a_medical_patient
)
[friend.item] => Array (
[0] => short_stay_friends
)
[family.item] => Array (
[0] => short_stay_family
[1] => short_stay_german_family_in_germany
[2] => short_stay_german_family_in_china
[3] => short_stay_non_german_family
)
[student_family.item] => Array (
[0] => short_stay_student
[1] => short_stay_entrance_exam
[2] => short_stay_scholar_exchange
[3] => short_stay_student_internship
)
[transit.item] => Array (
[0] => transit_transit
[1] => airport_transit_airport_transit
)
[other_cases.item] => Array (
[0] => short_stay_seaman
)
[ls.item] => Array (
[0] => ls_notification
)
[children] => Array()
)
)
Recursion function:
function plotTree($arr, $indent=0, $mother_run=true){
global $ini_array;
global $depth;
global $maxDepth;
if ($mother_run) {
// the beginning of plotTree. We're at rootlevel
echo "start\n";
}
foreach ($arr as $key => $value) {
if (is_array($value)) {
foreach ($value as $subKey => $subValue) {
if(in_array($subValue.".item", array_keys($ini_array['relation']))) {
$depth +=1;
plotTree($ini_array['relation'][$subValue.".item"],0,false);
}
}
$maxDepth = $maxDepth < $depth ? $depth : $maxDepth;
}
}
if ($mother_run) {
echo "end\n";
}
}
[Update} I don't want to find the number of dimensions. In the example above, the tree structure follows this: parent => cs => business => exhibition
So I see that you're trying to measure the depth based on 'relations' in a flat array rather than actual depth of the tree.
So here's what I've been able to come up with with some explanation below:
<?php
function getMaxDepth(array $arr, array $relations = array()) {
if (empty($relations)) {
return array_reduce($arr, function ($result, $elem) use ($arr) {
return max($result, getMaxDepth($elem, $arr));
});
}
// Return 1 + the depth of the deepest nested tree
return 1 + array_reduce($arr, function ($max, $elem) use ($relations) {
// If the field is not related to another field return the current max
if (!in_array($elem . '.item', array_keys($relations))) {
return $max;
}
// Return maximum of current maximum and the depth of related tree
return max($max, getMaxDepth($relations[$elem . '.item'], $relations));
}, 0);
}
Use like this:
getMaxDepth($yourArray['relation']); // returns 4
So first of all, you should avoid using global or static variables, especially when dealing with recursion. Instead, we want to keep everything we need in the stack by passing it as function arguments.
The first if statement applies getMaxDepth() to each subtree separately since we want to make sure we covered all of them.
Then we simply return the depth of the deepest 'nested' tree if there is one or 0, and add one for the current element.
Hope this helps you in the future.

Build an array out of another with a different structure

I have this array as follow:
Array (
[pageid_01_page_name] => first Page
[pageid_01_Style] => full
[thePageID_1] => pageid_01
[theElementID_1] => 1
[source_type_1] => slideshow_box
[pageid_01_1_slideshow_box] => Array ( [layout] => one
[content_id] => Array ( [0] => 856 )
[slideshow_timeout] => 8
[slideshow_height] => 400
[image_resize] => on
[image_crop] => on
[list_orderby] => date
[list_order] => DESC
[slideshow_buttons] => on )
[thePageID_2] => pageid_01
[theElementID_2] => 2
[source_type_2] => banner_box
[pageid_01_2_banner_box] => Array ( [layout] => one
[text] => test
[button_text] => Button Text
[button_link] => a link )
)
I need to build another array out of the one above, with this structure:
Array (
[pages] => Array ( [pageid_01] => stdClass Object
( [pageName] => first Page
[style] => full
[pageID] => pageid_001
[contents] => Array (
[2] => stdClass Object (
[element_id] => 1
[content_type] => slideshow_box
[layout] => one
[content_id] => Array ( [0] => 856 )
[slideshow_timeout] => 8
[slideshow_height] => 400
[image_resize] => on
[image_crop] => on
[list_orderby] => date
[list_order] => DESC
[slideshow_buttons] => on
)
[3] => stdClass Object (
[element_id] => 2
[content_type] => banner_box
[layout] => one
[text] => test
[button_text] =>Button text
[button_link] => a link
)
)
Updated with more details:
The way i'm building the second array right now is like this:
$pages = new \stdClass();
$i=0;
$page=0;
$thepagename = '_page_name';
if(isset($thepagename))
{
if(in_array($thepagename, $AllPages))
{ foreach($AllPages as $k => $v) {
$page = str_replace('_page_name','',$k);
#$pages->pages[$page]->pageID = $page;
#$pages->pages[$page]->pageName = $v;
if(stristr($k, 'theElementID_') == true) {
$element_id = $v;
}
if(stristr($k, '_slideshow_box') == true) {
$i++;
#$pages->pages[$page]->contents[$i]->element_id = $element_id;
$pages->pages[$page]->contents[$i]->content_type = 'slideshow_box';
$pages->pages[$page]->contents[$i]->layout = $v["layout"];
$pages->pages[$page]->contents[$i]->content_id = #$v["content_id"];
$pages->pages[$page]->contents[$i]->slideshow_timeout = $v["slideshow_timeout"];
$pages->pages[$page]->contents[$i]->slideshow_height = $v["slideshow_height"];
$pages->pages[$page]->contents[$i]->image_resize = $v["image_resize"];
$pages->pages[$page]->contents[$i]->image_crop = $v["image_crop"];
$pages->pages[$page]->contents[$i]->list_orderby = $v["slideshow_orderby"];
$pages->pages[$page]->contents[$i]->list_order = $v["slideshow_order"];
$pages->pages[$page]->contents[$i]->slideshow_buttons = $v["slideshow_buttons"];
}
}
}
}
and so on....
but this way takes a lot of coding while the contents can be much much more...
First of all,you can loop your array, get all values and keys, The second,built another array, push all key and values into the new array. just like
$new_array = ($new_array,values1,values2,.......);
hope it can help you

php - nav/subnav structure from array

I'm trying to create a "plugin" like script for adding to a "menu array".... I'll go straight in..
Lets say I initiate a nav item like so:
$sections->add_section('dashboard');
$DashBoardSection = $sections->load_section('dashboard');
$DashBoardSection->set_role(NULL);
$DashBoardSection->set_nav_item(array(
'identifier' => 'dashboard',
'text' => 'Dashboard',
'url' => NULL,
'position' => NULL
));
starts off by creating a new section and the getting the instance of.
We are then setting a "role" in which we will test against to see if the user is authenticated as being verified to view.
The set nav item simply stores the array. identifier is a reference to the item ( its for when we want to add sub items), all standard apart from "position" which states where it is to sit in the nav i.e. NULL is top level, array('topnav','subnav') will be topnav->subnav->dashboard.
as a test, this could be stored as follows:
Array
(
[0] => Array
(
[identifier] => dashboard
[text] => Dashboard
[url] =>
[position] =>
)
[1] => Array
(
[identifier] => dashboard2
[text] => Dashboard2
[url] =>
[position] => Array
(
[0] => dashboard
)
)
)
my question being how I turn that into the following structure:
Array
(
[0] => Array
(
[identifier] => dashboard
[text] => Dashboard
[url] =>
[position] =>
[children] => Array
(
[0] => Array
(
[identifier] => dashboard2
[text] => Dashboard2
[url] =>
)
)
)
)
pulling my hair out over this one, any help would be very much appreciated.
regards
I currently have
public function build_navigation( $role ){
$role = (int)$role;
$nav = array();
foreach( $this->sections as $section ) {
if( $section->get_role() === NULL || $section->get_role() === $role ) {
$nav_array = $section->get_nav();
foreach( $nav_array as $key => $nav_item ) {
if( $nav_item['position'] === NULL ) {
$nav[$nav_item['identifier']] = $nav_item;
}elseif( is_array( $nav_item['position'] ) ){
#...#
}
}
}
}
return $nav;
}
EDIT
Imagine this is the array given (it can be in any order)
Array
(
[0] => Array
(
[identifier] => dashboard_child2
[text] => Dashboard Child 2
[url] =>
[position] => Array
(
[0] => dashboard
)
)
[1] => Array
(
[identifier] => dashboard_child_child_1
[text] => Dashboard Child Child 1
[url] =>
[position] => Array
(
[0] => dashboard
[1] => dashboard_child1
)
)
[2] => Array
(
[identifier] => dashboard_child1
[text] => Dashboard Child 1
[url] =>
[position] => Array
(
[0] => dashboard
)
)
[3] => Array
(
[identifier] => dashboard
[text] => Dashboard
[url] =>
[position] =>
)
[4] => Array
(
[identifier] => dashboard2
[text] => Dashboard2
[url] =>
[position] => Array
(
[0] => dashboard
)
)
)
Which needs to be formatted as:
Array
(
[dashboard] => Array
(
[text] => Dashboard
[url] =>
[children] => Array
(
[dashboard_child2] => Array
(
[text] => Dashboard Child 2
[url] =>
)
[dashboard_child1] => Array
(
[text] => Dashboard Child 1
[url] =>
[children] => Array
(
[dashboard_child_child_1] => Array
(
[text] => Dashboard Child Child 1
[url] =>
)
)
)
[dashboard2] => Array
(
[text] => Dashboard2
[url] =>
)
)
)
)
Here's my take on the problem, solved with recursion.
You can use multiple positions (i guess this is why it's an array), it will ignore missing positions if at least on of the positions is found, but will complain if every position is missing.
function translate($in) {
$out = array();
// first pass, move root nodes to output
foreach ($in as $k => $row) {
if (!$row['position']) {
$out[$row['identifier']] = $row;
unset($in[$k]);
}
}
// while we have input
do {
$elements_placed = 0;
// step trough input
foreach ($in as $row_index => $row) {
foreach ($row['position'] as $pos) {
// build context for the node placing
$data = array(
'row' => $row,
'in' => &$in,
'row_index' => $row_index,
'elements_placed' => &$elements_placed,
'pos' => $pos,
);
// kick of recursion
walker($out, $data);
}
}
} while ($elements_placed != 0);
if (count($in)) {
trigger_error("Error in user data, can't place every item");
}
return $out;
}
function walker(&$out, $data) {
foreach ($out as &$row) {
// it looks like a node array
if (is_array($row) && isset($row['identifier'])) {
// if it has children recurse in there too
if (isset($row['children'])) {
walker($row['children'], $data);
}
// it looks like a node array that we are looking for place the row
if ($row['identifier'] == $data['pos']) {
if (!isset($row['children'])) {
$row['children'] = array($data['row']['identifier'] => $data['row']);
} else {
$row['children'][$data['row']['identifier']] = $data['row'];
}
// report back to the solver that we found a place
++$data['elements_placed'];
// remove the row from the $in array
unset($data['in'][$data['row_index']]);
}
}
}
}
$in = array (
array (
'identifier' => 'secondlevelchild2',
'text' => 'secondlevelchild2',
'url' => '',
'position' => array (
'dashboard2',
),
),
array (
'identifier' => 'secondlevelchild',
'text' => 'secondlevelchild',
'url' => '',
'position' => array (
'dashboard2',
),
),
array (
'identifier' => 'dashboard',
'text' => 'Dashboard',
'url' => '',
'position' => '',
),
array (
'identifier' => 'dashboard2',
'text' => 'Dashboard2',
'url' => '',
'position' => array (
'dashboard', 'home',
),
),
array (
'identifier' => 'thirdlevelchild',
'text' => 'thirdlevelchild',
'url' => '',
'position' => array (
'secondlevelchild2',
),
),
);
$out = translate($in);
var_export($out);
In it's current form it doesn't remove the identifier or position keys from the node arrays once they are placed.
You need a temporary array that maps the identifier to the children array of it so that you can add it there.
If I see that right, you have something like that already here when you add the parent:
$nav[$nav_item['identifier']] = $nav_item;
Edit: Adding the parent needs some caution as pointed out in the comment:
$node = &$nav[$nav_item['identifier']];
$children = isset($node['children']) ? $node['children'] : array();
$node = $nav_item;
$node['children'] = $children;
unset($node);
Just add to the children then:
foreach($nav_item['position'] as $identifier)
{
$nav[$identifier]['children'][] = $nav_item;
}
Also you could use objects, not arrays, so that you do not duplicate data that much (or you can change it later), however, just thinking, this must not be necessary to solve your problem so probably something for later.

Highlighting array values

I have an array of statistics on racing dogs. I need to highlight the maximum and minimum values in each element by adding another element. Let me explain.
Example of array
Array
(
[1] => Array
(
[fast_calc_7] => 31.06
[av_calc_7] => 25.03
)
[2] => Array
(
[fast_calc_7] => 16.74
[av_calc_7] => 18.06
)
[3] => Array
(
[fast_calc_7] => 30.93
[av_calc_7] => 31.06
)
[4] => Array
(
[fast_calc_7] => 29.01
[av_calc_7] => 25.08
)
[5] => Array
(
[fast_calc_7] => 30.72
[av_calc_7] => 31.02
)
[6] => Array
(
[fast_calc_7] => 31.16
[av_calc_7] => 36.02
)
)
Example of resulting array
I need to compare these values and add another element specifying if it is the highest of lowest value. For example, it should generate an array such as the following.
Array
(
[1] => Array
(
[fast_calc_7] => 31.06
[av_calc_7] => 25.03
)
[2] => Array
(
[fast_calc_7] => 16.74
[fast_calc_7_lowest] => TRUE
[av_calc_7] => 18.06
[av_calc_7_lowest] => TRUE
)
[3] => Array
(
[fast_calc_7] => 30.93
[av_calc_7] => 37.06
[av_calc_7_highest] => TRUE
)
[4] => Array
(
[fast_calc_7] => 29.01
[av_calc_7] => 25.08
)
[5] => Array
(
[fast_calc_7] => 30.72
[av_calc_7] => 31.02
)
[6] => Array
(
[fast_calc_7] => 31.16
[fast_calc_7_highest] => TRUE
[av_calc_7] => 36.02
)
)
I'm sure there is a really simple way to do this. The more I have to iterate through arrays, the slower my application gets!
Thanks guys.
EDIT: How data is sourced
JSON array gathered from greyhound website for each dogs "race history"
JSON array is processed to form a nice array for each dogs history with the data for example the time for each race, finishing position etc.
This data is then calculated for each dog to find the fastest time, average time and other data. This is the array above. (Each array is a different dog).
At this point, I need the array processed to add in the highlighting elements.
There isn't much to it. You need to loop over the array, maintaining variables with the indexes of the high/low elements so far for each attribute. After the loop, access those elements and add your extra data.
$lowAverage = $highAverage = array('index' => null, 'value' => 0);
$lowFast = $highFast = array('index' => null, 'value' => 0);
foreach($dogs as $index => $data) {
if($lowAverage['index'] == null || $data['av_calc_7'] < $lowAverage['value']) {
$lowAverage = array('index' => $index, 'value' => $data['av_calc_7']);
}
if($highAverage['index'] == null || $data['av_calc_7'] > $highAverage['value']) {
$highAverage = array('index' => $index, 'value' => $data['av_calc_7']);
}
// same for $lowFast, $highFast
}
if($lowAverage['index'] != null) {
$dogs[$lowAverage['index']]['av_calc_7_lowest'] = true;
}
// same for the other 3 values

How to propagate a current menu item flag upwards in a multidimensional array?

I have a menu being built via a multi-dimensional array. A current item is set by matching its url attribute with the current request. I want this value to bubble up to its parents, but I can't for the life of me get it working - I'm sure I've been close, but it's proving a bit tricky. Here's the array:
Array
(
[children] => Array
(
[Home] => Array
(
[url] => /
)
[The Challenge] => Array
(
[url] => /challenge
)
[The Finish] => Array
(
[url] => /finish
)
[Latest News] => Array
(
[url] => /latest_news
)
[Participants] => Array
(
[url] => /participants
[children] => Array
(
[Some guy] => Array
(
[url] => /participants/some_guy
[children] => Array
(
[Hats] => Array
(
[url] => /participants/some_guy/hats
[current] => 1
Essentially, I'm looking for a function that will iterate through every item until it finds the [current] flag, then propagate that value back up to its parents. There are no set limits on depth in the menu.
For the example above, it would result in:
Array
(
[children] => Array
(
[Home] => Array
(
[url] => /
)
[The Challenge] => Array
(
[url] => /challenge
)
[The Finish] => Array
(
[url] => /finish
)
[Latest News] => Array
(
[url] => /latest_news
)
[Participants] => Array
(
[url] => /participants
[current] => 1
[children] => Array
(
[Some guy] => Array
(
[url] => /participants/some_guy
[current] => 1
[children] => Array
(
[Hats] => Array
(
[url] => /participants/some_guy/hats
[current] => 1
like
function set_current($menu) {
if(!is_array($menu)) return 0;
if(isset($menu['current'])) return 1;
foreach($menu as $sub) {
if(set_current($sub))
return $menu['current'] = 1;
}
return 0;
}
not tested, because you posted data unsuitable for testing (oh dear, why why do they keep posting those var_dumps?)
Here's the modified, working version:
function set_current(&$menu) {
if(!is_array($menu)) return false;
if(isset($menu['current'])) return true;
if(isset($menu['children'])){
foreach($menu['children'] as $key => $value) {
if(set_current($menu['children'][$key])){
$menu['current'] = true;
return true;
}
}
}
return false;
}
Thanks for the help; I'd been very close repeatedly over the past few days, just needed to see it written slightly differently to finish it off.

Categories