如何将多维数组扁平化?

374

在PHP中,是否有可能在不使用递归或引用的情况下将(双/多)维数组展开?

我只对值感兴趣,可以忽略键,我考虑使用array_map()array_values()来实现。


18
为什么要避免使用递归?递归指的是一个函数在其内部调用自身。虽然递归能够解决某些问题,但它也有缺点。首先,递归需要更多的内存空间,因为每次函数调用时都会创建一个新的栈帧。如果递归层数过多,就会导致栈溢出。此外,递归通常比迭代执行速度慢,因为每次调用都需要一定的开销。因此,在编写代码时,应该尽可能避免使用递归,除非递归是必须的或者使用递归可以使代码更加清晰易懂。 - JorenB
6
主要是重复的问题 https://dev59.com/oXRB5IYBdhLWcg3wv5o_ - cletus
4
如果你想处理任意深度的数组中的所有元素,没有递归是不可能的(虽然你可以把它伪装成迭代,但本质上并没有区别)。如果你只是想避免自己编写递归处理代码,可以使用http://dk2.php.net/manual/en/function.array-walk-recursive.php函数,并提供一个回调函数将元素添加到可用的数组中(使用全局变量、userdata参数、将其全部放在一个类中并引用$this等方法均可) 。 - Michael Madsen
@JorenB:我希望能看到一个实现可以被实现的情况。 - Alix Axel
请查看Nspl中的flatten函数。您还可以使用它来指定深度。 - Ihor Burlachenko
31个回答

363

下面的答案中可以找到一个更近期的解决方案。

PHP 5.3开始,最简短的解决方案似乎是使用新的闭包语法array_walk_recursive()

function flatten(array $array) {
    $return = array();
    array_walk_recursive($array, function($a) use (&$return) { $return[] = $a; });
    return $return;
}

40
如果你想要键值对,使用以下函数将多维数组展平:function flatten(array $array) { $return = array(); array_walk_recursive($array, function($a,$b) use (&$return) { $return[$b] = $a; }); return $return; } - Brendon-Van-Heyzen
你能否重写这个程序以适用于 PHP 5.2? - Alex
2
很抱歉,@Alex,你需要使用“use”语法才能使array_walk_recursive与可选的$userdata参数一起正常工作,因为它不接受引用。 - Tim Seguine
1
看起来对于这样的数组可以正常工作 -> http://ideone.com/DsmApP 但是对于这样的不行 -> http://ideone.com/5Kltva还是我的问题? - Sebastian Piskorski
2
@Sebastian Piskorski这是因为您的值被视为键,因此一旦在数组中引入自己的键=>值对,您在第一个索引位置的数组值就被视为没有值的键。由于键必须是唯一的,在两个键匹配的情况下,您的值将添加到相同的键中。简单的解决方案是首先对数组进行排序。这是PHP固有的行为。 - Martyn Shutt
显示剩余6条评论

336
你可以使用标准PHP库(SPL)来“隐藏”递归。
$a = array(1,2,array(3,4, array(5,6,7), 8), 9);
$it = new RecursiveIteratorIterator(new RecursiveArrayIterator($a));
foreach($it as $v) {
  echo $v, " ";
}

打印

1 2 3 4 5 6 7 8 9 

439
我是唯一一个认为“RecursiveIteratorIterator”这个名字很傻的人吗? - nilamo
53
它更注重“逻辑性”而不是“引人入胜”。并非所有东西都能像JOGL、Knol或Azure那样有一个梦幻般的名称。 - VolkerK
9
对于空数组作为子元素,这种方法不起作用。它们会被返回为父级。 - hakre
51
iterator_to_array($it, false) 可以避免使用 foreach 循环。 - Alix Axel
10
在其他人的基础上,我能够编写出这个小助手:function flatten($arr){ $it = new RecursiveIteratorIterator(new RecursiveArrayIterator($arr)); return iterator_to_array($it, true); } 希望这对其他人有所帮助。 - Mike S.
显示剩余7条评论

267
在PHP 5.6及以上版本中,您可以使用...运算符展开外部数组后,使用array_merge来展平二维数组。这段代码简单明了。
array_merge(...$a);

这也适用于关联数组的集合。

$a = [[10, 20], [30, 40]];
$b = [["x" => "A", "y" => "B"], ["y" => "C", "z" => "D"]];

print_r(array_merge(...$a));
print_r(array_merge(...$b));

Array
(
    [0] => 10
    [1] => 20
    [2] => 30
    [3] => 40
)
Array
(
    [x] => A
    [y] => C
    [z] => D
)

在PHP 8.0及以下版本中,当外部数组具有非数字键时,无法使用数组展开功能。从PHP 8.1开始支持使用字符串键展开数组。为了支持8.0及以下版本,您应该先调用array_values
$c = ["a" => ["x" => "A", "y" => "B"], "b" => ["y" => "C", "z" => "D"]];
print_r(array_merge(...array_values($c)));

Array
(
    [x] => A
    [y] => C
    [z] => D
)

更新:根据 @MohamedGharib 的评论(适用于 PHP 7.3.x 及更早版本 ref

如果外部数组为空,则此操作将抛出错误,因为 array_merge 将被调用零个参数。可以通过将空数组作为第一个参数添加来避免此错误。

array_merge([], ...$a);

3
仅当数组的每个元素都是一个数组时,此方法才有效。如果数组包含不同类型的元素,比如标量,那么会出现错误。 - Otheus
1
@Otheus,这是因为上面的解决方案没有使用递归。正如你所说,它需要一个数组的数组。但好的一面是,这应该比其他方法快得多,因为它没有额外的函数调用开销。 - Joyce Babu
8
如果外部数组为空,将会抛出一个错误,但是可以通过与一个空数组合并来避免这种情况:array_merge([], ...$a); - Mohamed Gharib
1
哇,那是一些很酷的古怪语法...我有 5 年的 PHP 编程经验,从未听说过这个(...)操作符。不过它确实像魔法一样好用! - Robert Sinclair
1
非常酷。并不适用于所有情况,但在适当的时候是完美的。$a = array_merge( array(), ...array_values( $a ) ); 对我来说很有效,可用于展平使用 $wpdb->get_results( $sql, ARRAY_N ) 查询 WordPress 中的单列结果。 - squarecandy
显示剩余5条评论

102

解决二维数组问题的方案

请尝试以下方法:

$array  = your array

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

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

编辑:21-Aug-13

这是适用于多维数组的解决方案:

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

参考:http://php.net/manual/zh/function.call-user-func-array.php


谢谢,第一个解决方案适用于我从PDO获取的数组,而其他解决方案则不适用。 - JAL
7
这是一种不好的策略。call_user_func_array('array_merge', [])(注意空数组)返回 null 并触发 PHP 警告错误。如果你确定数组不会为空,那么这是一个巧妙的解决方案,但这并不是许多人可以做出的普遍假设。 - goat
OP特别要求非递归解决方案。 - Lux
酷啊,但是你有没有偶然间一个计数器函数数组展开? - FantomX1
1
第二个代码片段不可信,它可能会破坏数据。第一个代码片段仅适用于数组的数组。尽管它得分很高,但这个答案并不包含适合一般使用的可靠建议。 - mickmackusa
显示剩余2条评论

30

如果您想要不使用递归进行平铺,可以使用。当然,您可以将其放入自己的函数中,例如array_flatten。以下是一种不使用键的版本:

function array_flatten(array $array)
{
    $flat = array(); // initialize return array
    $stack = array_values($array); // initialize stack
    while($stack) // process stack until done
    {
        $value = array_shift($stack);
        if (is_array($value)) // a value to further process
        {
            array_unshift($stack, ...$value);
        }
        else // a value to take
        {
            $flat[] = $value;
        }
    }
    return $flat;
}

元素按照它们的顺序进行处理。因为子元素将被移动到堆栈的顶部,所以它们将被下一步处理。

也可以考虑将键值考虑在内,但是您需要使用不同的策略来处理堆栈。这是必要的,因为您需要处理子数组中可能存在的重复键。相关问题中有一个类似的答案:PHP Walk through multidimensional array while preserving keys

我不确定,但我曾经测试过这个:RecurisiveIterator确实使用递归,所以这取决于您实际需要什么。也应该可以基于堆栈创建递归迭代器:

foreach(new FlatRecursiveArrayIterator($array) as $key => $value)
{
    echo "** ($key) $value\n";
}

演示

我还没有实现基于RecursiveIterator的堆栈,我认为这是一个好主意。


太棒了,array_flatten函数非常出色。我不得不在else语句中添加if(!empty($value)){$flat[] = $value}以防止空值被添加到结果数组中。这个函数真是太棒了! - Alex Sarnowski
1
将展平操作从$stack = array_merge(array_values($value), $stack);简化为array_unshift($stack, ...$value);,对于大多数情况而言,array_values()调用可能是多余的,但由于该答案早于v5.6,因此不应进行编辑。 - Walf
@Walf:是的,这是唯一的原因。谢谢你的提示,我已经编辑好了。 - hakre

26

简明扼要的回答。

function flatten_array(array $array)
{
    return iterator_to_array(
         new \RecursiveIteratorIterator(new \RecursiveArrayIterator($array)));
}

用法:

$array = [
    'name' => 'Allen Linatoc',
    'profile' => [
        'age' => 21,
        'favourite_games' => [ 'Call of Duty', 'Titanfall', 'Far Cry' ]
    ]
];

print_r( flatten_array($array) );

输出(在 PsySH 中):

Array
(
    [name] => Allen Linatoc
    [age] => 21
    [0] => Call of Duty
    [1] => Titanfall
    [2] => Far Cry
)

现在你该如何处理这些键完全取决于你自己。干杯。


编辑(2017-03-01)

引用Nigel Alderton的担忧/问题:

仅作澄清,此方法保留键(包括数字键),因此具有相同键的值将会被覆盖。例如,$array = ['a',['b','c']] 变成了 Array([0] => b, [1] => c ) 。因为'b'也具有键0,所以'a'被覆盖了。

引用Svish的回答:

只需将iterator_to_array调用的第二个参数($use_keys)设置为false即可。


仅澄清一下,这将保留键(甚至是数字键),因此具有相同键的值将会丢失。例如 $array = ['a',['b','c']] 将变成 Array ([0] => b, [1] => c )。因为 'b' 也有一个键为 0,所以 'a' 就被丢失了。 - Nigel Alderton
1
@NigelAlderton 只需将 false 作为第二个参数($use_keys)添加到 iterator_to_array 调用中即可。 - Svish
使用 iterator_to_array($it, false); 时要小心!如果未设置此参数或将其设置为 TRUE,则会覆盖重复的键!!!如果您将其与 array(1,2,array(3,4, array(5,6,7), 8), 9); 一起使用,这将返回 5 6 7 9 而不是预期的 1 2 3 4 5 6 7 8 9 - ponsfrilus
使用迭代器的好处是在展开数组时保留数字二级键。使用“Flatten a 2d array while preserving numeric associative row keys”中的示例数据来实现这种方法,以查看其与此页面上其他技术不同的行为。证明:https://3v4l.org/9rEm4 - mickmackusa

26

我只是想指出这是一个折叠操作,所以可以使用array_reduce函数:

array_reduce($my_array, 'array_merge', array());

编辑:请注意,这可以用于展开任意层级。我们可以通过多种方式来做到这一点:

// Reduces one level
$concat   = function($x) { return array_reduce($x, 'array_merge', array()); };

// We can compose $concat with itself $n times, then apply it to $x
// This can overflow the stack for large $n
$compose  = function($f, $g) {
    return function($x) use ($f, $g) { return $f($g($x)); };
};
$identity = function($x) { return $x; };
$flattenA = function($n) use ($compose, $identity, $concat) {
    return  function($x) use ($compose, $identity, $concat, $n) {
        return ($n === 0)? $x
                         : call_user_func(array_reduce(array_fill(0, $n, $concat),
                                                       $compose,
                                                       $identity),
                                          $x);
    };
};

// We can iteratively apply $concat to $x, $n times
$uncurriedFlip     = function($f) {
    return  function($a, $b) use ($f) {
        return $f($b, $a);
    };
};
$iterate  = function($f) use ($uncurriedFlip) {
    return  function($n) use ($uncurriedFlip, $f) {
    return  function($x) use ($uncurriedFlip, $f, $n) {
        return ($n === 0)? $x
                         : array_reduce(array_fill(0, $n, $f),
                                        $uncurriedFlip('call_user_func'),
                                        $x);
    }; };
};
$flattenB = $iterate($concat);

// Example usage:
$apply    = function($f, $x) {
    return $f($x);
};
$curriedFlip = function($f) {
    return  function($a) use ($f) {
    return  function($b) use ($f, $a) {
        return $f($b, $a);
    }; };
};

var_dump(
    array_map(
        call_user_func($curriedFlip($apply),
                       array(array(array('A', 'B', 'C'),
                                   array('D')),
                             array(array(),
                                   array('E')))),
        array($flattenA(2), $flattenB(2))));

当然,我们也可以使用循环,但问题要求使用类似于array_map或array_values的组合函数。


多维 != 二维。 - Alix Axel
@atamur 这个适用于 PHP 5.3+。如 array_reduce 的变更日志所述,在 5.3 之前,$initial 只能是整数,然后允许它是“mixed”(即任何您的约简函数支持的内容)。 - Warbo
1
@AlixAxel 你说得对,多维数组并不等同于二维数组,但是可以组合成任意层数的平面数组。组合折叠的一个好处是它遵守固定的限制;如果我有一个嵌套到5层的数组,我可以将其“折叠”成4层,或者将其“折叠.折叠”以获得3层,或者将其“折叠.折叠.折叠”以获得2层等等。这也可以防止隐藏错误;例如,如果我想要展平一个5D数组,但我得到了一个4D数组,那么错误会立即触发。 - Warbo
我喜欢这个解决方案,适用于二维数组。完美地满足了要求。 - Tom Auger
我同意你的单层定义是最好的答案,而且非常整洁。但是我认为你错误地将其命名为 $concat,我认为你应该只称其为 $flattenarray_mergephp 中 concat 的等效函数。我曾经尝试过将 array_concat 添加为 array_merge 的别名,具体请见 这里 - icc97

18

使用递归。 希望通过看到它并不复杂,您对递归的恐惧会消散。

function flatten($array) {
    if (!is_array($array)) {
        // nothing to do if it's not an array
        return array($array);
    }

    $result = array();
    foreach ($array as $value) {
        // explode the sub-array, and add the parts
        $result = array_merge($result, flatten($value));
    }

    return $result;
}


$arr = array('foo', array('nobody', 'expects', array('another', 'level'), 'the', 'Spanish', 'Inquisition'), 'bar');
echo '<ul>';
foreach (flatten($arr) as $value) {
    echo '<li>', $value, '</li>';
}
echo '<ul>';

输出:

<ul><li>foo</li><li>nobody</li><li>expects</li><li>another</li><li>level</li><li>the</li><li>Spanish</li><li>Inquisition</li><li>bar</li><ul>

1
我不害怕递归,我只想学习其他实现相同功能的方式。 - Alix Axel
13
希望通过这个递归函数的例子,你能看到它并不复杂,从而消除你对递归的恐惧。 - oxygen
1
好的,这超出了我的能力范围。怎么可能呢?回复(“我不害怕递归”)比最初的声明(“(...)你对递归的恐惧会消散(...)”)早三年半(2009年8月24日),而最初的声明是在2013年2月5日发表的? - trejder

13

只对二维数组进行扁平化:

$arr = [1, 2, [3, 4]];
$arr = array_reduce($arr, function ($a, $b) {
     return array_merge($a, (array) $b);
}, []);

// Result: [1, 2, 3, 4]

7
自 PHP v7.4 起,你可以使用展开运算符将数组合并。简单而有效。
$flatArr = array_merge(...$originalArray);

2
或者,如果原始数组是关联数组,则可以使用以下代码将其转换为一维数组:$flatArr = array_merge(...array_values($originalArray)); - Alliswell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接