I have some PHP code I've been working on for a good few days now. I'm trying to generate a formatted list of rules from a flat array. I got help here before on how to turn the flat array into a tree array, but I'm having difficulty writing a recursive function that can go through it and successfully break it down at points at such depths where I'd like the rules to be members of an unordered list from the markup that gets printed.
<?php
$data = array(
'0' => 'Introduction',
'4' => 'General',
'4.1' => 'Chat',
'4.1.1' => 'Do',
'4.1.1.9' => 'This',
'4.1.1.10' => 'That',
'4.1.1.11' => 'Other',
);
$struct = array(
'children' => array()
);
foreach ($data as $ruleID => $content)
{
$parent =& $struct;
foreach (explode('.', $ruleID) as $val)
{
if (!isset($parent['children'][$val]))
{
$parent['children'][$val] = array(
'content' => '',
'children' => array()
);
}
$parent =& $parent['children'][$val];
}
$parent['content'] = $content;
}
$out = '';
$rules = array_pop($struct);
format_rule($rules);
var_dump($rules);
echo $out;
function format_rule($arr, $depth=0)
{
global $out;
echo "depth: $depth\n";
foreach($arr as $key => $val)
{
switch($depth)
{
case 0:
$out .= '<h1>'.$val['content']."</h1><br />\n";
break;
case 1:
$out .= '<h2>'.$val['content']."</h2><br />\n";
break;
case 2:
$out .= '<h3>'.$val['content']."</h3><br />\n";
break;
default:
$out .= '<li>'.$val['content']."</li>\n";
break;
}
if(isset($val['children']) && count($val['children']) > 0)
{
if($depth > 2)
{
$out .= '<ul>';
format_rule($val['children'], ++$depth);
$out .= '</ul>';
}
else
{
format_rule($val['children'], ++$depth);
}
}
}
}
The output at the moment is:
<h1>Introduction</h1><br />
<h1>General</h1><br />
<h2>Chat</h2><br />
<h3>Do</h3><br />
<li>This</li><br />
<li>That</li><br />
<li>Other</li><br />
Which is great, except from my code I'm pretty sure the section under 'Do' should have a <ul> around it.
change your code to :
if($depth >= 2)
note: remember the count starts at 0, not 1.
Try this:
<?php
$data = array(
'0' => 'Introduction',
'4' => 'General',
'4.1' => 'Chat',
'4.1.1' => 'Do',
'4.1.1.9' => 'This',
'4.1.1.10' => 'That',
'4.1.1.11' => 'Other',
);
function get_level($key){
return count(explode(".",$key));
}
function set_tag(&$array,$key,$item,&$ul_started){
$level = get_level($key);
switch($level){
case 1:
case 2:
case 3:
if($ul_started){
$array[$key] = "</ul><h".$level.">".$item."</h".$level."><br>";
$ul_started=false;
}else{
$array[$key] = "<h".$level.">".$item."</h".$level."><br>";
}
break;
default:
if(!$ul_started){
$array[$key] = "<ul><li><strong>".$item."</strong></li><br>";
$ul_started=true;
}else{
$array[$key] = "<li><strong>".$item."</strong></li><br>";
}
break;
}
}
$ul_started = false;
foreach($data as $key=>$item){
set_tag($data,$key,$item,$ul_started);
}
if($ul_started){
$keys = array_keys($data);
$data[$keys[count($data)-1]] .= "</ul>";
}
echo implode("",$data);
?>
Related
given a nested array of arbitrary depth like this:
$array = array(
1400=>
array(7300=>
array(
7301=> array(),
7302=> array(),
7305=> array(
7306=>array()
),
),
7314=>array()
),
);
how would one get the hierarchy of keys for any key.
for example:
getkeys(7305);
should return 1400,7300,7305 in that order
or
getkeys(7314);
should return 1400,7314
all array keys are unique values
Using RecursiveIteratorIterator
$array = array(
1400 => array(
7300 => array(
7301=> array(),
7302 => array(),
7305 => array(
7306=>array()
),
),
7314=>array()
),
);
function getKeys($key, $array) {
$found_path = [];
$ritit = new RecursiveIteratorIterator(new RecursiveArrayIterator($array), RecursiveIteratorIterator::SELF_FIRST);
foreach ($ritit as $leafValue) {
$path = array();
foreach (range(0, $ritit->getDepth()) as $depth) {
$path[] = $ritit->getSubIterator($depth)->key();
}
if (end($path) == $key) {
$found_path = $path;
break;
}
}
return $found_path;
}
print_r(getKeys(7305, $array));
// Array
// (
// [0] => 1400
// [1] => 7300
// [2] => 7305
// )
This is very interesting problem you have so I tried to make a function that will echo your keys. If this is not good enough pls let me know I can improve code. Thanks.
<?php
$a = array(
1400=>
array(7300=>
array(
7301=> array(),
7302=> array(),
7305=> array(
7306=>array()
),
),
7314=>array()
),
);
$mykey = 7306;
$level = 0;
$result = array();
$resultarray = test($a,$mykey,$level,$result);
function test($array,$mykey,$level,$result){
$level++;
foreach($array as $key => $element){
if($key == $mykey){
echo 'found';
print_r($result);
exit;
} else if(is_array($element)){
$result[$level] = $key;
$result1 = test($element,$mykey,$level,$result);
}
}
}
The idea is to check current array branch, and if the needle key isn't found, then iterate current items and check their array child nodes by recursive function calls. Before each step down we push a current key to stack, and pop the stack if the function does not found a needle key in whole branch. So if the key found, the function returns true by the chain, preserving successful keys in the stack.
function branchTraversing(& $branch, & $key_stack, $needle_key) {
$found = false;
if (!array_key_exists($needle_key, $branch)) {
reset($branch);
while (!$found && (list($key, $next_branch) = each($branch))) {
if (is_array($next_branch)) {
array_push($key_stack, $key);
$found = branchTraversing($next_branch, $key_stack, $needle_key);
if (!$found) {
array_pop($key_stack);
}
}
}
} else {
array_push($key_stack, $needle_key);
$found = true;
}
return $found;
}
function getPath(& $array, $needle_key) {
$path = [];
branchTraversing($array, $path, $needle_key);
return $path;
}
$test_keys = [1400, 7300, 7302, 7306, 7314, 666];
foreach ($test_keys as $search_key) {
echo '<p>' . $search_key . ' => [ '
. implode(', ', getPath($array, $search_key)) . ' ]</p>';
}
I've realized I need to stop banging my head and ask for help...
I have the following array:
$permissionTypes = array(
'system' => array(
'view' => 'View system settings.',
'manage' => 'Manage system settings.'
),
'users' => array(
'all' => array(
'view' => 'View all users.',
'manage' => 'Manage all users.'
)
),
'associations' => array(
'generalInformation' => array(
'all' => array(
'view' => 'View general information of all associations.',
'manage' => 'Manage general information of all associations.'
),
'own' => array(
'view' => 'View general information of the association the user is a member of.',
'manage' => 'Manage general information of the association the user is a member of.'
)
)
));
I'm trying to collapse / cascade the keys into a one-dimension array like so:
array(
'system_view',
'system_manage',
'users_all_view',
'users_all_manage',
'associations_generalInformation_all_view',
'associations_generalInformation_all_manage',
'associations_generalInformation_own_view',
'associations_generalInformation_own_manage'
)
I could use nested loops, but the array will be an undefined number of dimensions.
This is the closest I've gotten:
public function iterateKeys(array $array, $joiner, $prepend = NULL) {
if (!isset($formattedArray)) { $formattedArray = array(); }
foreach ($array as $key => $value) {
if(is_array($value)) {
array_push($formattedArray, $joiner . $this->iterateKeys($value, $joiner, $key));
} else {
$formattedArray = $prepend . $joiner . $key;
}
}
return $formattedArray;
}
Any ideas?
I think this should do it:
public function iterateKeys(array $array, $joiner, $prepend = NULL) {
if (!isset($formattedArray)) {
$formattedArray = array();
}
foreach ($array as $key => $value) {
if(is_array($value)) {
$formattedArray = array_merge($formattedArray, $this->iterateKeys($value, $joiner, $prepend . $joiner . $key));
} else {
$formattedArray[] = $prepend . $joiner . $key;
}
}
return $formattedArray;
}
Since the recursive call returns an array, you need to use array_merge to combine it with what you currently have. And for the non-array case, you need to push the new string onto the array, not replace the array with a string.
try this:
function flattern(&$inputArray, $tmp = null, $name = '')
{
if ($tmp === null) {
$tmp = $inputArray;
}
foreach($tmp as $index => $value) {
if (is_array($value)) {
flattern($inputArray, $value, $name.'_'.$index);
if (isset($inputArray[$index])) {
unset($inputArray[$index]);
}
} else {
$inputArray[$name.'_'.$index] = $value;
}
}
return $inputArray;
}
var_dump(flattern($permissionTypes));
function flattenWithKeys(array $array, array $path = []) {
$result = [];
foreach ($array as $key => $value) {
$currentPath = array_merge($path, [$key]);
if (is_array($value)) {
$result = array_merge($result, flattenWithKeys($value, $currentPath));
} else {
$result[join('_', $currentPath)] = $value;
}
}
return $result;
}
$flattened = flattenWithKeys($permissionTypes);
Working fine for me.
function array_key_append($source_array, $return_array = array(), $last_key = '', $append_with = "#")
{
if(is_array($source_array))
{
foreach($source_array as $k => $v)
{
$new_key = $k;
if(!empty($last_key))
{
$new_key = $last_key . $append_with . $k;
}
if(is_array($v))
{
$return_array = array_key_append($v, $return_array, $new_key, $append_with);
} else {
$return_array[$new_key] = $v;
}
}
}
return $return_array;
}
$StandardContactRequestDataforALL = array_key_append($StandardContactRequestDataforALL, $return_array = array(), $last_key = '', $append_with = "#");
I'm trying to write a function that takes a multi-dimensional array as input and outputs a multi-line string of keys like the following
['key']['subkey']
['key']['another_subkey']
['key']['another_subkey']['subkey_under_subkey']
['key']['yet_another_subkey']
['another_key']['etc']
Here is my attempt. It has problems when you get to the second level.
function get_array_keys_as_string($array){
$output = "";
foreach($array as $k => $v){
if(is_array($v)){
$string = get_array_keys_as_string($v);
$prepend = "['$k']";
$string = $prepend.str_replace("\n","\n".$prepend, $string);
$output .= $string;
}
else{
$output .= "['$k']\n";
}
}
return $output;
}
I know I need a recursive function, but so far my attempts have come up short.
To get the exact output you asked for use the following:
$arr = array(
"key" => array(
"subkey" => 1,
"another_subkey" => array(
"subkey_under_subkey" => 1
),
"yet_another_subkey" => 1
),
"another_key" => array(
"etc" => 1
)
);
function print_keys_recursive($array, $path = false) {
foreach($array as $key=>$value) {
if(!is_array($value)) {
echo $path."[".$key."]<br/>";
} else {
if($path) {
echo $path."[".$key."]<br/>";
}
print_keys_recursive($value, $path."[".$key."]");
}
}
return;
}
print_keys_recursive($arr);
Output:
[key][subkey]
[key][another_subkey]
[key][another_subkey][subkey_under_subkey]
[key][yet_another_subkey]
[another_key][etc]
Not sure how you want the output since you have not provided an example array, just the result, but here is an example based on the following array,
$array = array(
"key" => array(
"subkey" => 1,
"another_subkey" => array("2", "subkey_under_subkey" => 3),
"yet_another_subkey" => 4
),
"another_key" => array("etc"),
"last_key" => 0
);
Using the following function,
function recursive_keys($arr, $history = NULL)
{
foreach ($arr as $key => $value)
{
if (is_array($value))
recursive_keys($value, $history."['".$key."']");
else
echo $history."['".$key."']\n";
}
}
Output of recursive_keys($array) is,
['key']['subkey']
['key']['another_subkey']['0']
['key']['another_subkey']['subkey_under_subkey']
['key']['yet_another_subkey']
['another_key']['0']
['last_key']
Try this
function arrayMultiKeys($array,$depth = 0){
foreach($array as $k=>$v){
echo "['".$k."']";
if(is_array($v)){
arrayMultiKeys($v,$depth + 1);
}
if($depth == 0 ){
echo "<br>";
}
}
}
I have a html array structure..
I output this array with foreach loops. (inside get_output functions)
Is it possible to output results without using foreach?
$schema = array(
array(
'tag' => 'div',
'class' => 'lines',
array(
'tag' => 'div',
array(
'tag' => 'span',
'style' => 'margin:10px; padding:10px',
'key' => '$key-countryname',
),
'key' => '$value-country',
),
array(
'tag' => 'div',
array(
'tag' => 'span',
'style' => 'margin:10px; padding:10px',
'key' => '$key-countryname',
),
'key' => '$value-country',
),
)
);
My function is using foreach loops to output results
function get_output($schema, $t = -2){
$t++; $tag = ""; $atts = array(); $keys = array(); $code = array();
foreach($schema as $k => $v){
if(is_array($v)){
$keys[] = get_output($v, $t);
} else {
switch($k){
case "tag": $tag = $v; break;
case "key": $keys[] = $v; break;
case "type": break;
default: $atts[$k] = $v; break;
}
}
}
if(0 < $t){ $code[] = "\n".str_repeat("\t", $t); }
if($tag){
$code[] = "<$tag"; foreach($atts as $k=>$v){ $code[] = ' '.$k.'="'.$v.'"'; } $code[] = ">";
$code = array(implode('', $code));
}
foreach($keys as $k){ $code[] = $k; }
if($tag){
$code[] = "\n".str_repeat("\t", $t);
$code[] = '</'.$tag.'>';
}
//print_r($code);
return implode("", $code);
}
while and for are perfectly valid ways to loop array:
$a = array(1,2,3); // indexed
$b = array(
'a' => 1,
'b' => 2,
'c' => 3
); // associative
echo '$a indexed with "for": <br />';
for ($i = 0; $i < count($a); $i++) {
echo $a[$i] . '<br />';
}
echo '$a indexed with "while": <br />';
$i = 0; // reset counter
while ($i < count($a)) {
echo $a[$i] . '<br />';
$i++;
}
echo '$b assoc with "for": <br />';
for ($i = 0; $i < count($a); $i++) {
echo key($b) . ' => ' . current($b) . '<br />';
next($b); // step forward
}
echo '$b assoc with "while": <br />';
reset($b); // rewind array cursor to start
while ($value = current($b)) {
echo key($b) . ' => ' . $value . '<br />';
next($b); // step forward
}
Also as you already implemented in your second code snippet, multi-dim array can be looped with recursion (though compared to nested loops it consumes more memory)
I'm trying to produce a multi-level HTML list from a source array that is formatted like this:
/**
* id = unique id
* parent_id = "id" that this item is directly nested under
* text = the output string
*/
$list = array(
array(
'id' => 1,
'parent_id' => 0,
'text' => 'Level 1',
), array(
'id' => 2,
'parent_id' => 0,
'text' => 'Level 2',
), array(
'id' => 3,
'parent_id' => 2,
'text' => 'Level 2.1',
), array(
'id' => 4,
'parent_id' => 2,
'text' => 'Level 2.2',
), array(
'id' => 5,
'parent_id' => 4,
'text' => 'Level 2.2.1',
), array(
'id' => 6,
'parent_id' => 0,
'text' => 'Level 3',
)
);
The goal is a nested <ul> with an infinite depth. The expected output of the array above is this:
Level 1Level 2Level 2.1Level 2.2Level 2.2.1Level 3
If only the array items had a key called child or something that contained the actual sub-array, it would be easy to recurse though these and get the desired output with a function like this:
function makeList($list)
{
echo '<ul>';
foreach ($list as $item)
{
echo '<li>'.$item['text'];
if (isset($item['child']))
{
makeList($item['child']);
}
echo '</li>';
}
echo '</ul>';
}
Unfortunately that's not the case for me - the format of the source arrays can't be changed. So, long ago I wrote this very nasty function to make it happen, and it only works up to three levels (code is pasted verbatim with original comments). I know it's a long boring read, please bear with me:
function makeArray($links)
{
// Output
$nav = array();
foreach ($links as $k => $v)
{
// If no parent_id is present, we can assume it is a top-level link
if (empty($v['parent_id']))
{
$id = isset($v['id']) ? $v['id'] : $k;
$nav[$id] = $v;
// Remove from original array
unset($links[$k]);
}
}
// Loop through the remaining links again,
// we can assume they all have a parent_id
foreach ($links as $k => $v)
{
// Link's parent_id is in the top level array, so this is a level-2 link
// We already looped through every item so we know they are all accounted for
if (isset($nav[$v['parent_id']]))
{
$id = isset($v['id']) ? $v['id'] : $k;
// Add it to the top level links as a child
$nav[$v['parent_id']]['child'][$id] = $v;
// Set a marker so we know which ones to loop through to add the third level
$nav2[$id] = $v;
// Remove it from the array
unset($links[$k]);
}
}
// Last iteration for the third level
// All other links have been removed from the original array at this point
foreach ($links as $k => $v)
{
$id = isset($v['id']) ? $v['id'] : $k;
// Link's parent_id is in the second level array, so this is a level-3 link
// Orphans will be ignored
if (isset($nav2[$v['parent_id']]))
{
// This part is crazy, just go with it
$nav3 = $nav2[$v['parent_id']]['parent_id'];
$nav[$nav3]['child'][$v['parent_id']]['child'][] = $v;
}
}
return $nav;
}
This makes an array like:
array(
'text' => 'Level 1'
'child' => array(
array(
'text' => 'Level 1.2'
'child' => array(
array(
'text' => 'Level 1.2.1'
'child' => array(
// etc.
),
array(
'text' => 'Level 1.2.2'
'child' => array(
// etc.
),
)
)
)
)
);
Usage:
$nav = makeArray($links);
makeList($nav);
I've spent many spare hours trying to work this out, and the original code which I have given here is still the best solution I've been able to produce.
How can I make this happen without that awful function (which is limited to a depth of 3), and have an infinite number of levels? Is there a more elegant solution to this?
Print:
function printListRecursive(&$list,$parent=0){
$foundSome = false;
for( $i=0,$c=count($list);$i<$c;$i++ ){
if( $list[$i]['parent_id']==$parent ){
if( $foundSome==false ){
echo '<ul>';
$foundSome = true;
}
echo '<li>'.$list[$i]['text'].'</li>';
printListRecursive($list,$list[$i]['id']);
}
}
if( $foundSome ){
echo '</ul>';
}
}
printListRecursive($list);
Create multidimensional array:
function makeListRecursive(&$list,$parent=0){
$result = array();
for( $i=0,$c=count($list);$i<$c;$i++ ){
if( $list[$i]['parent_id']==$parent ){
$list[$i]['childs'] = makeListRecursive($list,$list[$i]['id']);
$result[] = $list[$i];
}
}
return $result;
}
$result = array();
$result = makeListRecursive($list);
echo '<pre>';
var_dump($result);
echo '</pre>';
Tested and working :)
$list = array(...);
$nested = array();
foreach ($list as $item)
{
if ($item['parent_id'] == 0)
{
// Woot, easy - top level
$nested[$item['id']] = $item;
}
else
{
// Not top level, find it's place
process($item, $nested);
}
}
// Recursive function
function process($item, &$arr)
{
if (is_array($arr))
{
foreach ($arr as $key => $parent_item)
{
// Match?
if (isset($parent_item['id']) && $parent_item['id'] == $item['parent_id'])
{
$arr[$key]['children'][$item['id']] = $item;
}
else
{
// Keep looking, recursively
process($item, $arr[$key]);
}
}
}
}
Some methods I recently wrote, maybe some will help, sorry I'm short on time and cannot rewite them to match your needs.
This code is actually a part of Kohana Framework Model, method ->as_array() is used to flat an Database_Result object.
function array_tree($all_nodes){
$tree = array();
foreach($all_nodes as $node){
$tree[$node->id]['fields'] = $node->as_array();
$tree[$node->id]['children'] = array();
if($node->parent_id){
$tree[$node->parent_id]['children'][$node->id] =& $tree[$node->id];
}
}
$return_tree = array();
foreach($tree as $node){
if($node['fields']['depth'] == 0){
$return_tree[$node['fields']['id']] = $node;
}
}
return $return_tree;
}
array_tree() is used to make a tree out of a flat array. The key feature is the =& part ;)
function html_tree($tree_array = null){
if( ! $tree_array){
$tree_array = $this -> array_tree();
}
$html_tree = '<ul>'."\n";
foreach($tree_array as $node){
$html_tree .= $this->html_tree_crawl($node);
}
$html_tree .= '</ul>'."\n";
return $html_tree;
}
function html_tree_crawl($node){
$children = null;
if(count($node['children']) > 0){
$children = '<ul>'."\n";
foreach($node['children'] as $chnode){
$children .= $this->html_tree_crawl($chnode);
}
$children .= '</ul>'."\n";
}
return $this->html_tree_node($node, $children);
}
html_tree_node() is a simple method to display current node and children in HTML.
Example below:
<li id="node-<?= $node['id'] ?>">
<?= $node['title'] ?>
<?= (isset($children) && $children != null) ? $children : '' ?>
</li>