Різниця між 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/…
Ленс Клівленд

Відповіді:


564
  • Зміна значень:
    • array_mapне може змінити значення всередині вхідних масивів (-ів), хоча array_walkможе; зокрема, 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
)

3
Посібник PHP говорить: "array_walk (): Тільки значення масиву потенційно можуть бути змінені;"
гонорар

10
"array_map не може працювати з клавішами масиву", це неправда:array_map(callback($key, $value), array_keys($array), $array)
Jarek Jakubowski,

12
Він досі не має доступу до ключів будь-якого масиву, це змінює значення, які ви поміщаєте в масив, який ви створили з клавіш. Це рішення, яке не скасовує твердження.
inarilo

в той час як array_map не неявно змінює значення, присвоюючи результат одному і тому ж масиву, він в основному змінює його, і "парадоксально" array_walk, який працює на одному і тому ж масиві, сам не змінить його значення безпосередньо, якщо тільки не передане значення за посиланням (масив walk може видалити індекси / елементи як array_filter опосередковано за допомогою анонімної функції, використовуючи пункт, передаючи початковий масив, але це обхідний шлях). Таким чином, зміна значень, а також якщо значення повернене чи передане посиланням, є менш різницевим, але прогулянка масиву працює з індексами та картою масиву з декількома масивами
FentomX1

Крім того, виглядає так, що незалежно від того, що прогулянка масиву приймає перший параметр масиву як опорний, коли людина хоче його змінити, він також повинен передавати значення елемента зворотного виклику як еталон
FentomX1

91

Ідея відображення функції для масиву даних походить від функціонального програмування. Ви не повинні думати про те, array_mapяк foreachцикл , який викликає функцію для кожного елемента масиву (незважаючи на те, що, як це реалізовано). Слід вважати, що функція застосовується до кожного елемента масиву незалежно.

В теорії такі речі, як картографування функцій, можна робити паралельно, оскільки функція, що застосовується до даних, повинна ТІЛЬКИ впливати на дані, а НЕ на глобальний стан. Це тому, що a array_mapможе вибрати будь-який порядок, в якому застосувати функцію до елементів у (хоча у PHP це не відбувається).

array_walkз іншого боку, це прямо протилежний підхід до обробки масивів даних. Замість того, щоб обробляти кожен елемент окремо, він використовує стан ( &$userdata) і може редагувати елемент на місці (подібно до циклу передбачення). Оскільки кожен раз, коли елемент $funcnameзастосовується до нього, він може змінити глобальний стан програми, і для цього потрібен єдиний правильний спосіб обробки елементів.

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

array_filterце дійсно додаток array_walk(або array_reduce), і це більш-менш просто передбачено для зручності.


5
+1 для розуміння вашого другого абзацу: "В теорії такі речі, як картографування функцій, можна робити паралельно, оскільки функція, що застосовується до даних, повинна ТОЛЬКО впливати на дані, а НЕ на глобальний стан". Для нас паралельних програмістів це корисна річ, яку слід пам’ятати.
ефір

Чи можете ви пояснити, як array_filter()це можна реалізувати за допомогою array_walk()?
pfrenssen

40

З документації,

bool array_walk (масив & $ масив, зворотний виклик $ funcname [, змішані $ userdata]) <-завернення bool

array_walk приймає масив та функцію Fта змінює її, замінюючи кожен елемент x на F(x).

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

array_map робить те саме, за винятком того, що замість зміни на місці він поверне новий масив з перетвореними елементами.

array array_filter (масив $ input [, зворотний виклик $ 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 зробить це лише у тому випадку, якщо функція $ (елемент $) буде ПРАВИЛЬНА.
  • $ FILTER додає оригінальний елемент, але $ MAP додає функцію $ ($ елемент).

Зауважте, що це далеко не марні дрібниці; ми можемо використовувати його, щоб зробити наші алгоритми ефективнішими!

Ми часто можемо бачити код, як ці два приклади:

// 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 замість циклів робить ці приклади досить приємними. Однак це може бути дуже неефективно, якщо $ вводи великі, оскільки перший виклик (карта чи фільтр) буде проходити $ введення та будувати проміжний масив. Цей проміжний масив передається прямо у другий виклик, який знову пройде все, тоді проміжний масив потрібно буде збирати сміттям.

Ми можемо позбутися цього проміжного масиву, скориставшись тим фактом, що 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 не використовуватиме "порожній" як його за замовчуванням функцію $. Крім того, ключі не збережуть.

Не важко змусити їх вести себе як PHP, але я відчував, що ці ускладнення зроблять основну ідею важче помітити.


1

Наступна редакція прагне більш чітко окреслити array_filer (), array_map () і array_walk () PHP, всі вони походять від функціонального програмування:

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 (). (Більше див. Тут ).


1
Зауважимо, що функції $lambdaі $callbackє лише ета-розширеннями існуючих функцій і, отже, є абсолютно зайвими. Ви можете отримати той же результат, передавши (ім'я) основної функції: $filtered = array_filter($array, 'ctype_alpha');і$nu = array_map('strtoupper', $filtered);
Warbo
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.