扁平和树形结构的几种互转

本文详细介绍了如何将后端返回的扁平数组转换为树形结构,并提供了扁平化和树形化的两种主要方法,包括递归、双层过滤以及队列和栈的实现。此外,还展示了查找特定节点的DFS和BFS算法。这些都是前端与后端交互中处理层级数据的实用工具。

后端有时会返回如下扁平数组:

[
  {
    id: '01',
    name: '衣服',
    pid: '0',
  },
  {
    id: '02',
    name: '裤子',
    pid: '0',
  },
  {
    id: '03',
    name: '鞋子',
    pid: '0',
  },
  {
    id: '11',
    name: 'zara大衣',
    pid: '01',
  },
  {
    id: '12',
    name: 'zara小易',
    pid: '01',
  },
  {
    id: '13',
    name: 'ur大衣',
    pid: '01',
  },
  {
    id: '21',
    name: 'zara大裤子',
    pid: '02',
  },
  {
    id: '22',
    name: 'zara小裤子',
    pid: '02',
  },
  {
    id: '23',
    name: 'GUCCI裤裤',
    pid: '02',
  },
  {
    id: '31',
    name: 'aj1号',
    pid: '03',
  },
  {
    id: '32',
    name: '空军一号',
    pid: '03',
  },
  {
    id: '311',
    name: 'aj1号玫瑰金',
    pid: '31',
  },
  {
    id: '312',
    name: 'aj1号土豪金',
    pid: '31',
  },
];

而我们需要的可能是一个树形结构如下:

[
    {
        "id": "01",
        "name": "衣服",
        "pid": "0",
        "children": [
            {
                "id": "11",
                "name": "zara大衣",
                "pid": "01",
                "children": []
            },
            {
                "id": "12",
                "name": "zara小易",
                "pid": "01",
                "children": []
            },
            {
                "id": "13",
                "name": "ur大衣",
                "pid": "01",
                "children": []
            }
        ]
    },
    {
        "id": "02",
        "name": "裤子",
        "pid": "0",
        "children": [
            {
                "id": "21",
                "name": "zara大裤子",
                "pid": "02",
                "children": []
            },
            {
                "id": "22",
                "name": "zara小裤子",
                "pid": "02",
                "children": []
            },
            {
                "id": "23",
                "name": "GUCCI裤裤",
                "pid": "02",
                "children": []
            }
        ]
    },
    {
        "id": "03",
        "name": "鞋子",
        "pid": "0",
        "children": [
            {
                "id": "31",
                "name": "aj1号",
                "pid": "03",
                "children": [
                    {
                        "id": "311",
                        "name": "aj1号玫瑰金",
                        "pid": "31",
                        "children": []
                    },
                    {
                        "id": "312",
                        "name": "aj1号土豪金",
                        "pid": "31",
                        "children": []
                    }
                ]
            },
            {
                "id": "32",
                "name": "空军一号",
                "pid": "03",
                "children": []
            }
        ]
    }
]

又或者从树形结构转成扁平结构相互转换
下面声明了一个普通数据项的类型:

interface IEmployee {
  id: string;
  name: string;
  pid: string;
  children?: IEmployee[];
}

扁平转树

  • 将所有数据项都当做父节点用自身id做为key节点做为value存入map结构,然后一个个去map里找父节点插入对应的children中
/**
 * @description 将所有数据项都当父级放入map数据结构 然后遍历数据一个一个去map里进行匹配,使用指针的便利性完成每个children的插入
 * @param data
 */
function toTree(data: IEmployee[]) {
  const tree: IEmployee[] = [];
  const map = {} as {
    [index: string]: IEmployee;
  }; // 记录所有项{id:数据项}的形式存储在字典里
  for (const item of data) {
    // 将数据项都装入map 使用id做为key方便后面匹配父级
    map[item.id] = item;
  }
  // 遍历所有数据项
  for (const item of data) {
    if (item.pid === '0') {
      tree.push(item); // 0直接放入根节点
      continue;
    }
    // 根据pid到map里找对应id的父级
    const parent = map[item.pid];
    if (parent !== void 0) {
      // 如果找到了则将item存入对应父级的children里
      (parent.children || (parent.children = [])).push(item);
    }
  }
  return tree;
}
  • 双重filter的实现,外层filter只返回根节点,内层filter返回对应id对应所有的pid数据
