有没有办法过滤具有多个动态条件的对象数组
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
的可能值为 is
、is not
、is less than
和 is 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"
}
]
}
]
})))
我有一个对象数组 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
的可能值为 is
、is not
、is less than
和 is 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"
}
]
}
]
})))