How to build a tree from a concatenated string in PHP? - php

I try to make a tree list in PHP from a hierarchy stored in a concatenated string in my mysql database
This my table :
and I'd like to reproduce something like this :
<ul>
<li>
<ul>
<li></li>
<li>
<ul>
<li></li>
<li></li>
</ul>
</li>
<li></li>
</ul>
</li>
<li></li>
<li></li>
</ul>
I know I have to use a recursive function I don't reach to do...
Maybe someone could help me

code without comments
see the usage and dataset section below to see what you need to pass and how to use these functions:
function items_to_tree( $items ){
$array = array();
foreach( $items as $item ) {
$parts = explode('.', $item['hierarchy']);
$last = array_pop( $parts );
$cursor = &$array;
foreach ( $parts as $part ) {
if ( !is_array($cursor[$part]) ) {
$cursor[$part] = array();
}
$cursor = &$cursor[$part];
}
$cursor[$last]['#item'] = $item;
}
return $array;
}
function tree_to_ul( $tree ){
$html = $children = '';
foreach( $tree as $key => $item ){
if ( substr($key,0,1) == '#' ) continue;
$children .= tree_to_ul( $item );
}
if ( isset($tree['#item']) ) {
$html .= '<li>' . PHP_EOL;
$html .= '<em>' . $tree['#item']['menu_text'] . '</em>' . PHP_EOL;
$html .= ( $children ? '<ul>' . $children . '</ul>' . PHP_EOL : '' );
$html .= '</li>' . PHP_EOL;
return $html;
}
else {
return $children;
}
}
code with comments and explanation
The code to convert your items to a tree structure:
function items_to_tree( $items ){
$array = array();
foreach( $items as $item ) {
/// split each hierarchy string into it's dot separated parts
$parts = explode('.', $item['hierarchy']);
/// pop off the last item of the array, we'll use this for assignment later
$last = array_pop( $parts );
/// create a reference to our position in the array we wish to fill out
$cursor = &$array;
/// step each hierarchy part and travel down the array structure,
/// just like you would if you typed an array path manually.
/// i.e. $array[$part][$part][...] and so on
foreach ( $parts as $part ) {
/// if at this point in the array, we don't have an array, make one.
if ( !is_array($cursor[$part]) ) {
$cursor[$part] = array();
}
/// ready for the next step, shift our reference to point to the next
/// $part in the array chain. e.g. if $cursor pointed to `$array[$part]`
/// before, after the next line of code the $cursor will point
/// to `$array[$oldpart][$part]`
$cursor = &$cursor[$part];
}
/// we popped the last item off the $parts array so we could easily
/// assign our final value to where the $cursor ends up pointing to.
/// starting with a hierarchy of '00001.00002.00003' would mean at this
/// point $cursor points to $array['00001']['00002'] and $last = '00003';
/// so finally we get $array['00001']['00002']['00003']['#item'] = $item;
$cursor[$last]['#item'] = $item;
/// use '#item' to keep our item's information separate from it's children.
}
/// return our built up array.
return $array;
}
The code to convert the tree structure to a UL:
function tree_to_ul( $tree ){
/// start with nothing
$html = $children = '';
/// step each item found in the current level of $tree
foreach( $tree as $key => $item ){
/// if the item's key starts with a # skip, these contain
/// our item's information and should not be treated as children
if ( substr($key,0,1) == '#' ) continue;
/// recurse this function so that we do the same for any child # any level.
$children .= tree_to_ul( $item );
}
/// if at this level a #item has been set, use this item information to
/// add a title to our level. You could change this to add whatever info
/// from your original database item that you'd like.
if ( isset($tree['#item']) ) {
$html .= '<li>' . PHP_EOL;
$html .= '<em>' . $tree['#item']['menu_text'] . '</em>' . PHP_EOL;
$html .= ( $children ? '<ul>' . $children . '</ul>' . PHP_EOL : '' );
$html .= '</li>' . PHP_EOL;
return $html;
}
/// if there wasn't an item, just return the traversed children.
else {
return $children;
}
}
dataset:
/// I simplified your dataset to an array, this could easily be generated
/// from a database query. You could also convert my code so that you
/// don't have to pre-generate an array, and instead could process after
/// each fetch from the database.
$items = array(
array('hierarchy' => '00001', 'menu_text' => 'One'),
array('hierarchy' => '00002', 'menu_text' => 'Two'),
array('hierarchy' => '00002.00001', 'menu_text' => 'Three'),
array('hierarchy' => '00002.00002', 'menu_text' => 'Four'),
array('hierarchy' => '00002.00003', 'menu_text' => 'Five'),
array('hierarchy' => '00002.00004', 'menu_text' => 'Six'),
array('hierarchy' => '00003', 'menu_text' => 'Seven'),
array('hierarchy' => '00003.00001', 'menu_text' => 'Eight'),
array('hierarchy' => '00003.00001.00001', 'menu_text' => 'Nine'),
array('hierarchy' => '00003.00001.00002', 'menu_text' => 'Ten'),
array('hierarchy' => '00003.00001.00003', 'menu_text' => 'Eleven'),
array('hierarchy' => '00003.00002', 'menu_text' => 'Twelve'),
);
usage:
/// Simple usage :) if a little complex explanation
$tree = items_to_tree( $items );
$html = tree_to_ul( $tree );
echo $html;
in the interests of codegolf ;)
The following could replace my items_to_tree function -- however it isn't advised.
$a = array();
foreach($items as $i){
eval('$a["'.str_replace('.','"]["',$i['hierarchy']).'"]=array("#item"=>$i);');
}

