首页 > 解决方案 > 正确过滤具有多个条件和数组的对象数组

问题描述

我正在创建一个 Todo 应用程序作为学习 PostgreSQL 和 javascript 函数式编程的练习。这个想法是可以有多个容器,每个容器都有单独的过滤器设置,每个都从 Todos 的主列表中读取,然后每个容器根据它的过滤器设置显示它的结果。这实际上都是在 React-Redux 和 Nodejs 中完成的,但我制作了下面的示例以更好地说明问题。

数据:

// Example todos
let a = {
  added: "2021-02-01T05:00:00.000Z",
  completed: null,
  description: "This is just a test.",
  name: "testing",
  tags: [],
  todo_id: 3,
}
let b = {
  added: "2021-02-01T05:00:00.000Z",
  completed: null,
  description: "This is just a test.",
  name: "testing",
  tags: ["one", "two"],
  todo_id: 4
}
let c = {
  added: "2021-02-01T05:00:00.000Z",
  completed: null,
  description: "This is just a test.",
  name: "tesin",
  tags: ["one"],
  todo_id: 5
}
let d = {
  added: "2021-02-01T05:00:00.000Z",
  completed: '2021-03-01T05:00:00.000Z',
  description: "This is just a test.",
  name: "testing",
  tags: ["two", "testing"],
  todo_id: 6
}

// Example filter_group 
// This is just to show what the initial state looks like in my React app
const filter_groups = {
  "main": {
    byTag: 'two',
    byName: '',
    byCompleted: true
  }
}

// Object.keys(filter_groups).map() passes the filter settings down as props
// Just stuck it in a variable for the example
const filter = filter_groups['main']

// Putting example Todos in array
const todo_list = [a,b,c,d]

当前的解决方案非常简单、丑陋且效率低下。它一次应用每个过滤器:

// ------- CURRENT SOLUTION ------- 

// if user is trying to filter byCompleted, then filter todo_list
// else do nothing todo_list and pass it to the next filter 
// (similar thing applies down the chain)
const byCompleted = filter.byCompleted === true ?
  todo_list.filter ( (t) => {
    return t.completed !== null
  })
  :
  todo_list


//IF user is trying to filter byTag...
const byTag = filter.byTag !== '' ?
  // THEN filter the byCompleted array above
  byCompleted.filter( (t) => {
    // IF there are tags on the Todo...
    let matches = t.tags ?
      // THEN filter for matches
      t.tags.filter( (tag) => {
        return tag === filter.byTag
      }) 
      : 
      // ELSE make this an empty array
      []

    // return any Todos with match
    return matches.length > 0
  })
  :
  byCompleted

// IF user is trying to filter byName...
const byName = filter.byName !== '' ?
  // THEN filter byTag Todos
  byTag.filter( (t) => {
    return t.name === P.filter.byName
  })
  :
  byTag

console.log(byName);

我真的很想知道是否有办法一次应用整个过滤器。在示例中,您将看到过滤器设置为byTag: 'two', byComplete: true使控制台仅打印一个结果。我过去尝试使用一个过滤器通过将返回所有带有two标签的东西。

我尝试查看文档并看到过滤器功能中还有更多内容,但它只是没有点击,而且我找不到任何非常适合我的用例的示例。

编辑:这就是代码沙盒上的全部内容:https ://codesandbox.io/s/recursing-almeida-cdz65?file=/src/index.js

标签: javascriptfunctional-programming

解决方案


您在问题中提到了函数编程。

下面的代码采用 currying 方法来解决您的过滤问题。

一旦建立了各个过滤器,将它们链接在一起就是一件简单的事情。每个过滤器函数将返回一个数组供下一个函数操作。

注意:我不得不推断一些细节。

let a = { added: "2021-02-01T05:00:00.000Z", completed: null, description: "This is just a test.", name: "testing", tags: [], todo_id: 3, } 
let b = { added: "2021-02-01T05:00:00.000Z", completed: null, description: "This is just a test.", name: "testing", tags: ["one", "two"], todo_id: 4 } 
let c = { added: "2021-02-01T05:00:00.000Z", completed: null, description: "This is just a test.", name: "tesin", tags: ["one"], todo_id: 5 } 
let d = { added: "2021-02-01T05:00:00.000Z", completed: '2021-03-01T05:00:00.000Z', description: "This is just a test.", name: "testing", tags: ["two", "testing"], todo_id: 6 } 

const filter_groups = { "main": { byTag: 'two', byName: '', byCompleted: true } }
const filter = filter_groups['main']

const todo_list = [a,b,c,d]

let byTag = td => td.tags.includes(filter.byTag);
let byName = td => filter.name ? td.tags.includes(filter.name) : true;
let byCompleted = td => filter.byCompleted ? td.completed !== null : false ;

let result = todo_list.filter(byTag).filter(byName).filter(byCompleted);

console.log(result);

编辑:OP 要求在一个代码块中执行此操作。该解决方案如下:

let a = { added: "2021-02-01T05:00:00.000Z", completed: null, description: "This is just a test.", name: "testing", tags: [], todo_id: 3, };
let b = { added: "2021-02-01T05:00:00.000Z", completed: null, description: "This is just a test.", name: "testing", tags: ["one", "two"], todo_id: 4 }; 
let c = { added: "2021-02-01T05:00:00.000Z", completed: null, description: "This is just a test.", name: "tesin", tags: ["one"], todo_id: 5 }; 
let d = { added: "2021-02-01T05:00:00.000Z", completed: '2021-03-01T05:00:00.000Z', description: "This is just a test.", name: "testing", tags: ["two", "testing"], todo_id: 6 }; 

const todo_list = [a,b,c,d];

const filter_groups = { "main": { tags: 'two', name: '', completed: true } };

let result2 = todo_list.filter(td=>{
  return Object.entries(filter_groups['main'])
    .filter(([k,v])=>!!v!=false)
    .map(f=>!!td[f[0]])
    .every(f=>f==true);
});

console.log(result2);

OP 还在他的原始代码和我的“三步”解决方案中提到了对性能的担忧。

为了让您有信心按照您通常的方式编写代码,我使用 jsben.ch.com 进行了一系列测试。下图显示了 OP 的原始代码始终位于 #2 或 #1。“三步”解决方案分享了这一结果。底线是更多的步骤并不一定等同于较差的性能。如果您可以让站点合作,请多次运行测试以了解性能差异。JSBEN.CH 测试

对这些结果持保留态度- 有利于显示比较,但仅此而已。

在此处输入图像描述


推荐阅读