如何在 PHP 中将多维数组“扁平化”为简单的数组?

这可能是一个初学者的问题,但是我已经花了很长的时间来阅读文档,我找不到任何解决方案。我认为我可以为每个维度使用内爆,然后把这些字符串和 str_split放回一起,形成新的简单数组。但是我从来不知道连接模式是否也在值中,因此在执行 str_split之后,我的原始值可能会中断。

对于多维数组中的数组,是否有类似于 combine($array1, $array2)的东西?

155189 次浏览

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;
}

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)

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;
}
// $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

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.

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
)

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()

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);
/*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;
}

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;
}
$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);

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.

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
};

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

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

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;
}

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()
);
}

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]]));

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);


?>

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.

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;

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;
}
}
}
}

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