1 заметка с тегом

массив

Уменьшаем итерабельную сложность массива в PHP

Задача. Объедините массив, приведённый ниже, таким образом, чтобы по значениям результирующего массива работал поиск array_search() и in_array(). Массив должен быть проходимым за один цикл. Ключи сохранять не обязательно.
$data = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
];

На первый взгляд кажется, что можно скормить массив в array_merge() и получить желаемый результат, но array_merge() принимает как минимум два аргумента с массивами, а у нас один. Заметьте, вложенные массивы с данными обёрнуты в ещё один массив. Это усложняет доступ к этим данным. Можно, конечно, обойти весь массив двумя foreach() и извлечь все данные, но такой подход довольно-таки топорный:

<?php

$data = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
];

$flattened = [];

foreach ($data as $array) {
    foreach ($array as $key => $value) {
        $flattened[] = $value;
    }
}

var_dump($flattened);

// вернёт [1, 2, 3, 4, 5, 6, 7, 8, 9]

Существует более изящный способ объединить все массивы в один — скормить массив с данными в array_merge(), но через call_user_func_array(). Данная функция вызывает другую функцию с массивом параметров, где первым аргументом идёт название функции, а вторым — параметры в виде индексированного массива. Получается, что массив-обёртку съест call_user_func_array(), а остальные пойдут как аргументы в array_merge() и в итоге мы получим склеенный массив:

<?php

$data = [ // <- съест call_user_func_array()
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
]; // <- съест call_user_func_array()

call_user_func_array('array_merge', $data);

// вернёт [1, 2, 3, 4, 5, 6, 7, 8, 9]

Начиная с версии 5.6 мы можем распаковать массив-обёртку, если перед передаваемым аргументом с массивом данных при вызове функции array_merge() подставим многоточие. Читайте подробнее про списки аргументов переменной длины. В нашем случае массив будет распакован и вложенные в него массивы объединятся:

<?php

$data = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
];

array_merge(...$data);

// вернёт [1, 2, 3, 4, 5, 6, 7, 8, 9]

Эти методы выполняют поставленную задачу, но только с двухуровневыми массивами, как на примерах выше. А что, если нужно склеить все значения многоуровневого массива в один? Разумеется необходимо рекурсивно пройти по каждому значению. Делается это с сохранением ключей и без, а также с учётом уровня вложенности. Ниже привожу все известные мне самописные функции:

<?php

///////
// 1 //
///////
if ( ! function_exists('array_flatten') ) {

    /**
     * Выравнивает массив без сохранения ключей
     *
     * @link https://gist.github.com/intedinmamma/246665
     *
     * @param array $data
     * @param array $flattened
     *
     * @return array
     */
    function array_flatten(array $data, array $flattened = [])
    {
        foreach($data as $value) {
            is_array($value)                               ?
            $flattened = array_flatten($value, $flattened) :
            $flattened[] = $value;
        }

        return $flattened;
    }
}

///////
// 2 //
///////
if ( ! function_exists('array_flatten') ) {

    /**
     * Выравнивает массив с сохранением ключей
     *
     * @link http://brandonwamboldt.github.io/utilphp/#array_flatten
     *
     * @param array $data
     * @param bool  $preserve_keys
     * @param array $flattened
     *
     * @return array
     */
    function array_flatten(
        array $data,
        bool $preserve_keys = true,
        array $flattened = []
    ) {
        array_walk_recursive($data, function ($value, $key) use (&$flattened, $preserve_keys) {
            $preserve_keys && !is_int($key) ?
            $flattened[$key] = $value       :
            $flattened[] = $value;
        });

        return $flattened;
    }
}

///////
// 3 //
///////
if ( ! function_exists('array_flatten') ) {

    /**
     * Выравнивает массив с помощью итератора с сохранением ключей или без
     *
     * @link http://stackoverflow.com/a/12205381
     *
     * @param array $data
     * @param bool  $use_keys
     *
     * @return array
     */
    function array_flatten(array $data, bool $use_keys = false)
    {
        $flattened = new \RecursiveIteratorIterator(
            new \RecursiveArrayIterator($data)
        );

        return iterator_to_array($flattened, $use_keys);
    }
}

///////
// 4 //
///////
if ( ! function_exists('array_flatten') ) {

    /**
     * Выравнивает массив по уровню вложенности
     *
     * @link https://github.com/spatie/array-functions#array_flatten
     *
     * @param array $array
     * @param int   $levels
     *
     * @return array
     */
    function array_flatten(array $array, int $levels = -1)
    {
        if ($levels === 0) {
            return $array;
        }

        $flattened = [];

        if ($levels !== -1) {
            --$levels;
        }

        foreach ($array as $value) {
            $flattened = array_merge(
                $flattened,
                is_array($value) ? array_flatten($value, $levels) : [$value]
            );
        }

        return $flattened;
    }
}


Пример для любой из функций array_flatten():

<?php

$data = [
    [1, 2, 3],
    [4, [5, 6, 7], 8],
    [9, [10, 11], 12],
];

array_flatten($data);

// вернёт [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
2017   php   массив