Удосконалюємо навички роботи з filter

6 хв. читання

filter — вбудований метод ітерації масивом, який приймає предикат, викликаний для кожного з його значень і повертає підмножину значень, що є істинними.

Розглянемо наведене визначення детальніше:

  • Вбудований означає, що метод є частиною мови: вам не потрібно додавати будь-яку бібліотеку для отримання доступу до функціоналу.
  • Метод ітерації означає виконання для кожного елементу масиву. Прикладами таких методів можуть слугувати map та reduce.
  • Предикат — функція, що повертає булеве значення.
  • Істинне значення — будь-яке значення, що може розглядатися як true. Майже всі значення можуть бути true за винятком undefined, null, false, 0, NaN, або "" (empty string).

Аби побачити filter у дії, спершу поглянемо на масив ресторанів.

const restaurants = [
{
 name: "Dan's Hamburgers",
 price: 'Cheap',
cuisine: 'Burger',
},
 {
 name: "Austin's Pizza",
 price: 'Cheap',
 cuisine: 'Pizza',
},
 {
 name: "Via 313",
price: 'Moderate',
cuisine: 'Pizza',
},
 {
 name: "Bufalina",
 price: 'Expensive',
 cuisine: 'Pizza',
},
{
name: "P. Terry's",
 price: 'Cheap',
 cuisine: 'Burger',
},
{
name: "Hopdoddy",
 price: 'Expensive',
 cuisine: 'Burger',
 },
{
 name: "Whataburger",
 price: 'Moderate',
cuisine: 'Burger',
 },
 {
 name: "Chuy's",
cuisine: 'Tex-Mex',
 price: 'Moderate',
 },
 {
name: "Taquerias Arandina",
cuisine: 'Tex-Mex',
price: 'Cheap',
 },
 {
 name: "El Alma",
 cuisine: 'Tex-Mex',
price: 'Expensive',
 },
{
name: "Maudie's",
 cuisine: 'Tex-Mex',
 price: 'Moderate',
 },
];

Тут багато інформації. Я зараз у настрої з'їсти бургер, тому відфільтруємо трохи масив.

const isBurger = ({cuisine}) => cuisine === 'Burger';
const burgerJoints = restaurants.filter(isBurger);

isBurger — предикат, а burgerJointsновий масив, що є підмножиною ресторанів. Важливо відмітити, що масив ресторанів залишається незмінним після застосування filter.

Нижче наведено простий приклад відображення двох списків: один — початковий масив restaurants, інший — відфільтрований burgerJoints масив.

Заперечення предикатів

Для кожного предикату існує рівний та протилежний предикат.

Як вже було зазначено, предикат — функція, що повертає булеве значення. Зважаючи на те, що таких значень два, значення предиката легко «перевернути».

Декілька годин пройшло з того часу, як я з'їв свій бургер, і тепер я голодний знову. Цього разу я хочу отримати результат фільтрації без бургерів аби скуштувати щось нове. Один з варіантів — написати новий предикат isNotBurger.

const isBurger = ({cuisine}) => cuisine === 'Burger';
const isNotBurger = ({cuisine}) => cuisine !== 'Burger';

Однак, подивіться на схожість двох предикатів. Цей код містить повторення. Інший спосіб — викликати предикат isBurger та змінити результат на протилежний.

const isBurger = ({cuisine}) => cuisine === 'Burger';
const isNotBurger = restaurant => !isBurger(restaurant);

Вже краще. Якщо критерій пошуку змінюється на протилежний, вам необхідно змінити логіку лише в одному місці. Однак, якщо існує декілька предикатів, які б ми хотіли заперечити. Така необхідність може виникати часто, тому гарною ідеєю буде написати функцію negate.

const negate = predicate => function() {
 return !predicate.apply(null, arguments);
}

const isBurger = ({cuisine}) => cuisine === 'Burger';
const isNotBurger = negate(isBurger);

const isPizza = ({cuisine}) => cuisine === 'Pizza';
const isNotPizza = negate(isPizza);

У вас можуть виникнути питання.

Що таке .apply?

MDN

