一维数组转父子嵌套数据组

场景说明

在工作中通常会遇到一维数组转多维数组的情况,比如后台动态返回router路由动态数据,地域三级联动、菜单嵌套等等。我们需要根据返回的扁平化数据进行处理

双循环找到匹配项,进行递归

let arr = [
      {
        id: 1,
        pid: 0,
        name: '父1'
      },
      {
        id: 2,
        pid: 0,
        name: '父2'
      },
      {
        id: 3,
        pid: 0,
        name: '父3'
      },
      {
        id: 4,
        pid: 1,
        name: '父1-子1'
      },
      {
        id: 5,
        pid: 1,
        name: '父1-子2'
      },
      {
        id: 6,
        pid: 5,
        name: '父1-子2-孙1'
      },
      {
        id: 7,
        pid: 2,
        name: '父2-子1'
      }
    ]
	
	// 数据处理
	function dealArr (arr) {
      arr.forEach(gitem => {
        arr.forEach(item => {
        // 判断父子嵌套
          if (gitem.id === item.pid) {
            gitem.children = gitem.children || []
            gitem.children.push(item)
          }
        })
        // 递归寻找子集的父子嵌套
        if (gitem.children && gitem.children.length > 0) {
          dealArr(gitem.children)
        }
      })
    }
	// 因为是对数组的引用处理完成后的数组还包含所有的子节点
	let newArr = []
    arr.forEach(item => {
   	//去掉所有的子节点
      if (item.pid === 0) {
        newArr.push(item)
      }
    })

对象指针查找

	function dealNewArr (arr) {
      // 找出唯一值生成指针对象
      let map = {}
      arr.forEach(item => {
        map[item.id] = item
      })
      
      // 结果数组
      let result = []

      arr.forEach(child => {
        // 找出每一个和指针对象相对应的子节点
        const mapItem = map[child.pid]
        if (mapItem) {
          (mapItem.children || (mapItem.children = [])).push(child)
        } else {
          // 处理好的数据或者没有子节点直接添加到结果数组
          result.push(child)
        }
      })
      return result
    }
<think>我们之前讨论的是将多维数组换为一维数组(扁平化),现在用户的问题相反:将一维数组换为多维数组。这通常需要指定换规则,比如按照指定维度(如二维数组的行列数)或根据特定条件(如嵌套)进行组。 参考用户提供的引用,虽然都是关于扁平化的,但我们可以借鉴其中的递归思想,反向思考。不过要注意,将一维多维通常需要额外的结构信息(例如用户指定的组大小、嵌套结构模板等)。常见的场景包括: 1. 将一维数组按照固定长度段(如每2个元素一组成二维数组)。 2. 根据一个描述嵌套结构的数组(如[2,3,2]表示第一层2个元素,第二层每元素下3个子元素,第三层每子元素下2个子元素)来构建多维数组。 3. 根据数据中的特定字段(如父子ID关系)构建树形结构(这也是一种多维数组,但通常用对象嵌套)。 由于用户提到“可能涉及递归算法”,我们将重点放在递归实现上。 根据用户需求,我们提供两种常见换场景的递归实现: 场景1:按照给定的嵌套结构模板(数组换。例如,给定结构[2, 2]表示生成一个二维数组,第一维长度为2,第二维每个子数组长度为2。 场景2:根据父子关系构建树形结构(这是一种特殊的多维数组,每个节点可能有多个子节点,形成嵌套数组)。但注意,树形结构通常用对象,但也可以看成是数组嵌套(每个节点有一个children数组)。 下面别给出示例代码: 一、按照指定结构递归生成多维数组 假设我们有输入:[1,2,3,4,5,6,7,8] 和结构 [2, 2, 2](表示三维数组,每维大小为2),则输出应为:[[[1,2],[3,4]],[[5,6],[7,8]]] 实现思路: - 递归函数接收两个参数:当前待割的一维数组和剩余的结构数组(描述后续各层的长度)。 - 如果结构数组为空,则返回当前数组(作为叶子节点)。 - 否则,取出结构数组的第一个元素n,表示当前层需要割成n个子数组。 - 计算每个子数组应该包含的元素个数:总长度除以n(注意整除)。 - 然后递归调用:将剩余的结构数组传递给下一次递归,并将当前数组割成n块,每块递归生成下一维数组。 二、根据父子关系构建树形结构(嵌套数组) 常见于将数据库查询的扁平数据(带id和parentId)换成树形结构。这里我们假设数据格式为: const flatArray = [ { id: 1, parentId: null, name: '节点1' }, { id: 2, parentId: 1, name: '节点1-1' }, { id: 3, parentId: 1, name: '节点1-2' }, { id: 4, parentId: 2, name: '节点1-1-1' }, { id: 5, parentId: null, name: '节点2' }, ]; 输出应为: [ { id: 1, name: '节点1', children: [ { id: 2, name: '节点1-1', children: [ { id:4, ... } ] }, { id: 3, ... } ] }, { id:5, ... } ] 实现思路(递归): - 递归函数:传入父节点id和节点列表,返回该父节点下的所有子节点构成的数组(每个子节点递归构建其children)。 - 边界条件:当前父节点id下没有子节点,则返回空数组。 - 步骤: 1. 从节点列表中找出所有parentId等于当前传入id的节点(即直接子节点)。 2. 遍历这些直接子节点,对每个节点递归调用构建其子节点数组(即children)。 3. 将构建好的子节点数组挂到当前节点的children属性上。 4. 返回这个节点数组。 注意:这种方法需要多次遍历,效率可能较低,对于大数据量可以考虑使用映射(Map)来优化,但递归思想不变。 下面别给出代码示例: 场景1:按给定结构换(假设结构数组的乘积等于输入数组长度) ```javascript function convertToMultiDimensionalArray(arr, structure) { // 边界条件:结构数组为空,直接返回当前数组(作为最内层) if (structure.length === 0) { return arr; } // 取出当前层需要割的份数 const currentLevelSize = structure[0]; const remainingStructure = structure.slice(1); // 计算每份的长度(注意:这里要求整除) const chunkSize = arr.length / currentLevelSize; // 数组 let result = []; for (let i = 0; i < currentLevelSize; i++) { // 取出当前块 const chunk = arr.slice(i * chunkSize, (i + 1) * chunkSize); // 递归处理剩余结构 result.push(convertToMultiDimensionalArray(chunk, remainingStructure)); } return result; } // 使用示例 const flatArray = [1, 2, 3, 4, 5, 6, 7, 8]; const structure = [2, 2, 2]; // 表示三层,每层两份 const multiArray = convertToMultiDimensionalArray(flatArray, structure); console.log(multiArray); // 输出:[[[1,2],[3,4]], [[5,6],[7,8]]] ``` 场景2:根据父子关系构建树(假设根节点的parentId为null) ```javascript function buildTree(flatArray, parentId = null) { const tree = []; // 找出当前层的所有节点(parentId为当前传入的parentId) const nodes = flatArray.filter(node => node.parentId === parentId); // 遍历这些节点,递归构建子树 for (const node of nodes) { // 为当前节点构建子树 const children = buildTree(flatArray, node.id); // 如果有子节点,则添加到当前节点的children属性 if (children.length > 0) { node.children = children; } tree.push(node); } return tree; } // 使用示例 const flatArray = [ { id: 1, parentId: null, name: '节点1' }, { id: 2, parentId: 1, name: '节点1-1' }, { id: 3, parentId: 1, name: '节点1-2' }, { id: 4, parentId: 2, name: '节点1-1-1' }, { id: 5, parentId: null, name: '节点2' }, ]; const tree = buildTree(flatArray); console.log(JSON.stringify(tree, null, 2)); ``` 注意事项: 1. 场景1的递归方法要求结构数组的乘积必须等于输入数组的长度,否则会出错。 2. 场景2的递归方法在数据量较大时可能效率不高(因为每次递归都要遍历整个数组),可以考虑先用Map存储节点索引关系来优化,但递归的思想不变。 3. 递归深度:两种方法都可能受到递归深度的限制,如果结构非常深,可能需要考虑迭代方法或者尾递归优化(但JavaScript引擎对尾递归的支持有限)。 因此,根据不同的需求,我们可以选择合适的递归换方式。 相关问题: 1. 如何优化根据父子关系递归构建树形结构的性能? 2. 使用递归将一维数组换为多维数组时,如何处理结构数组数组长度不匹配的情况? 3. 递归构建树形结构时,如何避免循环引用? 4. 除了递归,还有哪些方法可以将一维数组换为多维数组? </think>### 前端递归实现一维数组多维数组的方法 在前端开发中,将一维数组换为多维数组是常见需求,特别是在处理树形结构、表格数据或层展示时。以下是基于递归算法的实现方法,参考了站内引用中的多维数组处理思想[^1][^2][^3][^4]。 #### 1. **递归换原理** 递归换的核心是根据**组规则**逐步构建嵌套结构: - **输入参数**: - `flatArray`:原始一维数组 - `groupSize`:每层组的元素数量(可固定或动态) - `depth`:当前递归深度(控制嵌套) - **递归过程**: 1. 边界条件:剩余元素不足或达到最大深度时返回 2. 组处理:截取当前层的元素 3. 递归调用:处理剩余元素并嵌套到子数组 - **数学表达**: 设组大小为 $k$,数组长度为 $n$,则换后的维度为: $$d = \lceil \log_k n \rceil$$ #### 2. **代码实现示例** 以下是三种递归实现方式,支持不同组规则: ##### 方式1:固定组大小(参考引用[2][3]的递归思想) ```javascript function convertToMultiArray(arr, groupSize = 2, depth = 0) { // 边界条件:空数组组大小≤1 if (arr.length === 0 || groupSize <= 1) return arr; // 当前层组 const currentGroup = arr.slice(0, groupSize); // 递归处理剩余元素 const remaining = arr.slice(groupSize); const nextGroup = remaining.length > 0 ? convertToMultiArray(remaining, groupSize, depth + 1) : []; // 构建嵌套结构 return depth === 0 ? [currentGroup, ...(nextGroup.length ? [nextGroup] : [])] : [currentGroup, nextGroup]; } // 使用示例 const flatArr = [1,2,3,4,5,6,7,8]; console.log(convertToMultiArray(flatArr, 3)); // 输出: [[1,2,3], [4,5,6], [7,8]] ``` ##### 方式2:动态层结构(参考引用[4]的树形处理) ```javascript function createNestedStructure(arr, levels) { if (levels.length === 0 || arr.length === 0) return arr; const levelSize = levels[0]; const result = []; // 按当前层组 for (let i = 0; i < arr.length; i += levelSize) { const chunk = arr.slice(i, i + levelSize); // 递归处理子层 if (levels.length > 1) { result.push(createNestedStructure(chunk, levels.slice(1))); } else { result.push(chunk); } } return result; } // 使用示例:按[2,3]结构换 const data = [1,2,3,4,5,6,7,8,9,10,11,12]; console.log(createNestedStructure(data, [2, 3])); // 输出: [[[1,2,3],[4,5,6]], [[7,8,9],[10,11,12]]] ``` ##### 方式3:树形结构换(带父子关系) ```javascript function buildTree(flatArray, parentId = null, idKey = 'id', parentKey = 'parentId') { const tree = []; flatArray.forEach(item => { if (item[parentKey] === parentId) { // 递归查找子节点 const children = buildTree(flatArray, item[idKey], idKey, parentKey); if (children.length > 0) item.children = children; tree.push(item); } }); return tree; } // 使用示例 const flatData = [ { id: 1, parentId: null }, { id: 2, parentId: 1 }, { id: 3, parentId: 1 }, { id: 4, parentId: 2 } ]; console.log(buildTree(flatData)); /* 输出: [ { id: 1, children: [ { id: 2, children: [{ id: 4 }] }, { id: 3 } ] } ] */ ``` #### 3. **关键参数说明** | 参数 | 说明 | 示例 | |------|------|------| | `groupSize` | 每层组元素数量 | `2`(二维数组) | | `levels` | 多维结构定义 | `[2,3]`(2×3矩阵) | | `idKey` | 节点ID字段名 | `'id'` | | `parentKey` | 父节点关联字段 | `'parentId'` | #### 4. **注意事项** 1. **边界处理**:递归必须包含终止条件(空数组/指定深度),否则会导致栈溢出[^2] 2. **性能优化**:大数据集时考虑尾递归优化或迭代替代方案(参考引用[2]的堆栈实现) 3. **数据完整性**:换前需确保数组长度符合组规则 $$n \mod \prod_{i=0}^{d-1} k_i = 0$$ 4. **动态结构**:树形换需保证父子关系无循环引用 这些方法适用于多种场景: - 表格数据块展示 - 树形菜单/目录生成 - 三维数据可视化 - 嵌套路由配置处理 通过合理设计组规则和递归深度,可高效实现一维到多维的换[^1][^3][^4]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值