Другие ответы достаточно хорошо демонстрируют разницу между array_walk (модификация на месте) и array_map (вернуть измененную копию). Тем не менее, они на самом деле не упоминают array_reduce, которая является отличным способом понять array_map и array_filter.
Функция array_reduce принимает массив, функцию с двумя аргументами и «аккумулятор», например:
array_reduce(array('a', 'b', 'c', 'd'),
'my_function',
$accumulator)
Элементы массива объединяются с аккумулятором по одному, используя данную функцию. Результат вышеприведенного вызова такой же, как при выполнении этого:
my_function(
my_function(
my_function(
my_function(
$accumulator,
'a'),
'b'),
'c'),
'd')
Если вы предпочитаете думать с точки зрения циклов, это все равно что делать следующее (я фактически использовал это как запасной вариант, когда array_reduce не был доступен):
function array_reduce($array, $function, $accumulator) {
foreach ($array as $element) {
$accumulator = $function($accumulator, $element);
}
return $accumulator;
}
Эта циклическая версия проясняет, почему я назвал третий аргумент «аккумулятором»: мы можем использовать его для накопления результатов на каждой итерации.
Так что же это нужно делать с array_map и array_filter? Оказывается, они оба особого вида array_reduce. Мы можем реализовать их так:
array_map($function, $array) === array_reduce($array, $MAP, array())
array_filter($array, $function) === array_reduce($array, $FILTER, array())
Не обращайте внимания на тот факт, что array_map и array_filter принимают свои аргументы в другом порядке; это просто еще одна особенность PHP. Важным моментом является то, что правая часть идентична, за исключением функций, которые я назвал $ MAP и $ FILTER. Итак, как они выглядят?
$MAP = function($accumulator, $element) {
$accumulator[] = $function($element);
return $accumulator;
};
$FILTER = function($accumulator, $element) {
if ($function($element)) $accumulator[] = $element;
return $accumulator;
};
Как видите, обе функции берут $ аккумулятор и возвращают его снова. Есть два различия в этих функциях:
- $ MAP всегда будет добавляться к $ накопителю, но $ FILTER будет делать это только в том случае, если $ function ($ element) имеет значение TRUE.
- $ FILTER добавляет исходный элемент, но $ MAP добавляет функцию $ ($ element).
Обратите внимание, что это далеко не бесполезные мелочи; мы можем использовать его, чтобы сделать наши алгоритмы более эффективными!
Мы часто видим код, подобный этим двум примерам:
// Transform the valid inputs
array_map('transform', array_filter($inputs, 'valid'))
// Get all numeric IDs
array_filter(array_map('get_id', $inputs), 'is_numeric')
Использование array_map и array_filter вместо циклов делает эти примеры довольно привлекательными. Однако это может быть очень неэффективно, если $ input велико, так как первый вызов (map или filter) будет проходить по $ input и создавать промежуточный массив. Этот промежуточный массив передается прямо во второй вызов, который снова будет проходить через все это, затем промежуточный массив нужно будет собрать мусором.
Мы можем избавиться от этого промежуточного массива, используя тот факт, что array_map и array_filter являются примерами array_reduce. Объединив их, нам нужно пройти через входные данные только один раз в каждом примере:
// 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())
ПРИМЕЧАНИЕ. Мои реализации array_map и array_filter выше не будут вести себя точно так же, как PHP, поскольку мой array_map может обрабатывать только один массив за раз, а мой array_filter не будет использовать «empty» в качестве функции $ по умолчанию. Также ни один из них не сохранит ключи.
Не трудно заставить их вести себя как PHP, но я чувствовал, что эти сложности затруднят выявление основной идеи.