O'Reilly

Flatten Nested Arrays Using PHP

By on  

While browsing the MooTools 1.2 source code, I found Array's flatten() method. The flatten() method takes nested arrays and "flattens" them all into one array. I asked myself how I could do that using PHP. The following is what I came up with.

The PHP

$myarray = array('a', 'b', array(array(array('x'), 'y', 'z')), array(array('p')));

function array_flatten($array,$return) {
	for($x = 0; $x <= count($array); $x++) {
		if(is_array($array[$x])) {
			$return = array_flatten($array[$x], $return);
		}
		else {
			if(isset($array[$x])) {
				$return[] = $array[$x];
			}
		}
	}
	return $return;
}

$res = array_flatten($myarray, array());

The Result

Array
(
    [0] => a
    [1] => b
    [2] => x
    [3] => y
    [4] => z
    [5] => p
)

As you can see, array_flatten() is used recursively to sniff out values from the original array. While I don't believe I've ever found myself with an array as nested as my example, it's good to know that I can extract the values if necessary.

O'Reilly Velocity Conference
Save 20% with discount code AFF20

Recent Features

  • Page Visibility API

    One event that's always been lacking within the document is a signal for when the user is looking at a given tab, or another tab. When does the user switch off our site to look at something else? When do they come back?...

  • Convert XML to JSON with JavaScript

    If you follow me on Twitter, you know that I've been working on a super top secret mobile application using Appcelerator Titanium.  The experience has been great:  using JavaScript to create easy to write, easy to test, native mobile apps has been fun.  My...

Incredible Demos

  • Multiple Background CSS Animations

    CSS background animation has been a hot topic for a long time, mostly because they look pretty sweet and don't require additional elements.  I was recently asked if it was possible to have multiple background animations on a given element and the answer is yes...with...

  • Editable Content Using MooTools 1.2, PHP, and MySQL

    Everybody and their aerobics instructor wants to be able to edit their own website these days. And why wouldn't they? I mean, they have a $500 budget, no HTML/CSS experience, and extraordinary expectations. Enough ranting though. Having a website that allows for...