$refs = new stdClass();
//Assuming $data is the result array of your query to fetch the data.
foreach($data as $result)
{
$name = $result['hierarchy'];
$parent = substr($result['hierarchy'],0,strrpos($result['hierarchy'],'.'));
$thisref = &$refs->{$name};
foreach($result as $k => $v)
{
$thisref->{$k} = $v;
}
if ($parent == '') {
$tree->{$name} = &$thisref;
} else {
$refs->{$parent}->children->{$name} = &$thisref;
}
}
This will give you a nice object with every node's child in the property children.
function drawUL($level){
echo '<ul>';
foreach($level as $li){
echo '<li>'.$li->label;
if(isset($li->children))drawUl($li->children);
echo '</li>';
}
echo '</ul>';
}
drawUl($tree);

Related

Looping into a multidimensional array with PHP

I've got this array:
Array
(
[0] => Array
(
[name] => System
[order] => 1
[icon] => stats.svg
[0] => Array
(
[title] => Multilingual
)
[1] => Array
(
[title] => Coloring
)
[2] => Array
(
[title] => Team work
)
[3] => Array
(
[title] => Tutorials
)
)
)
I want to loop into this to show the section name and after all the features containing in the following array.
So, this is what I made:
foreach ($features as $feature => $info) {
echo '
'.$info['name'].'
<ul class="menu-vertical bullets">
';
foreach (array_values($info) as $i => $key) {
echo '
<li>'.$key['title'].'</li>
';
}
echo '
</ul>
';
}
It works except for the first third <li> where I have the first char of name, order and icon value.
Do you know why ?
Thanks.
array_values return value of array so for info values is name, order, icon, 0, 1, ...
Your values foreach is wrong if you just want print title you can use:
foreach ($features as $feature => $info) {
echo '
'.$info['name'].'
<ul class="menu-vertical bullets">
';
//Remove some keys from info array
$removeKeys = array('name', 'order', 'icon');
$arr = $info;
foreach($removeKeys as $key) {
unset($arr[$key]);
}
foreach (array_values($arr) as $i => $key) {
echo '
<li>'.$key['title'].'</li>
';
}
echo '
</ul>
';
}
In php, array_values means all the values of the array. So array_values($info) is array($info['name'], $info['order'], $info['icon'], $info[0], $info[1], $info[2], $info[3])
in your example, you can skip the non-integer keys of the $info to get your titles:
<?php
$features = array();
$info = array();
$info['name'] = 'System';
$info['order'] = 1;
$info['icon'] = 'stats.svg';
$info[] = array('title'=>'Multilingual');
$info[] = array('title'=>'Coloring');
$features[] = $info;
foreach ($features as $feature => $info) {
echo $info['name'] . PHP_EOL;
echo '<ul class="menu-vertical bullets">' . PHP_EOL;
foreach ($info as $k => $item) {
if(!is_int($k)) continue;
echo '<li>' . $item['title'] . '</li>' . PHP_EOL;
}
echo '</ul>' . PHP_EOL;
}
BUT, your original data structure is not well designed and hard to use. For a better design, you can consider the following code, move your items to a sub array of $info:
<?php
$features = array();
$info = array();
$info['name'] = 'System';
$info['order'] = 1;
$info['icon'] = 'stats.svg';
$info['items'] = array();
$info['items'][] = array('title'=>'Multilingual');
$info['items'][] = array('title'=>'Coloring');
$features[] = $info;
foreach ($features as $feature => $info) {
echo $info['name'] . PHP_EOL;
echo '<ul class="menu-vertical bullets">' . PHP_EOL;
foreach ($info['items'] as $item) {
echo '<li>' . $item['title'] . '</li>' . PHP_EOL;
}
echo '</ul>' . PHP_EOL;
}
Sample output of the two demos:
System
<ul class="menu-vertical bullets">
<li>Multilingual</li>
<li>Coloring</li>
</ul>
It works except for the first third li where I have the first char of name, order and icon value. Do you know why ?
Why you see first chars of the values of 'name', 'order', 'icon'? Let see how PHP works.
Take the first loop as an example: foreach (array_values($info) as $i => $key)
Then $i == 0, $key == 'System'
We know that $key[0] == 'S', $key[1] == 'y', $key[2] == 's', etc.
Then you try to access $key['title'], but the string 'title' is not valid as a string offset, so it is converted to an integer by PHP: intval('title') == 0.
Then $key['title'] == $key[intval('title')] == 'S'
That's what you see.
array_value() returns the values of the array, here you will get the value of the array $info and what I understand is that is not what you need. See details for array_value().
You can check if the key for the $info is an integer. if yes, echo the title. Give this a try.
foreach ($features as $feature => $info) {
echo $info['name'].'<ul class="menu-vertical bullets">';
foreach ($info as $key => $value) {
if (is_int($key)) {
echo '<li>'.$key['title'].'</li>';
}
}
echo '</ul>';
}

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);

