Difference between array_map, array_walk and array_filter
The idea of mapping an function to array of data comes from functional programming. You shouldn't think about array_map
as a foreach
loop that calls a function on each element of the array (even though that's how it's implemented). It should be thought of as applying the function to each element in the array independently.
In theory such things as function mapping can be done in parallel since the function being applied to the data should ONLY affect the data and NOT the global state. This is because an array_map
could choose any order in which to apply the function to the items in (even though in PHP it doesn't).
array_walk
on the other hand it the exact opposite approach to handling arrays of data. Instead of handling each item separately, it uses a state (&$userdata
) and can edit the item in place (much like a foreach loop). Since each time an item has the $funcname
applied to it, it could change the global state of the program and therefor requires a single correct way of processing the items.
Back in PHP land, array_map
and array_walk
are almost identical except array_walk
gives you more control over the iteration of data and is normally used to "change" the data in-place vs returning a new "changed" array.
array_filter
is really an application of array_walk
(or array_reduce
) and it more-or-less just provided for convenience.
From the documentation,
bool array_walk ( array &$array , callback $funcname [, mixed $userdata ] ) <-return bool
array_walk takes an array and a function F
and modifies it by replacing every element x with F(x)
.
array array_map ( callback $callback , array $arr1 [, array $... ] )<-return array
array_map does the exact same thing except that instead of modifying in-place it will return a new array with the transformed elements.
array array_filter ( array $input [, callback $callback ] )<-return array
array_filter with function F
, instead of transforming the elements, will remove any elements for which F(x)
is not true
- Changing Values:
array_map
cannot change the values inside input array(s) whilearray_walk
can; in particular,array_map
never changes its arguments.
- Array Keys Access:
array_map
cannot operate with the array keys,array_walk
can.
- Return Value:
array_map
returns a new array,array_walk
only returnstrue
. Hence, if you don't want to create an array as a result of traversing one array, you should usearray_walk
.
- Iterating Multiple Arrays:
array_map
also can receive an arbitrary number of arrays and it can iterate over them in parallel, whilearray_walk
operates only on one.
- Passing Arbitrary Data to Callback:
array_walk
can receive an extra arbitrary parameter to pass to the callback. This mostly irrelevant since PHP 5.3 (when anonymous functions were introduced).
- Length of Returned Array:
- The resulting array of
array_map
has the same length as that of the largest input array;array_walk
does not return an array but at the same time it cannot alter the number of elements of original array;array_filter
picks only a subset of the elements of the array according to a filtering function. It does preserve the keys.
- The resulting array of
Example:
<pre>
<?php
$origarray1 = array(2.4, 2.6, 3.5);
$origarray2 = array(2.4, 2.6, 3.5);
print_r(array_map('floor', $origarray1)); // $origarray1 stays the same
// changes $origarray2
array_walk($origarray2, function (&$v, $k) { $v = floor($v); });
print_r($origarray2);
// this is a more proper use of array_walk
array_walk($origarray1, function ($v, $k) { echo "$k => $v", "\n"; });
// array_map accepts several arrays
print_r(
array_map(function ($a, $b) { return $a * $b; }, $origarray1, $origarray2)
);
// select only elements that are > 2.5
print_r(
array_filter($origarray1, function ($a) { return $a > 2.5; })
);
?>
</pre>
Result:
Array
(
[0] => 2
[1] => 2
[2] => 3
)
Array
(
[0] => 2
[1] => 2
[2] => 3
)
0 => 2.4
1 => 2.6
2 => 3.5
Array
(
[0] => 4.8
[1] => 5.2
[2] => 10.5
)
Array
(
[1] => 2.6
[2] => 3.5
)
The other answers demonstrate the difference between array_walk (in-place modification) and array_map (return modified copy) quite well. However, they don't really mention array_reduce, which is an illuminating way to understand array_map and array_filter.
The array_reduce function takes an array, a two-argument function and an 'accumulator', like this:
array_reduce(array('a', 'b', 'c', 'd'),
'my_function',
$accumulator)
The array's elements are combined with the accumulator one at a time, using the given function. The result of the above call is the same as doing this:
my_function(
my_function(
my_function(
my_function(
$accumulator,
'a'),
'b'),
'c'),
'd')
If you prefer to think in terms of loops, it's like doing the following (I've actually used this as a fallback when array_reduce wasn't available):
function array_reduce($array, $function, $accumulator) {
foreach ($array as $element) {
$accumulator = $function($accumulator, $element);
}
return $accumulator;
}
This looping version makes it clear why I've called the third argument an 'accumulator': we can use it to accumulate results through each iteration.
So what does this have to do with array_map and array_filter? It turns out that they're both a particular kind of array_reduce. We can implement them like this:
array_map($function, $array) === array_reduce($array, $MAP, array())
array_filter($array, $function) === array_reduce($array, $FILTER, array())
Ignore the fact that array_map and array_filter take their arguments in a different order; that's just another quirk of PHP. The important point is that the right-hand-side is identical except for the functions I've called $MAP and $FILTER. So, what do they look like?
$MAP = function($accumulator, $element) {
$accumulator[] = $function($element);
return $accumulator;
};
$FILTER = function($accumulator, $element) {
if ($function($element)) $accumulator[] = $element;
return $accumulator;
};
As you can see, both functions take in the $accumulator and return it again. There are two differences in these functions:
- $MAP will always append to $accumulator, but $FILTER will only do so if $function($element) is TRUE.
- $FILTER appends the original element, but $MAP appends $function($element).
Note that this is far from useless trivia; we can use it to make our algorithms more efficient!
We can often see code like these two examples:
// Transform the valid inputs
array_map('transform', array_filter($inputs, 'valid'))
// Get all numeric IDs
array_filter(array_map('get_id', $inputs), 'is_numeric')
Using array_map and array_filter instead of loops makes these examples look quite nice. However, it can be very inefficient if $inputs is large, since the first call (map or filter) will traverse $inputs and build an intermediate array. This intermediate array is passed straight into the second call, which will traverse the whole thing again, then the intermediate array will need to be garbage collected.
We can get rid of this intermediate array by exploiting the fact that array_map and array_filter are both examples of array_reduce. By combining them, we only have to traverse $inputs once in each example:
// Transform valid inputs
array_reduce($inputs,
function($accumulator, $element) {
if (valid($element)) $accumulator[] = transform($element);
return $accumulator;
},
array())
// Get all numeric IDs
array_reduce($inputs,
function($accumulator, $element) {
$id = get_id($element);
if (is_numeric($id)) $accumulator[] = $id;
return $accumulator;
},
array())
NOTE: My implementations of array_map and array_filter above won't behave exactly like PHP's, since my array_map can only handle one array at a time and my array_filter won't use "empty" as its default $function. Also, neither will preserve keys.
It's not difficult to make them behave like PHP's, but I felt that these complications would make the core idea harder to spot.