How to "flatten" a multi-dimensional array to simple one in PHP? [duplicate]
Asked Answered
V

23

97

It's probably beginner question but I'm going through documentation for longer time already and I can't find any solution. I thought I could use implode for each dimension and then put those strings back together with str_split to make new simple array. However I never know if the join pattern isn't also in values and so after doing str_split my original values could break.

Is there something like combine($array1, $array2) for arrays inside of multi-dimensional array?

Vinery answered 8/2, 2009 at 22:43 Comment(3)
Please check this link for solution : #14952311Hallagan
Another good reference question with perhaps better answers: How to Flatten a Multidimensional Array?Agenda
This question contains no minimal reproducible example and is therefore not a great candidate as a canonical (since there are other pages that are more clear).Glogau
S
43

Use array_walk_recursive

<?php

$aNonFlat = array(
    1,
    2,
    array(
        3,
        4,
        5,
        array(
            6,
            7
        ),
        8,
        9,
    ),
    10,
    11
);

$objTmp = (object) array('aFlat' => array());

array_walk_recursive($aNonFlat, create_function('&$v, $k, &$t', '$t->aFlat[] = $v;'), $objTmp);

var_dump($objTmp->aFlat);

/*
array(11) {
  [0]=>
  int(1)
  [1]=>
  int(2)
  [2]=>
  int(3)
  [3]=>
  int(4)
  [4]=>
  int(5)
  [5]=>
  int(6)
  [6]=>
  int(7)
  [7]=>
  int(8)
  [8]=>
  int(9)
  [9]=>
  int(10)
  [10]=>
  int(11)
}
*/

?>

Tested with PHP 5.5.9-1ubuntu4.24 (cli) (built: Mar 16 2018 12:32:06)

Splendiferous answered 8/2, 2009 at 22:53 Comment(9)
Does anyone know why this doesn't work unless I use the (depreciated) call-time pass by reference. i.e. array_walk_recursive($array, create_function('&$v, $k, &$t', '$t[] = $v;'), &$flattened); The function definition is correctly defined as pass by reference. but doesn't work unless I pass by reference during call-time.Temperature
@jskilski Objects ($objTmp in this example) are passed by reference automatically; arrays are not. Try using an anonymous function (php.net/manual/en/functions.anonymous.php) instead of create_function.Lully
this doesnt work in php 5.3.3 due to a bug in array_walk_recursive - bugs.php.net/bug.php?id=52719Spancake
@Spancake The kink says also This bug has been fixed in SVN.Splendiferous
yup, but I had 5.3.3 on my server and spent a good amount of time trying to find out why it wasn't working. I just added it as an fyi for those running 5.3.3 :)Spancake
Worked great for what I needed, had to flatten a multi-dimensional array using only 1 key per object, Had users but only needed an array of their userTokens, since I'm working with an ORM I had to flatten the ORM down to a basic array, with only the key I needed. Ended up with: (Notice the $v->token) array_walk_recursive($registrationIds, create_function('&$v, $k, &$t', '$t->aFlat[] = $v->token;'), $objTmp);Leffert
create_function has bad performance and memory usage... php.net/manual/en/function.create-function.php. But what if we explicitly define the function? how will be the performance compare to others?Magazine
Why does this answer mention using array_values()? I can't see any use of that function involved in the answer at all.Unitarian
Appreciate I'm late to the party, but here's a version that uses an anonymous function and doesn't randomly put the result into an object! array_walk_recursive($models, function($v, $k) use(&$return) { $return[] = $v;});Albin
H
149
$array  = your array

$result = call_user_func_array('array_merge', $array);

echo "<pre>";
print_r($result);

REF: http://php.net/manual/en/function.call-user-func-array.php

Here is another solution (works with multi-dimensional array) :

function array_flatten($array) {

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

}

$array  = Your array

$result = array_flatten($array);