PHP - variable scope in recursively function

First assumption: Assume, we defined a variable (its name is $tmp) in a function(functioin name is 'ExpMenu') for temporary calculating and in end of function we return this variable.
Second assumption: Assume, we call that function recursively for create a navigation menu base on a multidimensional array.
My question is about scope of that variable ($tmp). In every call funtion, will its value overwritten? In other words, by every function call we lose previous value?
For more detail, please review below code:
/// --- { Declaration Block
$content = array(
array(
'level'=>'1',
'order'=>'1',
'text'=>'New Solution WorkFlow',
'is_parent'=>'yes',
'child'=> array(
array(
'level'=>'2',
'order'=>'1',
'text'=>'Define New Solution',
'is_parent'=>'no',
'url'=>'#'
),
array(
'level'=>'2',
'order'=>'2',
'text'=>'View Solutions',
'is_parent'=>'no',
'url'=>'#'
),
array(
'level'=>'2',
'order'=>'3',
'text'=>'View Solutions',
'is_parent'=>'no',
'url'=>'#'
)
)
),
array(
'level'=>'1',
'order'=>'2',
'text'=>'Solution Modify WorkFlow',
'is_parent'=>'yes',
'child'=> array(
array(
'level'=>'2',
'order'=>'1',
'text'=>'Request For Solution Modify',
'is_parent'=>'no',
'url'=>'#'
)
)
),
array(
'level'=>'1',
'order'=>'3',
'text'=>'Solution Close WorkFlow',
'is_parent'=>'yes',
'child'=> array(
array(
'level'=>'2',
'order'=>'1',
'text'=>'Declare For Solution Close',
'is_parent'=>'no',
'url'=>'#'
)
)
)
);
function ExpMenu($item_array ) {
$tmp='';
foreach ($item_array as $item) {
if ($item['is_parent']=='yes') {
$tmp = '<li class="hasChild">' . $item["text"] . '<ul>';
$tmp .= ExpMenu($item['child']);
$tmp .= '</ul></li>';
} else {
$tmp = '<li>';
$tmp .= ''. $item['text'] . '' ;
$tmp .= '</li>';
}
}
return $tmp;
}
/// --- }
$menu='<div><ul>';
$menu .= ExpMenu($content);
$menu.='</ul></div>';
echo $m . '<br />';
It seams by every call function we lose pervious value.
I thank #l0rkaY for her/him solution, But I found another solution that doesn't need add new parameter in my function.
Because $tmp scope is in 'ExpMenu' function and we call recursively function, therefore variable still alive and wasn't terminated.
So, I modify my function a bit:
function ExpMenu($item_array ) {
$tmp='';
foreach ($item_array as $item) {
if ($item['is_parent']=='yes') {
$tmp .= '<li class="hasChild">' . $item["text"] . '<ul>';
$tmp .= ExpMenu($item['child']);
$tmp .= '</ul></li>';
} else {
$tmp .= '<li>';
$tmp .= ''. $item['text'] . '' ;
$tmp .= '</li>';
}
}
return $tmp;
}
I assume your actual problem is, that your function generates only single item with a single child item.
It's because you are overwriting your previous item in your if/else blocks. That's why you only get the last item.
You just need to concatenate them to the existing items.
function ExpMenu($item_array ) {
$tmp='';
foreach ($item_array as $item) {
if ($item['is_parent']=='yes') {
$tmp .= '<li class="hasChild">' . $item["text"] . '<ul>';
$tmp .= ExpMenu($item['child']);
$tmp .= '</ul></li>';
} else {
$tmp .= '<li>';
$tmp .= ''. $item['text'] . '' ;
$tmp .= '</li>';
}
}
return $tmp;
}

