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

373

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

Они выполняют то же самое?
Могут ли они быть взаимозаменяемыми?

Буду признателен за помощь с иллюстративным примером, если они вообще отличаются.

Гра Двухместный
источник
Это крутой трюк для обработки именованного массива с помощью array_reduce (). Стоит прочитать, если вы исследуете array_map, array_walk и array_filter. stackoverflow.com/questions/11563119/…
Лэнс Кливленд

Ответы:

565
  • Изменение значений:
    • array_mapне может изменить значения внутри входных массивов, пока array_walkcan; в частности, array_mapникогда не меняет своих аргументов.
  • Доступ к массиву ключей:
    • array_mapне может работать с ключами массива, array_walkможет.
  • Возвращаемое значение:
    • 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
)
Artefacto
источник
3
В руководстве по PHP написано: «array_walk (): потенциально могут быть изменены только значения массива»;
feeela
10
«array_map не может работать с ключами массива», это не так:array_map(callback($key, $value), array_keys($array), $array)
Jarek Jakubowski
12
Он по-прежнему не обращается к ключам любого массива, он обрабатывает значения, которые вы помещаете в массив, который вы создали из ключей. Это обходной путь, это не отменяет утверждение.
Инарило
хотя array_map не изменяет значения неявным образом, присваивая результат одному и тому же массиву, он в основном меняет его, и «парадоксальным образом» array_walk, который работает с тем же самым массивом, не будет изменять его значения напрямую, если только значение не передается по ссылке (массив walk может удалить индексы / элементы как array_filter косвенно с помощью предложения use анонимной функции, передавая исходный массив, но это обходной путь). Таким образом, заключение о том, что изменение значений, а также то, что значение возвращается или передается по ссылке, менее эффективно, но
обход
кроме того, похоже, что независимо от того, что обход массива принимает первый параметр массива в качестве ссылки, когда кто-то хочет изменить его, он должен также передать значение элемента обратного вызова в качестве ссылки
FantomX1
91

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

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

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

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

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

Кендалл Хопкинс
источник
5
+1 за понимание второго абзаца «В теории такие вещи, как отображение функций, могут выполняться параллельно, поскольку функция, применяемая к данным, должна воздействовать ТОЛЬКО на данные, а НЕ на глобальное состояние». Для нас, параллельных программистов, полезно помнить об этом.
etherice
Можете ли вы объяснить, как array_filter()можно реализовать с помощью array_walk()?
pfrenssen
40

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

bool array_walk (array & $ array, обратный вызов $ funcname [, mixed $ userdata]) <-retol bool

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

массив array_map (обратный вызов $ callback, массив $ arr1 [, массив $ ...]) <- вернуть массив

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

массив array_filter (массив $ input [, callback $ callback]) <- вернуть массив

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

Стивен Шланскер
источник
Не могу понять, почему исчезли значения моего массива. Глядя на документацию, я предположил, что array_walkвернул массив вроде array_mapи понял, что проблема в моей функции. До тех пор, пока я не увидел это, я не понял, что тип возвращаемого значения является логическим.
Дилан Валаде
22

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

Warbo
источник
1

В следующей редакции делается попытка более четко разграничить 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);
?>

живой код здесь

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

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

<?php

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

живой код здесь

Код в этом случае применяет обратный вызов, используя встроенную функцию strtoupper (), но пользовательская функция также является еще одним приемлемым вариантом. Обратный вызов применяется к каждому элементу $ отфильтрованного и тем самым порождает $ 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 (). (Смотрите больше здесь ).

slevy1
источник
1
Обратите внимание, что функции $lambdaи $callbackявляются просто eta-расширениями существующих функций и, следовательно, являются полностью избыточными. Вы можете получить тот же результат, передав (имя) основную функцию: $filtered = array_filter($array, 'ctype_alpha');и$nu = array_map('strtoupper', $filtered);
Warbo