【问题标题】:Functional way to parse nested dictionary into array of each level's elements将嵌套字典解析为每个级别元素的数组的功能方法
【发布时间】:2021-08-24 22:11:00
【问题描述】:

我想用 javascript 的函数式风格解决一个问题。

我的字典看起来像这样:

[{
    "title": "A",
    "isFinal": false,
    "children": [{
            "title": "AA",
            "isFinal": false,
            "children": [{
                    "title": "AAA",
                    "isFinal": true
                },
                {
                    "title": "AAB",
                    "isFinal": true
                }
            ]
        },
        {
            "title": "AB",
            "isFinal": false,
            "children": [{
                    "title": "ABA",
                    "isFinal": true
                },
                {
                    "title": "ABB",
                    "isFinal": true
                }
            ]
        },
        {
            "title": "AC",
            "isFinal": true
        }
    ]
}]

所以这是一棵树,它可以有多达 N (6) 层和任何节点中的更多叶子。最后一个节点将isFinal 字段设置为true。

我想要以下输出

[["A"], ["AA", "AB", "AC"], ["AAA", "AAB", "ABA", "ABB"]]

这是同一数组中同一级别上每个节点的标题。

我认为它与算法递归下降有某种关系,但我无法弄清楚。

到目前为止我可以访问第一级:


function parseData (data) {
    const data = new Array();

    return data.filter((dataLevelChild) => (!dataLevelChild.isFinal)).map((dataLevelChild, index) => (
      dataLevelChild.title
    ))
}

但我真的不知道如何传递二级元素并将所有内容存储在一个数组中。

另一种方法是使用forEach