Метод .apply приймає функцію з заданими this значеннями та аргументами у вигляді масиву (чи масивоподібного об'єкту).

Що таке аргументи?

MDN

Об'єкт arguments — локальна змінна, доступна у всіх функціях (за винятком стрілкових). Ви можете посилатися на аргументи функції всередині функції, використовуючи об'єкт arguments.

Навіщо повертати стару добру функцію замість нової стрілкової функції?

У даному випадку повернення традиційної функції є необхідним, тому що аргументи доступні лише у таких функціях.

(Але ви можете використовувати negate зі стрілковою функцією, використовуючи параметри rest)

Повернення предикатів

На прикладі нашої negate легко впевнитись у тому, що функція у JavaScript з легкістю повертає нову. Це може бути корисним для написання «творців предикатів». Наприклад, звернемося знову до вже знайомих предикатів isBurger та isPizza.

const isBurger = ({cuisine}) => cuisine === 'Burger';
const isPizza = ({cuisine}) => cuisine === 'Pizza';

Наведені предикати поділяють спільну логіку: вони відрізняються лише у порівняннях. Саме тому ми можемо огорнути загальну логіку функцією isCuisine.

const isCuisine = comparison => ({cuisine}) => cuisine === comparison;
const isBurger = isCuisine('Burger');
const isPizza = isCuisine('Pizza');

Усе чудово! Що буде, коли ми захочемо перевірити ціну?

const isPrice = comparison => ({price}) => price === comparison;
const isCheap = isPrice('Cheap');
const isExpensive = isPrice('Expensive');

Тепер isCheap та isExpensive, а також isPizza та isBurger не повторюють код, але isPrice та isCuisine поділяють їх логіку! На щастя, немає обмежень щодо кількості функцій, що повертаються.

const isKeyEqualToValue = key => value => object => object[key] === value;

// це можна переписати
const isCuisine = isKeyEqualToValue('cuisine');
const isPrice = isKeyEqualToValue('price');

// це не треба змінювати 
const isBurger = isCuisine('Burger');
const isPizza = isCuisine('Pizza');
const isCheap = isPrice('Cheap');
const isExpensive = isPrice('Expensive');

Як на мене, продемонстровано красу стрілкових функцій. В один рядок ви можете елегантно створити вкладену функцію. isKeyEqualToValue— функція, що повертає функцію isPrice, яка у свою чергу повертає функцію isCheap.

Тепер ви бачите як легко можна створити декілька відфільтрованих списків з початкового масиву restaurants.

Поєднання предикатів

Тепер ви маєте змогу відфільтрувати масив за наявністю бургерів чи за ціною... але що, якщо ви хочете дешеві бургери? Як варіант, можна зв'язати обидва фільтри.

const cheapBurgers = restaurants.filter(isCheap).filter(isBurger);

Іншим варіантом є «складання» двох предикатів в один.

const isCheapBurger = restaurant => isCheap(restaurant) && isBurger(restaurant);
const isCheapPizza = restaurant => isCheap(restaurant) && isPizza(restaurant);

Погляньте на весь цей повторюваний код. Його ми можемо об'єднати у нову функцію.

const both = (predicate1, predicate2) => value =>
 predicate1(value) && predicate2(value);

const isCheapBurger = both(isCheap, isBurger);
const isCheapPizza = both(isCheap, isPizza);

const cheapBurgers = restaurants.filter(isCheapBurger);
const cheapPizza = restaurants.filter(isCheapPizza);

А якщо ви не проти піци й гамбургерів?

const either = (predicate1, predicate2) => value =>
 predicate1(value) || predicate2(value);

const isDelicious = either(isBurger, isPizza);
const deliciousFood = restaurants.filter(isDelicious);

Ми зробили крок у правильному напрямку, але якщо у вас є понад дві страви на вибір? Тож підхід є не дуже масштабованим. Існує два вбудованих методи масивів, що приходять на допомогу. .every та .some є методами предикатів, що також приймають їх. .every перевіряє чи кожен елемент масиву відповідає предикату, у той час як .some перевіряє чи є елемент масиву, що відповідає предикату.

const isDelicious = restaurant =>
 [isPizza, isBurger, isBbq].some(predicate => predicate(restaurant));

const isCheapAndDelicious = restaurant =>
 [isDelicious, isCheap].every(predicate => predicate(restaurant));

Звичайно, додамо трохи необхідної абстракції.

const isEvery = predicates => value =>
 predicates.every(predicate => predicate(value));

const isAny = predicates => value =>
 predicates.some(predicate => predicate(value));

const isDelicious = isAny([isBurger, isPizza, isBbq]);
const isCheapAndDelicious = isEvery([isCheap, isDelicious]);

isEvery та isAny обидва приймають масив предикатів та повертають єдиний предикат.

Оскільки такі предикати запросто створюються функціями вищого порядку, їх створення та застосування на основі взаємодії з користувачем не повинно викликати труднощів. Для засвоєння викладеного, нижче наведено приклад застосунку, що шукає ресторани на основі застосованих фільтрів-кнопок.

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.6K
Приєднався: 8 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація