Применение нескольких фильтров к массиву - PullRequest
0 голосов
/ 05 октября 2018

У меня есть проект, у которого есть страница поиска, где у меня есть базовая форма фильтра.Мне удалось отфильтровать данные, но только с одним фильтром за раз.Я не могу понять логику применения нескольких фильтров для сужения данных.

Пример:

let data = [{
    budget: "220",
    categories: ["party", "school"]
  },
  {
    budget: "450",
    categories: ["self"]
  },
  {
    budget: "600",
    categories: ["dev", "work"]
  }
];

const filters = {
  budget: ["200","500"],
  categories: ["party"]
}

//////Expected behavior:
Outputs only the first object because it's budget it's between 200 and 500 and it has "party" as it's category.

Этот пример имитирует в основном то, что есть в моем приложении.Как видите, у меня есть массив объектов.Каждый объект имеет бюджет и несколько категорий.Для фильтров, скажем, я применяю бюджетный фильтр (который будет фильтром диапазона) и фильтр одной категории.Как мне соединить эти фильтры вместе, чтобы правильно фильтровать данные?

Ответы [ 4 ]

0 голосов
/ 05 октября 2018

Как кто-то предложил выше, это отличное упражнение по программированию.Так что это мой взгляд на это.Может быть немного сложнее понять И это не просто FP, скажем так, но вы можете изменить и добавить столько фильтров к каждому, пока данные являются объектом только одного уровня.

function datafilter (datas, filters, filterops) {
 //the follwing two function convert the filters from [key, [op, operands]] to [key, filterFunction] based on your filters and filterops
 var getFilterFunctionFor = ([operation, operands])=> filterops[operation](operands)
 var filterFuncs = filters.map(([key, operation])=>[key, getFilterFunctionFor(operation)])

 //now filter the data by aplying each filterFunction to mathcing key in data 
 return datas.filter((data)=>{
    return filterFuncs.reduce((prevOpTrue, [key, applyFilterTo])=>(key in data) && prevOpTrue && applyFilterTo(data[key]), true)
 }) 
}

var datas = [{
    budget: "220",
    categories: ["party", "school"]
  },
  {
    budget: "450",
    categories: ["self"]
  },
  {
    budget: "600",
    categories: ["dev", "work"]
  }
];

var ops = {
    rangeBetween : ([min, max])=> (value)=>((value>= min)&&(value<=max)),
    contains : (key) => (list) => list.includes(key)
    }

var filters = [
    ['budget', ['rangeBetween', ["200", "500"]]],
    ['categories',['contains', 'party']]
]
console.log(datafilter(datas,filters, ops))
0 голосов
/ 05 октября 2018

Каждый фильтр должен иметь функцию, которая может обрабатывать проверку.filterHandlers - это Карта таких обработчиков.

Массив и объект фильтров передаются в applyFilters().Метод получает ключи фильтров (через Object.keys()) и перебирает элементы с помощью Array.filters().Используя Array.every(), элемент проверяется всеми фильтрами (используя соответствующий обработчик).Если все проверки пройдены, элемент будет включен в результирующий массив.

Всякий раз, когда вам нужно добавить еще один фильтр, вы добавляете другой обработчик на карту.

const filterHandlers = new Map([
  [
    'budget', 
    (val, [min, max]) => val >= min && val <= max
  ],
  [
    'categories', 
    (current, categories) => current.some( // some - at least one, every - all of them 
      (c) => categories.includes(c)
    )
  ],
  []
]);

const applyFilters = (arr, filters) => {
  const filterKeys = Object.keys(filters);
  
  return arr.filter(o => filterKeys.every((key) => {
    const handler = filterHandlers.get(key);
    
    return !handler || handler(o[key], filters[key]);
  }));
}

const data = [{"budget":"220","categories":["party","school"]},{"budget":"450","categories":["self"]},{"budget":"600","categories":["dev","work"]}];

const filters = {"budget":["200","500"],"categories":["party"]};

const result = applyFilters(data, filters);

console.log(result);
0 голосов
/ 05 октября 2018

Этот вопрос является отличным оправданием для упражнения по функциональному программированию: 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));
0 голосов
/ 05 октября 2018

Вы можете использовать filter() для фильтрации массива.Используйте && для нескольких условий.Используйте .every(), чтобы проверить, все ли элементы filters.categories находятся в текущей записи.

let data = [{
    budget: "220",
    categories: ["party", "school"]
  },
  {
    budget: "450",
    categories: ["self"]
  },
  {
    budget: "600",
    categories: ["dev", "work"]
  }
];

const filters = {
  budget: ["200", "500"],
  categories: ["party"]
}

let result = data.filter(o => filters.budget[0] <= o.budget && filters.budget >= o.budget[1] && filters.categories.every(e => o.categories.includes(e)))

console.log(result);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...