function parseData (data) {
    const parsedData = new Array();

    const parse = (e) => {
      parsedData.push({
          id: e.title,
      });

      e.children && e.children.forEach(parse);
  }

  return data.forEach(parse);

这是不正确的,但至少我可以访问每个元素。它是否具有功能性?在我看来它不像,因为通常你不会像这样在函数式样式中使用 .push

【问题讨论】:

  • 你需要一个递归函数来处理任意嵌套。用函数式编程来做这件事会很棘手。
  • @Barmar:你为什么这么说?递归是 FP 中非常常见的技术。例如,我的答案是 FP 解决方案。
  • @ScottSauyet 我知道递归在 FP 中很常见,但将特定递归级别的所有结果放入最终结果中的适当数组元素似乎很棘手。
  • @Barmar:那是唯一让我慢下来的事情,在我通常的广度优先遍历代码之上构建它。我需要考虑一下那些额外的[ - ] 括号会去哪里。但代码最终相当干净。

标签: javascript recursion functional-programming


【解决方案1】:

您可以收集所有嵌套级别并将下一个级别的标题分配给收集数组。

const
    getTitles = data => data.reduce((r, { title, children }) => {
        (r[0] ??= []).push(title);
        if (children) getTitles(children)
            .forEach((a, i) => (r[i + 1] ??= []).push(...a));
        return r;
    }, []),
    data = [{ title: "A", isFinal: false, children: [{ title: "AA", children: [{ title: "AAA", isFinal: true }, { title: "AAB", isFinal: true }] }, { title: "AB", children: [{ title: "ABA", isFinal: true }, { title: "ABB", isFinal: true }] }, { title: "AC", isFinal: true }] }],
    result = getTitles(data);

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

另一种方法,分别收集标题和子项,并在函数的另一个调用中获取子项。

const
    getTitles = data => {
        const [titles, children] = getPairs(data);
        return children.length
            ? [titles, ...getTitles(children)]
            : [titles];
    },
    getPairs = data => data.reduce(
        ([t, c], { title, children = [] }) => [[...t, title], [...c, ...children]],
        [[], []]
    ),
    data = [{ title: "A", isFinal: false, children: [{ title: "AA", children: [{ title: "AAA", isFinal: true }, { title: "AAB", isFinal: true }] }, { title: "AB", children: [{ title: "ABA", isFinal: true }, { title: "ABB", isFinal: true }] }, { title: "AC", isFinal: true }] }],
    result = getTitles(data);

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

【讨论】:

  • 虽然这段代码一如既往地优雅,但 OP 确实特别要求提供函数式编程解决方案。在对pushforEach 的调用之间,我认为这不符合条件。
  • @ScottSauyet,请查看第二个代码 sn-p。
  • 是的,这是一种更实用的方法!
【解决方案2】:

你可以只使用递归:

function myFunc(arr = []){
  let toRet = [];
  function recursion(obj = {}, depth){
    toRet[depth] = [...(toRet[depth]|| []), obj.title];
    if(!obj.isFinal){
      obj.children.forEach(el => recursion(el, depth + 1))
    }
  }
  arr.forEach(el => recursion(el, 0))
  return toRet;
}

let input = [{
    "title": "A",
    "isFinal": false,
    "children": [{
            "title": "AA",
            "isFinal": false,
            "children": [{
                    "title": "AAA",
                    "isFinal": true
                },
                {
                    "title": "AAB",
                    "isFinal": true
                }
            ]
        },
        {
            "title": "AB",
            "isFinal": false,
            "children": [{
                    "title": "ABA",
                    "isFinal": true
                },
                {
                    "title": "ABB",
                    "isFinal": true
                }
            ]
        },
        {
            "title": "AC",
            "isFinal": true
        }
    ]
}]
console.log(myFunc(input))

【讨论】:

    【解决方案3】:

    您可以在简单的广度优先遍历上执行此操作。

    这里levelMap 将一个函数映射到所有嵌套元素上,并按级别对它们进行分组。

    getTitle 部分适用于上述从节点中提取标题的函数。

    const levelMap = (fn) => (xs = []) => 
      xs .length == 0 
        ? [] 
        : [xs .flatMap (x => [fn (x)])]
              .concat (levelMap (fn) (xs .flatMap (({children = []}) => children)))
    
    const getTitles = levelMap (x => x.title)
    
    const input = [{title: "A", isFinal: false, children: [{title: "AA", isFinal: false, children: [{title: "AAA", isFinal: true}, {title: "AAB", isFinal: true}]}, {title: "AB", isFinal: false, children: [{title: "ABA", isFinal: true}, {title: "ABB", isFinal: true}]}, {title: "AC", isFinal: true}]}]
    
    console .log (getTitles (input))
    .as-console-wrapper {max-height: 100% !important; top: 0}

    如果您不想按级别分组(我通常不会),那么您可以简单地替换

        : [xs .flatMap (x => [fn (x)])]
    

        : xs .flatMap (x => [fn (x)])
    

    (此时我可能会将其重命名为breadthFirstMap。)

    这种方法纯粹是功能性的。处处无突变,功能纯正。

    显然,您可以编写一个函数来执行此操作,并在此过程中取消fn 参数。但是levelMap 是通用的,很容易将我们的函数放在上面。

    【讨论】:

      【解决方案4】:

      如其他答案所示,递归是处理树结构的好工具。

      用简洁易读的代码解决这个代码挑战确实非常具有挑战性......我相信我的解决方案可以改进很多。

      const deepPluck = (prop, tree = []) => {
        const res = [];
      
        const drilldown = (prop, list, depth) => {    
          for (const item of list) {
            const { children = [], [prop]: plucked } = item;
      
            res[depth] = (res[depth] ?? []).concat(plucked);
            drilldown(prop, children, depth + 1);
          }
        }
      
        drilldown(prop, tree, 0);
        return res;
      }
      
      // =
      const data = [{ title: "A", isFinal: false, children: [{ title: "AA", children: [{ title: "AAA", isFinal: true }, { title: "AAB", isFinal: true }] }, { title: "AB", children: [{ title: "ABA", isFinal: true }, { title: "ABB", isFinal: true }] }, { title: "AC", isFinal: true }] }];
      
      console.log(
        JSON.stringify(deepPluck('title', data)),
      );

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-07-31
        • 1970-01-01
        • 2021-08-13
        • 2013-03-28
        • 1970-01-01
        • 2013-12-07
        • 2021-05-05
        • 1970-01-01
        相关资源
        最近更新 更多