Transparently flatten an array
Reading this question Merge and group by several arrays i got the following idea: when working with multilevel arrays, with possibly repeating keys, it would be practical to have a function that would iterate such an array as it were flat, like
foreach(flatten($deepArray) as $key => $val)....
any ideas how to write flatten()
? Is there any standard soluti开发者_开发百科on?
(note that flatten()
cannot simply return a new array because of repeating keys).
Example usage of RecursiveArrayIterator
$array = array(
0 => 'a',
1 => array('subA','subB',array(0 => 'subsubA', 1 => 'subsubB', 2 => array(0 => 'deepA', 1 => 'deepB'))),
2 => 'b',
3 => array('subA','subB','subC'),
4 => 'c'
);
foreach (return new RecursiveIteratorIterator(new RecursiveArrayIterator($array))
as $key => $val) {
printf(
'%s: %s' . "\n",
$key, $val
);
}
/* Output:
0: a
0: subA
1: subB
0: subsubA
1: subsubB
0: deepA
1: deepB
2: b
0: subA
1: subB
2: subC
4: c
*/
extending RecursiveIteratorIterator
to return the current key-stack
class MyRecursiveIteratorIterator extends RecursiveIteratorIterator
{
public function key() {
return json_encode($this->getKeyStack());
}
public function getKeyStack() {
$result = array();
for ($depth = 0, $lim = $this->getDepth(); $depth < $lim; $depth += 1) {
$result[] = $this->getSubIterator($depth)->key();
}
$result[] = parent::key();
return $result;
}
}
foreach ($it = new MyRecursiveIteratorIterator(new RecursiveArrayIterator($array))
as $key => $val) {
printf('%s (%s): %s' . "\n", implode('.', $it->getKeyStack()), $key, $val);
}
/* Output:
0 ([0]): a
1.0 ([1,0]): subA
1.1 ([1,1]): subB
1.2.0 ([1,2,0]): subsubA
1.2.1 ([1,2,1]): subsubB
1.2.2.0 ([1,2,2,0]): deepA
1.2.2.1 ([1,2,2,1]): deepB
2 ([2]): b
3.0 ([3,0]): subA
3.1 ([3,1]): subB
3.2 ([3,2]): subC
4 ([4]): c
*/
Yet another version, using no RecursiveArrayIterator this time:
function flatten(array $array = array(), $keyStack = array(), $result = array()) {
foreach ($array as $key => $value) {
$keyStack[] = $key;
if (is_array($value)) {
$result = flatten($value, $keyStack, $result);
}
else {
$result[] = array(
'keys' => $keyStack,
'value' => $value
);
}
array_pop($keyStack);
}
return $result;
}
foreach (flatten($array) as $element) {
printf(
'%s: %s (depth: %s)' . "\n",
implode('.', $element['keys']),
$element['value'],
sizeof($element['keys'])
);
}
/*
0: a (depth: 1)
1.0: subA (depth: 2)
1.1: subB (depth: 2)
1.2.0: subsubA (depth: 3)
1.2.1: subsubB (depth: 3)
1.2.2.0: deepA (depth: 4)
1.2.2.1: deepB (depth: 4)
2: b (depth: 1)
3.0: subA (depth: 2)
3.1: subB (depth: 2)
3.2: subC (depth: 2)
4: c (depth: 1)
*/
You can also write a simple traversal function:
function flatten($node, $fn, $keys = array()) {
if (! is_array($node)) {
$fn($node, $keys);
} else {
foreach ($node as $k => $v) {
$new_keys = $keys;
$new_keys[] = $k;
flatten($v, $fn, $new_keys);
}
}
}
$array = array(
0 => 'a',
1 => array('subA','subB',array(0 => 'subsubA', 1 => 'subsubB', 2 => array(0 => 'deepA', 1 => 'deepB'))),
2 => 'b',
3 => array('subA','subB','subC'),
4 => 'c'
);
// will output: a subA subB subsubA subsubB deepA deepB b subA subB subC c
flatten($array, function($v, $k) {
echo $v . ' ';
});
If you don't want to call it each time with another function as a parameter, I've also written an adapter that will return an array:
function flatten_array($node) {
$acc = array();
flatten($node, function($node, $keys) use (&$acc) {
$acc[implode('.', $keys)] = $node;
});
return $acc;
}
// will spit out the same output as that in Yoshi's answer:
foreach (flatten_array($array) as $k => $v) {
echo $k .' => ' . $v . "\n";
}
Notes:
- array_walk_recursive cannot be used/is not the same thing, as it skips over the keys that hold an array
- I've written my examples with anonymous functions; if your PHP isn't new enough, you have to name the functions and call them with
call_user_func
.
精彩评论