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.

6
  • possible duplicate of Multidimensional array iteration Commented Oct 21, 2011 at 20:31
  • also stackoverflow.com/questions/4312425/… Commented Oct 21, 2011 at 20:32
  • @Gordon: Great answer, didn't know about RecursiveIteratorIterator :) Should come to good use. Commented Oct 21, 2011 at 20:33
  • RecursiveIteratorIterator seems to drop keys holding other arrays as well as array_walk_recursive.. Or am I wrong? Commented Oct 21, 2011 at 20:41
  • @carlo not when you use SELF_FIRT: codepad.org/pAmiN8Ha Commented Oct 21, 2011 at 21:02

4 Answers 4

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.

Sign up to request clarification or add additional context in comments.

3 Comments

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: 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.
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 . ' - ';
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

4 Comments

Isn't there any other way not having to use many nested loops? Thanks anyway :)
You must nest loops to iterate multidimensional arrays.
@carlo Recursion is really the easiest solution for this problem.
@Korvin Szanto: You do not must nest loops, you can. See stackoverflow.com/questions/7854940/…
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));

Comments

0

Another take on this that worked best for me with handling deep nested multidimensional associative and non-associative arrays:

/**
 * Flatten a Multidimensional Array
 */
function array_multidimensional_flatten( $array, $position = [], $separator = '.' ) {
  if ( !is_array( $array ) ) {
    return false;
  }

  $result = [];

  foreach ( $array as $key => $value ) {
    if ( is_array( $value ) ) {
      $position[] = $key;
      $result = array_merge( $result, array_multidimensional_flatten( $value, $position ) );
    } else {
      if ( $position ) {
        $key = implode( $separator, $position ) . "{$separator}{$key}";
      }
      $result = array_merge( $result, [$key => $value] );
    }
  }

  return $result;
}

Using it with a large multidimensional array will output something like this:

[
  'hero__overlay-opacity' => '100',
  'hero__lead-in' => 'Underwritten by TruStage® Insurance',
  'hero__optimize' => '1',
  'hero__layout' => 'centered',
  'flexible-content.0.acf_fc_layout' => 'title',
  'flexible-content.0.acfe_flexible_toggle' => '',
  'flexible-content.0.text' => 'Enabling Smart Financial Decisions',
  'flexible-content.0.size.value' => 'lg',
  'flexible-content.0.size.label' => 'L',
  'flexible-content.0.size.alignment.value' => 'center',
  'flexible-content.0.size.alignment.label' => 'Center',
  'flexible-content.0.size.alignment.weight.value' => 'normal',
  'flexible-content.0.size.alignment.weight.label' => 'Regular',
  'flexible-content.0.1.acf_fc_layout' => 'grid-stats',
  'flexible-content.0.1.acfe_flexible_toggle' => '',
  'flexible-content.0.1.columns.0.statistic' => '85+',
  'flexible-content.0.1.columns.0.description' => 'Years of Experience',
  'flexible-content.0.1.columns.0.1.statistic' => 'A',
  'flexible-content.0.1.columns.0.1.description' => 'Financial rating1',
  'flexible-content.0.1.columns.0.1.2.statistic' => '4.75 / 5',
  'flexible-content.0.1.columns.0.1.2.description' => 'Rating on TrustPilot',
  'flexible-content.0.1.2.acf_fc_layout' => 'icon-columns',
  'flexible-content.0.1.2.acfe_flexible_toggle' => '',
  'flexible-content.0.1.2.title' => 'Insurance Products',
  'flexible-content.0.1.2.background' => 'rgb(246,246,246)',
  'flexible-content.0.1.2.alignment.value' => 'center',
  'flexible-content.0.1.2.alignment.label' => 'Center',
  'flexible-content.0.1.2.alignment.style.value' => 'default',
  'flexible-content.0.1.2.alignment.style.label' => 'Bordered Circle',
  'flexible-content.0.1.2.alignment.style.column.0.icon_type' => 'icon',
  'flexible-content.0.1.2.alignment.style.column.0.icon' => 'insurance-add',
  'flexible-content.0.1.2.alignment.style.column.0.image' => '',
  'flexible-content.0.1.2.alignment.style.column.0.title' => 'AD&D Insurance',
  'flexible-content.0.1.2.alignment.style.column.0.link' => '/insurance/accidental-death-dismemberment/',
]

3 Comments

I do not support the conditional return of false. PHP doesn't have a false coalescing operator. If you are going to conditionally return a non-array, then use null (or even better throw an exception). These options will allow you to declare the return type as array (or slightly worse ?array). I recommend type declarations on the first parameter array $array anyhow.
Instead of if ( $position ) {$key = implode( $separator, $position ) . "{$separator}{$key}";}, I might suggest unconditionally writing: $key = implode($separator, array_merge($position, $key)); for brevity. If you still want the condition, you can still wrap that in a if ($position) check.
$result = array_merge( $result, [$key => $value] ); can be simplified to $result[$key] = $value];

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.