Создаем одномерный массив из многомерного
Задача, которой я хотел бы посвятить эту статью, у некоторых не так часто встречается или пока ещё вовсе не попадалась - создание одномерного массива из многомерного. Однако, на форумах время от времени эта тема поднимается, а кроме того, за последние два месяца, мне приходилось несколько раз решать нестандартные ситуации, связанные с этим вопросом.
Всё достаточно просто, если исходный массив у нас двумерный. Возьмём, для примера, такой:
$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 |
Разница в скорости - более, чем в семь раз и кода меньше примерно во столько же! Дальнейшие комментарии излишни... ;)
В общем, вывод можно сделать следующий: создать одномерный массив из многомерного можно достаточно легко, но если еще и хорошо порыться в документации, то "не изобретая свой велосипед", это можно сделать буквально несколькими строками кода и работать будет лучше и быстрее.