2

I've got a multidimensional array of which I can't know the depth. The array could for example look like this:

$array = array(
    1 => array(
        5 => array(
            3 => 'testvalue1'
        )
    ),
    2 => array(
        6 => 'testvalue2'
    ),
    3 => 'testvalue3',
    4 => 'testvalue4',
);

With this array I want to create a table of contents. That means the keys need to be preserved as I'm using them as "chapter numbers". For example, "testvalue1" is in chapter 1.5.3.
Now I want to walk through the array while preserving all keys - not using array_walk_recursive as the keys containing another array are dropped (correct?) and preferably not using nested foreach loops considering the speed.
Any suggestions how I should do this? Thanks in advance.

PS: For my script it doesn't matter if the keys are strings ("1" instead of 1) or integers, if having strings as key will make array_walk_recursive preserve them.

Gordon
  • 312,688
  • 75
  • 539
  • 559
carlo
  • 700
  • 2
  • 9
  • 25
  • possible duplicate of [Multidimensional array iteration](http://stackoverflow.com/questions/2207599/multidimensional-array-iteration) – Gordon Oct 21 '11 at 20:31
  • also http://stackoverflow.com/questions/4312425/multidimensional-arrays-nested-to-unlimited-depth/4333381#4333381 – Gordon Oct 21 '11 at 20:32
  • @Gordon: Great answer, didn't know about `RecursiveIteratorIterator` :) Should come to good use. – Markus Hedlund Oct 21 '11 at 20:33
  • RecursiveIteratorIterator seems to drop keys holding other arrays as well as array_walk_recursive.. Or am I wrong? – carlo Oct 21 '11 at 20:41
  • @carlo not when you use `SELF_FIRT`: http://codepad.org/pAmiN8Ha – Gordon Oct 21 '11 at 21:02
  • possible duplicate of [Transaprently flatten an array](http://stackoverflow.com/questions/7011451/transaprently-flatten-an-array) – hakre Oct 22 '11 at 10:22

3 Answers3

9

You can iterate over your array with a help of a stack to build your toc.

$stack = &$array;
$separator = '.';
$toc = array();

while ($stack) {
    list($key, $value) = each($stack);
    unset($stack[$key]);
    if (is_array($value)) {
        $build = array($key => ''); # numbering without a title.
        foreach ($value as $subKey => $node)
            $build[$key . $separator . $subKey] = $node;
        $stack = $build + $stack;
        continue;
    }
    $toc[$key] = $key. ' ' . $value;
}

print_r($toc);

Output:

Array
(
    [1] => 1
    [1.5] => 1.5
    [1.5.3] => 1.5.3 testvalue1
    [2] => 2
    [2.6] => 2.6 testvalue2
    [3] => 3 testvalue3
    [4] => 4 testvalue4
)

You can additionally handle the level as well if you need to, but that was not clear from your question.

array_walk_recursive does not work, because it won't give you the keys of the parent element(s). See this related question as well: Transparently flatten an array, it has a good answer and is helpful for more generic cases as well.

Community
  • 1
  • 1
hakre
  • 193,403
  • 52
  • 435
  • 836
  • There still is one problem; When the loop is one or more levels deeper into the array, it will only parse the first key / value and then returning to the first level of the array. For example, if you have $arr[1][0][1] and $arr[1][0][2], only $arr[1][0][1] will be parsed and then the loop will continue with $arr[2]... and soforth.. How can this be solved? – carlo Oct 23 '11 at 10:38
  • @Carlo: Didn't see you noticed that here. I edited the code to not have the issue. The `array_shift` function did re-number your array(s) as you are using integer-numeric keys, that is a specialty with PHP's arrays. If you do not need the numbers w/o a title, I've left a comment where they are added. – hakre Aug 16 '12 at 09:15
  • Perfect. Only I wanted $toc to be string, so I replaced $toc = array(); with $toc = ''; and replaced the line $toc[$key] = $key. ' ' . $value; with $toc .= '['.$key. ']' . $value . ' - '; – jazkat Feb 06 '14 at 09:59
2
<?php
    $td = array(1=>array(5=>array(3=>'testvalue1',array(6=>'testvalue2'))),2=>array(6=>'testvalue2',array(6=>'testvalue2',array(6=>'testvalue2'),array(6=>'testvalue2',array(6=>'testvalue2')))),3=>'testvalue3',4=>'testvalue4');
    print_r($td);
    $toc = '';

    function TOC($arr,$ke='',$l=array()) {
            if (is_array($arr)) {
            if ($ke != '') array_push($l,$ke);
            foreach($arr as $k => $v)
                TOC($v,$k,$l);
        }
        else {
            array_push($l,$ke);
            $GLOBALS['toc'].=implode('.',$l)." = $arr\n";
        }
    }
    toc($td);
    echo "\n\n".$toc;
?>

http://codepad.org/4l4385MZ

hakre
  • 193,403
  • 52
  • 435
  • 836
Korvin Szanto
  • 4,531
  • 4
  • 19
  • 49
0

Try this:

<?php
$ar = array(
    1 => array(
        5 => array(
            3 => 'testvalue1',
            5 => 'test',
            6 => array(
                9 => 'testval 9'
            )
        ),
        8 => 'testvalue9'
    ),
    2 => array(
        6 => 'testvalue2',
        7 => 'testvalue8',
        2 => array(
            6 => 'testvalue2',
            7 => 'testvalue8'
        ),
    ),
    3 => 'testvalue3',
    4 => 'testvalue4'
);

function getNestedItems($input, $level = array()){
    $output = array();

    foreach($input as $key => $item){
        $level[] = $key;
        if(is_array($item)){
            $output = (array)$output + (array)getNestedItems($item, $level);
        } else {
            $output[(string)implode('.', $level)] = $item;
        }
        array_pop($level);
     }
     return $output;
}

var_dump(getNestedItems($ar));
Kevin Vandenborne
  • 1,397
  • 1
  • 10
  • 28