¿Es posible, en PHP, aplanar una matriz (bi / multi) dimensional sin usar recursividad o referencias?
Solo me interesan los valores para que las claves puedan ignorarse, estoy pensando en las líneas de array_map()
y array_values()
.
¿Es posible, en PHP, aplanar una matriz (bi / multi) dimensional sin usar recursividad o referencias?
Solo me interesan los valores para que las claves puedan ignorarse, estoy pensando en las líneas de array_map()
y array_values()
.
Respuestas:
Puede usar la Biblioteca PHP estándar (SPL) para "ocultar" la recursividad.
$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, " ";
}
huellas dactilares
1 2 3 4 5 6 7 8 9
iterator_to_array($it, false)
evita la necesidad del foreach.
function flatten($arr){ $it = new RecursiveIteratorIterator(new RecursiveArrayIterator($arr)); return iterator_to_array($it, true); }
espero que esto ayude a otros.
A partir de PHP 5.3, la solución más corta parece ser array_walk_recursive()
con la nueva sintaxis de cierres:
function flatten(array $array) {
$return = array();
array_walk_recursive($array, function($a) use (&$return) { $return[] = $a; });
return $return;
}
use
sintaxis para que funcione, array_walk_recursive
ya que no aceptará el $userdata
parámetro opcional por referencia
Solución para arreglo bidimensional
Por favor intente esto:
$array = your array
$result = call_user_func_array('array_merge', $array);
echo "<pre>";
print_r($result);
EDITAR: 21-ago-13
Aquí está la solución que funciona para una matriz multidimensional:
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);
Ref: http://php.net/manual/en/function.call-user-func-array.php
call_user_func_array('array_merge', [])
(observe la matriz vacía) devuelve nulo y desencadena un error de advertencia de php. Es una solución ingeniosa si sabe con certeza que su matriz no estará vacía, pero esa no es una suposición común que muchos pueden hacer.
$result = $array ?call_user_func_array('array_merge', $array) : [];
En PHP 5.6 y superior, puede aplanar matrices bidimensionales array_merge
después de desempaquetar la matriz externa con el ...
operador. El código es simple y claro.
array_merge(...$a);
Esto también funciona con la colección de matrices asociativas.
$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
)
Pero no funciona cuando la matriz externa tiene claves no numéricas. En ese caso, array_values
primero deberá llamar .
$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
)
Actualización: Basado en el comentario de @MohamedGharib
Esto arrojará un error si la matriz externa está vacía, ya array_merge
que se llamaría con cero argumentos. Se puede evitar agregando una matriz vacía como primer argumento.
array_merge([], ...$a);
array_merge([], ...$a);
Para aplanar sin recursión (como ha pedido), puede usar una pila . Naturalmente, puede poner esto en una función propia array_flatten
. La siguiente es una versión que funciona sin teclas :.
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;
}
Los elementos se procesan en su orden. Como los subelementos se moverán sobre la pila, se procesarán a continuación.
También es posible tener en cuenta las claves, sin embargo, necesitará una estrategia diferente para manejar la pila. Eso es necesario porque necesita lidiar con posibles claves duplicadas en los sub-arrays. Una respuesta similar en una pregunta relacionada: PHP Recorre la matriz multidimensional mientras conserva las claves
No estoy específicamente seguro, pero lo he probado en el pasado: RecurisiveIterator
utiliza la recursividad, por lo que depende de lo que realmente necesita. También debería ser posible crear un iterador recursivo basado en pilas:
foreach(new FlatRecursiveArrayIterator($array) as $key => $value)
{
echo "** ($key) $value\n";
}
No llegué hasta ahora, para implementar la pila en base a la RecursiveIterator
cual creo que es una buena idea.
if(!empty($value)){$flat[] = $value}
dentro de la instrucción else para evitar que se agregue vacío a la matriz de resultados. Función impresionante!
Respuesta directa y de una sola línea .
function flatten_array(array $array)
{
return iterator_to_array(
new \RecursiveIteratorIterator(new \RecursiveArrayIterator($array)));
}
Uso:
$array = [
'name' => 'Allen Linatoc',
'profile' => [
'age' => 21,
'favourite_games' => [ 'Call of Duty', 'Titanfall', 'Far Cry' ]
]
];
print_r( flatten_array($array) );
Salida (en PsySH):
Array
(
[name] => Allen Linatoc
[age] => 21
[0] => Call of Duty
[1] => Titanfall
[2] => Far Cry
)
Ahora depende de ti cómo manejarás las llaves. Salud
EDITAR (2017-03-01)
Citando la preocupación / problema de Nigel Alderton :
Solo para aclarar, esto conserva las claves (incluso las numéricas) para que los valores que tienen la misma clave se pierdan. Por ejemplo se
$array = ['a',['b','c']]
convierteArray ([0] => b, [1] => c )
. El'a'
se pierde porque'b'
también tiene una clave de0
Citando la respuesta de Svish :
Simplemente agregue falso como segundo parámetro
($use_keys)
a la llamada iterator_to_array
$array = ['a',['b','c']]
convierte Array ([0] => b, [1] => c )
. El 'a'
se pierde porque 'b'
también tiene una clave de 0
.
false
como segundo parámetro ( $use_keys
) a la iterator_to_array
llamada.
Utiliza la recursividad. Con suerte, al ver lo poco complejo que es, su miedo a la recurrencia se disipará una vez que vea lo poco complejo que es.
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>';
Salida:
<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>
Solo pensé en señalar que esto es un pliegue, por lo que se puede usar array_reduce:
array_reduce($my_array, 'array_merge', array());
EDITAR: Tenga en cuenta que esto se puede componer para aplanar cualquier número de niveles. Podemos hacer esto de varias maneras:
// 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))));
Por supuesto, también podríamos usar bucles, pero la pregunta pide una función de combinador a lo largo de las líneas de array_map o array_values.
fold
colocarla en 4 niveles, o fold . fold
obtener 3 niveles, o fold . fold . fold
obtener 2 niveles, etc. Esto también evita que los errores se oculten; p.ej. si quiero aplanar una matriz 5D pero me dan una matriz 4D, el error se activará de inmediato.
$concat
, creo que deberías llamarlo $flatten
. array_merge
es el equivalente php de concat. Traté de ser array_concat
agregado como un alias para array_merge
.
Esta solución no es recursiva. Tenga en cuenta que el orden de los elementos será algo mixto.
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
el valor de la matriz y agregarlo nuevamente al final no tiene mucho sentido. Supongo que querías hacerlo en su array_merge()
lugar?
Creo que esta es la solución más limpia sin usar mutaciones ni clases desconocidas.
<?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));
Pruebe la siguiente función simple:
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;
}
Entonces de esto:
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',
),
),
),
),
),
),
)
usted obtiene:
array (
'first_name' => 'First name',
'last_name' => 'Last name',
'thoroughfare' => 'Address 1',
'premise' => 'Address 2',
'locality' => 'Town/City',
'administrative_area' => 'County',
'postal_code' => 'Postcode',
)
El truco es pasar las matrices de origen y de destino por referencia.
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));
}
);
Si realmente no te gusta una recursión ... prueba a cambiar :)
$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];
}
}
Nota: en esta versión simple, esto no admite claves de matriz.
continue
, es algo más rápido.
¿Qué tal usar un generador recursivo? 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;
}
}
}
Para 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;
}
Esta versión puede hacer niveles profundos, superficiales o específicos:
/**
* @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;
}
Porque el código aquí parece aterrador. Aquí hay una función que también convertirá una matriz multidimensional en una sintaxis compatible con formularios html, pero que es más fácil de leer.
/**
* 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;
}
Esto se puede lograr usando 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);
Ejemplo de trabajo: - https://3v4l.org/FpIrG
Esta es mi solución, usando una referencia:
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;
}
?>
Cualquiera que busque una solución realmente limpia para esto; Aquí hay una opción:
$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) ) ;
Huellas dactilares
0 0 0 0 0 0 derp 0 0 0 0 0 0 0 0 0
Solo publicando una solución más)
function flatMultidimensionalArray(array &$_arr): array
{
$result = [];
\array_walk_recursive($_arr, static function (&$value, &$key) use (&$result) {
$result[$key] = $value;
});
return $result;
}
Si desea conservar también sus claves, esa es la solución.
function reduce(array $array) {
$return = array();
array_walk_recursive($array, function($value, $key) use (&$return) { $return[$key] = $value; });
return $return;
}
Desafortunadamente, solo genera matrices anidadas finales, sin claves intermedias. Entonces para el siguiente ejemplo:
$array = array(
'sweet' => array(
'a' => 'apple',
'b' => 'banana'),
'sour' => 'lemon');
print_r(flatten($fruits));
Salida es:
Array
(
[a] => apple
[b] => banana
[sour] => lemon
)
Necesitaba representar la matriz multidimensional de PHP en formato de entrada 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'; ...
.
Si tiene una matriz de objetos y desea aplanarla con un nodo, simplemente use esta función:
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;
}