Как выровнять многомерный массив?

Возможно ли в PHP сгладить (двух- или многомерный) массив без использования рекурсии или ссылок?

Меня интересуют только значения, поэтому ключи можно игнорировать, я думаю, что array_map() а также array_values(),

35 ответов

Решение

Вы можете использовать стандартную библиотеку 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 

Начиная с PHP 5.3, самое короткое решение array_walk_recursive() с новым синтаксисом замыканий:

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

В PHP 5.6 и выше вы можете сгладить двумерные массивы с помощью array_merge после распаковки внешнего массива с ... оператор. Код прост и понятен.

$a = [[10, 20], [30, 40]];
$b = [["x" => "X", "y" => "Y"], ["p" => "P", "q" => "Q"]];

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

Array
(
    [0] => 10
    [1] => 20
    [2] => 30
    [3] => 40
)
Array
(
    [x] => X
    [y] => Y
    [p] => P
    [q] => Q
)

Но это не работает, когда внешний массив имеет не числовые ключи. В этом случае вам придется позвонить array_values первый.

$c = ["a" => ["x" => "X", "y" => "Y"], "b" => ["p" => "P", "q" => "Q"]];
print_r(array_merge(...array_values($c)));

Array
(
    [x] => X
    [y] => Y
    [p] => P
    [q] => Q
)

Решение для двумерного массива

Пожалуйста, попробуйте это:

$array  = your array

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

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

РЕДАКТИРОВАТЬ: 21 августа-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/en/function.call-user-func-array.php

Чтобы сгладить без рекурсии (как вы просили), вы можете использовать стек. Естественно, вы можете поместить это в функцию своего собственного как 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
        {
            $stack = array_merge(array_values($value), $stack);
        }
        else // a value to take
        {
           $flat[] = $value;
        }
    }
    return $flat;
}

Элементы обрабатываются в их порядке. Поскольку подэлементы будут перемещены поверх стека, они будут обработаны следующим.

Также можно принять во внимание ключи, однако вам потребуется другая стратегия для обработки стека. Это необходимо, потому что вам нужно иметь дело с возможными дублирующимися ключами в подмассивах. Аналогичный ответ в связанном вопросе: PHP пройти через многомерный массив с сохранением ключей

Я не совсем уверен, но я проверял это в прошлом: RecurisiveIterator действительно использует рекурсию, так что это зависит от того, что вам действительно нужно. Должна быть возможность создать рекурсивный итератор также на основе стеков:

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

демонстрация

Я не сделал это до сих пор, чтобы реализовать стек на основе RecursiveIterator что я думаю, это хорошая идея.

Простой и однострочный ответ.

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 ), 'a' потерян, потому что 'b' также имеет ключ 0

Svish ответ Свиша:

Просто добавьте false в качестве второго параметра ($use_keys) к вызову iterator_to_array

Просто подумал, что я укажу, что это сгиб, поэтому можно использовать 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.

Использует рекурсию. Надеюсь, увидев, насколько это не сложно, ваш страх рекурсии рассеется, как только вы увидите, как это не сложно.

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>

Выравнивает только двумерные массивы:

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

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

Laravel помощник для выравнивания массивов Arr:: расплющить ()

Начиная с PHP версии 7.4, вы можете использовать оператор распространения и объединять массивы. Просто и эффективно.

      $flatArr = array_merge(...$originalArray);

Это решение не является рекурсивным. Обратите внимание, что порядок элементов будет несколько смешанным.

function flatten($array) {
    $return = array();
    while(count($array)) {
        $value = array_shift($array);
        if(is_array($value))
            foreach($value as $sub)
                $array[] = $sub;
        else
            $return[] = $value;
    }
    return $return;
}

Я считаю, что это самое чистое решение без использования каких-либо мутаций или незнакомых классов.

<?php

function flatten($array)
{
    return array_reduce($array, function($acc, $item){
        return array_merge($acc, is_array($item) ? flatten($item) : [$item]);
    }, []);
}


// usage
$array = [1, 2, [3, 4], [5, [6, 7]], 8, 9, 10];
print_r(flatten($array));

Если вы хотите сохранить свои ключи, это решение.

function reduce(array $array) {
    $return = array();
    array_walk_recursive($array, function($value, $key) use (&$return) { $return[$key] = $value; });
    return $return;
}

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

$array = array(
    'sweet' => array(
        'a' => 'apple',
        'b' => 'banana'),
    'sour' => 'lemon'); 
