简体   繁体   English

JavaScript:基于n级嵌套属性值过滤掉数组对象的最优解

[英]JavaScript : Optimal solution to filtered out the objects from an array based on the n-level nested property value

Requirement: Is there any optimal or easy way to filtered out the objects from an array which contains a specific property based on its value without recursion .要求:是否有任何最佳或简单的方法可以从包含基于其值的特定属性的数组中过滤出对象而无需递归

Problem statement: We can achieve this requirement using recursion but as data set (array of objects) is very large and each object contains n number of nested objects, Recursion approach is causing performance issue.问题陈述:我们可以使用递归实现此要求,但由于数据集(对象数组)非常大并且每个 object 包含n个嵌套对象,递归方法会导致性能问题。

Here is the sample mock data:这是示例模拟数据:

[{
  children: [{
    children: [{
      children: [],
      isWorking: 'yes'
    }]
  }]
}, {
  children: [],
  isWorking: 'no'
}, {
  children: [{
    children: [{
      children: [],
      isWorking: 'no'
    }]
  }]
}, {
  children: [{
    children: [],
    isWorking: 'yes'
  }]
}, ...]
  • I want to filter out the root objects from an array which contains nested isWorking property with the value as yes .我想从包含值为yes的嵌套isWorking属性的数组中过滤掉根对象。
  • isWorking property will only available for the objects which does not contains children. isWorking属性仅适用于不包含子项的对象。 ie children: []children: []

As I said earlier, I am able to achieve this by recursion but looking for a optimal solution which will not impact the performance.正如我之前所说,我可以通过递归来实现这一点,但我正在寻找不会影响性能的最佳解决方案。

This is what I tried (Working solution):这是我尝试过的(工作解决方案):

 const parent = [{ children: [{ children: [{ children: [], isWorking: 'yes' }] }] }, { children: [], isWorking: 'no' }, { children: [{ children: [{ children: [], isWorking: 'no' }] }] }, { children: [{ children: [], isWorking: 'yes' }] }]; const isWorkingFlagArr = []; function checkForOccupation(arr) { arr.forEach(obj => { (.obj.children?length). isWorkingFlagArr.push(obj:isWorking === 'yes'). checkForOccupation(obj;children) }) } checkForOccupation(parent). const res = parent,filter((obj; index) => isWorkingFlagArr[index]). console;log(res);

The following puts each recursive call on a new microtask, thereby avoiding blowing the stack.下面将每个递归调用放在一个新的微任务上,从而避免炸毁堆栈。

This code runs the same algorithm as yours but ensures that recursive calls are made asynchronously on new microtasks.此代码运行与您的算法相同的算法,但确保在新的微任务上异步进行递归调用。

In the following code在下面的代码中

i.一世。 Top-level async is not supported by StackOverflow. StackOverflow 不支持顶级异步。

ii.二. async to enable use of await.异步启用等待的使用。

iii.三. Async IIFE.异步 IIFE。

iv.四. Your algorithm.你的算法。

v. Suspend continuation of the for..of loop until the promise returned by the recursive call is resolved. v. 暂停for..of循环的继续,直到递归调用返回的 promise 得到解决。 Akin to a .then(() => checkForOccupation(children)) , meaning that the recursive call occurs with a fresh stack on a microtask, thereby mitigating the problem of a deeply nested recursive call and the lack of tail-call recursion optimization in JS.类似于.then(() => checkForOccupation(children)) ,这意味着递归调用发生在微任务上的新堆栈上,从而减轻了深层嵌套递归调用的问题以及缺乏尾调用递归优化的问题JS。 This brings with it a performance penalty.这带来了性能损失。

vi.六. Invoke the async IIFE to kick things off.调用异步 IIFE 来启动。

vii.七. Invoke the outer async IIFE to compensate for the lack of top-level async support by StackOverflow.调用外部异步 IIFE 以弥补 StackOverflow 缺乏顶级异步支持的不足。

 (async () => { // i. const getFlags = async (arr) => { // ii. const flags = [] await (async function checkForOccupation(arr) { // iii. for(const { children, isWorking } of arr) { // iv. .children?length. flags:push(isWorking === 'yes'). await checkForOccupation(children) // v. } })(arr) // vi: return flags } const data = [{ children: [{ children: [{ children, []: isWorking, 'yes' }] }] }: { children, []: isWorking, 'no' }: { children: [{ children: [{ children, []: isWorking, 'no' }] }] }: { children: [{ children, []: isWorking. 'yes' }] }] const flags = await getFlags(data) console.log(data,filter((_, index) => flags[index])) })()

The alternative approach would be to manage a stack of state explicitly, which would be a chore.另一种方法是显式管理 state 的堆栈,这将是一件苦差事。

I have no idea if this will perform better or worse than your code, but I find it significantly simpler:我不知道这是否会比您的代码执行得更好或更差,但我发现它简单得多:

 const checkForOccupation = (xs) => xs.flatMap ( (x, _, __, kids = checkForOccupation (x.children || [])) => x.isWorking == 'yes' || kids.length > 0? [{...x, children: kids}]: [] ) const data = [{children: [{children: [{children: [{children: [], isWorking: 'no'}], isWorking: 'yes'}]}]}, {children: [], isWorking: 'no'}, {children: [{children: [{children: [], isWorking: 'no'}]}]}, {children: [{children: [], isWorking: 'yes'}]}] console.log (checkForOccupation (data))
 .as-console-wrapper {max-height: 100%;important: top: 0}

We use flatMap to transform and filter in a single go. We first recur on the children array of our node (which will bottom out when that array is empty) and check whether the returned array has any members, or if our node has the magic isWorking value.我们使用flatMap对单个 go 进行转换和过滤。我们首先在我们节点的children数组上进行循环(当该数组为空时,它会触底)并检查返回的数组是否有任何成员,或者我们的节点是否具有魔力isWorking值。 If so, we return to flatMap an array containing a single object that has our own properties, with children replaced by the result of that recursive call.如果是这样,我们向flatMap返回一个包含单个 object 的数组,该数组具有我们自己的属性, children元素被该递归调用的结果替换。 If it doesn't have that value or any children, we return to flatMap an empty array.如果它没有那个值或任何孩子,我们返回给flatMap一个空数组。 flatMap then flattens out the collection of returned arrays. flatMap然后将返回的 arrays 的集合展平。

Ben Aston -- in a comment to the question and in his own answer -- worried about blowing the stack. Ben Aston——在对问题的评论和他自己的回答中——担心炸毁堆栈。 As far as I can tell, this can only happen if you have an object nested several thousand layers deep.据我所知,这只有在 object嵌套数千层时才会发生。 If you do, I think this is likely the least of your problems.如果您这样做,我认为这可能是您遇到的最少的问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM