有没有办法过滤具有多个动态条件的对象数组

Is there a way to filter an array of objects with multiple dynamic conditions

我有一个对象数组 options 类似于:

const options = [
    {
        "apiName": "tomato",
        "category": "veggie",
        "color": "red",
        "price": "90"
    },
    {
        "apiName": "banana",
        "category": "fruit",
        "color": "yellow",
        "price": "45"
    },
    {
        "apiName": "brinjal",
        "category": "veggie",
        "color": "violet",
        "price": "35"
    },
]

我想使用类似于

的过滤条件对象(动态生成)来过滤这个数组
Example filterGroup 1
let filterGroup = {
      type: 'and',
      filters: [
        {
          key: 'category',
          condition: 'is',
          value: 'veggie'
          type: 'filter'

        },
        {
          key: 'price',
          condition: 'is less than',
          value: '45',
          type: 'filter'
        }
      ]
    }

Example filterGroup 2
let filterGroup = {
      key: 'category',
      condition: 'is',
      value: 'veggie'
      type: 'filter'
    }

在上面的 filterGroup 对象中,过滤器数组中的每个元素充当单独的过滤器,options 中的每个 option 都应该满足这些过滤器。 condition 的可能值为 isis notis less thanis greater than.

如何使用 JavaScript 以最有效的方式使用 conditions 对象过滤 options 数组?