echo "<pre>";
print_r($result);
Hallagan answered 20/2, 2013 at 5:11 Comment(4)
This answer is much faster than the accepted answer.Thay
Since php5.3 you can now use the splat operator: $result = array_merge(...$array); php.net/manual/en/…Potato
Your first answer doesn't work with a multi-dimensional array. 3v4l.org/tY8vDFretwell
And the second snippet fails on this simple example -- it destroys data while pushing into the result.Glogau
L
71

This is a one line, SUPER easy to use:

$result = array();
array_walk_recursive($original_array,function($v) use (&$result){ $result[] = $v; });

It is very easy to understand, inside the anonymous function/closure. $v is the value of your $original_array.

Lacasse answered 11/4, 2013 at 2:4 Comment(1)
This is the only one that worked for me in a two level array.Plainsong
S
43

Use array_walk_recursive

<?php

$aNonFlat = array(
    1,
    2,
    array(
        3,
        4,
        5,
        array(
            6,
            7
        ),
        8,
        9,
    ),
    10,
    11
);

$objTmp = (object) array('aFlat' => array());

array_walk_recursive($aNonFlat, create_function('&$v, $k, &$t', '$t->aFlat[] = $v;'), $objTmp);

var_dump($objTmp->aFlat);

/*
array(11) {
  [0]=>
  int(1)
  [1]=>
  int(2)
  [2]=>
  int(3)
  [3]=>
  int(4)
  [4]=>
  int(5)
  [5]=>
  int(6)
  [6]=>
  int(7)
  [7]=>
  int(8)
  [8]=>
  int(9)
  [9]=>
  int(10)
  [10]=>
  int(11)
}
*/

?>

Tested with PHP 5.5.9-1ubuntu4.24 (cli) (built: Mar 16 2018 12:32:06)

Splendiferous answered 8/2, 2009 at 22:53 Comment(9)
Does anyone know why this doesn't work unless I use the (depreciated) call-time pass by reference. i.e. array_walk_recursive($array, create_function('&$v, $k, &$t', '$t[] = $v;'), &$flattened); The function definition is correctly defined as pass by reference. but doesn't work unless I pass by reference during call-time.Temperature
@jskilski Objects ($objTmp in this example) are passed by reference automatically; arrays are not. Try using an anonymous function (php.net/manual/en/functions.anonymous.php) instead of create_function.Lully
this doesnt work in php 5.3.3 due to a bug in array_walk_recursive - bugs.php.net/bug.php?id=52719Spancake
@Spancake The kink says also This bug has been fixed in SVN.Splendiferous
yup, but I had 5.3.3 on my server and spent a good amount of time trying to find out why it wasn't working. I just added it as an fyi for those running 5.3.3 :)Spancake
Worked great for what I needed, had to flatten a multi-dimensional array using only 1 key per object, Had users but only needed an array of their userTokens, since I'm working with an ORM I had to flatten the ORM down to a basic array, with only the key I needed. Ended up with: (Notice the $v->token) array_walk_recursive($registrationIds, create_function('&$v, $k, &$t', '$t->aFlat[] = $v->token;'), $objTmp);Leffert
create_function has bad performance and memory usage... php.net/manual/en/function.create-function.php. But what if we explicitly define the function? how will be the performance compare to others?Magazine
Why does this answer mention using array_values()? I can't see any use of that function involved in the answer at all.Unitarian
Appreciate I'm late to the party, but here's a version that uses an anonymous function and doesn't randomly put the result into an object! array_walk_recursive($models, function($v, $k) use(&$return) { $return[] = $v;});Albin
G
38

If you specifically have an array of arrays that doesn't go further than one level deep (a use case I find common) you can get away with array_merge and the splat operator.

<?php

$notFlat = [[1,2],[3,4]];
$flat = array_merge(...$notFlat);
var_dump($flat);

Output:

array(4) {
  [0]=>
  int(1)
  [1]=>
  int(2)
  [2]=>
  int(3)
  [3]=>
  int(4)
}

The splat operator effectively changes the array of arrays to a list of arrays as arguments for array_merge.

Glarum answered 21/12, 2018 at 21:42 Comment(2)
This looks like the best answer to me. It won't work with string keys, but can be easily modified to do so: $flat = array_merge( array_keys( $notFlat ), ...array_values( $notFlat ) );Trippet
Worth noting that this only works in PHP 7.4+Herbartian
T
19
// $array = your multidimensional array

