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

296

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

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

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

  • 0
    Это крутой трюк для обработки именованных массивов с помощью array_reduce (). Стоит прочитать, если вы исследуете array_map, array_walk и array_filter. stackoverflow.com/questions/11563119/...
Теги:
callback
arrays

5 ответов

415
Лучший ответ
  • array_map не имеет побочных эффектов, а array_walk can; в частности, array_map никогда не меняет своих аргументов.
  • array_map не может работать с ключами массива, array_walk может.
  • array_map возвращает массив, array_walk возвращает только true/false. Следовательно, если вы не хотите создавать массив в результате прохождения одного массива, вы должны использовать array_walk.
  • array_map также может принимать произвольное количество массивов, а array_walk работает только на одном.
  • array_walk может получить дополнительный произвольный параметр для передачи обратного вызова. Это в основном не имеет значения, поскольку PHP 5.3 (когда были анонимные функции 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
)
  • 3
    В руководстве по PHP написано: «array_walk (): потенциально могут быть изменены только значения массива»;
  • 0
    Ты сэкономил мое время
Показать ещё 2 комментария
82

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

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

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

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

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

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

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

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) не является истинным

  • 2
    Не могли бы вы показать их на примере, пожалуйста?
  • 0
    Не могу понять, почему исчезли значения моего массива. Глядя на документацию, я предположил, что array_walk вернул массив, подобный array_map и решил, что проблема в моей функции. До тех пор, пока я не увидел это, я не понял, что тип возвращаемого значения является логическим.
15

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

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);
?>

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(). (Подробнее здесь).

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню