Создаем одномерный массив из многомерного
Задача, которой я хотел бы посвятить эту статью, у некоторых не так часто встречается или пока ещё вовсе не попадалась - создание одномерного массива из многомерного. Однако, на форумах время от времени эта тема поднимается, а кроме того, за последние два месяца, мне приходилось несколько раз решать нестандартные ситуации, связанные с этим вопросом.
Всё достаточно просто, если исходный массив у нас двумерный. Возьмём, для примера, такой:
$arrIn = array(
array('a','b','c','d'),
array(1,2,3),
array('e',4,'f',5),
);
В самом обычном цикле, делаем слияние каждого из вложенных массивов с результирующим массивом (изначально пустым), используя функцию array_merge() и записывая результат слияния в этот же конечный массив:
$arrOut = array();
foreach($arrIn as $subArr){
$arrOut = array_merge($arrOut,$subArr);
}
Можно конечно и поизвращаться, но больше для расширения кругозора, чем для реальной практики в данной задаче. Будем использовать две функций: array_map() - применяет callback-функцию к каждому элементу массива и функцию array_merge(), которую рассматривали выше.
$arrOut = array();
array_map(function($a) use(&$arrOut){
$arrOut = array_merge($arrOut,$a);
}, $arrIn);
Немного расшифрую: в качестве callback-функции, мы используем анонимную функцию, которая получает поочерёдно каждый из вложенных массивов в переменную "$a" и сливает с массивом "$arrOut", который в итоге и будет содержать все значения исходного массива, но уже как одномерным. Так как внутри callback-функции массив "$arrOut" будет не виден (если только он не объявлен глобальным), мы используем ключевое слово use, которое позволяет использовать внешние переменные. Но передаём массив не как значение, а как "ссылку", поставив перед переменной символ амперсанда "&". Если бы мы этого не сделали, то каждый раз, мы получали бы пустой массив "$arrOut", который сливали с текущим "$a".
Для новичков всё это пока малопонятно и, пытаясь адаптировать код под свои нужды, могут наделать ошибок. Не пугайтесь - есть решение "в пару строк";) Используем функцию call_user_func_array(), которая, по сути, сделает всё то, что мы делали во втором примере, но, так сказать, одним махом.
$arrOut = call_user_func_array('array_merge', $arrIn);
В результате всех вышеупомянутых вариантов, мы получим массив такого вида:
Array
(
[0] => a
[1] => b
[2] => c
[3] => d
[4] => 1
[5] => 2
[6] => 3
[7] => e
[8] => 4
[9] => f
[10] => 5
)
Хочу отметить, что в случае, если вложенные массивы являются ассоциативными, то при совпадении ключей, последующий элемент будет перезаписывать предыдущий с таким же ключом. Чтобы избежать такой неувязочки и получить все значения, мы немного доработаем наш код функцией array_values():
$arrOut = array();
foreach($arrIn as $subArr){
$arrOut = array_merge($arrOut,array_values($subArr));
}
Остаётся выяснить, какой из трёх вариантов работает быстрее. Я сгенерировал массив, состоящий из сотни вложенных массивов, в каждом из которых по десять элементов и вот средние результаты, которые говорят сами за себя:
Способ | секунд |
---|---|
Цикл + array_merge | 0.0109 |
array_map + array_merge | 0.0170 |
call_user_func_array | 0.0009 |
С двумерными массивами немного разобрались, теперь перейдём к более сложной задаче - трехмерные (и более) массивы или многоуровневые массивы с неизвестной вложенностью. И первое решение, которое напрашивается - это использование рекурсии.
$arrIn = array( // исходный массив
'A' => array('first',2,3),
'B' => array(
'b1' => array(4,5,6,7),
'b2' => array('a','b','c')
),
'C' => array(
0 => array(8,9),
1 => array(
'c01' => array(
array(10,11,12),
'last'
)
)
)
);
function makeSingleArray($arr){
if(!is_array($arr)) return false;
$tmp = array();
foreach($arr as $val){
if(is_array($val)){
$tmp = array_merge($tmp, makeSingleArray($val));
} else {
$tmp[] = $val;
}
}
return $tmp;
}
$arrOut = makeSingleArray($arrIn);
Вполне нормальный способ, который, на мой взгляд, не требует каких-то подробных разъяснений и хорошо справляется со своей задачей. Многие, даже не задумываясь, именно его бы и применили. Но я хочу показать, как это же можно сделать буквально парой строк кода, используя "итераторы" из стандартной библиотеки PHP (SPL):
$iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($arrIn));
$arrOut = iterator_to_array($iterator, false);
Вот и всё! И результат будет, как и в первом случае такой:
Array
(
[0] => first
[1] => 2
[2] => 3
[3] => 4
[4] => 5
[5] => 6
[6] => 7
[7] => a
[8] => b
[9] => c
[10] => 8
[11] => 9
[12] => 10
[13] => 11
[14] => 12
[15] => last
)
Ну, и как в первом случае, показываю среднюю скорость выполнения двух вариантов:
Способ | секунд |
---|---|
Рекурсия | 0.2558 |
Классы итераторов | 0.0346 |
Разница в скорости - более, чем в семь раз и кода меньше примерно во столько же! Дальнейшие комментарии излишни... ;)
В общем, вывод можно сделать следующий: создать одномерный массив из многомерного можно достаточно легко, но если еще и хорошо порыться в документации, то "не изобретая свой велосипед", это можно сделать буквально несколькими строками кода и работать будет лучше и быстрее.