Разница между array_map, array_walk и array_filter

В чем же разница между array_map, array_walk и array_filter. Что я вижу из документации, так это то, что вы можете передать функцию обратного вызова для выполнения действия в поставляемом массиве. Но я, кажется, не обнаружил особых различий между ними.

Выполняют ли они то же самое?
Можно ли их использовать взаимозаменяемо?

Я был бы признателен за вашу помощь с иллюстративным примером, если они разные.

Ответ 1

  • Изменение значений:
    • array_map не может изменять значения внутри входных массивов, в то время как array_walk может; в частности, array_map никогда не меняет своих аргументов.
  • Доступ к массиву ключей:
  • Возвращаемое значение:
    • array_map возвращает новый массив, array_walk возвращает только true. Следовательно, если вы не хотите создавать массив в результате обхода одного массива, вы должны использовать array_walk.
  • Итерация нескольких массивов:
    • array_map также может принимать произвольное количество массивов и может итерировать их параллельно, в то время как array_walk работает только с одним.
  • Передача произвольных данных в обратный вызов:
    • array_walk может получить дополнительный произвольный параметр для передачи в обратный вызов. Это в основном не имеет отношения к PHP 5.3 (когда были введены анонимные функции).
  • Длина возвращаемого массива:
    • Результирующий массив array_map имеет ту же длину, что и длина самого большого входного массива; array_walk не возвращает массив, но в то же время он не может изменить количество элементов исходного массива; array_filter выбирает только подмножество элементов массива в соответствии с функцией фильтрации. Это сохраняет ключи.

Пример:

<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>

Результат:

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
)

Ответ 2

Идея отображения функции в массив данных происходит из функционального программирования. Вы не должны думать о array_map как о цикле foreach который вызывает функцию для каждого элемента массива (даже если он так реализован). Это следует рассматривать как применение функции к каждому элементу в массиве независимо.

Теоретически такие вещи, как отображение функций, могут выполняться параллельно, поскольку функция, применяемая к данным, должна влиять ТОЛЬКО на данные, а НЕ на глобальное состояние. Это связано с тем, что array_map может выбирать любой порядок, в котором применяется функция к элементам (хотя в PHP это не так).

array_walk другой стороны, array_walk - это совершенно противоположный подход к обработке массивов данных. Вместо того, чтобы обрабатывать каждый элемент отдельно, он использует состояние (&$userdata) и может редактировать элемент на месте (подобно циклу foreach). Поскольку каждый раз, когда к элементу применяется $funcname, он может изменить глобальное состояние программы, и для этого требуется единственный правильный способ обработки элементов.

В PHP, array_map и array_walk практически идентичны, за исключением того, что array_walk дает вам больший контроль над итерацией данных и обычно используется для "изменения" данных на месте по сравнению с возвратом нового "измененного" массива.

array_filter - это приложение array_walk (или array_reduce), и оно более или менее просто предоставлено для удобства.

Ответ 3

Из документации

bool array_walk (array & $array, callback $funcname [, mixed $userdata]) < -return bool

array_walk принимает массив и функцию F и изменяет его, заменяя каждый элемент x на F(x).

массив array_map (обратный вызов обратного вызова, array $arr1 [, array $...]) < -return array

array_map делает то же самое кроме, что вместо изменения на месте он вернет новый массив с преобразованные элементы.

массив array_filter (массив $input [, callback $callback]) < -return array

array_filter с функцией F вместо преобразования элементов удалят любые элементы, для которых F(x) не является истинным

Ответ 4

Другие ответы демонстрируют разницу между 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 велико, так как первый вызов (карта или фильтр) будет пересекать $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 может обрабатывать только один массив за один раз, и мой массив_filter не будет использовать "пустую" в качестве функции $по умолчанию. Кроме того, ни один из них не сохранит ключи.

Не сложно заставить их вести себя как PHP, но я чувствовал, что эти сложности затруднят определение основной идеи.

Ответ 5

В следующей редакции делается попытка более четко определить PHP array_filer(), array_map() и array_walk(), все из которых исходят из функционального программирования:

array_filter() отфильтровывает данные, создавая в результате новый массив, содержащий только нужные элементы прежнего массива, следующим образом:

<?php
$array = array(1, "apples",2, "oranges",3, "plums");

$filtered = array_filter( $array, "ctype_alpha");
var_dump($filtered);
?>

live code здесь

Все числовые значения отфильтровываются из массива $, оставляя $filter только с фруктами.

array_map() также создает новый массив, но в отличие от array_filter() результирующий массив содержит каждый элемент входного файла $filter, но с измененными значениями из-за применения обратного вызова к каждому элементу следующим образом:

<?php

$nu = array_map( "strtoupper", $filtered);
var_dump($nu);
?>

live code здесь

В этом случае код применяет обратный вызов с использованием встроенного strtoupper(), но определенная пользователем функция также является еще одним жизнеспособным вариантом. Обратный вызов применяется к каждому элементу $filter и тем самым порождает $nu, элементы которого содержат заглавные значения.

В следующем фрагменте массив walk() перемещает $nu и вносит изменения в каждый элемент по отношению к ссылочному оператору '&'. Изменения происходят без создания дополнительного массива. Каждое значение элемента изменяется на место в более информативную строку с указанием ее ключа, категории и значения.

<?php

$f = function(&$item,$key,$prefix) {
    $item = "$key: $prefix: $item";
}; 
array_walk($nu, $f,"fruit");
var_dump($nu);    
?>    

См. демо

Примечание. Функция обратного вызова по отношению к array_walk() принимает два параметра, которые автоматически получат значение элемента и его ключ и в этом порядке также при вызове array_walk(). (Подробнее здесь).