print_r(flatten($fruits));

Выход:

Array
(
    [a] => apple
    [b] => banana
    [sour] => lemon
)

Попробуйте следующую простую функцию:

function _flatten_array($arr) {
  while ($arr) {
    list($key, $value) = each($arr); 
    is_array($value) ? $arr = $value : $out[$key] = $value;
    unset($arr[$key]);
  }
  return (array)$out;
}

Итак, из этого:

array (
  'und' => 
  array (
    'profiles' => 
    array (
      0 => 
      array (
        'commerce_customer_address' => 
        array (
          'und' => 
          array (
            0 => 
            array (
              'first_name' => 'First name',
              'last_name' => 'Last name',
              'thoroughfare' => 'Address 1',
              'premise' => 'Address 2',
              'locality' => 'Town/City',
              'administrative_area' => 'County',
              'postal_code' => 'Postcode',
            ),
          ),
        ),
      ),
    ),
  ),
)

ты получаешь:

array (
  'first_name' => 'First name',
  'last_name' => 'Last name',
  'thoroughfare' => 'Address 1',
  'premise' => 'Address 2',
  'locality' => 'Town/City',
  'administrative_area' => 'County',
  'postal_code' => 'Postcode',
)

Как насчет использования рекурсивного генератора? https://ideone.com/d0TXCg

<?php

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

foreach (iterate($array) as $item) {
    var_dump($item);
};

function iterate($array)
{
    foreach ($array as $item) {
        if (is_array($item)) {
            yield from iterate($item);
        } else {
            yield $item;
        }
    }
}

Вы можете сделать это с вкусностями Узо:

 $result = Arrays::flatten($multidimensional);

Смотрите: здесь

Хитрость заключается в передаче массива источника и назначения по ссылке.

function flatten_array(&$arr, &$dst) {
    if(!isset($dst) || !is_array($dst)) {
        $dst = array();
    }
    if(!is_array($arr)) {
        $dst[] = $arr;
    } else {
        foreach($arr as &$subject) {
            flatten_array($subject, $dst);
        }
    }
}

$recursive = array('1', array('2','3',array('4',array('5','6')),'7',array(array(array('8'),'9'),'10')));
echo "Recursive: \r\n";
print_r($recursive);
$flat = null;
flatten_array($recursive, $flat);

echo "Flat: \r\n";
print_r($flat);

// If you change line 3 to $dst[] = &$arr; , you won't waste memory,
// since all you're doing is copying references, and imploding the array 
// into a string will be both memory efficient and fast:)

echo "String:\r\n";
echo implode(',',$flat);
/**
 * For merging values of a multidimensional array into one 
 *
 * $array = [
 *     0 => [
 *         0 => 'a1',
 *         1 => 'b1',
 *         2 => 'c1',
 *         3 => 'd1'
 *     ],
 *     1 => [
 *         0 => 'a2',
 *         1 => 'b2',
 *         2 => 'c2',
 *     ]
 * ];
 *
 * becomes : 
 *
 * $array = [
 *     0 => 'a1',
 *     1 => 'b1',
 *     2 => 'c1',
 *     3 => 'd1',
 *     4 => 'a2',
 *     5 => 'b2',
 *     6 => 'c2',
 *     
 * ]
 */
array_reduce
(
    $multiArray
    , function ($lastItem, $currentItem) {
        $lastItem = $lastItem ?: array();
        return array_merge($lastItem, array_values($currentItem));
    }
);

Фрагмент Gist

Если вам действительно не нравится рекурсия... попробуйте вместо этого переключиться:)

$a = array(1,2,array(3,4, array(5,6,7), 8), 9);
$o = [];
for ($i=0; $i<count($a); $i++) {
    if (is_array($a[$i])) {
        array_splice($a, $i+1, 0, $a[$i]);
    } else {
        $o[] = $a[$i];
    }
}

Примечание: в этой простой версии это не поддерживает ключи массива.

Любой, кто ищет действительно чистое решение для этого; вот вариант:

$test_array = array(
    array('test' => 0, 0, 0, 0),
    array(0, 0, 'merp' => array('herp' => 'derp'), 0),
    array(0, 0, 0, 0),
    array(0, 0, 0, 0)
);
$it = new RecursiveIteratorIterator(new RecursiveArrayIterator($test_array));
var_dump( iterator_to_array($it, false) ) ; 

Печать

 0 0 0 0 0 0 derp 0 0 0 0 0 0 0 0 0