我尝试过的(REPL Link - https://replit.com/@pcajanand/DarkseagreenEnlightenedTests#index.js),

制作了一些过滤功能的创作者

const eq = (propertyAccessKey, compareValue) => (item) => (item[propertyAccessKey] === compareValue)
const ne = (propertyAccessKey, compareValue) => (item) => (item[propertyAccessKey] === compareValue)
const lt = (propertyAccessKey, compareValue) => (item) => (item[propertyAccessKey] < compareValue)
const gt = (propertyAccessKey, compareValue) => (item) => (item[propertyAccessKey] > compareValue)

创建了一个函数来创建带有单个过滤器(type = filter)的过滤器函数

const makeFilterFunction = ({condition, value, key}) => {
      if (condition === 'is') {
      return (eq(key, value))
    } else if (condition === 'is greater than') {
      return (gt(key, value))
    } else if (condition === 'is less than') {
      return (lt(key, value))
    } else if (condition === 'is not') {
      return (ne(key, value))
    }
}

创建过滤器函数并将它们推入数组,

let fnArray = []
if (filters.type === 'and') {
  filters.filters.forEach((filter) => {
    fnArray.push(makeFilterFunction(filter))
  })
} else if (filters.type === 'filter') {
  fnArray.push(makeFilterFunction(filters))
}

遍历每个选项,检查每个过滤条件,然后将传递所有条件的项目作为过滤结果推送到数组。

const res = opts.reduce((acc, next) => {
  let fnIndex = 0
  let fnArrayLength = fnArray.length
  let itemPassed = true
  while(fnIndex < fnArrayLength) {
    const fnPassed = fnArray[fnIndex](next)
    if (!fnPassed) {
      itemPassed = false
      break
    }
    fnIndex += 1
  }
  if (itemPassed) {
    return acc.concat(next)
  } else {
    return acc
  }
}, [])

虽然这可行(我认为?),但我想知道是否有其他更有效的方法来执行此操作。或者,如果我完全遗漏了某些东西并使事情过于复杂。

TLDR - 想要过滤具有多个链接条件的对象数组。

这里不是英语母语者,如果问题有歧义,请见谅。 感谢阅读!

你可以稍微简化一下,这里有一个例子:

const options = [{
    "apiName": "tomato",
    "category": "veggie",
    "color": "red",
    "price": "90"
  },
  {
    "apiName": "banana",
    "category": "fruit",
    "color": "yellow",
    "price": "45"
  },
  {
    "apiName": "brinjal",
    "category": "veggie",
    "color": "violet",
    "price": "35"
  },
];

const filterGroup1 = {
  type: 'and',
  filters: [{
      key: 'category',
      condition: 'is',
      value: 'veggie',
      type: 'filter'

    },
    {
      key: 'price',
      condition: 'is less than',
      value: '45',
      type: 'filter'
    }
  ]
}

const filterGroup2 = {
  key: 'category',
  condition: 'is',
  value: 'veggie',
  type: 'filter'
}

const filterFunConstructor = {
"is": (propertyAccessKey, compareValue) => (item) => (item[propertyAccessKey] === compareValue),
"is not": (propertyAccessKey, compareValue) => (item) => (item[propertyAccessKey] !== compareValue),
"is less than": (propertyAccessKey, compareValue) => (item) => (item[propertyAccessKey] < compareValue),
"is greater than": (propertyAccessKey, compareValue) => (item) => (item[propertyAccessKey] > compareValue)
}

const process = (options, filterGroup) => {
  let filterFun;
  if (filterGroup.type === 'and') {
    filterFun = filterGroup.filters.reduce((a, c) => (a.push(filterFunConstructor[c.condition](c.key, c.value)), a),[]);
  } else {
    filterFun = [filterFunConstructor[filterGroup.condition](filterGroup.key, filterGroup.value)]
  }
  return options.filter((v) => filterFun.every((fn) => fn(v)));
}

console.log(process(options, filterGroup1));
console.log(process(options, filterGroup2));

这样做的目的是使用filterGroup创建一个函数数组,然后过滤options数组以查看其中的项目是否会return true 当 运行 通过所有这些功能时。

您可以构建函数并过滤数据。这种方法具有嵌套搜索条件。

使用 type: 'and' 过滤的小视图:

带条件的过滤会返回一个函数,该函数稍后用作过滤的回调。这意味着它从 options 中获取一个对象并根据给定条件和移交的数据执行检查,既来自过滤器也来自选项的对象。

现在 and,您需要不止一个函数,并且在所有函数 return true 中,对象应该在结果集中。

要检查多个功能,Array#every cones in handy by checking all items and return either true, if all conditions are true or [=22] =],如果一个条件returns false。在这种情况下,迭代也会中断。

让我们看一下 returned 函数:

(c => o => c.every(fn => fn(o)))(filters.map(filterBy))

它是 c 上的闭包,其值为所有需要的过滤条件

(c =>                          )(filters.map(filterBy))

最终 returned 函数是内部部分

      o => c.every(fn => fn(o))

其中使用来自 options.

的对象获取并调用每个约束函数

const
    conditions = {
        'is': (a, b) => a === b,
        'is less than': (a, b) => a < b
    },
    options = [{ apiName: "tomato", category: "veggie", color: "red", price: "90" }, { apiName: "banana", category: "fruit", color: "yellow", price: "45" }, { apiName: "brinjal", category: "veggie", color: "violet", price: "35" }],
    filterGroup = { type: 'and', filters: [{ key: 'category', condition: 'is', value: 'veggie', type: 'filter' }, { key: 'price', condition: 'is less than', value: '45', type: 'filter' }] },
    filterGroup2 = { key: 'category', condition: 'is', value: 'veggie', type: 'filter' },
    filterBy = ({ type, filters, key, condition, value}) => {
        if (type === 'filter') return o => conditions[condition](o[key], value);
        if (type === 'and') return (c => o => c.every(fn => fn(o)))(filters.map(filterBy));
    };

console.log(options.filter(filterBy(filterGroup)));
console.log(options.filter(filterBy(filterGroup2)));
.as-console-wrapper { max-height: 100% !important; top: 0; }

您实质上是在实现一种领域特定语言,您需要在其中将语言表达式转换为可运行的程序。对于这种特定的语言,我们希望将表达式从普通 JavaScript 对象转换为 JavaScript 函数 -

function evaluate(expr) {
  switch (expr?.type) {
    case "filter":
      return v => evaluateFilter(v, expr)
    case "and":
      return v => expr.filters.every(e => evaluate(e)(v))
    case "or":
      return v => expr.filters.some(e => evaluate(e)(v))
  //case ...:
  //  implement any other filters you wish to support
    default:
      throw Error(`unsupported filter expression: ${JSON.stringify(expr)}`)
  }
}

然后我们将生成的函数直接插入 Array.prototype.filter。基本用法如下所示 -

myinput.filter(evaluate({ /* your domain-specific expression here */ })

接下来,evaluateFilter就是你已经写好的底层函数。这里它被实现为一个单一的功能,但如果你愿意,你可以将它更多地分开 -

function evaluateFilter(t, {key, condition, value}) {
  switch (condition) {
    case "is":
      return t?.[key] == value
    case "is greater than":
      return t?.[key] > value
    case "is less than":
      return t?.[key] < value
    case "is not":
      return t?.[key] != value
  //case ...:
  //  implement other supported conditions here
    default:
      throw Error(`unsupported filter condition: ${condition}`)
  }
}

给定一些 input 例如 -

const input = [
  { type: "fruit", name: "apple", count: 3 },
  { type: "veggie", name: "carrot", count: 5 },
  { type: "fruit", name: "pear", count: 2 },
  { type: "fruit", name: "orange", count: 7 },
  { type: "veggie", name: "potato", count: 3 },
  { type: "veggie", name: "artichoke", count: 8 }
]

我们现在可以用一个过滤器编写简单的表达式 -

input.filter(evaluate({
  type: "filter",
  condition: "is",
  key: "type", value: "fruit"
}))
[
  {
    "type": "fruit",
    "name": "apple",
    "count": 3
  },
  {
    "type": "fruit",
    "name": "pear",
    "count": 2
  },
  {
    "type": "fruit",
    "name": "orange",
    "count": 7
  }
]

或使用 and and/or or -

组合多个过滤器的丰富表达式
input.filter(evaluate({
  type: "and",
  filters: [
    {
      type: "filter",
      condition: "is not",
      key: "type",
      value: "fruit"
    },
    {
      type: "filter",
      condition: "is greater than",
      key: "count",
      value: "3"
    }
  ]
}))
[
  {
    "type": "veggie",
    "name": "carrot",
    "count": 5
  },
  {
    "type": "veggie",
    "name": "artichoke",
    "count": 8
  }
]

计算器是递归的,因此您可以以任何可以想象的方式组合 and and/or or -

input.filter(evaluate({
  type: "or",
  filters: [
    {
      type: "filter",
      condition: "is less than",
      key: "count",
      value: 3
    },
    {
      type: "and",
      filters: [
        {
          type: "filter",
          condition: "is not",
          key: "type",
          value: "fruit"
        },
        {
          type: "filter",
          condition: "is greater than",
          key: "count",
          value: "3"
        }
      ]
    }
  ]
}))
[
  {
    "type": "veggie",
    "name": "carrot",
    "count": 5
  },
  {
    "type": "fruit",
    "name": "pear",
    "count": 2
  },
  {
    "type": "veggie",
    "name": "artichoke",
    "count": 8
  }
]

展开代码段以在您自己的浏览器中验证结果 -

function evaluate(expr) {
  switch (expr?.type) {
    case "filter":
      return v => evaluateFilter(v, expr)
    case "and":
      return v => expr.filters.every(e => evaluate(e)(v))
    case "or":
      return v => expr.filters.some(e => evaluate(e)(v))
    default:
      throw Error(`unsupported filter expression: ${JSON.stringify(expr)}`)
  }
}

function evaluateFilter(t, {key, condition, value}) {
  switch (condition) {
    case "is":
      return t?.[key] == value
    case "is greater than":
      return t?.[key] > value
    case "is less than":
      return t?.[key] < value
    case "is not":
      return t?.[key] != value
    default:
      throw Error(`unsupported filter condition: ${condition}`)
  }
}

const input = [
  { type: "fruit", name: "apple", count: 3 },
  { type: "veggie", name: "carrot", count: 5 },
  { type: "fruit", name: "pear", count: 2 },
  { type: "fruit", name: "orange", count: 7 },
  { type: "veggie", name: "potato", count: 3 },
  { type: "veggie", name: "artichoke", count: 8 }
]

console.log(input.filter(evaluate({
  type: "filter",
  condition: "is",
  key: "type", value: "fruit"
})))

console.log(input.filter(evaluate({
  type: "and",
  filters: [
    {
      type: "filter",
      condition: "is not",
      key: "type",
      value: "fruit"
    },
    {
      type: "filter",
      condition: "is greater than",
      key: "count",
      value: "3"
    }
  ]
})))

console.log(input.filter(evaluate({
  type: "or",
  filters: [
    {
      type: "filter",
      condition: "is less than",
      key: "count",
      value: 3
    },
    {
      type: "and",
      filters: [
        {
          type: "filter",
          condition: "is not",
          key: "type",
          value: "fruit"
        },
        {
          type: "filter",
          condition: "is greater than",
          key: "count",
          value: "3"
        }
      ]
    }
  ]
})))