在数组上应用多个过滤器

时间:2018-10-05 14:07:30

标签: javascript arrays filter

我有一个项目,该项目有一个搜索页面,其中有一个基本的过滤器表单。我设法过滤数据,但一次只能过滤一个。我无法弄清楚应用多个过滤器以缩小数据范围的逻辑。

示例:

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 :(得分:2)

这个问题对于函数式编程练习: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 时,我实际上会推荐这种方法。

但是,如果我们想在能够过滤谓词 all any 的项之间切换,我们需要另一种方法。幸运的是,有someevery

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

组成谓词

除了遍历谓词数组之外,我们还可以采用其他方法。我们可以使用两个小的助手botheither将谓词组成到新的谓词中:

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

答案 1 :(得分:1)

您可以使用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);

答案 2 :(得分:1)

每个过滤器应具有可以处理检查的功能。 filterHandlers是此类处理程序的Map

数组和过滤器的对象传递到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);

答案 3 :(得分:0)

正如上面有人建议的那样,它具有出色的功能编程技巧。这就是我的看法。可能有点难以理解。每个人说的并不是纯粹的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))