Создаем одномерный массив из многомерного

Задача, которой я хотел бы посвятить эту статью, у некоторых не так часто встречается или пока ещё вовсе не попадалась - создание одномерного массива из многомерного. Однако, на форумах время от времени эта тема поднимается, а кроме того, за последние два месяца, мне приходилось несколько раз решать нестандартные ситуации, связанные с этим вопросом.

Всё достаточно просто, если исходный массив у нас двумерный. Возьмём, для примера, такой:

$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

Разница в скорости - более, чем в семь раз и кода меньше примерно во столько же! Дальнейшие комментарии излишни... ;)


В общем, вывод можно сделать следующий: создать одномерный массив из многомерного можно достаточно легко, но если еще и хорошо порыться в документации, то "не изобретая свой велосипед", это можно сделать буквально несколькими строками кода и работать будет лучше и быстрее.

Incode Pro logo

9 комментариев

Гость 30.03.2015 16:24
ууууу. Спасибо большое очень помогло!
Гость 27.04.2015 11:09
$iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($arrIn));
$arrOut = iterator_to_array($iterator, false);


а можно в этом случае еще и ключи вывести?
Incode 27.04.2015 12:34
а можно в этом случае еще и ключи вывести?
В многомерном массиве, на разных уровнях вложенности, ключи элементов могут повторяться (что в реальных случаях бывает чаще всего), а в одномерном массиве - каждый ключ уникален и, добавляя элемент с уже существующим ключом, он просто перезапишет его значение. Однако, если вы уверены, что каждый ключ многомерного массива уникален, то замените второй параметр функции iterator_to_array() на true.
$arrIn = [
    'val_0_with_key_0',
    'key_1'=>'val_1',
    'key_2' => [
        'key_3'=>'val_3',
        'key_4' => 'val_4',
        [
            'key_5' => 'val_5',
        ]
    ],
    ' ' => 'with_empty_key'
]; 
$iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($arrIn));
$arrOut = iterator_to_array($iterator, true);
Результат:
Array
(
    [0] => val_0_with_key_0
    [key_1] => val_1
    [key_3] => val_3
    [key_4] => val_4
    [key_5] => val_5
    [ ] => with_empty_key
)
Гость 30.12.2015 21:38
Спасибо Вам большое. Я задолбался искать решение. Foreach выводил последнее значение и все. Особенно бесят "ЗНАТОКИ" форумов. Все решено УРА!!!!!1
Гость 11.02.2016 09:19
Решение шик! Спасибо.
Гость 16.05.2016 15:25
в закладки=)
спасибо
Гость 10.06.2017 19:14
Ещё вместо call_user_func_array('array_merge', $arrIn) можно выполнить array_merge(...$arrIn). Также написал пост про уменьшение итерабельной сложности массивов. В конце поста есть несколько функций для объединения многомерных массивов с сохранением ключей и без.
Гость 06.09.2017 13:31
Ребзя, помогите, примерно тоже самое, есть многомерный массив, в него записывается куча параметров, около 5-6. Мне нужно тоже самое что и было описано выше, но только для части массива, по ключу "id". То есть, записать в массив, одномерный, поля с айдишником из многомерного, битый час ломаю голову, кому не сложно, подскажите, пожалуйста, как это сделать?
Incode 06.09.2017 14:25
пожалуйста, как это сделать?
Если бы вы показали пример вашего массива и массив, который должен получиться на выходе, то было бы проще вам ответить. Пока я понял вашу задачу так, что нужно из двумерного массива получить все значения с ключом "id". Для этого можно использовать стандартную функцию array_column(). Первый пример по ссылке очень близок к вашей проблеме. Если изменить строку
$first_names = array_column($records, 'first_name');
на эту
$ids = array_column($records, 'id');
то в переменной $ids вы и получите все значения.
Ваш комментарий:
X