【问题标题】:Recursive filtering of array of objects without child nodes没有子节点的对象数组的递归过滤
【发布时间】:2019-08-03 18:37:41
【问题描述】:

我想过滤一个对象数组,递归地基于过滤值并过滤掉“空节点”的输入数组。

这是一个例子:

const nodes = [{
  value: 'World',
  children: [{
    value: 'Europe',
    children: [{
      value: 'BeNeLux',
      children: [{
        value: 'NL'
      }, {
        value: 'DE'
      }, {
        value: 'LU'
      }]
    }, {
      value: 'AL'
    }, {
      value: 'ZW'
    }, {
      value: 'UK' // lol
    }]
  }]
}]

在上面的数组中,节点有:value 和可选的children

我想要一个函数,我提供一个输入数组,该数组应该过滤上面列出的 nodes 数组的值 + 子项,所以:

const valuesToBeRemoved = ['NL', 'DE', 'LU', 'ZW', 'UK']

那么预期的输出应该是:

const expectedNodesOutput = [{
  value: 'World',
  children: [{
    value: 'Europe',
    children: [{
      value: 'AL'
    }]
  }]
}]

这是我尝试过的,但它不能正常工作,因为它没有正确递归,它应该过滤掉空节点(也很好奇其他人如何提出可能更清洁的解决方案):

const filterNodes = (filter, node) => {
  if (node.children) {
    const children = node.children
      .map(child => filterNodes(filter, child))
      .filter(x => x)

    if (children.length) {
      return {
        ...node,
        children
      }
    }
  } else {
    if (!filter.includes(node.value)) {
      return node
    }
  }
}

const getFilteredNodes = (filter, nodes) => {
  return filterNodes(filter, { children: nodes })?.children || []
}

这是一个代码笔:https://codepen.io/anon/pen/KOXBLe

【问题讨论】:

  • @alfasin 不确定,代码没有按预期工作
  • @alfasin 不,不是。代码审查要求现有代码按预期工作。问题中明确指出现有代码没有。
  • 然后澄清你的问题,现在你问“是否会有更清洁/更有效的方式”,这意味着这段代码有效。
  • 我没有投反对票,但老实说,我认为这个问题在清理之前不应该被投赞成票......
  • @dfhwze 它本可以阻止本网站上一半的问题 LoL :)

标签: javascript recursion filtering


【解决方案1】:

filter 不尊重嵌套属性,否则,您需要改变给定的数据结构。

相反,您可以通过查看子节点来减少节点结构,如果返回非空数组,则也取实际值。

function filter(array, remove) {
    return array.reduce((r, { value, children }) => {
        if (remove.includes(value)) return r;
        if (!children) {
            r.push({ value });
            return r;
        }
        children = filter(children, remove);
        if (children.length) r.push({ value, children });
        return r;
    }, []);
}

var nodes = [{ value: 'World', children: [{ value: 'Europe', children: [{ value: 'BeNeLux', children: [{ value: 'NL' }, { value: 'DE' }, { value: 'LU' }] }, { value: 'AL' }, { value: 'ZW' }, { value: 'UK' }] }] }],
    remove = ['NL', 'DE', 'LU', 'ZW', 'UK'],
    result = filter(nodes, remove);

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

【讨论】:

    【解决方案2】:

    这是一种从递归结构中删除值和修剪空数组的不可变方法。它们需要单独执行,以便修剪尊重由删除值引起的修改:

    function remove (nodes, values) {
      return nodes.map(
        ({ value, children }) => children
          ? { value, children: remove(children, values) }
          : { value }
      ).filter(
        ({ value }) => !values.includes(value)
      );
    }
    
    function prune (nodes) {
      return nodes.map(
        ({ value, children }) => children
          ? { value, children: prune(children) }
          : { value }
      ).filter(
        ({ children }) => !children || children.length > 0
      );
    }
    
    const nodes = [{
      value: 'World',
      children: [{
        value: 'Europe',
        children: [{
          value: 'BeNeLux',
          children: [{
            value: 'NL'
          }, {
            value: 'DE'
          }, {
            value: 'LU'
          }]
        }, {
          value: 'AL'
        }, {
          value: 'ZW'
        }, {
          value: 'UK'
        }]
      }]
    }];
    
    const valuesToBeRemoved = ['NL', 'DE', 'LU', 'ZW', 'UK'];
    
    console.log(prune(remove(nodes, valuesToBeRemoved)));

    您甚至可以通过使用高阶函数来定义remove()prune(),使代码更加干燥:

    const recurseBy = key => filter => function callback (nodes, ...args) {
      return nodes.map(
        ({ [key]: nodes, ...rest }) => nodes
          ? { ...rest, [key]: callback(nodes, ...args) }
          : { ...rest }
      ).filter(
        item => filter(item, ...args)
      );
    };
    
    const filterBy = recurseBy('children');
    const remove = filterBy(({ value }, values) => !values.includes(value));
    const prune = filterBy(({ children }) => !children || children.length > 0);
    
    const nodes = [{
      value: 'World',
      children: [{
        value: 'Europe',
        children: [{
          value: 'BeNeLux',
          children: [{
            value: 'NL'
          }, {
            value: 'DE'
          }, {
            value: 'LU'
          }]
        }, {
          value: 'AL'
        }, {
          value: 'ZW'
        }, {
          value: 'UK'
        }]
      }]
    }];
    
    const valuesToBeRemoved = ['NL', 'DE', 'LU', 'ZW', 'UK'];
    
    console.log(prune(remove(nodes, valuesToBeRemoved)));

    我在问题中注意到您使用了可选链接语法。如果您的项目允许 ESnext(又名 TC39 提案),您可以使用管道运算符和部分应用程序语法来清理调用。

    console.log(prune(remove(nodes, valuesToBeRemoved)));
    

    等价于

    nodes
      |> remove(?, valuesToBeRemoved)
      |> prune(?)
      |> console.log(?)
    

    【讨论】:

      猜你喜欢
      • 2016-11-03
      • 2019-01-22
      • 1970-01-01
      • 1970-01-01
      • 2018-10-31
      • 1970-01-01
      • 1970-01-01
      • 2017-05-04
      • 2019-09-27
      相关资源
      最近更新 更多