/**
 * @description 双重filter实现 也是使用指针的便利性 只返回根节点 在数据项中返回所有与自身pid匹配的父节点id
 * @param data
 * @returns
 */
function toTree2(data: IEmployee[]) {
  return data.filter((item: IEmployee) => {
    item.children = data.filter((each: IEmployee) => item.id === each.pid);
    return item.pid === '0';
  });
}
  • 递归查找子集
/**
 * @description 递归查找子集实现
 * @param data
 * @param pid 每个项都当做父级去查找对应pid的子集
 * @param result
 * @returns
 */
function toTree3(
  data: IEmployee[],
  pid: string = '0',
  result: IEmployee[] = []
): IEmployee[] {
  for (const item of data) {
    if (item.pid === pid) {
      result.push(item);
      toTree3(data, item.id, (item.children = []));
    }
  }
  return result;
}
console.log('递归转树', toTree3(data));

树转扁平

  • 递归+concat版本的forof循环实现
function flat1(data: IEmployee[]): IEmployee[] {
  let result: IEmployee[] = [];
  for (const item of data) {
    if (item.children) {
      // 如果有children则递归查找,每一层返回的结果要追加到result
      result = result.concat(flat1(item.children));
    }
    result.push({ id: item.id, name: item.name, pid: item.pid }); // 将当前item自身push进去
  }
  return result;
}
  • 递归+concat版本的reduce实现
function flat2(data: IEmployee[]): IEmployee[] {
  return data.reduce(
    (resultArr, { id, name, pid, children = [] }) =>
      resultArr.concat([{ id, name, pid }], flat2(children)),
    []
  );
}
  • 使用栈和队列转扁平
function flatViaStack(tree: IEmployee[]) {
  const result: IEmployee[] = [];
  const stack: IEmployee[] = [...tree];
  while (stack.length) {
    const lastNode = stack.pop();
    result.push(lastNode);
    lastNode.children && stack.push(...lastNode.children);
  }
  return result;
}
console.log('使用栈转扁平', flatViaStack(tree));
function flatViaQueue(tree: IEmployee[]) {
  const result: IEmployee[] = [];
  const queue: IEmployee[] = [...tree];
  while (queue.length) {
    const firstNode = queue.shift();
    result.push(firstNode);
    firstNode.children && queue.push(...firstNode.children);
  }
  return result;
}
console.log('使用队列转扁平', flatViaQueue(tree));

查找节点

我们可能要根据id获取某一个节点的信息,可以使用DFS和BFS查找

  • DFS实现:
/**
 * @description 使用栈深度优先查找指定id的节点
 * @param tree 树的数据
 * @param id 要查找的id
 * @returns 查找到的节点
 */
function getNodeByIdViaDFS(tree: IEmployee[], id: string): IEmployee | null {
  const stack: IEmployee[] = [...tree];
  while (stack.length > 0) {
    const lastNode = stack.pop();
    if (lastNode.id === id) {
      return lastNode;
    }
    lastNode.children && stack.push(...lastNode.children);
  }
  return null;
}
console.log(getNodeByIdViaDFS(tree, '312'));
  • BFS实现
/**
 * @description 使用队列广度优先查找指定id的节点
 * @param tree 树的数据
 * @param id 要查找的id
 * @returns 查找到的节点
 */
function getNodeByIdViaBFS(tree: IEmployee[], id: string): IEmployee | null {
  const queue: IEmployee[] = [...tree];
  while (queue.length > 0) {
    const firstNode = queue.shift();
    if (firstNode.id === id) {
      return firstNode;
    }
    firstNode.children && queue.push(...firstNode.children);
  }
  return null;
}
console.log(getNodeByIdViaBFS(tree, '312'));
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值