Nested lists using PHP's iterator?

I'm trying to display this kind of array:
$nodes = array(
1 => array(
'title' => 'NodeLvl1',
'children' => array(),
),
2 => array(
'title' => 'NodeLvl1',
'children' => array(
1 => array(
'title' => 'NodeLvl2',
'children' => array(),
),
2 => array(
'title' => 'NodeLvl2',
'children' => array(
1 => array(
'title' => 'NodeLvl3',
'children' => array(),
),
2 => array(
'title' => 'NodeLvl3',
'children' => array(),
),
),
),
),
),
3 => array(
'title' => 'NodeLvl1',
'children' => array(),
),
);
like this:
<ul>
<li>
NodeLvl1
</li>
<li>
NodeLvl1
<ul>
<li>NodeLv2</li>
...
</ul>
</li>
...
Basically a nested list taking into account the "children" property. So far I've come up with this:
class It extends RecursiveIteratorIterator{
protected
$tab = "\t";
public function beginChildren(){
if(count($this->getInnerIterator()) == 0)
return;
echo str_repeat($this->tab, $this->getDepth())."<ul>\n";
}
public function endChildren(){
if(count($this->getInnerIterator()) == 0)
return;
echo str_repeat($this->tab, $this->getDepth())."\n</ul>";
}
public function nextElement(){
echo str_repeat($this->tab, $this->getDepth() + 1).'<li>';
}
}
$it = new It(new RecursiveArrayIterator($nodes));
foreach($it as $key => $item)
echo $item;
Which doesn't work quite right: I get each item wrapped between <ul>s and I don't know how can I close <li>s...
Any ideas on how to make this work? Also is it possible to get all the array properties (the actual element), instead of just the "title" property inside my foreach() loop? And can this be done with objects instead of arrays?
Do you need a class iterator for this? You could do this with just a simple function...
function arrayToListHTML($array, $level = 0) {
static $tab = "\t";
if (empty($array)) return;
$tabs = str_repeat($tab, $level * 2);
$result = "{$tabs}<ul>\n";
foreach ($array as $i => $node):
$result .= "{$tabs}{$tab}<li>\n{$tabs}{$tab}{$tab}{$node['title']}\n".arrayToListHTML($node['children'], $level + 1)."{$tabs}{$tab}</li>\n";
endforeach;
$result .= "{$tabs}</ul>\n";
return $result;
}
Which will produce this output:
<ul>
<li>
NodeLvl1
</li>
<li>
NodeLvl1
<ul>
<li>
NodeLvl2
</li>
<li>
NodeLvl2
<ul>
<li>
NodeLvl3
</li>
<li>
NodeLvl3
</li>
</ul>
</li>
</ul>
</li>
<li>
NodeLvl1
</li>
</ul>
This covers what you've shown us, but I'm not sure what you mean by other properties. Are there more properties in each array other than title and children?
Instead of trying to use your class like an array in foreach() consider using your class to perform the function. For instance, the following code will output correctly but the function is performed inside the class.
class It extends RecursiveIteratorIterator{
protected
$tab = "\t";
public function beginChildren(){
if(count($this->getInnerIterator()) == 0)
return;
echo str_repeat($this->tab, $this->getDepth())."<ul>\n";
}
public function endChildren(){
if(count($this->getInnerIterator()) == 0)
return;
echo str_repeat($this->tab, $this->getDepth)."\n</ul>";
}
public function nextElement(){
echo str_repeat($this->tab, $this->getDepth())."<li>".$this->current()."</li>\n";
}
}
$it = new It(new RecursiveArrayIterator($nodes));
foreach($it as $key => $item)
//echo $item;
//it will be better to write a function inside your custom iterator class to handle iterations
?>
You can use RecursiveCachingIterator to do what you want. Here is an example, (source: https://github.com/cballou/PHP-SPL-Iterator-Interface-Examples/blob/master/recursive-caching-iterator.php)
<?php
// example navigation array
$nav = array(
'Home' => '/home',
'Fake' => array(
'Double Fake' => array(
'Nested Double Fake' => '/fake/double/nested',
'Doubly Nested Double Fake' => '/fake/double/doubly'
),
'Triple Fake' => '/fake/tripe'
),
'Products' => array(
'Product 1' => '/products/1',
'Product 2' => '/products/2',
'Product 3' => '/products/3',
'Nested Product' => array(
'Nested 1' => '/products/nested/1',
'Nested 2' => '/products/nested/2'
)
),
'Company' => '/company',
'Privacy Policy' => '/privacy-policy'
);
class NavBuilder extends RecursiveIteratorIterator {
// stores the previous depth
private $_depth = 0;
// stores the current iteration's depth
private $_curDepth = 0;
// store the iterator
protected $_it;
/**
* Constructor.
*
* #access public
* #param Traversable $it
* #param int $mode
* #param int $flags
*/
public function __construct(Traversable $it, $mode = RecursiveIteratorIterator::SELF_FIRST, $flags = 0)
{
parent::__construct($it, $mode, $flags);
// store the caching iterator
$this->_it = $it;
}
/**
* Override the return values.
*
* #access public
*/
public function current()
{
// the return output string
$output = '';
// set the current depth
$this->_curDepth = parent::getDepth();
// store the difference in depths
$diff = abs($this->_curDepth - $this->_depth);
// get the name and url of the nav item
$name = parent::key();
$url = parent::current();
// close previous nested levels
if ($this->_curDepth < $this->_depth) {
$output .= str_repeat('</ul></li>', $diff);
}
// check if we have the last nav item
if ($this->hasNext()) {
$output .= '<li>' . $name . '';
} else {
$output .= '<li class="last">' . $name . '';
}
// either add a subnav or close the list item
if ($this->hasChildren()) {
$output .= '<ul>';
} else {
$output .= '</li>';
}
// cache the depth
$this->_depth = $this->_curDepth;
// return the output ( we could've also overridden current())
return $output;
}
}
?>
Usage
<?php
try {
// generate the recursive caching iterator
$it = new RecursiveCachingIterator(new RecursiveArrayIterator($nav));
// build the navigation with the iterator
$it = new NavBuilder($it, RecursiveIteratorIterator::SELF_FIRST);
// display the resulting navigation
echo '<ul id="nav">' . PHP_EOL;
foreach ($it as $value) {
echo $value . "\n";
}
echo '</ul>' . PHP_EOL;
} catch (Exception $e) {
var_dump($e); die;
}
?>
First let me explain few things to you. Your array has two pattens
One with numeric indexes
One with string indexes, with title and children which has be parsed differently
I think a recursive function plays very nice role on this part, rather than complex logics. And our recursive function has to be able to handle both patterns separately.
Here is my version of the function you could use with explanation
function arraytolist(Array $array) { //ensure what you receive is array
if(count($array)) { //only if it has some items
//In case the array has `title` index we encountered out PATTERN 2
if(isset($array['title'])) {
$o = "<li>";
$o .= $array['title']; //simply add the title
$o .= arraytolist($array['children']); //and pass the children to this function to verify again
$o .= "</li>";
} else { //if its a normal array, //PATTERN 1
$o = "<ul>";
foreach($array as $value) {
$n = "";
if(is_array($value)) { //in case its an array again,
//send it to this very same function so that it will return as output again
$n .= arraytolist($value);
} else {
$n .= "<li>$value</li>";
}
$o .= strlen($n) ? $n : ""; //if $n has something use it otherwise not
}
$o .= "</ul>"; //lets close the ul
}
return $o;
}
}
Some Advantage of this function
No iteration level
As long as its an array and has item, keeps on building them
Power of simple logic in PHP
I would opt for a simple recursive function that flattens the array into the text/html format:
function arrToList( $arr, $embedded = false ) {
$output = array();
if ( $embedded ) $output[] = '<li>';
$output[] = '<ul>';
foreach ( $arr as $key => $values ) {
$output[] = '<li>'.$values['title'].'</li>';
if ( $values['children'] ) {
$output[] = arrToList( $values['children'], true );
}
}
$output[] = '</ul>';
if ( $embedded ) $output[] = '</li>';
return implode(PHP_EOL, $output);
}
Output from using your input:
NodeLvl1
NodeLvl1
NodeLvl2
NodeLvl2
NodeLvl3
NodeLvl3
NodeLvl1
or the actual code:
<ul>
<li>NodeLvl1</li>
<li>NodeLvl1</li>
<li>
<ul>
<li>NodeLvl2</li>
<li>NodeLvl2</li>
<li>
<ul>
<li>NodeLvl3</li>
<li>NodeLvl3</li>
</ul>
</li>
</ul>
</li>
<li>NodeLvl1</li>
</ul>
Cheers

PHP — How to determine number of parents of a child array?

I'm not so strong with arrays but I need to determine how to count the number of parents a child array has in order to determine the indenting to display it as an option in a SELECT.
So, if I have this array:
array(
'World'=>array(
'North America'=>array(
'Canada'=>array(
'City'=>'Toronto'
)
)
)
);
How would I go about determining how many parents 'City' has in order to translate that into the number of spaces I want to use as an indent?
Thanks for any help.
EDIT: Let's see if I can explain myself better:
I have this code I'm using to build the OPTIONS list for a SELECT:
function toOptions($array) {
foreach ($array as $key=>$value) {
$html .= "<option value=\"" . $key . "\" >";
$html .= $value['title'];
$html .= "</option>";
if (array_key_exists('children', $value)) {
$html .= toOptions($value['children']);
}
}
return $html;
}
print toOptions($list);
So, I'm trying to determine how to get the number of parents in order to add spaces before the title in this line:
$html .= $value['title'];
Like:
$html .= " " . $value['title'];
But, I'm not sure how to figure out how many spaces to add.
Hopefully this is more clear.
Thanks for any help so far.
$x = array(
'World'=>array(
'North America'=>array(
'Canada'=>array(
'City'=>'Toronto'
)
)
)
);
// This function do something with the key you've found in the array
function visit($name, $depth)
{
echo $name . ' has ' . $depth . ' parents.';
}
// This function visits all the contents aff $array
function find_recursive($array, $depth = 0)
{
if (is_array($array)) {
foreach ($array as $k => $value) {
visit($k, $depth + 1);
find_recursive($array, $depth + 1);
}
}
}
For visiting:
find_recursive($x);
Well. Off the top what you are dealing with is a multi dimensional array.
You could run a count w/ foreach on each level of the array, and use the count number returned +1 for each level the foreach loops through.
I'm not sure if this answers your question, but I am trying to see exactly what it is you are trying to achieve.
As you are already using a recursive function to display that data, you can just extend your function. There is no need to traverse the array more often than one time:
function getWhitespaces($count) {
$result = '';
while($count--) {
$result .= '$nbsp;';
}
return $result;
}
function toOptions($array, $level=0) {
foreach ($array as $key=>$value) {
$html .= "<option value=\"" . $key . "\" >";
$html .= getWhitespaces($level) + $value['title'];
$html .= "</option>";
if (array_key_exists('children', $value)) {
$html .= toOptions($value['children'], $level + 1);
}
}
return $html;
}
print toOptions($list);
Try the following.. Your solution screams for recursion in my mind. Its a bit ugly but it seems to work
$totraverse = array(
'Moon' => array(
'Dark Side' => "Death Valley"
),
'Halley Commet' => "Solar System",
'World' => array(
'North America' => array(
'Canada' => array(
'City' => 'Toronto'
)
), 'South America' => array(
'Argentina' => array(
'City' => 'Toronto'
)
)
)
);
function traverse($totraverse_, $path="", $count=0) {
global $array;
// echo count($totraverse_) . " count\n";
if (!is_array($totraverse_)) {
echo "returning $path and $key\n";
return array($path, $count);
} else {
foreach ($totraverse_ as $key => $val) {
echo "assting $path and $key\n";
$result = traverse($val, $path . "/" . $key, $count + 1);
if($result){
$array[]=$result;
}
}
}
echo false;
}
$array = array();
traverse($totraverse);
foreach($array as $item){
echo "{$item[0]}--->{$item[1]}\n";
}

Categories