$flat_array = array();

foreach(new RecursiveIteratorIterator(new RecursiveArrayIterator($array)) as $k=>$v){

$flat_array[$k] = $v;

}

Also documented: http://www.phpro.org/examples/Flatten-Array.html

Typo answered 1/12, 2009 at 6:57 Comment(3)
Note: Only use for arrays of primitives. "RecursiveArrayIterator treats all objects as having children, and tries to recurse into them." php.net/manual/en/class.recursivearrayiterator.php#106519Cheapen
@hakre: +1 agreed: adding iterator_to_array() to this answer would negate the need for the foreach loop. It could be a simple one-liner function. (albeit a somewhat long one-line)Mortal
I know this is old but still useful, however the $k needs to be replaced by something unique, such as a counter. Just using $k causes elements to be removed if the names are the same in inner arrays as the main one.Reaves
T
11

Sorry for necrobumping, but none of the provided answers did what I intuitively understood as "flattening a multidimensional array". Namely this case:

[
  'a' => [
    'b' => 'value',
  ]
]

all of the provided solutions would flatten it into just ['value'], but that loses information about the key and the depth, plus if you have another 'b' key somewhere else, it will overwrite them.

I wanted to get a result like this:

[
  'a_b' => 'value',
]

array_walk_recursive doesn't pass the information about the key it's currently recursing, so I did it with just plain recursion:

function flatten($array, $prefix = '') {
    $return = [];
    foreach ($array as $key => $value) {
        if (is_array($value)) {
            $return = array_merge($return, flatten($value, $prefix . $key . '_'));
        } else {
            $return[$prefix . $key] = $value;
        }
    }
    return $return;
}

Modify the $prefix and '_' separator to your liking.

Playground here: https://3v4l.org/0B8hf

Towhee answered 9/7, 2020 at 5:26 Comment(1)
It appears that you want a different behavior versus what is being asked in this question. You want to create string keys which represent the path to the original value. There are probably better pages to post this answer on.Glogau
O
6

With PHP 7, you can use generators and generator delegation (yield from) to flatten an array:

function array_flatten_iterator (array $array) {
    foreach ($array as $value) {
        if (is_array($value)) {
            yield from array_flatten_iterator($value);
        } else {
            yield $value;
        }
    }
}

function array_flatten (array $array) {
    return iterator_to_array(array_flatten_iterator($array), false);
}

Example:

$array = [
    1,
    2,
    [
        3,
        4,
        5,
        [
            6,
            7
        ],
        8,
        9,
    ],
    10,
    11,
];    

var_dump(array_flatten($array));

http://3v4l.org/RU30W

Obeah answered 29/6, 2015 at 15:12 Comment(0)
N
5

A non-recursive solution (but order-destroying):

function flatten($ar) {
    $toflat = array($ar);
    $res = array();

    while (($r = array_shift($toflat)) !== NULL) {
        foreach ($r as $v) {
            if (is_array($v)) {
                $toflat[] = $v;
            } else {
                $res[] = $v;
            }
        }
    }

    return $res;
}
Nous answered 8/2, 2009 at 22:48 Comment(0)
N
5
function flatten_array($array, $preserve_keys = 0, &$out = array()) {
    # Flatten a multidimensional array to one dimension, optionally preserving keys.
    #
    # $array - the array to flatten
    # $preserve_keys - 0 (default) to not preserve keys, 1 to preserve string keys only, 2 to preserve all keys
    # $out - internal use argument for recursion
    foreach($array as $key => $child)
        if(is_array($child))
            $out = flatten_array($child, $preserve_keys, $out);
        elseif($preserve_keys + is_string($key) > 1)
            $out[$key] = $child;
        else
            $out[] = $child;
    return $out;
}
Nitrous answered 8/2, 2009 at 23:16 Comment(1)
Sorry but it doesn't seem to handle multidimensional arrays properly - DemoMassacre
R
5

Another method from PHP's user comments (simplified) and here:

