【问题标题】:Transforming a source flat JavaScript array in a nested array [duplicate]在嵌套数组中转换源平面 JavaScript 数组 [重复]
【发布时间】:2021-11-21 02:52:20
【问题描述】:

我有一个源数组如下:

 var source = [
     {
        node : 1, 
        text : "pen",
        parent : null 
     },
     {
        node : 11, 
        text : "pencil",
        parent : 1
     },
     {
         node : 12, 
         text : "mango",
         parent : 1
     },
     {
          node : 111, 
          text : "mango",
          parent : 11
     },
     {
            node : 112, 
           text : "banana",
           parent : 11
     },
      {
            node : 211, 
            text : "Cilli",
            parent : 12
     },
     {
           node : 1111, 
           text : "banana",
           parent : 111
      },
     {
           node : 2, 
           text : "Grapes",
           parent : null
     },
     {
          node : 21, 
           text : "Mango",
          parent : 2
    },
    {
         node : 3, 
        text : "banana",
        parent : null
    },

   ]

所以你看到节点 1 的父节点为空,因此它是父节点。 此外,节点 11 的父节点是 1,因此 11 是节点 1 的子节点,依此类推。我想要一个如下创建的目标数组:我需要使用下面的数组来创建一棵树

     var target = [

{
  node : 1 ,
  children :[
    {
      node : 11,
      children : [
          {
              node : 111,
                children :[
                      {
                              node:1111,
                             children :[]
                     }]
         },
        {
          node : 112,
          children:[]
        }

      ]
    },
     {
      node : 12,
      children : []
    },

  ]
},
{
  node : 2,
  children :[
  {
    node : 21,
    children :[]
  }]
},
{
  node:3,
  children:[]
}


]

我正在尝试 array.forEach 但这并没有多大帮助

【问题讨论】:

  • 如果有机会我会再看一遍,但你应该看看 array.reduce。这就是处理此类事情的方法

标签: javascript arrays


【解决方案1】:
// using an object `childrenFor` to keep track of all `.children` arrays while we fill them.
const target = source.reduce((childrenFor, item) => {
  // adding the children array to item
  item.children = childrenFor[item.node] ??= [];

  // adding the item to the children array for its parent
  (childrenFor[item.parent] ??= []).push(item);

  //passing the cache through to the next iteration
  return childrenFor;
}, {})[null]; // selecting the list for items with `parent: null`

field ??= [] 执行“获取或创建数组”。

var source = [{
    node: 1,
    text: "pen",
    parent: null
  },
  {
    node: 11,
    text: "pencil",
    parent: 1
  },
  {
    node: 12,
    text: "mango",
    parent: 1
  },
  {
    node: 111,
    text: "mango",
    parent: 11
  },
  {
    node: 112,
    text: "banana",
    parent: 11
  },
  {
    node: 211,
    text: "Cilli",
    parent: 12
  },
  {
    node: 1111,
    text: "banana",
    parent: 111
  },
  {
    node: 2,
    text: "Grapes",
    parent: null
  },
  {
    node: 21,
    text: "Mango",
    parent: 2
  },
  {
    node: 3,
    text: "banana",
    parent: null
  },
];

const target = source.reduce((childrenFor, item) => {
  item.children = childrenFor[item.node] ??= [];
  (childrenFor[item.parent] ??= []).push(item);
  
  return childrenFor;
}, {})[null];

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

编辑以回复Mulans answer

我写 JS 有一段时间了,我花了几分钟的时间来理解这几行代码以及数据是如何流动的。

在我的团队中,仅凭这一点就可以取消某些代码的资格,因为它过于复杂,因此容易出错(我的意见)。 但我也认为这段代码过于复杂。

这是我的通用makeTree 函数版本,具有完全相同的接口,并且函数之间没有任何递归或乒乓调用:

function makeTree(input, indexer, maker, root = null) {
  const cache = new Map;
  // get or create (and return) children array for the passed key;
  const getChildrenArray = key => cache.get(key) || cache.set(key, []).get(key);

  for (let item of input) {
    getChildrenArray(indexer(item)).push(
      maker(item, getChildrenArray)
    );
  }
  
  return cache.get(root);
}

function makeTree(input, indexer, maker, root = null) {
  const cache = new Map;
  // get or create (and return) children array for the passed key;
  const getChildrenArray = key => cache.get(key) || cache.set(key, []).get(key);

  for (let item of input) {
    getChildrenArray(indexer(item)).push(
      maker(item, getChildrenArray)
    );
  }
  
  return cache.get(root);
}


const source = [
  { node: 1, text: "pen", parent: null },
  { node: 11, text: "pencil", parent: 1 },
  { node: 12, text: "mango", parent: 1 },
  { node: 111, text: "mango", parent: 11 },
  { node: 112, text: "banana", parent: 11 },
  { node: 211, text: "Cilli", parent: 12 },
  { node: 1111, text: "banana", parent: 111 },
  { node: 2, text: "Grapes", parent: null },
  { node: 21, text: "Mango", parent: 2 },
  { node: 3, text: "banana", parent: null },
];

const target = makeTree(
  source,
  item => item.parent,
  (item, getChildrenFor) => ({
    ...item,
    children: getChildrenFor(item.node)
  })
);

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

