Reputation: 700
I've got a multidimensional array containing some id's, stored in keys called 'name'. Each entry can have other sub-arrays, containing other id's. The array is dynamic; the depth and entries are unknown. Here is an example:
Array
(
[0] => Array
(
[name] => test1
[subs] => Array
(
[0] => Array
(
[name] => test2
)
[1] => Array
(
[name] => test3
[subs] => Array
(
[name] => test4
)
)
)
)
[1] => Array
(
[name] => test5
)
)
Now I want to convert this multidimensional array to a 'flat' array, while keeping hold of the depth. The scope of the new array is some kind of table of contents, where the key represents a chapter and the value an id. For example, 'test4' should be chapter 1.2.1, 'test2' should be 1.1 and 'test5' should be chapter 2. Each level deeper means the entry is a child of the parent level. Therefore I have to store every previous depth-'level' while looping the array. So far I haven't found a way to do this.
QUESTION UPDATE:
I've got the first part working. Now I want to add new chapters to the array, and have the chapter numbers of the existing entries update themselves. The array now looks like this:
Array
(
[1] => test1
[1.1] => test2
[1.2] => test3
[1.2.1] => test4
[2] => test5
)
So now I would like to add chapter 'test6' as first-child of 1.2, which means the current 1.2.1 would become 1.2.2 and the new child will be 1.2.1 instead.
Upvotes: 1
Views: 2072
Reputation: 781
function array_flat($array, $prefix = '') {
$result = array();
foreach ($array as $key => $value) {
$new_key = $prefix . (empty($prefix) ? '' : '.') . $key;
if (is_array($value)) {
$result = array_merge($result, array_flat($value, $new_key));
} else {
$result[$new_key] = $value;
}
}
return $result;
}
Upvotes: 0
Reputation: 88647
Code:
// Mmmm... functiony goodness
function array_to_toc ($in, &$out, $level = '') {
if (!$level) $out = array(); // Make sure $out is an empty array at the beginning
foreach ($in as $key => $item) { // Loop items
$thisLevel = ($level) ? "$level.".($key + 1) : ($key + 1); // Get this level as string
$out[$thisLevel] = $item['name']; // Add this item to $out
if (isset($item['subs']) && is_array($item['subs']) && count($item['subs'])) array_to_toc($item['subs'],$out,$thisLevel); // Recurse children of this item
}
}
// Here is your test data (slightly modified - I think you stated it wrong in the question)
$array = array (
0 => array (
'name' => 'test1',
'subs' => array (
0 => array (
'name' => 'test2'
),
1 => array (
'name' => 'test3',
'subs' => array (
0 => array (
'name' => 'test4'
)
)
)
)
),
1 => array (
'name' => 'test5'
)
);
// $result is passed by reference and will hold the output after the function has run
$result = array();
array_to_toc($array, $result);
print_r($result);
Output:
Array
(
[1] => test1
[1.1] => test2
[1.2] => test3
[1.2.1] => test4
[2] => test5
)
EDIT
These two (plus one supporting) functions allow you add and remove chapters from the input array by chapter reference. Then, you can recalculate the TOC from the new structure.
function chapter_exists ($array, $chapterId) {
$chapterParts = explode('.',$chapterId);
foreach ($chapterParts as &$chapter) $chapter--;
$lastId = array_pop($chapterParts);
return eval('return isset($array['.implode("]['subs'][",$chapterParts).((count($chapterParts)) ? "]['subs'][" : '')."$lastId]);");
}
function add_chapter (&$array, $chapterId, $item) {
$chapterParts = explode('.',$chapterId);
foreach ($chapterParts as &$chapter) $chapter--; // Decrement all the values
$lastId = array_pop($chapterParts);
if (count($chapterParts) && !chapter_exists($array, implode('.',$chapterParts))) return FALSE; // Return FALSE if the level above the chapter we are adding doesn't exist
if (chapter_exists($array, $chapterId)) { // See if the chapter reference already exists
eval('array_splice($array'.((count($chapterParts)) ? '['.implode("]['subs'][",$chapterParts)."]['subs']" : '').",$lastId,0,array(\$item));"); // Insert an item
} else {
eval('$array['.implode("]['subs'][",$chapterParts).((count($chapterParts)) ? "]['subs'][" : '')."$lastId] = \$item;"); // Insert an item
}
return TRUE;
}
function remove_chapter (&$array, $chapterId) {
$chapterParts = explode('.',$chapterId);
foreach ($chapterParts as &$chapter) $chapter--; // Decrement all the values
$lastId = array_pop($chapterParts);
return (chapter_exists($array, $chapterId)) ? eval('$removed = array_splice($array'.((count($chapterParts)) ? '['.implode("]['subs'][",$chapterParts)."]['subs']" : '').",$lastId,1); return array_shift(\$removed);") : FALSE;
}
The best way to demonstrate how they work is with an example. Say we start with the array structure above, which is held in a variable called $structure
. As we know, our resulting TOC array looks like this:
Array
(
[1] => test1
[1.1] => test2
[1.2] => test3
[1.2.1] => test4
[2] => test5
)
Now, we decide we want to remove chapter 1.2
and all it's sub-chapters - we can do this:
// Remove the chapter from $structure
remove_chapter($structure, '1.2');
// recalculate the TOC
array_to_toc($structure, $result2);
print_r($result2);
/*
Outputs:
Array
(
[1] => test1
[1.1] => test2
[2] => test5
)
*/
Now lets say we want to add a chapter called test6
as chapter 1.1
, and test2
will be re-indexed to 1.2
- we'll be working with the result of the above example for this one:
// Add the new chapter to $structure
add_chapter($structure, '1.1', array('name'=>'test6'));
// recalculate the TOC
array_to_toc($structure, $result3);
print_r($result3);
/*
Outputs:
Array
(
[1] => test1
[1.1] => test6
[1.2] => test2
[2] => test5
)
*/
OK, seems fairly simple. But what if we wanted to move a sub-chapter, so it was at the top level of the tree? Let's go back to our original version of $structure
to demonstrate this - we'll move chapter 1.2
, so that it is now chapter 3
:
/*
A quick reminder of what we are starting with:
Array
(
[1] => test1
[1.1] => test2
[1.2] => test3
[1.2.1] => test4
[2] => test5
)
*/
// Remove the chapter from $structure - this time, we'll catch the items we remove in a variable
$removed = remove_chapter($structure, '1.2');
// Add it again, only this time as chapter 3
add_chapter($structure, '3', $removed);
// recalculate the TOC
array_to_toc($structure, $result4);
print_r($result4);
/*
Outputs:
Array
(
[1] => test1
[1.1] => test2
[2] => test5
[3] => test3
[3.1] => test4
)
*/
Hopefully I've explained it well enough there.
chapter_exists()
returns a boolean. Fairly self explanatory as to what it means, if feel. Pass the $structure
array as the first parameter, and the chapter ID you want to check as the second. This function is required, as it is used by the other two internally.
add_chapter()
returns a boolean, so you can test whether the operation was successful. It will fail if the parent of the chapter doesn't exist - for example, if you try to add 1.2.1
when 1.2
hasn't been defined, it won't work. If you add a chapter that already exists, all the chapter numbers at that level will be shifted up by 1.
remove_chapter()
will return the item that was removed on success (i.e. an array) or boolean FALSE
on failure - it will fail if you try and remove a chapter that doesn't exist.
NB: I had to make heavy use of eval()
for this, in order to accommodate for arbitrary level depth. I hate to use it, but I couldn't think of any other way - if anyone reading this has any bright ideas about alternative approaches (preferably that don't involve some nightmarish looping structure), please let me know...
Upvotes: 3
Reputation: 522085
function toc(array $data, array $level = array()) {
$toc = array();
foreach ($data as $i => $node) {
$currentLevel = array_merge($level, array($i + 1));
$toc[] = join('.', $currentLevel) . ': ' . $node['name'];
if (!empty($node['subs'])) {
$toc = array_merge($toc, toc($node['subs'], $currentLevel));
}
}
return $toc;
}
echo join("\n", toc($array));
Upvotes: 0