Discussion

  1. Alan

    Here is another function without the need for recursion.

    function array_flatten($array, $maintain_keys=false) {
      $array = print_r($array, true);
      preg_match_all("/[(.+?)].*?\=>(.+?)\n/", $array, $matches);
      $array = array();
      foreach ($matches[0] as $k=>$v) {
        $key = trim($matches[1][$k]);
        $value = trim($matches[2][$k]);
        if (preg_match("/^array$/i", $value)) continue;
        if (array_key_exists($key, $array) || !$maintain_keys) {
          $array[] = $value;
        } else $array[$key] = $value;
      }
      return $array;
    }
    
    $arr = array('alpha'=>'a','b',array(array(array('beta'=>'x'),'alpha'=>'y','z')),array(array('p'))); 
    echo '' . print_r($arr, true) . '';
    echo '' . print_r(array_flatten($arr) , true) . '';
    echo '' . print_r(array_flatten($arr, true) , true) . '';
    
  2. Harsha M V

    Wonder if the array keys will be the same when used with mysql_fetch_assoc

  3. I went through this same issue with python a couple years ago[1]. Strangely, in all my years of using php, I never really needed flatten (well maybe a couple times it would have been handy). Python doesn’t handle recursion very well, so an iterative version is pretty much a necessity for it to be of any use. Here is a php translation of the python version I ended up using:

    function array_flatten ($array)
    {
        $i = 0;
        while ($i < count ($array))
        {
            while (is_array ($array[$i]))
            {
                if (!$array[$i])
                {
                    array_splice ($array, $i, 1);
                    --$i;
                    break;
                }
                else
                {
                    array_splice ($array, $i, 1, $array[$i]);
                }
            }
            ++$i;
        }
        return $array;
    }
    

    It’s much slower than your implementation (I assume because of the overhead of array_splice compared to python’s list.pop, since the python version performs comparably yours–it just doesn’t grok 2000 levels of recursion. In fact, when I use a translation of your php version, python dies very soon with “maximum recursion depth exceeded”).

    Ps. One small note, on 5.2.6 I get warnings about trying to accessing invalid indexes from the $array[$x] on lines 7 and 13–prefixing them with “@” prevents them from incurring the slowdown of invoking the warning of course (just thought I’d mention it for posterity).

    __

    [1] http://rightfootin.blogspot.com/2006/09/more-on-python-flatten.html

  4. Alexandre Valiquette

    Replace line #5 from

    for($x = 0; $x <= count($array); $x++)
    

    to

    for($x = 0; $x < count($array); $x++)
    

    (<= become <)
    otherwise you get a notice “error” (Undefined offset) for every array fetched by the function.

  5. I did a different approach, since the requirement was also different, and the outcome is boasted
    about at php-trivandrum, as function array_flatten

  6. Manu

    Thanks for the post, this function is very handy.
    Although, as we are retrieving a new array with new indexes it might be nice to have the same function that can flatten an associative array.
    Here is your function modified to work with associative array:

    function array_flatten($array, $return=array()) {
        foreach ($array AS $key => $value) {
        if(is_array($value))
        {
            $return = array_flatten($value,$return);
        }
        else
        {
            if($value)
            {
                $return[] = $value;
            }
        }
    }
    return $return;
    }
    

    So $myarray = array('a','b',array(array('test' => array('x'),'y','nested2' => 'z')),array('nested3' => array('p'))) will also work as expected.

    • silvan

      and this keeps the keys!

      //flattenEachArrayToAnyDepth-------------------//
      	//$res = array_flatten($myarray,array());
      	function array_flatten($array,$return){
      		foreach($array as $key => $value){
      			if(@is_array($value)){
      		  		$return = $this->array_flatten($value,$return);
      			}elseif(@$value){
      		  		$return[$key] = $value;
      			}
      	  	}
      	  	return $return;
      	}
      
  7. David! You just saved me hours!

  8. @Manu: if you replace $return[] = $value; with $return[$key] = $value; you get the preserve the index names of the source array(s). Don’t know if that’s everyone preference, but that was what I was looking for. If one wanted to make this optional, he could add a 3rd optional parameter to this function to indicate wether or not to preserve index names, like so:

    function array_flatten($array, $return=array(), $preserve_index_names = false) {
    
      foreach ($array AS $key => $value) {
        if(is_array($value)) {
          $return = array_flatten($value,$return, $preserve_index_names);
        }
        else {
          if($value) {
            if($preserve_index_names === false) {
              $return[] = $value;
            }
            else {
              $return[$key] = $value;
            }
          }
        }
      }
    
      return $return;
    }
    

    And then run it like so to get a flattened array back with index names preserved:

    $result = array_flatten($input, array(), true);
    

    And like this if you don’t want the index names preserved:

    $result = array_flatten($input);
    
  9. Great function, thanks David.

  10. This is what i use, sposed to be faster than most methods:

    function flattenArray(array $array){
      $ret_array = array();
      foreach(new RecursiveIteratorIterator(new RecursiveArrayIterator($array)) as $value)
         {
         $ret_array[] = $value;
         }
      return $ret_array;
      }
  11. DkN

    NOTE: this function will omit zero values. for example:

    $test[0][0] = '0';
    $test[0][1] = 'b';
    
    $test[1][0] = 'c';
    $test[1][1] = 'd';
    

    array_flatten($test) will output:

    Array ( [0] => b [1] => c [2] => d )
    

    to fix this problem, on line 13 use:

    if(isset($array[$x]))
    

    instead of:

    if($array[$x])
    

    here’s the whole function with the line replaced:

    function array_flatten($array,$return=array())
    {
    	for($x = 0; $x <= count($array); $x++)
    	{
    		if(is_array($array[$x]))
    		{
    			$return = array_flatten($array[$x],$return);
    		}
    		else
    		{
    			if(isset($array[$x]))
    			{
    				$return[] = $array[$x];
    			}
    		}
    	}
    	return $return;
    }
    
  12. I know this post is a bit old, but here’s what I came up with – it’s super fast and concise.

    function array_flatten( $array ) {
      $arr = array();
      function flatten( $item, $key, $flattened ) {
        $flattened[] = $item;
      };
      array_walk_recursive($array, 'flatten', &$arr);
      return $arr;
    }
  13. Made a few improvements/simplifications, allowed associative arrays, etc.


    function array_flatten(array $array, array $return = array()) {

    foreach ($array as $k => $item) {
    if (is_array($item))
    $return = array_flatten($item, $return);
    elseif ($item)
    $return[] = $item;
    }

    return $return;
    }

  14. about the code on very top. their is an issue on using for loop, if the nested array have a key, it wont show the value. Instead use foreach loop. I have some revised on the code above, I hope it will help.

    function array_flatten($array, $return)
    {

    foreach($array as $key => $val)
    {
    if(is_array($val))
    {
    $return = array_flatten($val, $return);
    }
    else
    {
    if($val)
    {
    $return[] = $val;
    }
    }
    }
    return $return;
    }

    $test= array(‘ddd’,
    array(“hello” => ‘one’,’two’,array(array(‘inner’=>’innerval’, ‘innerval2′,array(‘key_inner’=>’inner_inner’)))),’ccc’,
    array(‘a’,’b’),
    array(‘dog’,’cat’)
    );
    $result = array_flatten($test,array());
    print_r($result);

    Output:
    Array
    (
    [0] => ddd
    [1] => one
    [2] => two
    [3] => innerval
    [4] => innerval2
    [5] => inner_inner
    [6] => ccc
    [7] => a
    [8] => b
    [9] => dog
    [10] => cat
    )

  15. Luca Borrione

    Replace
    if ($array[$x])

    With
    if (in_array($x, array_keys($array)))

    Or equivalent, otherwise a zero value won’t be returned.

    Example:
    var_dump( array_flatten(array(range(0, 5), 10)));

    With you code will give
    array
    0 => int 1
    1 => int 2
    2 => int 3
    3 => int 4
    4 => int 5
    5 => int 10
    )

    Instead of:
    array
    0 => int 0
    1 => int 1
    2 => int 2
    3 => int 3
    4 => int 4
    5 => int 5
    6 => int 10
    )

  16. Luca Borrione

    Replace

    if ($array[$x])

    With

    if (in_array($x, array_keys($array)))

    Or equivalent, otherwise a zero value won’t be returned.
    Example:

    var_dump( array_flatten(array(range(0, 5), 10)));

    With you code will give

    array
    0 => int 1
    1 => int 2
    2 => int 3
    3 => int 4
    4 => int 5
    5 => int 10

    Instead of:

    array
    0 => int 0
    1 => int 1
    2 => int 2
    3 => int 3
    4 => int 4
    5 => int 5
    6 => int 10

  17. Luca Borrione

    Replace

    if ($array[$x])

    With

    if (in_array($x, array_keys($array)))

    Or equivalent, otherwise a zero value won’t be returned.

    Example:
    var_dump( array_flatten(array(range(0, 5), 10)));
    With you code will give

    array
    0 => int 1
    1 => int 2
    2 => int 3
    3 => int 4
    4 => int 5
    5 => int 10

    Instead of:

    array
    0 => int 0
    1 => int 1
    2 => int 2
    3 => int 3
    4 => int 4
    5 => int 5
    6 => int 10

  18. Running count() inside a for loop isn’t the best way to go. The count should be run once and stored in a variable rather than computed for every iteration.

    $count = count($array);
    for($x = 0; $x <= $count; $x++)  :
    //Do stuff
    endfor;
    
  19. Here’s one of the methods that is faster than the recursive iterator version above. Note that by design, this function does NOT preserve all data! You can tweak it to preserve the last entries encountered, by default it returns the first. Tested with random Google Maps API JSON data:

    function FLATTEN($a,$key=NULL)
    {	// (c) Peter Mugane Kionga-Kamau http://www.visionhive.com - Free for unrestricted use while this notice remains intact.
    	// Flattens an array (takes k-v pairs from sub-arrays and returns them in one 2-d array).
    	// Note: the last found value for each key will be used. If you want to use the first 
    	//       found value, switch the parameters like so: array_merge(FLATTEN($v,$k),$r)
    	$r=array();
    	if(is_array($a))foreach($a as $k=>$v)$r=array_merge($r,FLATTEN($v,$k));
    	else $r[$key]=$a;
    	return $r;
    }
  20. Worth noting, if you use that function (this is from the PHP Manual):

    “If the input arrays have the same string keys, then the later value for that key will overwrite the previous one. If, however, the arrays contain numeric keys, the later value will not overwrite the original value, but will be appended.

    Values in the input array with numeric keys will be renumbered with incrementing keys starting from zero in the result array. “

  21. Rafael

    Good function and best comments! Great!

  22. Jaco

    Couldn’t figure out why the original function required a second parameter, so I rewrote the function.

    //  Pre-condition: the parameter is an array.
    function array_flatten($array)
    {
        $result = array();
    
        foreach ($array as $element) {
            if (is_array($element)) {
                $result = array_merge($result, array_flatten($element));
            } else {
                $result[] = $element;
            }
        }
    
        return $result;
    }
    
  23. I expanded upon Jaco’s implementation by having it now be able to handle string keys. I also added the option of defining how much flattening is done. The examples provided should make this more clear. It’s not perfect but I’ve found it useful.

    /**
     * Flattens a nested array.
     *
     * Based on:
     * {@link http://davidwalsh.name/flatten-nested-arrays-php#comment-56256}
     *
     * @param array $array     - The array to flatten.
     * @param int   $max_depth - How many levels to flatten.  Negative numbers
     *                           mean flatten all levels.  Defaults to -1.
     * @param int   $_depth    - The current depth level.  Should be left alone.
     */
    function array_flatten(array $array, $max_depth = -1, $_depth = 0) {
      $result = array();
    
      foreach ($array as $key => $value) {
        if (is_array($value) && ($max_depth < 0 || $_depth < $max_depth)) {
          $flat = array_flatten($value, $max_depth, $_depth + 1);
          if (is_string($key)) {
            $duplicate_keys = array_keys(array_intersect_key($array, $flat));
            foreach ($duplicate_keys as $k) {
              $flat["$key.$k"] = $flat[$k];
              unset($flat[$k]);
            }
          }
          $result = array_merge($result, $flat);
        }
        else {
          if (is_string($key)) {
            $result[$key] = $value;
          }
          else {
            $result[] = $value;
          }
        }
      }
    
      return $result;
    }
    
    
    Examples:
    
    
    $myarray = array('a','b',array(array(array('x'),'y','z')),array(array('p')));
    $a = array_flatten($myarray, 0);     // -> array('a','b',array(array(array('x'),'y','z')),array(array('p')))
    $b = array_flatten($myarray, 1);     // -> array('a','b',array(array('x'),'y','z'),array('p'))
    $c = array_flatten($myarray, 2);     // -> array('a','b',array('x'),'y','z','p')
    $d = array_flatten($myarray, 100);   // -> array('a','b','x','y','z','p')
    $e = array_flatten($myarray);        // -> array('a','b','x','y','z','p')
    
    $myarray2 = array('a','b',array(array('test'=>array('x'),'y','nested2'=>'z')),array('nested3'=>array('p')));
    $f = array_flatten($myarray2, 1);    // -> array('a','b',array('test'=>array('x'),'y','nested2'=>'z'),'nested3'=>array('p'))
    $g = array_flatten($myarray2, 2);    // -> array('a','b','test'=>array('x'),'y','nested2'=>'z','p')
    $h = array_flatten($myarray2);       // -> array('a','b','test.0'=>'x','y','nested2'=>'z','p')
    
    $myarray3 = array(array(0,1,2,3,4,5),10);
    $i = array_flatten($myarray3);       // -> array(0,1,2,3,4,5,10)
    
    $myarray4 = array('nested'=>array('abc'=>'p'));
    $j = array_flatten($myarray4);       // -> array('abc'=>'p')
    
    $myarray5 = array('thingy'=>array('same'=>'g','notsame'=>'u'),'same'=>'c');
    $k = array_flatten($myarray5);       // -> array('notsame'=>'u','thingy.same'=>'g','same'=>'c')
    
    $myarray6 = array('same'=>'c','thingy'=>array('same'=>'g','diff'=>'v'));
    $l = array_flatten($myarray6);       // -> array('same'=>'c','diff'=>'v','thingy.same'=>'g')
    

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!

