# Array flatten with recursion

I'd been using this form for a while now with the functional methods map, filter, reduce:

// For example...
$result = array_reduce(array_map(function($value) {
return explode('-', $value); },$input), function($carry,$item) {
return array_merge($carry,$item);
}, array());


The array_reduce() part, where I need to collapse depth in the result. So I was thinking about this, and this is what I came up with. It actually works as a callback to array_reduce() too:

/**
* Flatten a multi-dimensional array to a single-level array,
* sorting the entries in order to depth first.
*
* @param array $carry Array to initialize, will be the subject if the only argument passed. * @param mixed$subject Optional Array or value that has data in potential multi-dimensions.
*
* @return array
*/
function array_flatten(?array $carry = [],$subject = null): array
{
return array_reduce((array) ($subject ??$carry), function(array $carry,$item) {
return is_array($item) ? array_flatten($carry, $item) : array_merge($carry, (array) $item); },$subject !== null ? (array) $carry : []); // Don't merge onto the$subject...
}


https://3v4l.org/J1NeJ

Thing is, the way it works, I think it literally goes one level further than there are levels to traverse. The reason I think this is because the array_merge() is getting passed a "boxed" element. So the way I'm adding to the array is to box/unbox an array wrapper so it can be merged.

That seems like the most amount of extra effort for one little thing; it happens to every non-array it comes across. Questioning whether I should use it.

It can be called like array_reduce():

array_reduce([1,2,[3,[4]],5], 'array_flatten');


Or more likely:

array_flatten([1,2,[3,[4]],5]);


Is this something I should avoid?

array_walk_recursive() traverses your "leaf nodes" for you in one step:

Code: (Demo)

//$multi=[1,2,[3,[4]],5];$multi=[array('A',
array('B',
array('CC','CCCC','CCCCCC'),
array('DDD','EFG','HIJ','LMNOP')
)
),
array('2'),
'b',
array(array(),array(),array())];
array_walk_recursive($multi,function($v)use(&$flat){$flat[]=$v;}); var_export($flat);


Output:

array (
0 => 'A',
1 => 'B',
2 => 'CC',
3 => 'CCCC',
4 => 'CCCCCC',
5 => 'DDD',
6 => 'EFG',
7 => 'HIJ',
8 => 'LMNOP',
9 => '2',
10 => 'b',
)

• Yeah, but passing and using a referenced array in functional programming is entirely beside the point. That's simply a shorthand-ish way of writing a recursive for loop without the for syntax. – Jared Farrish Oct 1 '17 at 14:26
• – Jared Farrish Oct 1 '17 at 14:56

I don't see the benefit of being able to call array_flatten() from array_reduce(). If there is an important use, please let me know as I'd like to be educated on this point. My code to follow cannot be called like this: array_reduce([1,2,[3,[4]],5], 'array_flatten'); as yours was designed to do.

I have made a few modifications and have a php<7 friendly version to offer... more specifically, I've omitted the type (and nullable) declarations and null coalescing operator so that PHP<7 versions can use the snippet. I'm not suggesting that your type declarations are a bad idea, I only mean to make the function more usable to a wider audience.

Code: (Demo)

$multi=[['A',['B',['CC','CCCC','CCCCCC'],['DDD','EFG','HIJ','LMNOP']]],['2'],'b',[[],[],[]]]; function array_flatten($array,$accumulator=[]){ return array_reduce($array,function($carry,$item){
return is_array($item) ? array_flatten($item,$carry) : array_merge($carry,[$item]); },$accumulator);
}
var_export(array_flatten($multi));  • There isn't a benefit, it's just that it fits the same signature. That was for the recursion. – Jared Farrish Oct 2 '17 at 13:30 If you're just working on arrays this might satisfy your functional urge for selecting out of a deep data structure: function recursive_select($data,
callable $callback,$flag = RecursiveIteratorIterator::SELF_FIRST,
$preserve_keys = FALSE ) { return iterator_to_array(new CallbackFilterIterator( new RecursiveIteratorIterator( new RecursiveArrayIterator($data),
$flag ),$callback
), $preserve_keys); }  Usage here: https://3v4l.org/TDIvu $multi=[['A',['B',['CC','CCCC','CCCCCC'],['DDD','EFG','HIJ','LMNOP']]],['2'],'b',[[],[],[]]];
$array = array(1,2,[3,4],5,'six',array(array('78',9)),10);$multi_output = recursive_select($multi, function ($value, $key,$iterator) : bool {
return is_string($value); });$array_output = recursive_select($array, function ($value, $key,$iterator) : bool {
return is_numeric($value) || is_string($value);
});

var_dump($multi_output,$array_output);


Output:

Array
(
[0] => A
[1] => B
[2] => CC
[3] => CCCC
[4] => CCCCCC
[5] => DDD
[6] => EFG
[7] => HIJ
[8] => LMNOP
[9] => 2
[10] => b
)
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
[4] => 5
[5] => six
[6] => 78
[7] => 9
[8] => 10
)


If you need something that works like that on objects and arrays I've got a version for that, its not a quite a one liner though because behaviour changed subtly in RecursiveArrayIterator between PHP 5 and 7 despite what the PHP documentation says on this :|

The $iterator parameter has a method on it called getDepth() which can be used for fetching from a particular level within the comparison function • Sorry for the late response. I like it, with the only quibble I have being that the $callback requires a signature that embeds a requirement the function itself doesn't have, which makes it feel weird to me. So it really feels like syntactic glue. – Jared Farrish Dec 3 '17 at 14:38
• The signature of the callable of recursive_select is compatible with array_filter rather than array_reduce, if you wanted to accumulate I'd suggest filtering with recursive_select and then accumulating after, filtering and accumulating in the same loop with array_reduce is closer to common foreach behaviour imo, where as the result of array_filter/recursive_select forces you to name your selection, which means it's easy to do multiple operations on it, whilst inside array_reduce accumulation and filtering is tightly coupled and hard to pass outside the function in a non accumulated fashion – arcanine Dec 11 '17 at 13:24
• That said, there can always be other things to consider like performance , or you may know you will not need access to the filtered data ever again, context trumps all if you're not ignorant to the trade offs being made, personally I err on the side of profiling first and being ready for code mutations that will need to occur in the future – arcanine Dec 11 '17 at 13:45