Этот вопрос является отличным оправданием для упражнения по функциональному программированию: D
Array.prototype.sort
принимает функцию предиката .Предикат принимает один аргумент и возвращает логическое значение.Хотя javascript не будет жаловаться, имеет смысл, что типы элементов в массиве соответствуют типам, которые может обрабатывать ваш предикат.
Один предикат
Вы уже приступили к фильтрации содин предикат, но я все равно приведу пример:
// [ number ]
const numbers = [ 1, 2, 3, 4, 5, 6 ];
// number -> bool
const lt5 = x => x < 5;
// [ number ] -> (number -> bool) -> [ number ]
const result = numbers.filter(lt5);
console.log(result); // [ 1, 2, 3, 4 ]
Два предиката
Теперь предположим, что вам нужны только числа даже , которые меньше 5 ... Как мыприменить несколько фильтров?Самый простой способ - отфильтровать дважды:
// [ number ]
const numbers = [ 1, 2, 3, 4, 5, 6 ];
// number -> bool
const lt5 = x => x < 5;
// number -> bool
const even = x => x % 2 === 0;
const result = numbers
.filter(lt5) // [ 1, 2, 3, 4 ]
.filter(even);
console.log(result); // [ 2, 4 ]
Любые или все предикаты?
Хотя некоторые люди будут жаловаться на эффективность (это повторяется 10 раз), я на самом деле рекомендую этот подход всякий раз, когда вам нужноэлементы для передачи всех нескольких фильтров.
Однако, если мы хотим переключиться между возможностью фильтрации элементов, которые либо all , либо any наших предикатов, нам нужен другой подход.К счастью, есть some
и every
!
// [ number ]
const numbers = [ 1, 2, 3, 4, 5, 6 ];
// number -> bool
const lt5 = x => x < 5;
// number -> bool
const even = x => x % 2 === 0;
// number -> bool
const lt5_OR_even = x => [lt5, even].some(f => f(x));
// number -> bool
const lt5_AND_even = x => [lt5, even].every(f => f(x));
console.log(
numbers.filter(lt5_OR_even) // [ 1, 2, 3, 4, 6 ]
);
console.log(
numbers.filter(lt5_AND_even) // [ 2, 4 ]
);
Составление предикатов
Вместо циклического перебора массивов предикатов мы также можем использовать другой подход.Мы можем составить наши предикаты в новые, используя двух маленьких помощников, both
и either
:
// (a -> bool) -> (a -> bool) -> a -> bool
const both = (f, g) => x => f(x) && g(x);
// (a -> bool) -> (a -> bool) -> a -> bool
const either = (f, g) => x => f(x) || g(x);
const numbers = [ 1, 2, 3, 4, 5, 6 ];
const lt5 = x => x < 5;
const even = x => x % 2 === 0;
console.log(
numbers.filter(either(lt5, even)) // [ 1, 2, 3, 4, 6 ]
);
console.log(
numbers.filter(both(lt5, even)) // [ 2, 4 ]
);
С этими помощниками мы можем взять любой массив предикатов и объединить их в один!Единственное, что нам нужно добавить - это «семя», чтобы мы могли reduce
безопасно:
// (a -> bool) -> (a -> bool) -> a -> bool
const both = (f, g) => x => f(x) && g(x);
// (a -> bool) -> (a -> bool) -> a -> bool
const either = (f, g) => x => f(x) || g(x);
// any -> bool
const True = _ => true;
const Filter = (predicates, comparer = both) =>
predicates.reduce(comparer, True);
const myPred = Filter([
x => x > 5,
x => x < 10,
x => x % 2 === 0
]);
console.log(
[1,2,3,4,5,6,7,8,9,10,11].filter(myPred) // [ 6, 8 ]
);
Назад к вашим данным!
Собрав все это вместе, вы начинаете понимать, что это усложняет вещи для простых примеров ?.Тем не менее, все еще интересно посмотреть, как мы можем использовать и комбинировать функциональные тестируемые функции единственного назначения.
const both = (f, g) => x => f(x) && g(x);
const either = (f, g) => x => f(x) || g(x);
const True = _ => true;
const gte = min => x => +x >= +min;
const lte = max => x => +x <= +max;
const overlap = (xs, ys) => xs.some(x => ys.includes(x));
const Filter = (predicates, comparer = both) =>
predicates.reduce(comparer, True);
const BudgetFilter = ([min, max]) => ({ budget }) =>
Filter([ gte(min), lte(max) ]) (budget);
const CategoryFilter = allowed => ({ categories }) =>
overlap(allowed, categories);
const EventFilter = (cfg, opts) => Filter(
Object
.entries(opts)
.map(([k, v]) => cfg[k](v))
);
// App:
const filterConfig = {
budget: BudgetFilter,
categories: CategoryFilter
};
const cheapPartyFilter = EventFilter(
filterConfig,
{
budget: ["200", "500"],
categories: ["party"]
}
);
let data = [{ budget: "220", categories: ["party", "school"] }, { budget: "450", categories: ["self"] }, { budget: "600", categories: ["dev", "work", "party"] }];
console.log(data.filter(cheapPartyFilter));