Multidimension PHP array parent tack value - php

I've been working on some code today where I got stuck at a little multidimensional array problem. First of all it's maybe handy to read some code I wrote to get a better view on the problem itself:
public function treeLeaves(array $elements, $parent = 0) {
$branch = array();
foreach($elements as $element) {
$leaf = array('pageid' => $element['pageid'],
'page_parent' => $element['page_parent'],
'label' => ucfirst($element['page_print'][0]['print_title']),
'uri' => $element['page_alias']);
if($element['page_parent'] == $parent) {
$children = $this->treeLeaves($elements, $leaf['pageid']);
if($children) {
foreach($children as $key => $child) {
$leaf['pages'][] = $children[$key];
}
}
$branch[] = $leaf;
}
}
return $branch; }
For some reason I can't figure out how to glue the parent URI alias onto all the separate child URIs. The desired result I'm looking for should look something like this: http://pastebin.com/Eh9ExBjG
I hope some master can help me out here. I've been trying so many different stuff, but can't figure this thing out, even though I feel that it is relatively easy to solve.

Somewhat simplified, but I think you would get the idea:
function treeLeaves($elements, $parent = 0, $baseUri = '/index') {
$branch = array();
foreach($elements as $element) {
if ($element['page_parent'] == $parent) {
$leaf = array(
'uri' => $baseUri . '/' . $element['page_alias'];
);
$leaf['pages'] = treeLeaves($elements, $element['pageid'], $leaf['uri']);
$branch[] = $leaf;
}
}
return $branch;
}

Related

Tree map by categories

I'm trying to build a tree-map from categories.
I have the categories (I have a lot of categories and I want to remove duplicates and show them in a tree-map view)
$cat = array(
"Sneakers/Men",
"Sneakers/Women",
"Accessories/Jewellery/Men",
"Accessories/Jewellery/Women",
"Accessories/Jewellery/Men
");
...and I want them like this
$categories = array(
"Sneakers" => array(
"Men" => array(),
"Women" => array()
),
"Accessories" => array(
"Jewellery" => array(
"Men" => array(),
"Women" => array()
)
)
);
to print them like this
- Sneakers
-- Men
-- Women
- Accessories
-- Jewellery
--- Men
--- Women
Try this:
<?php
$cat = array(
"Sneakers/Men",
"Sneakers/Women",
"Accessories/Jewellery/Men",
"Accessories/Jewellery/Women",
"Accessories/Jewellery/Men
");
function buildTree($categories, $result = []){
$temp = [];
foreach($categories as $categoryString){
$catParts = explode('/',$categoryString);
if(count($catParts) > 1){
$temp[$catParts[0]][] = str_replace($catParts[0].'/','',$categoryString);
} else {
$temp[$catParts[0]] = [];
}
}
foreach($temp as $elemName => $elemVal){
$result[$elemName] = buildTree($elemVal);
}
return $result;
}
var_dump(buildTree($cat));
The most simple way is to use references, like this:
$out = [];
foreach ($cat as $str) {
$lookup =& $out;
foreach (explode("/", $str) as $part) {
$lookup =& $lookup[$part];
if (!isset($lookup)) {
$lookup = [];
}
}
}
$lookup initially refers to the whole expected result, then the reference is extended at each step to follow the path of nested members.
Note that each new member added looks like member-name => [], so that actually even final leaves are arrays: it may seem a bit weird, but is a pretty way to have a reduced code (each member is always ready to receive children).
And it's not a difficulty, though, to use the resulting array to then print it like the OP asked:
function nest_print($src, $level = 0) {
$prefix = '<br />' . str_repeat('- ', ++$level);
foreach ($src as $key => $val) {
echo $prefix . $key;
if ($val) {
nest_print($val, $level);
}
}
}
nest_print($out);
EDIT
Here is an alternate solution, including the count of final leaves, as asked by the OP in his comment:
$out = [];
foreach ($cat as $str) {
$lookup =& $out;
$parts = explode("/", $str);
foreach ($parts as $part) {
$lookup =& $lookup[$part];
if (!isset($lookup)) {
$lookup = [];
}
// when $part is a final leaf, count its occurrences
if ($part == end($parts)) {
$lookup = is_array($lookup) ? 1 : ++$lookup;
}
}
}
(might likely be improved in a more elegant way, though)
And here is how to modify the print-result snippet accordingly:
function nest_print($src, $level = 0) {
$prefix = '<br />' . str_repeat('- ', ++$level);
foreach ($src as $key => $val) {
echo $prefix . $key;
if (is_array($val)) {
nest_print($val, $level);
} else {
echo ': ' . $val;
}
}
}
nest_print($out);

How to search multidimensional array with array of keys

Imagine that you have the following data structures. The config one is a hash of config values. The search is an array of hash keys to pull a config value from the config hash.
$config['users']['students']['default']['school'] = 'Garfield High';
$config['users']['students']['default']['domain'] = 'ghs.com';
$config['users']['teacher']['default']['fruit'] = 'apple';
$config['school']['superintendent'] = 'Boris York';
$search[] = 'users';
$search[] = 'students';
$search[] = 'default';
$search[] = 'school';
What's the most efficient way to use the $search array to get the value "Garfield High."
This sounds like a school assignment, but really it isn't. I've wandered down a rabbit hole, and while I'll probably abandon this thing that I'm doing, I'm curious how best to solve this problem. It seems like it should be easy, but for some reason, I'm stumped.
The method to extract values from $config should work with any $search array size. It needs to work with $search = array('school', 'superintendent') as well.
public function get($search, $config) {
// Somehow pull value from $config
}
It's a simple loop, using each element of $search as the key in the next level of the array being searched.
public function get($search, $config) {
$result = $config;
foreach ($search as $key) {
if (is_array($result) && isset($result[$key])) {
$result = $result[$key];
} else {
return false; // not found
}
}
return $result;
}
DEMO
Alternative solution with RecursiveIteratorIterator class:
$config = ['users' => ['students' => ['default' => ['school' => 'Garfield High'], 'highschool' => ['domain' => 'highschool.ghs.com']]]];
$search1 = ['users', 'students', 'default', 'school'];
$search2 = ['users', 'students', 'highschool', 'domain'];
function getConfigItem($search = [], $config = []) {
$iterator = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($config), \RecursiveIteratorIterator::SELF_FIRST);
$result = "";
foreach ($iterator as $k => $v) {
if ($search[$iterator->getDepth()] == $k && is_string($v)) {
$result = $v;
}
}
return $result;
}
var_dump(getConfigItem($search1, $config)); // string 'Garfield High'
var_dump(getConfigItem($search2, $config)); // string 'highschool.ghs.com'
http://php.net/manual/en/class.recursiveiteratoriterator.php

Reduce amount of queries and increase performance for categories and subcategories queries

EDIT 3: Got it down to 300-500 ms by changing flatten method to only merge arrays if not empty.
EDIT 2: Got it down to 1.6 seconds by only calling array_replace for non empty array. Now all that is left to do is optimize the function sort_categories_and_sub_categories. That is NOW the bottleneck. If I remove that I am down to 300ms. Any ideas?
get_all_categories_and_sub_categories
foreach(array_keys($categories) as $id)
{
$subcategories = $this->get_all_categories_and_sub_categories($id, $depth + 1);
if (!empty($subcategories))
{
$categories = array_replace($categories, $subcategories);
}
}
EDIT
I improved performance by over 50% (6 seconds --> 2.5 seconds) by doing a cache in the get_all method. It reduces the amount of queries to 1 from 3000. I am still wondering why it is slow.
I have the following method for getting categories and nested sub categories. If a user has a couple hundred (or thousand) top level categories it does a bunch of queries for each category to find the children. In one case I have 3000 categories and it did 3000 queries. Is there a way to optimize this to do less queries? OR should I just check to see if they have a lot of categories NOT to try to show nested too.
function get_all_categories_and_sub_categories($parent_id = NULL, $depth = 0)
{
$categories = $this->get_all($parent_id);
if (!empty($categories))
{
foreach($categories as $id => $value)
{
$categories[$id]['depth'] = $depth;
}
foreach(array_keys($categories) as $id)
{
$categories = array_replace($categories, $this->get_all_categories_and_sub_categories($id, $depth + 1));
}
return $categories;
}
else
{
return $categories;
}
}
function get_all($parent_id = NULL, $limit=10000, $offset=0,$col='name',$order='asc')
{
static $cache = array();
if (!$cache)
{
$this->db->from('categories');
$this->db->where('deleted',0);
if (!$this->config->item('speed_up_search_queries'))
{
$this->db->order_by($col, $order);
}
$this->db->limit($limit);
$this->db->offset($offset);
foreach($this->db->get()->result_array() as $result)
{
$cache[$result['parent_id'] ? $result['parent_id'] : 0][] = array('name' => $result['name'], 'parent_id' => $result['parent_id'], 'id' => $result['id']);
}
}
$return = array();
$key = $parent_id == NULL ? 0 : $parent_id;
if (isset($cache[$key]))
{
foreach($cache[$key] as $row)
{
$return[$row['id']] = array('name' => $row['name'], 'parent_id' => $row['parent_id']);
}
return $return;
}
return $return;
}
function sort_categories_and_sub_categories($categories)
{
$objects = array();
// turn to array of objects to make sure our elements are passed by reference
foreach ($categories as $k => $v)
{
$node = new StdClass();
$node->id = $k;
$node->parent_id = $v['parent_id'];
$node->name = $v['name'];
$node->depth = $v['depth'];
$node->children = array();
$objects[$k] = $node;
}
// list dependencies parent -> children
foreach ($objects as $node)
{
$parent_id = $node->parent_id;
if ($parent_id !== null)
{
$objects[$parent_id]->children[] = $node;
}
}
// clean the object list to make kind of a tree (we keep only root elements)
$sorted = array_filter($objects, array('Category','_filter_to_root'));
// flatten recursively
$categories = self::_flatten($sorted);
$return = array();
foreach($categories as $category)
{
$return[$category->id] = array('depth' => $category->depth, 'name' => $category->name, 'parent_id' => $category->parent_id);
}
return $return;
}
static function _filter_to_root($node)
{
return $node->depth === 0;
}
static function _flatten($elements)
{
$result = array();
foreach ($elements as $element)
{
if (property_exists($element, 'children'))
{
$children = $element->children;
unset($element->children);
}
else
{
$children = null;
}
$result[] = $element;
if (isset($children))
{
$flatened = self::_flatten($children);
if (!empty($flatened))
{
$result = array_merge($result, $flatened);
}
}
}
return $result;
}

PHP - unset object in nested foreach loops

so long story short - unsetting works fine when trying to unset $this->models[$modelKey] in first foreach, $this->models[$modelKey]->equipmentList->equipment[$eqKey] points to the correct object, but unsetting it in nested foreach doesn't work. Any ideas? Thanks in advance for help.
public function processModelsEquipmentList() {
foreach ($this->models as $modelKey => $model) {
if (!strstr('#', $model->id)) {
foreach ($model->equipmentList->equipment as $eqKey => $equipment) {
if (in_array($equipment->code, $this->specialVersionsCodes)) {
$newModel = clone $model;
$newModel->name.= ' ' . $equipment->name;
$newModel->id.= '#' . $equipment->id;
if (strlen($newModel->code) < 4) {
$newModel->code.=$equipment->code;
}
$newModel->order = $newModel->order + 1;
$newEquipmentList = new EquipmentList($newModel->id, true);
$newEquipmentList->add(clone $equipment);
$newModel->setNewEquipmentList($newEquipmentList);
$this->addModel($newModel);
//echo $this->models[$modelKey]->equipmentList->equipment[$eqKey]->name;die();
unset($this->models[$modelKey]->equipmentList->equipment[$eqKey]);
}
}
}
}
}
isn't it accessed via $model and not $this->models? And with $equipment the same.
Just try this:
unset($equipment[$eqKey]);
Some expansion:
foreach ($this->models as $modelKey => $model) {
//here you use $model to access $this->models[X]
foreach ($model->equipmentList->equipment as $eqKey => $equipment) {
//here you use $equipment to access $this->models[X]->equipmentList->equipment[x]
}
}
EDIT:
Now I got the solution:
foreach ($this->models as $modelKey => $model) {
foreach ($model->equipmentList->equipment as $eqKey => $equipment) {
//use the $modelKey key and not the $eqKey
unset($model->equipmentList->equipment[$modelKey]);
}
}
In case I'm spot on, I'll make this an answer, but if I'm wrong, I'll move this to a comment:
First, you clone the object you are trying to unset at the end. Why not just unset it right after the clone?
Second, I think that object is passed by reference to the foreach loop by design, so you don't need to reference the key.
If I'm right, this might work:
public function processModelsEquipmentList() {
foreach ($this->models as $modelKey => $model) {
if (!strstr('#', $model->id)) {
foreach ($model->equipmentList->equipment as $eqKey => $equipment) {
if (in_array($equipment->code, $this->specialVersionsCodes)) {
$newModel = clone $model;
unset($model); // <-- Unesetting after clone, using loop var name.
$newModel->name.= ' ' . $equipment->name;
$newModel->id.= '#' . $equipment->id;
if (strlen($newModel->code) < 4) {
$newModel->code.=$equipment->code;
}
$newModel->order = $newModel->order + 1;
$newEquipmentList = new EquipmentList($newModel->id, true);
$newEquipmentList->add(clone $equipment);
$newModel->setNewEquipmentList($newEquipmentList);
$this->addModel($newModel);
}
}
}
}
}

Building a multidimensional associative array using PHP and MYSQL

I'm trying to build a multidimensional associative array while fetching the results from a MySQL query.
But i can't figure out how to achieve this.
I want to build an array like this one:
array ("cat_name" => "category1",
"id_cat" => "1",
"sub_cat" => array ("name_sub_cat" => "subCategory",
"id_sub_cat" => "4",
"ss_cat" =>array ("ss_cat_name" =>"ss_cat1",
"id_ss_cat" => "4"
)
)
);
Here's where i'm building the array:
Edit : I've ended up with something like that, not very sexy, but if think i'm almost there
while($row = mysql_fetch_assoc($resultQueryCat)){
$menuArray[$row["id_cat"]]["cat_name"] = $row["cat_name"];
$menuArray[$row["id_cat"]][$row["id_sub_cat"]]["id_sub_cat"] = $row["id_sub_cat"];
$menuArray[$row["id_cat"]][$row["id_sub_cat"]]["name_sub_cat"] = $row["name_sub_cat"];
$menuArray[$row["id_cat"]][$row["id_sub_cat"]][$row["ss_cat_name"]]["ss_cat_name"] = $row["ss_cat_name"];
$menuArray[$row["id_cat"]][$row["id_sub_cat"]][$row["ss_cat_name"]]["id_ss_cat"] = $row["id_ss_cat"];
}
Edit2: The code to display the array
$menu.='<ul>';
foreach ($menuArray as $key) {
$compteur_cat++;
$menu.= '<div id="collapsiblePanelCol'.$compteur_cat.'" class="collapsiblePanelCol floatleft">
<li class="categorie"> '.$key["cat_name"].'
<ul>';
foreach ($key as $key1) {
if (is_array($key1){/* One of the thing i forgot which totally screwed up my results*/
$menu.= '<ul>
<li class="ss_categorie">'.$key1["name_sub_cat"].'<ul>';
foreach ($key1 as $key2) {
if (is_array($key2)){
$menu.= '<li class="element">'.$key2["ss_cat_name"].'</li>'; }
}
$menu.= '</ul></li></ul>';
}
}
$menu.='</ul>
</li>
</div>';
}
$menu.= '</ul>';
Thanks.
Final Edit: My code is working :) i edited the previous code to make it correct
It's not an easy task, as data from DB might contain loops :-)
I'd better save it in id->data hashmap so that you can build the structure easier.
Not sure why you need multidimensions.
The way I build multidimensional arrays is through classes and their objects.
Example:
class Categories
{
public $cat_name;
public $id_cat;
function __construct($cat_name, $id_cat)
{
$this->id = $id_cat;
$this->name = $cat_name;
$this->sub_cat = array();
}
}
class SubCategories
{
public $name_sub_cat;
public $id_sub_cat;
function __construct($name_sub_cat, $id_sub_cat)
{
$this->id = $id_sub_cat;
$this->name = $name_sub_cat;
$this->ss_cat = array();
}
}
class SSCategories
{
public $ss_cat_name;
public $id_ss_cat;
function __construct($ss_cat_name, $id_ss_cat)
{
$this->id = $id_ss_cat;
$this->name = $ss_cat_name;
}
}
Then to write to those classes (after fetching MySQL DB Data [assuming OOP mysql]):
if($result)
{
$array = array();
while($row = $result->fetch_array())
{
$array[] = new Categories($row["cat_name"], $row["id_cat"]);
}
foreach($array as $main)
{
... (fetching sub category info)
if($sub_result)
{
$sub_array = array();
while($sub_row = $sub_result->fetch_array())
{
$sub_array[] = new SubCategories($sub_row["name_sub_cat"], $sub_row["id_sub_cat"]);
}
$main->sub_cat = $sub_array;
You would repeat for each within original if loop for as many sub arrays as you have. This took a while :).
NOTE: Close if loops (heirarchally) if there are no more sub arrays.
echo '<pre>';
print_r($array);
echo '</pre>';
The above code will help show the levels of your array!

Categories