【讨论】:

    【解决方案2】:

    我们可以把makeTree写成一个有3个参数的泛型函数-

    1. 作为节点的平面列表输入
    2. 识别每个节点的父节点的函数
    3. 以所需形状构建树节点的函数
    const source =
      [{node:1,text:"pen",parent:null },{node:11,text:"pencil",parent:1},{node:12,text:"mango",parent:1},{node:111,text:"mango",parent:11},{node:112,text:"banana",parent:11},{node:211,text:"Cilli",parent:12},{node:1111,text:"banana",parent:111},{node:2,text:"Grapes",parent:null},{node:21,text:"Mango",parent:2},{node:3,text:"banana",parent:null},]
    
    const result =
      makeTree
        ( source                                                   // 1
        , (node) => node.parent                                    // 2
        , (node, next) => ({ ...node, children: next(node.node) }) // 3
        )
    
    

    makeTree 具有适当的通用性,允许它在 any 输入数组上工作。我们有一个很好的机会来了解相互递归 -

    1. many 接受许多输入并为每个调用 one
    2. one 构建一个新节点并为所有子节点调用 many

    事实证明,这种循环排列非常适合在所需输出中分支递归结构,例如树 -

    function makeTree(input, indexer, maker, root = null) {
      const index = makeIndex(input, indexer)
      const many = (all = []) => all.map(one)                             // 1
      const one = (single = {}) => maker(single, r => many(index.get(r))) // 2
      return many(index.get(root))
    }
    

    makeTree 使用了另一个泛型 makeIndex,即使对于大型输入也非常快,它使用 Map 进行快速查找 -

    function makeIndex(items, indexer) {
      const insert = (r, k, v) => r.set(k, (r.get(k) ?? []).concat([ v ]))
      return items.reduce((r, i) => insert(r, indexer(i), i), new Map)
    }
    

    我们已经完成了实施。让我们看看result -

    console.log(JSON.stringify(result, null, 2))
    

    展开下面的sn-p,在自己的浏览器中验证结果-

    function makeTree(input, indexer, maker, root = null) {
      const index = makeIndex(input, indexer)
      const many = (all = []) => all.map(one)
      const one = (single = {}) => maker(single, r => many(index.get(r)))
      return many(index.get(root))
    }
    
    function makeIndex(items, indexer) {
      const insert = (r, k, v) => r.set(k, (r.get(k) ?? []).concat([ v ]))
      return items.reduce((r, i) => insert(r, indexer(i), i), new Map)
    }
    
    const source =
      [{node:1,text:"pen",parent:null },{node:11,text:"pencil",parent:1},{node:12,text:"mango",parent:1},{node:111,text:"mango",parent:11},{node:112,text:"banana",parent:11},{node:211,text:"Cilli",parent:12},{node:1111,text:"banana",parent:111},{node:2,text:"Grapes",parent:null},{node:21,text:"Mango",parent:2},{node:3,text:"banana",parent:null},]
    
    const result =
      makeTree
        ( source
        , (node) => node.parent
        , (node, next) => ({ ...node, children: next(node.node) })
        )
    
    console.log(JSON.stringify(result, null, 2))
    [
      {
        "node": 1,
        "text": "pen",
        "parent": null,
        "children": [
          {
            "node": 11,
            "text": "pencil",
            "parent": 1,
            "children": [
              {
                "node": 111,
                "text": "mango",
                "parent": 11,
                "children": [
                  {
                    "node": 1111,
                    "text": "banana",
                    "parent": 111,
                    "children": []
                  }
                ]
              },
              {
                "node": 112,
                "text": "banana",
                "parent": 11,
                "children": []
              }
            ]
          },
          {
            "node": 12,
            "text": "mango",
            "parent": 1,
            "children": [
              {
                "node": 211,
                "text": "Cilli",
                "parent": 12,
                "children": []
              }
            ]
          }
        ]
      },
      {
        "node": 2,
        "text": "Grapes",
        "parent": null,
        "children": [
          {
            "node": 21,
            "text": "Mango",
            "parent": 2,
            "children": []
          }
        ]
      },
      {
        "node": 3,
        "text": "banana",
        "parent": null,
        "children": []
      }
    ]
    

    请参阅此 related Q&A 以获取有关如何将 makeTreemakeIndex 抽象到自己的模块中的建议。此处解释的技术将帮助您了解我们如何以及为什么以这种方式设计程序的基础知识。如果您有任何问题,请提出。

    【讨论】:

    • 这有点难嚼。对此,我已更新 my answer。我发现这不必要地复杂,但我会仔细研究相互递归。
    • 很高兴能启发您调整答案。如果你只对命令式风格有经验,那么你会在函数式风格上挣扎是有道理的。尽管您最初观察到,在函数式风格中,某些技术和规则用于使程序更健壮并消除错误的可能性。如果您看到linked Q&A,我将展示您如何以及为什么要为此选择模块化方法,它如何促进更大的代码重用和更轻松的测试。
    • 另一方面,您的修订版将所有这些捆绑到一个单一用途的函数中,其中索引或“缓存”不是通用的,并且更难应用粒度测试,并且更难确保它正在做你想做的事想象/打算 - 从另一个角度看时的不同类型的复杂性。如果您想了解有关相互递归的更多信息,我有other posts 讨论该主题。
    猜你喜欢
    • 1970-01-01
    • 2017-10-11
    • 1970-01-01
    • 1970-01-01
    • 2013-07-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多