function array_flatten_recursive($array) { 
   if (!$array) return false;
   $flat = array();
   $RII = new RecursiveIteratorIterator(new RecursiveArrayIterator($array));
   foreach ($RII as $value) $flat[] = $value;
   return $flat;
}

The big benefit of this method is that it tracks the depth of the recursion, should you need that while flattening.
This will output:

$array = array( 
    'A' => array('B' => array( 1, 2, 3)), 
    'C' => array(4, 5) 
); 
print_r(array_flatten_recursive($array)); 

#Returns: 
Array ( 
    [0] => 1 
    [1] => 2 
    [2] => 3 
    [3] => 4 
    [4] => 5 
)
Redstart answered 10/6, 2011 at 10:4 Comment(1)
Note: Only use for arrays of primitives. "RecursiveArrayIterator treats all objects as having children, and tries to recurse into them." php.net/manual/en/class.recursivearrayiterator.php#106519Cheapen
A
4

In PHP>=5.3 and based on Luc M's answer (the first one) you can make use of closures like this

array_walk_recursive($aNonFlat, function(&$v, $k, &$t){$t->aFlat[] = $v;}, $objTmp);

I love this because I don't have to surround the function's code with quotes like when using create_function()

Ambrosine answered 24/8, 2011 at 22:58 Comment(2)
if you're using anonymous functions, you might as well use a captured closure variable directly rather than this objTemp stuffMalang
There is a bug in PHP5.3.3 which causes this to crash - bugs.php.net/bug.php?id=52719Spancake
P
2

