admin管理员组

文章数量:1323714

I have this project which has a search page where I have a basic filter form. I managed to filter the data but only with one filter at a time. I can't figure the logic behind applying multiple filters in order to narrow down the data.

Example:

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.

This example simulates basically what I have in my app. As you can see, I have an array of objects. Each object has a budget and multiple categories. For the filters, let's say I apply a budget filter (which is going to be a range filter) and one category filter. How should I chain this filters together in order to filter the data properly?

I have this project which has a search page where I have a basic filter form. I managed to filter the data but only with one filter at a time. I can't figure the logic behind applying multiple filters in order to narrow down the data.

Example:

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.

This example simulates basically what I have in my app. As you can see, I have an array of objects. Each object has a budget and multiple categories. For the filters, let's say I apply a budget filter (which is going to be a range filter) and one category filter. How should I chain this filters together in order to filter the data properly?

Share Improve this question edited Oct 5, 2018 at 14:50 meta4 7361 gold badge11 silver badges25 bronze badges asked Oct 5, 2018 at 14:07 Ovidiu GOvidiu G 1,2735 gold badges25 silver badges48 bronze badges
Add a ment  | 

4 Answers 4

Reset to default 5

Each filter should have a function that can handle the check. The filterHandlers is a Map of such handlers.

The array and the object of filters are passed to applyFilters(). The method gets the keys of the filters (via Object.keys()), and iterates the items with Array.filters(). Using Array.every() the item is checked by all filters (using the relevant handler). If all checks pass, the item will be included in the resulting array.

Whenever you need to add another filter, you add another handler to the Map.

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);

This question is a great excuse for a functional programming excercise :D

Array.prototype.filter takes a predicate function. A predicate takes one argument and returns a boolean. Although javascript won't plain, it makes sense that the types of the elements in the array match the types your predicate can handle.

One predicate

You already got to the point of filtering with one predicate, but I'll include an example anyway:

// [ 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 ]

Two predicates

Now, let's say you only want the even numbers that are less than 5... How do we apply multiple filters? The most straightforward way is to filter twice:

// [ 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 ]

Any or All predicates?

Although some people will plain about efficiency (this loops 10 times), I'd actually remend this approach whenever you need the elements to pass all of multiple filters.

However, if we want to switch between being able to filter items that either all, or any of our predicates, we need another approach. Luckily, there's some and 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 ]
); 

Composing predicates

Instead of looping over arrays of predicates, we can also take a different approach. We can pose our predicates in to new ones using two small helpers, both and 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 ]
);

With these helpers, we can take any array of predicates and merge them in to one! The only thing we need to add is a "seed" so we can reduce safely:

// (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, parer = both) =>
  predicates.reduce(parer, 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 ]
);

Back to your data!

Putting it all together, you start to realize this overplicates things for simple examples

本文标签: javascriptApplying multiple filters on an arrayStack Overflow