Другие ответы достаточно хорошо демонстрируют разницу между 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 добавляет $ function ($ 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, но я чувствовал, что эти сложности затруднят выявление основной идеи.