Можно ли в PHP сгладить (двух- или многомерный) массив без использования рекурсии или ссылок?
Меня интересуют только значения, так что ключи можно игнорировать, я думаю, в строках array_map()
и array_values()
.
Можно ли в PHP сгладить (двух- или многомерный) массив без использования рекурсии или ссылок?
Меня интересуют только значения, так что ключи можно игнорировать, я думаю, в строках array_map()
и array_values()
.
Ответы:
Вы можете использовать стандартную библиотеку 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
iterator_to_array($it, false)
избегает необходимости в foreach.
function flatten($arr){ $it = new RecursiveIteratorIterator(new RecursiveArrayIterator($arr)); return iterator_to_array($it, true); }
надеюсь, это поможет другим.
Начиная с PHP 5.3, самое короткое решение кажется array_walk_recursive()
с новым синтаксисом замыканий:
function flatten(array $array) {
$return = array();
array_walk_recursive($array, function($a) use (&$return) { $return[] = $a; });
return $return;
}
use
синтаксис для этой работы, array_walk_recursive
поскольку он не будет принимать необязательный $userdata
параметр по ссылке
Решение для двумерного массива
Пожалуйста, попробуйте это:
$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
call_user_func_array('array_merge', [])
(обратите внимание на пустой массив) возвращает ноль и вызывает предупреждение об ошибке php. Это отличное решение, если вы точно знаете, что ваш массив не будет пустым, но многие не могут этого сделать.
$result = $array ?call_user_func_array('array_merge', $array) : [];
В PHP 5.6 и выше вы можете сгладить двумерные массивы с помощью array_merge
после распаковки внешнего массива с помощью ...
оператора. Код прост и понятен.
array_merge(...$a);
Это работает и с коллекцией ассоциативных массивов.
$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
)
Обновление: на основе комментария @MohamedGharib
Это выдаст ошибку, если внешний массив пуст, так как array_merge
будет вызван с нулевыми аргументами. Этого можно избежать, добавив пустой массив в качестве первого аргумента.
array_merge([], ...$a);
array_merge([], ...$a);
Чтобы сгладить без рекурсии (как вы просили), вы можете использовать стек . Естественно, вы можете поместить это в функцию своего собственного как 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
котором, я думаю, это хорошая идея.
if(!empty($value)){$flat[] = $value}
внутри оператора else, чтобы предотвратить добавление пустого в массив результатов. Потрясающая функция!
Простой и однострочный ответ.
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)
Цитируя озабоченность / проблему Найджела Алдертона :
Просто чтобы прояснить, это сохраняет ключи (даже числовые), поэтому значения с одинаковым ключом теряются. Например
$array = ['a',['b','c']]
становитсяArray ([0] => b, [1] => c )
. Это'a'
потеряно, потому что'b'
также имеет ключ0
Цитирование Svish ответа «S:
Просто добавьте false в качестве второго параметра
($use_keys)
к вызову iterator_to_array
$array = ['a',['b','c']]
становится Array ([0] => b, [1] => c )
. Это 'a'
потеряно, потому что 'b'
также имеет ключ 0
.
false
качестве второго параметра ( $use_keys
) к iterator_to_array
вызову.
Использует рекурсию. Надеюсь, увидев, насколько он не сложный, ваш страх рекурсии рассеется, как только вы увидите, насколько он не сложен.
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>
Просто подумал, что укажу, что это фолд, поэтому можно использовать 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.
fold
его на 4 уровня, или fold . fold
получить 3 уровня, или fold . fold . fold
2 уровня и т. д. Это также предотвращает скрытие ошибок; например. если я хочу сгладить массив 5D, но мне дан массив 4D, ошибка немедленно сработает.
$concat
, я думаю, вы должны просто назвать это $flatten
. array_merge
является php-эквивалентом concat. Я пытался получить array_concat
добавлен в качестве псевдонима array_merge
.
Это решение не является рекурсивным. Обратите внимание, что порядок элементов будет несколько смешанным.
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;
}
shifting
значение из массива и добавление его в конец не имеет особого смысла. Я думаю, что вы хотели array_merge()
вместо этого?
Я считаю, что это самое чистое решение без использования каких-либо мутаций или незнакомых классов.
<?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 _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',
)
Хитрость заключается в передаче массива источника и назначения по ссылке.
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));
}
);
Вы можете сделать это с вкусностями Узо :
$result = Arrays::flatten($multidimensional);
Смотрите: здесь
Если вам действительно не нравится рекурсия ... попробуйте вместо этого переключиться :)
$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];
}
}
Примечание: в этой простой версии это не поддерживает ключи массива.
continue
, это несколько быстрее.
Как насчет использования рекурсивного генератора? 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;
}
}
}
Для 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;
}
Эта версия может делать глубокие, мелкие или определенное количество уровней:
/**
* @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;
}
Это может быть достигнуто с помощью 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
Это мое решение, используя ссылку:
function arrayFlatten($array_in, &$array_out){
if(is_array($array_in)){
foreach ($array_in as $element){
arrayFlatten($element, $array_out);
}
}
else{
$array_out[] = $array_in;
}
}
$arr1 = array('1', '2', array(array(array('3'), '4', '5')), array(array('6')));
arrayFlatten($arr1, $arr2);
echo "<pre>";
print_r($arr2);
echo "</pre>";
<?php
//recursive solution
//test array
$nested_array = [[1,2,[3]],4,[5],[[[6,[7=>[7,8,9,10]]]]]];
/*-----------------------------------------
function call and return result to an array
------------------------------------------*/
$index_count = 1;
$flatered_array = array();
$flatered_array = flat_array($nested_array, $index_count);
/*-----------------------------------------
Print Result
-----------------------------------------*/
echo "<pre>";
print_r($flatered_array);
/*-----------------------------------------
function to flaten an array
-----------------------------------------*/
function flat_array($nested_array, & $index_count, & $flatered_array) {
foreach($nested_array AS $key=>$val) {
if(is_array($val)) {
flat_array($val, $index_count, $flatered_array);
}
else {
$flatered_array[$index_count] = $val;
++$index_count;
}
}
return $flatered_array;
}
?>
Любой, кто ищет действительно чистое решение для этого; вот вариант:
$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
Просто выкладываю еще какое-то решение)
function flatMultidimensionalArray(array &$_arr): array
{
$result = [];
\array_walk_recursive($_arr, static function (&$value, &$key) use (&$result) {
$result[$key] = $value;
});
return $result;
}
Если вы хотите сохранить ваши ключи, это решение.
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
)
Мне нужно было представить многомерный массив 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"
}
$var['a']['b']['c'][0] = 'a'; ...
.
Если у вас есть массив объектов и вы хотите сгладить его с помощью узла, просто используйте эту функцию:
function objectArray_flatten($array,$childField) {
$result = array();
foreach ($array as $node)
{
$result[] = $node;
if(isset($node->$childField))
{
$result = array_merge(
$result,
objectArray_flatten($node->$childField,$childField)
);
unset($node->$childField);
}
}
return $result;
}