Using higher-order functions (note: I'm using inline anonymous functions, which appeared in PHP 5.3):

function array_flatten($array) {
    return array_reduce(
        $array,
        function($prev, $element) {
            if (!is_array($element))
                $prev[] = $element;
            else
                $prev = array_merge($prev, array_flatten($element));
            return $prev;
        },
        array()
    );
}
Percolator answered 10/5, 2016 at 18:43 Comment(0)
G
2

I found a simple way to convert multilevel array into one. I use the function "http_build_query" which converts the array into a url string. Then, split the string with explode and decode the value.

Here is a sample.

$converted = http_build_query($data);
$rows = explode('&', $converted);
$output = array();
foreach($rows AS $k => $v){
   list($kk, $vv) = explode('=', $v);
   $output[ urldecode($kk) ] =  urldecode($vv);
}
return $output;
Grouchy answered 10/1, 2019 at 21:52 Comment(0)
F
1

If you're okay with loosing array keys, you may flatten a multi-dimensional array using a recursive closure as a callback that utilizes array_values(), making sure that this callback is a parameter for array_walk(), as follows.

<?php  

$array = [1,2,3,[5,6,7]];
$nu_array = null;
$callback = function ( $item ) use(&$callback, &$nu_array) {
    if (!is_array($item)) {
    $nu_array[] = $item;
    }
    else
    if ( is_array( $item ) ) {
     foreach( array_values($item) as $v) {
         if ( !(is_array($v))) {
             $nu_array[] = $v;
         }
         else
         { 
             $callback( $v );
         continue;
         }    
     }
    }
};

array_walk($array, $callback);
print_r($nu_array);

The one drawback of the preceding example is that it involves writing far more code than the following solution which uses array_walk_recursive() along with a simplified callback:

<?php  

$array = [1,2,3,[5,6,7]];

$nu_array = [];
array_walk_recursive($array, function ( $item ) use(&$nu_array )
                     {
                         $nu_array[] = $item;
                     }
);
print_r($nu_array);

See live code

This example seems preferable to the previous one, hiding the details about how values are extracted from a multidimensional array. Surely, iteration occurs, but whether it entails recursion or control structure(s), you'll only know from perusing array.c. Since functional programming focuses on input and output rather than the minutiae of obtaining a result, surely one can remain unconcerned about how behind-the-scenes iteration occurs, that is until a perspective employer poses such a question.

Female answered 10/4, 2011 at 22:40 Comment(0)
W
1

A new approach based on the previous example function submited by chaos, which fixes the bug of overwritting string keys in multiarrays:

# Flatten a multidimensional array to one dimension, optionally preserving keys.
# $array - the array to flatten
# $preserve_keys - 0 (default) to not preserve keys, 1 to preserve string keys only, 2 to preserve all keys
# $out - internal use argument for recursion

function flatten_array($array, $preserve_keys = 2, &$out = array(), &$last_subarray_found) 
{
        foreach($array as $key => $child)
        {
            if(is_array($child))
            {
                $last_subarray_found = $key;
                $out = flatten_array($child, $preserve_keys, $out, $last_subarray_found);
            }
            elseif($preserve_keys + is_string($key) > 1)
            {
                if ($last_subarray_found)
                {
                    $sfinal_key_value = $last_subarray_found . "_" . $key;
                }
                else
                {
                    $sfinal_key_value = $key;
                }
                $out[$sfinal_key_value] = $child;
            }
            else
            {
                $out[] = $child;
            }
        }

        return $out;
}

Example:
$newarraytest = array();
$last_subarray_found = "";
$this->flatten_array($array, 2, $newarraytest, $last_subarray_found);
Williawilliam answered 31/8, 2011 at 11:46 Comment(0)
L
1
/*consider $mArray as multidimensional array and $sArray as single dimensional array
this code will ignore the parent array
*/

function flatten_array2($mArray) {
    $sArray = array();

    foreach ($mArray as $row) {
        if ( !(is_array($row)) ) {
            if($sArray[] = $row){
            }
        } else {
            $sArray = array_merge($sArray,flatten_array2($row));
        }
    }
    return $sArray;
}
Lomond answered 11/9, 2011 at 1:25 Comment(0)
S
1

you can try this:

function flat_an_array($a)
{
    foreach($a as $i)
    {
        if(is_array($i)) 
        {
            if($na) $na = array_merge($na,flat_an_array($i));
            else $na = flat_an_array($i);
        }
        else $na[] = $i;
    }
    return $na;
}
Sclerophyll answered 21/2, 2012 at 15:17 Comment(0)
S
0

You can use the flatten function from Non-standard PHP library (NSPL). It works with arrays and any iterable data structures.

assert([1, 2, 3, 4, 5, 6, 7, 8, 9] === flatten([[1, [2, [3]]], [[[4, 5, 6]]], 7, 8, [9]]));
Steersman answered 21/5, 2016 at 19:40 Comment(0)
I
0

Simple approach..See it via recursion..

<?php

function flatten_array($simple){
static $outputs=array();
foreach ( $simple as $value)
{
if(is_array($value)){
    flatten_array($value);
}
else{
    $outputs[]=$value;
}

}
return $outputs;
}

$eg=['s'=>['p','n'=>['t']]];
$out=flatten_array($eg);
print_r($out);

?>
Iguanodon answered 11/12, 2016 at 13:18 Comment(2)
Why is using static a potentially bad idea for this task? Unintended data retention. This will certainly catch programmers by surprise if they don't know/expect this behavior. Look at this demonstration.Glogau
You have posted a "code-only" answer -- these are low value on StackOverflow because they fail to educate the OP and future researchers. Please take a moment to improve this answer by including how your answer works and why you feel it is a better idea versus the earlier answers.Glogau
N
0

Someone might find this useful, I had a problem flattening array at some dimension, I would call it last dimension so for example, if I have array like:

array (
  'germany' => 
  array (
    'cars' => 
    array (
      'bmw' => 
      array (
        0 => 'm4',
        1 => 'x3',
        2 => 'x8',
      ),
    ),
  ),
  'france' => 
  array (
    'cars' => 
    array (
      'peugeot' => 
      array (
        0 => '206',
        1 => '3008',
        2 => '5008',
      ),
    ),
  ),
)

Or:

array (
  'earth' => 
  array (
    'germany' => 
    array (
      'cars' => 
      array (
        'bmw' => 
        array (
          0 => 'm4',
          1 => 'x3',
          2 => 'x8',
        ),
      ),
    ),
  ),
  'mars' => 
  array (
    'france' => 
    array (
      'cars' => 
      array (
        'peugeot' => 
        array (
          0 => '206',
          1 => '3008',
          2 => '5008',
        ),
      ),
    ),
  ),
)

For both of these arrays when I call method below I get result:

array (
  0 => 
  array (
    0 => 'm4',
    1 => 'x3',
    2 => 'x8',
  ),
  1 => 
  array (
    0 => '206',
    1 => '3008',
    2 => '5008',
  ),
)

So I am flattening to last array dimension which should stay the same, method below could be refactored to actually stop at any kind of level:

function flattenAggregatedArray($aggregatedArray) {
    $final = $lvls = [];
    $counter = 1;
    $lvls[$counter] = $aggregatedArray;


    $elem = current($aggregatedArray);

    while ($elem){
        while(is_array($elem)){
            $counter++;
            $lvls[$counter] = $elem;
            $elem =  current($elem);
        }

        $final[] = $lvls[$counter];
        $elem = next($lvls[--$counter]);
        while ( $elem  == null){
            if (isset($lvls[$counter-1])){
                $elem = next($lvls[--$counter]);
            }
            else{
                return $final;
            }
        }
    }
}
Neoplasty answered 18/2, 2020 at 15:52 Comment(0)
E
-1

If you're interested in just the values for one particular key, you might find this approach useful:

function valuelist($array, $array_column) {
    $return = array();
    foreach($array AS $row){
        $return[]=$row[$array_column];
    };
    return $return;
};

Example:

Given $get_role_action=

array(3) {
  [0]=>
  array(2) {
    ["ACTION_CD"]=>
    string(12) "ADD_DOCUMENT"
    ["ACTION_REASON"]=>
    NULL
  }
  [1]=>
  array(2) {
    ["ACTION_CD"]=>
    string(13) "LINK_DOCUMENT"
    ["ACTION_REASON"]=>
    NULL
  }
  [2]=>
  array(2) {
    ["ACTION_CD"]=>
    string(15) "UNLINK_DOCUMENT"
    ["ACTION_REASON"]=>
    NULL
  }
}

than $variables['role_action_list']=valuelist($get_role_action, 'ACTION_CD'); would result in:

$variables["role_action_list"]=>
  array(3) {
    [0]=>
    string(12) "ADD_DOCUMENT"
    [1]=>
    string(13) "LINK_DOCUMENT"
    [2]=>
    string(15) "UNLINK_DOCUMENT"
  }

From there you can perform value look-ups like so:

if( in_array('ADD_DOCUMENT', $variables['role_action_list']) ){
    //do something
};
Eal answered 23/7, 2014 at 20:44 Comment(2)
This is a PHP knock-off of a CFML function by the same name.Eal
I have downvoted because it is the right answer to the wrong question.Glogau
P
-1

any of this didnt work for me ... so had to run it myself. works just fine:

function arrayFlat($arr){
$out = '';
    foreach($arr as $key => $value){

        if(!is_array($value)){
            $out .= $value.',';
        }else{
            $out .= $key.',';
            $out .= arrayFlat($value);
        }

    }
    return trim($out,',');
}


$result = explode(',',arrayFlat($yourArray));
echo '<pre>';
print_r($result);
echo '</pre>';
Pless answered 25/9, 2015 at 6:38 Comment(1)
This code-only answer doesn't work as desired. 3v4l.org/U3bfp <-- proof This is the reason for my downvote.Glogau
B
-1

Given multi-dimensional array and converting it into one-dimensional, can be done by unsetting all values which are having arrays and saving them into first dimension, for example:

function _flatten_array($arr) {
  while ($arr) {
    list($key, $value) = each($arr); 
    is_array($value) ? $arr = $value : $out[$key] = $value;
    unset($arr[$key]);
  }
  return (array)$out;
}
Borer answered 11/11, 2015 at 22:47 Comment(1)
I have downvoted this answer because it doesn't work on any version. 3v4l.org/7cO9N (Proof) Also, each() is deprecated from php7.2.Glogau

© 2022 - 2024 — McMap. All rights reserved.