Recently on David Walsh Blog

  • Prevent Chrome from Translating a Page

    A while back I shared my favorite Google Chrome extension:  Google Art Project.  I've enjoyed seeing beautiful art when I open a new tab -- it's brought genuine happiness to my day, however small that happiness may be.  About a week ago, however, the art presented had...

  • Create Any Type Of Website With These Multi-Purpose Themes

    We have selected what we believe are the very best multipurpose WordPress themes on the market today. Our list contains a number of best sellers, several newcomers that are proving to be highly popular, and a few themes that are ideal for creating the types of...

  • An Introduction to Static Site Generators

    Static site generators seem to have been becoming more and more popular recently, but they’re not one of those ephemeral novelty things that grow in popularity as quickly as they fall into oblivion shortly after. For over a decade, many different projects — 394 of...

  • Automated Tests for Visual Responsive Layouts

    Today it's all about testing. In 2015, many developers knows about TDD and I personally think that testing is one of the key for quality products. But what about testing in a Front-end environment? How do you guys write your tests for a responsive page or...

  • Getting Dicey With Flexbox

    What if you could build complex CSS layouts in minutes? Flexbox is a new CSS layout spec that makes it easy to construct dynamic layouts. With flexbox, vertical centering, same-height columns, reordering, and direction agnosticism are a piece of cake. There's a popular myth floating around that...