Васильев Андрей Михайлович, 2022
Версии презентации
Мы изучили базовые инструменты функционального итерирования по массивам. Однако реальные задачи зачастую сложнее и требуют применения сразу набора из данных методов. В рамках данной лекции рассмотрим подходы к созданию цепочек из функциональных инструментов
В рамках цепочек каждый шаг прост и понятен, но вместе они могут решать сложные задачи по обработке данных
Команда взаимодействия предполагает, что лояльные клиенты совершают также и наибольшие покупки. Надо узнать стоимость наибольшей покупки от лояльных клиентов
Отдельные задачи мы можем успешно решать:
Данную задачу можно решить путём формирования цепочки из функций
Начнём с определения функции:
function biggestPurchasesBestCustomers(customers) {}
Данная функция должна возвращать список из стоимости наибольших покупок
Выполним поиск лояльных покупателей с помощью фильтрации
function biggestPurchasesBestCustomers(customers) {
var bestCustomers = filter(customers, function(customer) {
return customer.purchases.length >= 3;
});
Найдём наибольшую стоимость покупки, совершённой данным покупателем. Деталей мы пока что не знаем, но для реализации можно точно использовать map()
function biggestPurchasesBestCustomers(customers) {
var bestCustomers = filter(customers, function(customer) {
return customer.purchases.length >= 3;
});
var biggestPurchases = map(bestCustomers, function(customer) {
return ...; // Надо будет реализовать
});
return biggestPurchases;
};
Для поиска наибольшей покупки мы можем воспользоваться reduce()
function biggestPurchasesBestCustomers(customers) {
var bestCustomers = filter(customers, function(customer) {
return customer.purchases.length >= 3;
});
var biggestPurchases = map(bestCustomers, function(customer) {
return reduce(customer.purchases, {total: 0}, // Для каждого клиента
function(biggest, purchase) {
if(biggest.total > purchase.total) // Находим дорогую покупку
return biggest;
else
return purchase;
});
});
return biggestPurchases;
};
Функция решает поставленную задачу, но использует очень много слов для решения
Сравним reduce-шаг с функцией max из предыдущей главы
reduce(customer.purchases,
{total: 0},
function(biggest, purchase) {
if(biggest.total > purchase.total)
return biggest;
else
return purchase;
});
reduce(numbers,
Number.MIN_VALUE,
function(m, n) {
if(m > n)
return m;
else
return n;
});
Основное отличие между данными функциями — сравнение свойства total
слева и обычных чисел справа. Обобщим данный подход
// Оригинал
reduce(customer.purchases,
{total: 0},
function(biggest, purchase) {
if(biggest.total > purchase.total)
return biggest;
else
return purchase;
});
// Обобщённая функция
function maxKey(array, init, f) {
return reduce(array, init,
function(biggest, element) {
if(f(biggest) > f(element)) {
return biggest;
} else {
return element;
}
});
}
maxKey(customer.purchases, {total: 0},
function(purchase) { return purchases.total; }
);
Используем эту функцию для упрощения оригинальной функции
function biggestPurchasesBestCustomers(customers) {
var bestCustomers = filter(customers, function(customer) {
return customer.purchases.length >= 3;
});
var biggestPurchases = map(bestCustomers, function(customer) {
return maxKey(customer.purchases, {total: 0}, function(purchase) {
return purchase.total;
});
});
return biggestPurchases;
}
maxKey
, который позволяет получить наибольший элемент массива в соответствии с правилом-функциейФункции maxKey()
и max()
очень похожи. То есть в теории могут иметь очень похожий код
Предположите, что вам необходимо написать одну функцию в терминах другой функции
max()
должна быть реализована в терминах maxKey()
Можно реализовать max()
с помощью maxKey()
, передавая ей функцию возвращающую свой аргумент
function max(array, init) {
return maxKey(array, init, function(x) {
return x;
});
}
Граф вызовов: max() -> maxKey() -> reduce() -> forEach() -> for loop
maxKey()
находится ниже max()
. max()
является более специализированной версией maxKey()
Один из способов по облегчению восприятия шагов в цепочке вызовов — добавление имён к шагам
// Оригинал
function biggestPurchasesBestCustomers(customers) {
var bestCustomers = filter(customers, function(customer) { // Шаг №1
return customer.purchases.length >= 3;
});
var biggestPurchases = map(bestCustomers, function(customer) { // Шаг №2
return maxKey(customer.purchases, {total: 0}, function(purchase) {
return purchase.total;
});
});
return biggestPurchases;
}
// Выделенные методы
function selectBestCustomers(customers) {
return filter(customers, function(customer) {
return customer.parchaces.length >= 3;
});
}
function getBiggestPurchases(customers) {
return map(customers, getBiggestPurchace);
}
function getBiggestPurchase(customer) {
return maxKey(customer.purchases, {total: 0}, function(purchase) {
return purchase.total;
});
}
function biggestPurchasesBestCustomers(customers) {
var bestCustomers = selectBestCustomers(customers);
var biggestPurchases = getBiggestPurchases(bestCustomers);
return biggestPurchases;
}
Минусы реализации
return
-выраженийМожно ли добиться повторного использования написанного кода?
Второй подход заключается в предоставлении названий для функций обратного вызова, которые изначально объявлены анонимно
// Оригинал
function biggestPurchasesBestCustomers(customers) {
var bestCustomers = filter(customers, function(customer) { // Шаг №1
return customer.purchases.length >= 3;
});
var biggestPurchases = map(bestCustomers, function(customer) { // Шаг №2
return maxKey(customer.purchases, {total: 0}, function(purchase) {
return purchase.total;
});
});
return biggestPurchases;
}
// Выделяем названия для функций обратного вызова
function isGoodCustomer(customer) {
return customer.purchases.length >= 3;
}
function getBiggestPurchase(customer) {
return maxKey(customer.purchases, {total: 0}, getPurchaseTotal);
}
function getPurchaseTotal(purchase) {
return purchase.total;
}
function biggestPurchasesBestCustomers(customers) {
var bestCustomers = filter(customers, isGoodCustomer);
var biggestPurchases = map(bestCustomers, getBiggestPurchase);
return biggestPurchases;
}
isGoodCustomer
предоставляет информацию об одном клиенте, можно применять в любой ситуации при работе с одним клиентомselectBestCustomers
работает только лишь с массивом клиентовfunction biggestPurchasesBestCustomers(customers) {
var bestCustomers = selectBestCustomers(customers);
var biggestPurchases = getBiggestPurchases(bestCustomers);
return biggestPurchases;
}
function selectBestCustomers(customers) {
return filter(customers, function(customer) {
return customer.parchaces.length >= 3;
});
}
function getBiggestPurchases(customers) {
return map(customers, getBiggestPurchace);
}
function getBiggestPurchase(customer) {
return maxKey(customer.purchases, {total: 0}, function(purchase) {
return purchase.total;
});
}
function biggestPurchasesBestCustomers(customers) {
var bestCustomers = filter(customers, isGoodCustomer);
var biggestPurchases = map(bestCustomers, getBiggestPurchase);
return biggestPurchases;
}
function isGoodCustomer(customer) {
return customer.purchases.length >= 3;
}
function getBiggestPurchase(customer) {
return maxKey(customer.purchases, {total: 0}, getPurchaseTotal);
}
function getPurchaseTotal(purchase) {
return purchase.total;
}
Нам необходимо отправить письма-приветствия покупателям, которые совершили только одну покупку в магазине
var firstTimers = filter(customers, function(customer) {
return customer.purchases.length === 1;
});
var firstTimerEmails = map(firstTimers, function(customer) {
return customer.email;
});
// Можно дать имена анонимным встроенным функциям
var firstTimers = filter(customers, isFirstTimer);
var firstTimerEmails = map(firstTimers, getCustomerEmail);
function isFirstTimer(customer) {
return customer.purchases.length === 1;
}
function getCustomerEmail(customer) {
return customer.email;
}
Отдел маркетинга хочет найти клиентов, которые сделали хотя бы одну покупку на 100$ и как минимум две покупки всего
Реализованная функция должна быть удобна для восприятия
function bigSpenders(customers) {
var withBigPurchases = filter(customers, hasBigPurchase);
var withTwoOrMorePurchases = filter(customers, hasTwoOrMorePurchases);
return withTwoOrMorePurchases;
}
function hasBigPurchase(customer) {
return filter(customer.purchases, isBigPurchase).length > 0;
}
function isBigPurchase(purchase) {
return purchase.total > 100;
}
function hasTwoOrMorePurchases(customer) {
return customer.purchases.length >= 2;
}
Необходимо реализовать функцию, которая вычисляет среднее значение в массиве из чисел
Вариант решения
function average(numbers) {
return reduce(numbers, 0, plus) / numbers.length;
}
function plus(a, b) {
return a + b;
}
Необходимо вычислить среднюю стоимость покупок для каждого клиента. Можно считать, что функция avegage()
из предыдущего упражнения существует
Вариант решения
function averagePurchaseTotals(customers) {
return map(customers, function(customer) {
var purchasesTotal = map(customer.purnchases, function(purchase) {
return purchase.total;
});
return average(purchaseTotals);
});
}
Функции filter()
и map()
создают новые массивы и потенциально добавляют множество элементов в них
map()
#
Если для решения задачи необходимо вызвать несколько раз map, то вместо этого можно вызвать один раз с применением комбинированного метода
var names = map(customers, getFullName);
var nameLengths = map(names, stringLength);
var nameLengths = map(customers, function(customer) {
return stringLength(getFullName(customer));
});
При выполнении нескольких фильтров подряд мы де-факто объединяем условия с помощью логического И
var goodCustomers = filter(customers, isGoodCustomer);
var withAddress = filter(goodCustomers, hasAddress);
var withAddress = filter(customers, function(customer) {
return isGoodCustomer(customer) && hasAddress(customer);
});
map()
с последующим reduce()
#
Мы обсуждали, что с помощью reduce() можно реализовать другие функции. Даже можно реализовать совмещение функций
var purchaseTotals = map(purchases, getPurchaseTotal);
var purchaseSum = recude(purchaseTotals, 0, plus);
var purchaseSum = reduce(purchases, 0, function(total, purchase) {
return tolal + getPurchaseTotal(purchase);
});
Можно выделить 2 стратегии по преобразованию
Первый подход в целом работает, но до какого-то предела в сложности цикла
var answer = [];
var window = 5;
for(var i = 0; i < array.length; i++) {
var sum = 0;
var count = 0;
for(var w = 0; w < window; w++) {
var idx = i + w;
if(idx < array.length) {
sum += array[idx];
count += 1;
}
}
answer.push(sum/count);
}
// Выделим элементы в коде
var answer = []; // Результатом вычислений является массив
var window = 5; // Магическая переменная
// Обход элементов массива array
for(var i = 0; i < array.length; i++) {
var sum = 0;
var count = 0;
// Цикл по промежутку от 0 до 4
for(var w = 0; w < window; w++) {
var idx = i + w; // Новый индекс
if(idx < array.length) {
sum += array[idx];
count += 1; // Вычисление значений
}
}
answer.push(sum/count); // Добавление их в массив
}
При использовании низкоуровневых инструментов мы не формируем промежуточные данные, которые получаются при использовании инструментов map()
, filter()
, …
Можно попытаться выделить элементы, по которым происходит итерирование
// Оригинал
var answer = [];
var window = 5;
for(var i = 0; i < array.length; i++) {
var sum = 0;
var count = 0;
// Проход по части массива array
for(var w = 0; w < window; w++) {
var idx = i + w;
if(idx < array.length) {
sum += array[idx];
count += 1;
}
}
answer.push(sum/count);
}
var answer = [];
var window = 5;
for(var i = 0; i < array.length; i++) {
var sum = 0;
var count = 0;
var subarray = array.slice(i, i + window);
for(war w = 0; w < subarray.length; w++) {
sum += subarray[w];
count += 1;
}
answer.push(sum/count);
}
После выделения конкретных объектов, по которым происходит итерирование, можно применить функциональные инструменты для выполнения действий
В рамках примера для выделенного массива вычисляется среднее значение, можно воспользоваться функцией average
var answer = [];
var window = 5;
for(var i = 0; i < array.length; i++) {
var sum = 0;
var count = 0;
var subarray = array.slice(i, i + window);
for(war w = 0; w < subarray.length; w++) {
sum += subarray[w];
count += 1;
}
answer.push(sum/count);
}
// Используем функцию average
var answer = [];
var window = 5;
for(var i = 0; i < array.length; i++) {
var subarray = array.slice(i, i + window);
answer.push(average(subarray));
}
Получившийся for-цикл проходит не по элементам массива, а по подмножествам массива. В результате мы не можем применить известные функции высшего порядка напрямую
Подмножества получаются по индексам элементов, а не по значению элементов
var indicies = [];
for(var i = 0; i < array.length; i++)
indicies.push(i);
var window = 5;
var answer = map(indicies, function(index) {
var subarray = array.slice(index, index + window);
return average(subarray);
});
Мы можем улучшить подход, упростив функцию, которую мы передаём в map()
:
var indicies = [];
for(var i = 0; i < array.length; i++)
indicies.push(i);
var window = 5;
var windows = map(indicies, function(index) {
return array.slice(i, i + window);
});
var answer = map(windows, average);
Мы также можем провести рефакторинг, выделив функцию range()
, которая будет создавать массив из индексов нужного промежутка
function range(start, end) {
var result = [];
for(var i = start; i < end; i++)
result.push(i);
return result;
}
var indicies = range(0, array.length);
var window = 5;
var windows = map(indicies, function(index) {
return array.slice(i, i + window);
});
var answer = map(windows, average);
Императивный подход
var answer = [];
var window = 5;
for(var i = 0; i < array.length; i++) {
var sum = 0;
var count = 0;
for(var w = 0; w < window; w++) {
var idx = i + w;
if(idx < array.length) {
sum += array[idx];
count += 1;
}
}
answer.push(sum/count);
}
Функциональный подход
var indicies = range(0, array.length);
var window = 5;
var windows = map(indicies, function(index) {
return array.slice(i, i + window);
});
var answer = map(windows, average);
// Инструмент общего назначения
function range(start, end) {
var result = [];
for(var i = start; i < end; i++)
result.push(i);
return result;
}
Вопросы: в каком месте графа вызовов стоит расположить новый метод range()
? Что его позиция говорит о возможности повторного применения, тестирования и поддержке?
Функциональные инструменты ориентированы на использование с массивами данных. Необходимо явно выделить данные массиве в коде
Необходимо определить функцию высшего порядка, которая наиболее точно подходит для решения задачи путём обработки всего массива
Если кажется, что код стал выполнять много действий, то выделение небольших и понятных последовательностей действий может помочь в выполнении преобразования
filter()
#
Если в рамках цикла for присутствует условие, то оно зачастую направлено на пропуск элементов. Такие условия можно удобно заменить на вызов filter()
Помимо map()
, filter()
и reduce()
существует множество функциональных инструментов. Рекомендуется выделять функции-помощники, чтобы познакомиться с другими инструментами. Давайте им имена и повторно применяйте их
Для того, чтобы научиться эффективному использованию функциональных инструментов необходимо их применять, практиковаться. При этом не стоит ограничивать себя только известными схемами комбинирования данных инструментов
Рассмотрите следующий код из приложения магазина. Преобразуйте его в цепочку вызова функциональных инструментов. Существует множество способов решения этой задачи
function shoesAndSocksInventory(products) {
var inventory = 0;
for(var p = 0; p < products.length; p++) {
var product = products[p];
if(product.type === "shoes" || product.type === "socks") {
inventory += product.numberInInventory;
}
}
return inventory;
}
function shoesAndSocksInventory(products) {
var shouesAndSocks = filter(products, function(product) {
return product.type === "shoes" || product.type === "socks";
});
var inventories = map(shoesAndSocks, function(product) {
return product.numberInInventory;
});
return reduce(inventories, 0, plus);
}
Во время работы с функциональным подходом код может стать слишком абстрактным и сложным для понимания особенно, когда что-то идёт не так
Достаточно легко забыть как выглядят данные после прохождения ряда этапов по обработке. Рекомендуется давать понятные названия переменным, хранящим промежуточные этапы обработки
Для того чтобы понять состояние данных после очередного этапа обработки, добавляйте отладочный вывод между этапами. После выполняйте свой код и анализируйте отладочный вывод
Каждый функциональный инструмент работает с конкретными типами данных. В языке JavaScript они присутствуют, хотя и не проверяются во время компиляции
Используйте информацию о типах данных для достижения результата
map()
возвращает новый массив. Что находится внутри? То, что возвращает переданная функция.filter()
возвращает массив из тех же данных, что мы передаём в качестве аргументаreduce()
возвращает тип данных, которые возвращает переданная ему функцияИспользуя эту информацию можно достаточно быстро разобраться в назначении кода
Рассмотрим другие функции, которые могут быть полезны для решения задач. Это только небольшой набор методов из большого арсенала
pluck()
— получение значения полей
#
Данная функция позволяет создать массив из значений полей объектов, которые находятся в оригинальном массиве
function pluck(array, field) {
return map(array, function(object) {
return object[field];
});
}
var prices = pluck(products, 'price');
// Вариант
function invokeMap(array, method) {
return map(array, function(object) {
return object[method]();
});
}
concat()
— убрать уровень вложенности массива
#
Данная функция убирает один уровень вложенности массива. То есть делает из двухмерного массива одномерный со всеми данными из оригинального массива
function concat(arrays) {
var result = [];
forEach(arrays, function(array) {
forEach(array, function(element) {
result.push(element);
});
});
return result;
});
var purchaseArrays = pluck(customers, "purchases");
var allPurchases = concat(purchaseArrays);
// Вариант
function concatMap(array, f) {
return concat(map(array, f));
}
frequenciesBy
и groupBy
— подсчёт частоты и группировка элементов
#
function frequenciesBy(array, f) {
var result = {};
forEach(array, function(element) {
var key = f(element);
if(result[key]) result[key] += 1;
else result[key] = 1;
});
return result;
}
var howMany = frequenciesBy(products, function(p) {
return p.type;
});
function groupBy(array, f) {
var result = {};
forEach(array, function(element) {
var key = f(element);
if(result[key]) result[key].push(element);
else result[key] = [element];
});
return ret;
});
var groups = groupBy(range(0, 10), isEven);
Библиотека Lodash содержит большое количество функций, «недостающие элементы стандартной библиотеки»
Стандартная библиотека содержит большое количество функций для работы с последовательностями. Ключевая проблема — их очень много
Данный стандартный модуль языка Haskell содержит небольшой набор функциональных инструментов, которые предоставляют мощные возможности для функционального программирования. Документация, краткий обзор
Начиная с 8 выпуска языка в язык Java стали добавлять расширенную поддержку функциональных инструментов
map()
true
или false
, удобна для filter()
reduce()
Язык Ruby предлагает большое количество функциональных инструментов:
В рамках курса не рассматривается вопрос о функциональном программировании на языке JavaScript, не рассматриваются встроенные функциональные инструменты
Рассмотрим кратко встроенные функциональные средства языка JavaScript
// Подход из книги
var customerNames = map(customers, function(c) {
return c.firstName + " " + c.lastName;
});
// Встроенные инструменты
var customerNames = customers.map(function(c) {
return c.firstName + " " + c.lastName;
});
Ввиду того, что методы map
встроены в массив и создают новый массив, то можно выполнять вызов данных методов последовательно
// Подход из книги
var window = 5;
var indicies = range(0, array.length);
var windows = map(indicies, function(i) {
return array.slice(i, i + window);
});
var answer = map(windows, average);
// Цепочка методов
var window = 5;
var answer =
range(0, array.length)
.map(function(i) {
return array.slice(i, i + window);
})
.map(average);
В актуальной версии JavaScript добавили возможность удобного описания коротких анонимных функций, которые упрощают использование методов map
, filter
и reduce
var window = 5;
var answer =
range(0, array.length)
.map(i => array.slice(i, i + window))
.map(average);
Также функция map
помимо самого элемента передаёт ещё и индекс элемента, что может заменить собой другие функции
var window = 5;
var average = array => array.reduce((sum, e) => sum + e, 0) / array.length;
var answer = array.may((e, i) => array.slice(i, i + window)).map(average);
reduce()
для вычисления значений
#
До этого мы рассматривали reduce()
только как средство для вычисления значения путём комбинирования различных элементов
Другой способ использования метода — создание значений
Рассмотрим сценарий. Мы записали названия товаров, которые были добавлены в корзину пользователем:
var itemsAdded = ["shirt", "shoes", "shirt", ...]
Можем ли мы сформировать текущее состояние корзины, имея эту информацию?
Ответ: да, с использованием reduce()
Технический шаг: сформируем код по созданию shoppingCart из списка товаров
var shoppingCart = reduce(itemsAdded, {}, function(cart, item) {
});
Предположим, что корзина ещё не содержит товар, выбранный пользователем
var shoppingCart = reduce(itemsAdded, {}, function(cart, item) {
if(!cart[item])
return add_item(cart,
{name: item, quantity: 1, price: priceLookup(item)});
});
Реализуем второй вариант: когда товар уже есть в корзине
var shoppingCart = reduce(itemsAdded, {}, function(cart, item) {
if(!cart[item])
return add_item(cart,
{name: item, quantity: 1, price: priceLookup(item)});
else {
var quantity = cart[item].quantity;
return setFieldByName(cart, item, 'quantity', quantity + 1);
}
});
Функция, которая передаётся как аргумент reduce()
, является полезной сама по себе и её можно добавить в барьер из абстракций для корзины
function addOne(cart, item) {
if(!cart[item])
return add_item(cart,
{name: item, quantity: 1, price: priceLookup(item)});
else {
var quantity = cart[item].quantity;
return setFieldByName(cart, item, 'quantity', quantity + 1);
}
}
На предыдущем этапе мы реализовали создание корзины из списка товаров. Однако этого недостаточно, чтобы реализовать отмену действия, т.к. оно может быть как добавлением, так и удалением
Необходимо расширить массив информацией о действиях пользователя
var itemOps = [["add", "shirt"], ["add", "shoes"], ["remove", "shirt"],
["add", "scoks"], ["remove", "hat"], ...];
Теперь можно легко реализовать поддержку двух операций:
var shoppingCart = reduce(itemOps, {}, function(cart, itemOp) {
var op = itemOp[0];
var item = itemOp[1];
if(op === "add") return addOne(cart, item);
if(op === "remove") return removeOne(cart, item);
});
Рассмотрим реализацию метода removeOne
function removeOne(cart, item) {
if(!cart[item])
return cart;
else {
var quantity = cart[item].quantity;
if(quantity == 1)
return remove_item_by_name(cart, item);
else
return setFieldByName(cart, item, "quantity", quantity - 1);
}
};
Компания планирует принять участие в соревнованиях по софтболу и ей необходимо сформировать команду для участия в соревнованиях. Для каждого сотрудника была проведена оценка его способностей для той позиции, в которой они лучше всего показали себя. Они уже отсортированы по набранным баллам
var evaluations = [
{name: "John", position: "pitcher", score: 13},
{name: "Jane", position: "catcher", score: 10},
{name: "Harry", position: "pitcher", score: 5},
...
];д
Ваша задача — сформировать ростер компании, который должен выглядеть так:
var roster = {
"pitcher": "John",
"catcher": "Jane",
"first base": "Ellen"
};
Вариант решения
var roster = reduce(evaluations, {}, function(roster, evaluation) {
var position = evaluation["position"];
if(roster[position])
return roster;
return setObject(roster, position, evaluation["name"]);
});
Для участия в соревнованиях необходимо провести оценку возможностей сотрудников для каждой из позиции в игре. Для решения этой задачи для одного человека у нас есть метод recommendedPosition(name)
, которому передаётся имя сотрудника и который возвращает подходящую позицию
Вам был предоставлен список имён сотрудников. На его основании необходимо сформировать список рекомендованных позиций в формате
{
name: "Jane",
position: "Catcher"
}
var employeeNames = ["John", "Jane", "Harry", ...];
Вариант решения
var positions = map(employeeNames, function(name) {
return {name: name, position: recommendedPosition(name)};
});
После определения наилучшей позиции нам необходимо оценить навык конкретно для данной позиции, чтобы найти наиболее подходящего сотрудника для каждой позиции. Чем выше полученный бал, тем больше этот сотрудник подходит для данной позиции
Для оценки пригодности сотрудника есть метод scorePlayer(name, position)
, который возвращает балл
Вам предоставили список из рекомендованных позиций (из предыдущей практики). На его основе необходимо подготовить расширенный список, включающий информацию как о позиции, так и баллы
{ name: "Jane", position: "catcher", 10 }
var recommendations = [{name: "Jane", position: "catcher"},
{name: "John", position: "pitcher"}, ...];
Вариант решения
var evaluations = map(recommendations, function(recommendation) {
return objectSet(recommendation, "score",
scorePlayer(recommendation.name, recommendation.position));
});
Теперь необходимо объединить все три предыдущие практики в рамках одно функционирующей системы. На вход ей подаётся список сотрудников, из которых нужно сформировать хорошо функционирующую команду
Помимо разработанных методов также потребуются методы
sortBy(array, f)
— возвращает копию массива, отсортированного по признаку формируемому переданной функциейreverse(array)
— возвращает копию массива, в которой элементы находятся в обратном порядкеvar employeeNames = ["John", "Harry", "Jane", ...];
Вариант решения
var employeeNames = ["John", "Harry", "Jane", ...];
var recommendations = map(employeeNames, function(name) {
return {name: name, position: recommendedPosition(name)};
});
var evaluations = map(recommendations, function(recommendation) {
return objectSet(position, "score": scorePlayer(recommendation.name, recommendation.position));
});
var evaluationsAscending = sortBy(evaluations, function(evaluation) {
return evaluation.score;
});
var evaluationsDescending = reverse(evaluationsAscending);
var roster = reduce(evaluationsDescending, {}, function(roster, evaluation) {
var position = evaluation.position;
if(roster[position])
return roster;
return objectSet(roster, position, evaluation.name);
});
Зачастую обработку информации с помощью функциональных инструментов оформляют в виде цепочки вызовов методов, которые находятся один под другим. Длинная цепочка не только радует глаз программиста, но также показывает, что функциональные инструменты используются верно
Рассмотрим решение задачи вычисления среднего значения на разных языках
function movingAverage(numbers) {
return numbers
.map((_e, i) => numbers.slice(i, i + window))
.map(average);
}
function movingAverage(numbers) {
return _.chain(numbers)
.map(function(_e, i) { return numbers.slice(i, i + window) }
.map(average)
.value();
}
public static double average(List<Double> numbers) {
return numbers.
.stream()
.reduce(0.0, Double::sum) / numbers.size();
}
public static List<Double> movingAverage(List<Double> numbers) {
return IntStream
.range(0, numbers.size())
.mapToObj(i -> numbers.subList(i, Math.min(i + 3, numbers.size())))
.map(Utils::average)
.collect(Collectors.toList());
}
public static IEnumerable<Double> movingAverage(IEnumerable<Double> numbers) {
return Enumerable
.Range(0, numbers.Count())
.Select(i => numbers.ToList().GetRange(i, Math.Min(3, numbers.Count() - i)))
.Select(l => l.Average());
}
В данной лекции мы рассмотрели процесс построения цепочек из функций для обработки данных. На каждом этапе происходит преобразование данных в форму, которая чуть ближе к желаемому финальному результату