Ах, без рекурсии!!! даже карта, уменьшение, фильтрация включают в себя рекурсию. Это невозможно, если вы не используете некоторые хакерские способы PHP, как показано ниже. Серьезно, никакой цикл не задействован в приведенном ниже коде, извините, ребята, если он убил хорошие функции массива:):)

$arr=array(1,2,array(3,4, array(5,6,7), 8), 9);
$json=json_encode($arr);
$hacked_json=str_replace(['[',']'],"",$json);
$hacked_array=json_decode('['.$hacked_json.']');
echo "<pre>";
print_r($hacked_array);

выход

Array
(
    [0] => 1
    [1] => 2
    [2] => 3
    [3] => 4
    [4] => 1
    [5] => 2
    [6] => 6
    [7] => 7
    [8] => 8
    [9] => 9
)

работает и для пары "вложенный ключ-значение"

$arr=array(1,2,array(3,4, array(array("s"=>array(1),"w"=>array('q'=>1,'v'=>12)),6,7), 8), 9);
echo "<pre>";
print_r($arr);
$json=json_encode($arr);
$hacked_json=str_replace(['[',']'],"",$json);
$hacked_array=json_decode('['.$hacked_json.']');
echo "<pre>";
print_r(json_decode(json_encode($hacked_array), True));

выход

Array
(
    [0] => 1
    [1] => 2
    [2] => 3
    [3] => 4
    [4] => Array
        (
            [s] => 1
            [w] => Array
                (
                    [q] => 1
                    [v] => 12
                )

        )

    [5] => 6
    [6] => 7
    [7] => 8
    [8] => 9
)

Этого можно добиться, используя array_walk_recursive

$a = array(1,2,array(3,4, array(5,6,7), 8), 9);
array_walk_recursive($a, function($v) use (&$r){$r[]=$v;});
print_r($r);

Рабочий пример: - https://3v4l.org/FpIrG

Эта версия может делать глубокие, мелкие или определенное количество уровней:

/**
 * @param  array|object $array  array of mixed values to flatten
 * @param  int|boolean  $level  0:deep, 1:shallow, 2:2 levels, 3...
 * @return array
 */
function flatten($array, $level = 0) {
    $level = (int) $level;
    $result = array();
    foreach ($array as $i => $v) {
        if (0 <= $level && is_array($v)) {
            $v = flatten($v, $level > 1 ? $level - 1 : 0 - $level);
            $result = array_merge($result, $v);
        } elseif (is_int($i)) {
            $result[] = $v;
        } else {
            $result[$i] = $v; 
        }
    }
    return $result;
}

Потому что код здесь выглядит страшно. Вот функция, которая также преобразует многомерный массив в синтаксис, совместимый с HTML-формой, но который легче читать.

/**
 * Flattens a multi demensional array into a one dimensional
 * to be compatible with hidden html fields.
 *
 * @param array $array
 *  Array in the form:
 *  array(
 *    'a' => array(
 *      'b' => '1'
 *    )
 *  )
 *
 * @return array
 *  Array in the form:
 *  array(
 *    'a[b]' => 1,
 *  )
 */
function flatten_array($array) {
  // Continue until $array is a one-dimensional array.
  $continue = TRUE;
  while ($continue) {
    $continue = FALSE;

    // Walk through top and second level of $array and move 
    // all values in the second level up one level.
    foreach ($array as $key => $value) {
      if (is_array($value)) {
        // Second level found, therefore continue.
        $continue = TRUE;

        // Move each value a level up.
        foreach ($value as $child_key => $child_value) {
          $array[$key . '[' . $child_key . ']'] = $child_value;
        }

        // Remove second level array from top level.
        unset($array[$key]);
      }
    }
  }

  return $array;
}

Для php 5.2

function flatten(array $array) {
    $result = array();

    if (is_array($array)) {
        foreach ($array as $k => $v) {
            if (is_array($v)) {
                $result = array_merge($result, flatten($v));
            } else {
                $result[] = $v;
            }
        }
    }

    return $result;
}

Нерекурсивная реализация, не основанная на ссылках, как было задано, что может быть проще для понимания, чем реализация на основе рекурсии. Может управлять произвольными глубокими многомерными массивами, не может сглаживать ассоциативные массивы. Он работает, сглаживая массив на один уровень за цикл, пока он не станет полностью действительным.

      function array_flatten(): array{
    $result = func_get_args();
    // check all elements of $list are not arrays
    $_is_flat = function (array $list): bool {
        foreach ($list as $val) {
            if (is_array($val)) {
                return false;
            }
        }
        return true;
    };
    do {
        $tmp = [];
        foreach ($result as $val) {
            if (is_array($val)) {
                if (!array_is_list($val)) {
                    throw new \Exception(sprintf("array_flatten can't handle associative arrays: %s", json_encode($val)));
                }
                $tmp = array_merge($tmp, $val);
            } else {
                $tmp[] = $val;
            }
        }
        $result = $tmp;
    } while (!$_is_flat($result));
    return $result;
}

Это случаи, которые он обрабатывает:

      assertEquals(array_flatten(1, 2), $expected = [1, 2], 'array_flatten 1a');
assertEquals(array_flatten([1], [2]), $expected = [1, 2], 'array_flatten 1b');
assertEquals(array_flatten([1], [[2], 3]), $expected = [1, 2, 3], 'array_flatten 1c');
assertEquals(array_flatten(1, [2, 3], [4, 5]), $expected = [1, 2, 3, 4, 5], 'array_flatten 2');
assertEquals(array_flatten(2, 3, [4, 5], [6, 7], 8), $expected = [2, 3, 4, 5, 6, 7, 8], 'array_flatten 3');
assertEquals(array_flatten([2, 3, [4, 5], [6, 7], 8]), $expected = [2, 3, 4, 5, 6, 7, 8], 'array_flatten 4');
assertEquals(array_flatten([2, [3, [4, [5]], [6, [7]], 8]]), $expected = [2, 3, 4, 5, 6, 7, 8], 'array_flatten complex');

Если вы хотите сохранить промежуточные ключи:

      function flattenArray(array &$result, $value, string $key = "")
{
    if (!is_array($value)) {
        $result[$key] = $value;
        return $result;
    }
    foreach ($value as $subKey => $subArray) {
        $newKey = $key !== "" ? $key . "_" . $subKey : $subKey;
        flattenArray($result, $subArray, $newKey);
    }
    return $result;
}

$nestedArray = [
    "name" => "John",
    "pets" => [
        ["id" => 1, "name" => "snooop"],
        ["id" => 2, "name" => "medor"],
    ],
    "job" => ["title" => "developper"],
];

$intermediateResult = [];
$flattened = flattenArray($intermediateResult, $nestedArray);
var_dump($flattened);

Это выведет:

      array(6) {
["name"]=>
  string(4) "John"
        ["pets_0_id"]=>
  int(1)
  ["pets_0_name"]=>
  string(6) "snooop"
        ["pets_1_id"]=>
  int(2)
  ["pets_1_name"]=>
  string(5) "medor"
        ["job_title"]=>
  string(10) "developper"
}

См. https://ideone.com/KXLtzZ#stdout

Возможно ли в PHP сгладить (двух- или многомерный) массив без использования рекурсии или ссылок?

Начиная с PHP 7, не то, что я знаю. Я создал решение, которое я называю array_moonwalk, который использует как ссылки, так и рекурсию для выравнивания и дедупликации многомерного массива и сохраняет глубину самого глубокого узла для каждого дубликата, найденного для реконструкции многомерного массива.

Обрабатывает дедупликацию листовых узлов на разных глубинах без использования foreach и единственный ответ на данный момент, чтобы использовать array_walk, Не проверено на производительность. YMMV.

Мне нужно было представить многомерный массив PHP в формате ввода HTML.

$test = [
    'a' => [
        'b' => [
            'c' => ['a', 'b']
        ]
    ],
    'b' => 'c',
    'c' => [
        'd' => 'e'
    ]
];

$flatten = function ($input, $parent = []) use (&$flatten) {
    $return = [];

    foreach ($input as $k => $v) {
        if (is_array($v)) {
            $return = array_merge($return, $flatten($v, array_merge($parent, [$k])));
        } else {
            if ($parent) {
                $key = implode('][', $parent) . '][' . $k . ']';

                if (substr_count($key, ']') != substr_count($key, '[')) {
                    $key = preg_replace('/\]/', '', $key, 1);
                }
            } else {
                $key = $k;
            }           

            $return[$key] = $v;
        }
    }

    return $return;
};

die(var_dump( $flatten($test) ));

array(4) {
  ["a[b][c][0]"]=>
  string(1) "a"
  ["a[b][c][1]"]=>
  string(1) "b"
  ["b"]=>
  string(1) "c"
  ["c[d]"]=>
  string(1) "e"
}
Другие вопросы по тегам