golang扁平数据列表转化为树状结构

4,069 阅读3分钟

在数据表中,数据是以一条一条,这种扁平化的数据保存的,然后通过外键这种形式关联起来,如果是一个树形结构,在扁平化的一维列表中 是这个样子的, 假设数据保存在Mysqltmp表,那么数据格式为:

DROP TABLE IF EXISTS `tmp`;
CREATE TABLE `tmp` (
  `id` int NOT NULL AUTO_INCREMENT,
  `pid` int NOT NULL DEFAULT '0',
  `content` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb3;
SET FOREIGN_KEY_CHECKS = 1;
INSERT INTO `tmp` (`id`, `pid`, `content`) VALUES (1, 0, '1');
INSERT INTO `tmp` (`id`, `pid`, `content`) VALUES (2, 1, '1-2');
INSERT INTO `tmp` (`id`, `pid`, `content`) VALUES (3, 1, '1-3');
INSERT INTO `tmp` (`id`, `pid`, `content`) VALUES (4, 3, '1-3-4');
INSERT INTO `tmp` (`id`, `pid`, `content`) VALUES (5, 0, '5');
INSERT INTO `tmp` (`id`, `pid`, `content`) VALUES (6, 5, '5-6');
INSERT INTO `tmp` (`id`, `pid`, `content`) VALUES (7, 6, '5-6-7');
INSERT INTO `tmp` (`id`, `pid`, `content`) VALUES (8, 6, '5-6-8');

-- 然后按顺序查询出来

mysql> SELECT * FROM tmp ORDER BY CONCAT(pid, '-', id) ASC;
+----+-----+---------+
| id | pid | content |
+----+-----+---------+
|  1 |   0 | 1       |
|  5 |   0 | 5       |
|  2 |   1 | 1-2     |
|  3 |   1 | 1-3     |
|  4 |   3 | 1-3-4   |
|  6 |   5 | 5-6     |
|  7 |   6 | 5-6-7   |
|  8 |   6 | 5-6-8   |
+----+-----+---------+
8 rows in set (0.01 sec)

扁平的为一维列表的树状结构

var items = []ItemType{
        {Id: 1, Pid: 0,  Content: "1"},
        {Id: 2, Pid: 1,  Content: "1-2"},
        {Id: 3, Pid: 1,  Content: "1-3"},
        {Id: 4, Pid: 3,  Content: "1-3-4"},
        {Id: 5, Pid: 0,  Content: "5"},
        {Id: 6, Pid: 5,  Content: "5-6"},
        {Id:7 , Pid: 6,  Content: "5-6-7"},
        {Id: 8, Pid: 6,  Content: "5-6-8"},
    }

像这样的数据是可以保存在数据库中的,又通过外键的关联描述出了一个树状结构。那么把扁平的一维数据列表转化为树下状,大概是这样子:

package main
import (
    "encoding/json"
    "fmt"
)

type ItemType struct {
    Content string
    Pid     int64
    Id      int64
}
type TreeItem struct {
    ItemType
    Children []*TreeItem
}
type IdMapTreeType map[int64]*TreeItem

func main() {
    var items = []ItemType{
        {Id: 1, Pid: 0,  Content: "1"},
        {Id: 2, Pid: 1,  Content: "1-2"},
        {Id: 3, Pid: 1,  Content: "1-3"},
        {Id: 4, Pid: 3,  Content: "1-3-4"},
        {Id: 5, Pid: 0,  Content: "5"},
        {Id: 6, Pid: 5,  Content: "5-6"},
        {Id:7 , Pid: 6,  Content: "5-6-7"},
        {Id: 8, Pid: 6,  Content: "5-6-8"},
    }
    var tree []*TreeItem
    idMapTreeItem := make(IdMapTreeType)
    for _, item := range items {
        var treeItem TreeItem
        treeItem.Id = item.Id
        treeItem.Pid = item.Pid
        treeItem.Content = item.Content
        // 根节点收集
        if item.Pid == 0 {
            tree = append(tree, &treeItem)
        } else {
            // 子节点收集
            idMapTreeItem[item.Pid].Children = append(idMapTreeItem[item.Pid].Children, &treeItem)
        }
        // 把节点映射到map表
        idMapTreeItem[item.Id] = &treeItem
    }
    jsonRes, _ := json.Marshal(tree)
    fmt.Println(string(jsonRes))
}

列表转化为树结构

[
  {
    "Content": "1",
    "Pid": 0,
    "Id": 1,
    "Children": [
      {
        "Content": "1-2",
        "Pid": 1,
        "Id": 2,
        "Children": null
      },
      {
        "Content": "1-3",
        "Pid": 1,
        "Id": 3,
        "Children": [
          {
            "Content": "1-3-4",
            "Pid": 3,
            "Id": 4,
            "Children": null
          }
        ]
      }
    ]
  },
  {
    "Content": "5",
    "Pid": 0,
    "Id": 5,
    "Children": [
      {
        "Content": "5-6",
        "Pid": 5,
        "Id": 6,
        "Children": [
          {
            "Content": "5-6-7",
            "Pid": 6,
            "Id": 7,
            "Children": null
          },
          {
            "Content": "5-6-8",
            "Pid": 6,
            "Id": 8,
            "Children": null
          }
        ]
      }
    ]
  }
]

原理就是每一条数据在树中都有一个特定的位置,数据项是根节点则直接加入到根节点中, 并注册id映射到它的内存地址(指针), 如果不是根节点,则通过它的上级pid在映射表中把上级的内存地址拿到并把数据写进去,并也是一样把自己注册到映射表中。然后就还原出来了。 文章转载自《重学golang.算法.# 扁平数据列表转化为树状结构》