簡體   English   中英

有沒有辦法過濾具有多個動態條件的對象數組

[英]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"
    },
]

我想使用過濾條件 object(動態生成)過濾這個數組,類似於

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 object 中,過濾器數組中的每個元素都充當單獨的過濾器, options中的每個option都應滿足。 condition的可能值isis notis less thanis greater than

如何使用 JavaScript 以最有效的方式使用conditions object 過濾options數組?

我嘗試過的(REPL 鏈接 - https://replit.com/@pcajanand/DarkseagreenEnlightenedTests#index.js ),

制作了一些過濾器 function 創作者

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)

制作了 function 以創建帶有單獨過濾器的過濾器 function(類型 = 過濾器)

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 - 想要過濾具有多個鏈接條件的對象數組。

這里的非英語母語人士,如果問題含糊不清,請見諒。 謝謝閱讀!

您實際上是在實現一種特定於領域的語言,您需要將語言表達式轉換為可運行的程序。 對於這種特定的語言,我們希望將表達式從普通的 JavaScript 對象轉換為 JavaScript function -

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)}`)
  }
}

然后我們將生成的 function 直接插入到Array.prototype.filter中。 基本用法如下所示 -

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

接下來, evaluateFilter是您已經編寫的低級 function。 在這里,它被實現為單個 function,但如果您願意,可以將其分開更多 -

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和/或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 / 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" } ] } ] })))

您可以稍微簡化一下,這是一個示例:

 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數組以查看其中的項目在運行所有這些函數時是否會返回true

您可以構建函數並過濾數據。 這種方法具有嵌套搜索條件。

使用type: 'and'進行過濾的小視圖:

帶有條件的過濾返回一個 function,它稍后用作過濾的回調。 這意味着它從options中獲取一個 object,並根據給定條件和移交的數據執行檢查,這既來自過濾器,也來自選項的 object。

現在對於and ,您需要多個 function 並且所有函數都返回true , object 應該在結果集中。

要檢查多個 function, Array#every cones 通過檢查所有項目並返回true ,如果所有條件為truefalse ,如果一個條件返回false 在這種情況下,迭代也會中斷。

我們來看看返回的 function:

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

它是c的閉包,具有所有需要的過濾條件的值

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

最后返回的 function 是內部

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

其中每個約束 function 都被采用並使用options中的 object 調用。

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM