初级前端都能学会的扁平数据结构转Tree,还能互转!

105 阅读2分钟

初级前端都能学会的扁平数据结构转Tree,还能互转!

面试了十几个高级前端,竟然连(扁平数据结构转Tree)都写不出来

之前看到了一篇文章面试了十几个高级前端,竟然连(扁平数据结构转Tree)都写不出来,里面列举了使用递归和map的方式来进行转换。虽然是说用map,但实际上还是用的Object。今天给小伙伴带来一个真正使用Map、类型全面、易读、易理解的方式!

思路

const arr = [
  { id: 1, name: '部门A', parentId: 0 },
  { id: 2, name: '部门B', parentId: 1 },
  { id: 3, name: '部门C', parentId: 1 },
  { id: 4, name: '部门D', parentId: 2 },
  { id: 5, name: '部门E', parentId: 2 },
  { id: 6, name: '部门F', parentId: 3 },
]

循环数组,将每一项构建成一个treeNode,然后将该treeNode加入到父级的children中。那么如何构建父子关系?使用Map

// key: 就是当前item的id
// value: 就是当前item构建成的treeNode
const map: Map<number, ITreeNode> = new Map()

类型定义

interface IArrayItem {
  id: number
  name: string
  parentId: number
}

interface ITreeNode {
  id: number
  name: string
  children?: ITreeNode[]
}

具体代码

/**
 * @description: 数组转tree
 * @param {IArrayItem} arr
 * @return {*}
 */
function convert(arr: IArrayItem[]): ITreeNode | null {
  // 用于 id 和 treeNode 的映射
  const idToTreeNode: Map<number, ITreeNode> = new Map()

  let root = null

  arr.forEach((item) => {
    const { id, name, parentId } = item

    // 定义 tree node 并加入 map
    const treeNode: ITreeNode = { id, name }
    idToTreeNode.set(id, treeNode)

    // 找到 parentNode 并加入到它的 children
    const parentNode = idToTreeNode.get(parentId)
    if (parentNode) {
      if (parentNode.children == null) parentNode.children = []
      parentNode.children.push(treeNode)
    }

    // 找到根节点
    if (parentId === 0) root = treeNode
  })

  return root
}

思考

那么我想把转换后的tree,在转成之前的数组咋办呢?

将得到的tree进行一次广度遍历不就好了吗?那么问题来了:如何获取到父子关系,也就是数组中每一项对应的parentId?

当然继续使用map,如下:

// key: 当前treeNode
// value: 当前treeNode的父级treeNode
const map = new Map<ITreeNode, ITreeNode>()

具体代码:

function treeConvertToArray(root: ITreeNode): IArrayItem[] {
  // Map
  const nodeToParent: Map<ITreeNode, ITreeNode> = new Map()

  const arr: IArrayItem[] = []

  // 广度优先遍历,queue
  const queue: ITreeNode[] = []
  
  // 根节点 入队
  queue.unshift(root) 

  while (queue.length > 0) {
    // 出队
    const curNode = queue.pop() 
    if (curNode == null) break

    const { id, name, children = [] } = curNode

    // 创建数组 item 并 push
    const parentNode = nodeToParent.get(curNode)
    const parentId = parentNode?.id || 0
    const item = { id, name, parentId }
    arr.push(item)

    // 子节点入队
    children.forEach((child) => {
      // 映射 parent
      nodeToParent.set(child, curNode)
      // 入队
      queue.unshift(child)
    })
  }
  return arr
}

核心:在广度优先遍历的基础上使用map记录了父子关系

总结

数组转Tree:构建treeNode,在使用map映射 id 和 treeNode,用于构建父子关系

tree转数组:广度优先遍历,map映射当前treeNode和父级treeNode