Інші відповіді доволі добре демонструють різницю між 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, але я відчував, що ці ускладнення зроблять основну ідею важче помітити.