Comments (3)
我稍微总结一下你预期的东西:
目标
将使用 parent id 的树形结构表,在内存中重新构建起来整棵树
API
目前来看你只需要一个能够将切片转化为树形结构的东西。也就是:
// BuildTree 参数:list 源切片数据,rootId 最顶层rootId, opt 配配置, fn 转换器,用于处理字段映射
func BuildTree[T comparable, E constraints.Ordered](list []any, rootId T, opt TreeOptional, fu func(src any) TreeNode[T, E]) Tree[T] {
}
这个 API 的定义,太定制化了。也就是它完全针对的是你自己的业务需求。实际上,如果我们要设计一个通用的这种 API,那么它可能长成这样:
func ToTree[T any](data []T) Tree[T] {
}
type Tree[T any] struct {
Root Node[T]
}
ToTree
会将 data 转化为一棵多叉树,其大体行为是:
- 用户需要指定怎么获得 parent id。我们考虑到将来,有些树形结构是使用路径来表达的。比如说 /a/b/c,这方面要留下扩展
- 子节点排序。如果用户指定了子节点的排序方式,那么我们就需要排序
其中 T 是你数据,比如说:
type City struct {
ParentID int64
Name
Sort int
}
而 TreeOptional 则是一个更加针对你的业务,而不是一个通用的设计。
站在 ekit 的角度来说,我们提供的API 可能就只有:
// 深度优先遍历
func (t *Tree[T]) DFS(func(T))
// 广度优先遍历
func (t *Tree[T]) BFS(func(T))
在选择遍历的时候,需要你自己控制什么时候中断,这个时候我们可能要暴露额外的数据,比如说这个节点的深度。
from ekit.
我稍微总结一下你预期的东西:
目标
将使用 parent id 的树形结构表,在内存中重新构建起来整棵树
API
目前来看你只需要一个能够将切片转化为树形结构的东西。也就是:
// BuildTree 参数:list 源切片数据,rootId 最顶层rootId, opt 配配置, fn 转换器,用于处理字段映射 func BuildTree[T comparable, E constraints.Ordered](list []any, rootId T, opt TreeOptional, fu func(src any) TreeNode[T, E]) Tree[T] { }这个 API 的定义,太定制化了。也就是它完全针对的是你自己的业务需求。实际上,如果我们要设计一个通用的这种 API,那么它可能长成这样:
func ToTree[T any](data []T) Tree[T] { } type Tree[T any] struct { Root Node[T] }
ToTree
会将 data 转化为一棵多叉树,其大体行为是:
- 用户需要指定怎么获得 parent id。我们考虑到将来,有些树形结构是使用路径来表达的。比如说 /a/b/c,这方面要留下扩展
- 子节点排序。如果用户指定了子节点的排序方式,那么我们就需要排序
其中 T 是你数据,比如说:
type City struct { ParentID int64 Name Sort int }而 TreeOptional 则是一个更加针对你的业务,而不是一个通用的设计。
站在 ekit 的角度来说,我们提供的API 可能就只有:
// 深度优先遍历 func (t *Tree[T]) DFS(func(T)) // 广度优先遍历 func (t *Tree[T]) BFS(func(T))在选择遍历的时候,需要你自己控制什么时候中断,这个时候我们可能要暴露额外的数据,比如说这个节点的深度。
我后面自己试着实现了一下,一开始的idea的确欠妥。
我的设想是除了能把切片转换成树结构,还要能配置struct 切片中哪些字段用于映射tree 的id,parentId,sortkey等关键属性。
tree中的id,父id等字段,可以转换属性名,方便最终提供json给前端。
同时算法上做一些时间复杂度优化。
为了灵活度,最终选择map 作为函数的输出...
实现思路:
1.定义一个stuct:Tree 作为最终输出
2.定义一个builder 作为辅助构造,冗余存储一些数据,辅助构造树,并最终提供build 方法构造树
3.定义一个config 类,用于配置struct 中哪些字段是id,parentId ..以及排序,深度等策略..
4. 由用户输入一个Parser映射函数,将struct转换为map
实现的不是很优雅,也确实太定制化了,就不提pr了。
package tree
import (
"sort"
)
// Tree 本体
type Tree[T comparable] struct {
Mp map[string]any
//父节点的指针,辅助构造树
parent *Tree[T]
}
// 增加子节点,同时关联子节点的父节点为当前节点
func (cur *Tree[T]) addChildren(config *Config, children ...*Tree[T]) {
if len(children) > 0 {
childrenMap := make([]map[string]any, 0, len(children))
for _, elem := range children {
childrenMap = append(childrenMap, elem.Mp)
}
if m, ok := cur.Mp[config.ChildrenKEY]; ok {
cur.Mp[config.ChildrenKEY] = append(m.([]map[string]any), childrenMap...)
} else {
cur.Mp[config.ChildrenKEY] = childrenMap
}
}
for _, node := range children {
node.parent = cur
}
}
// Config 配置类
type Config struct {
IdKey string
ParentIdKey string
//递归深度 从0即不限制
Deep int
Name string
ChildrenKEY string
//排序字段
Sort string
}
var DefaultConfig = &Config{IdKey: "id", ParentIdKey: "parentId", Deep: -1, Name: "name", ChildrenKEY: "children", Sort: "sort"}
type Builder[T comparable] struct {
root *Tree[T]
//标记位,防止build被重复调用
isBuild bool
config *Config
//中间map
idTreeMap map[T]*Tree[T]
//按sortKey 对treeId 进行排序
sortList []T
}
// NewBuilder 创建构造器
func NewBuilder[T comparable](config *Config, rootId T) *Builder[T] {
root := &Tree[T]{Mp: map[string]any{}}
root.Mp[config.IdKey] = rootId
return &Builder[T]{root: root, config: config, idTreeMap: map[T]*Tree[T]{}}
}
// Append 向构造器中添加元素
func Append[T comparable, E any](builder *Builder[T], list []E, nodeParser func(E, *Tree[T])) {
if builder.isBuild {
panic("树已经被构造")
}
for _, elem := range list {
node := &Tree[T]{Mp: map[string]any{}}
nodeParser(elem, node)
id := node.Mp[builder.config.IdKey]
builder.idTreeMap[id.(T)] = node
builder.sortList = append(builder.sortList, id.(T))
}
if builder.config.Sort != "" {
sort.Slice(builder.sortList, func(i, j int) bool {
id1 := builder.sortList[i]
id2 := builder.sortList[j]
sort1 := builder.idTreeMap[id1].Mp[builder.config.Sort].(int)
sort2 := builder.idTreeMap[id2].Mp[builder.config.Sort].(int)
return sort1 < sort2
})
}
}
// Build 构造tree
func (builder *Builder[T]) Build() *Tree[T] {
if builder.isBuild {
panic("树已经被构造")
}
flag := false
config := builder.config
for _, id := range builder.sortList {
node := builder.idTreeMap[id]
if id == builder.root.Mp[config.IdKey] {
builder.root = node
flag = true
continue
}
parentId := node.Mp[config.ParentIdKey]
if builder.root.Mp[config.IdKey] == parentId {
builder.root.addChildren(config, node)
continue
}
parentNode, ok := builder.idTreeMap[parentId.(T)]
if ok {
parentNode.addChildren(config, node)
}
}
//todo deep剪枝
builder.isBuild = true
if !flag {
return &Tree[T]{}
}
return builder.root
}
调用方式
var MockList = []area{
{1, -1, "**", 0},
{2, 1, "北京市", -0},
{3, 1, "上海市", 1},
{4, 3, "浦东", 1},
{5, 3, "徐汇区", 2},
{6, 2, "朝阳区", 3},
{7, 2, "海淀区", 4},
{8, 9, "海淀区", 4},
}
func DemoNodeParser(x area, t2 *Tree[int]) {
t2.Mp["id"] = x.Id
t2.Mp["parentId"] = x.ParentId
t2.Mp["name"] = x.Name
t2.Mp["sort"] = x.Sort
}
builder := NewBuilder(DefaultConfig, rootId)
Append(builder, MockList , DemoNodeParser)
got := builder.Build()
from ekit.
这给了我启发。我准备设计一些通用的方法和数据结构,支持数据库树形表接口设计:
- 邻接表设计
- 闭包表设计
- Path 设计
你可以后续保持关注 ekit,哈哈哈,我主要是太忙没时间设计和实现
from ekit.
Related Issues (20)
- Tuple: 新增 Pair 和 Triple 两种类型 HOT 8
- retry: Strategy 接口设计与等间隔重试实现
- retry: 指数退避重试策略实现 HOT 1
- pool相关总issue
- sqlx:ScanRows 和 ScanAll方法 HOT 9
- mapx: MutipleTreeMap
- mapx: 为 MultipleMap 添加 PutVals 方法
- mapx: LinkedMap 特性 HOT 12
- copier: ReflectCopier 支持忽略字段 HOT 1
- copier: 支持类型转换 HOT 20
- randx: 随机验证码生成器 HOT 5
- ekit:AnyValue 支持类型转换 - String 转数字类型
- unsafe 转换 string 和 []byte HOT 1
- sqlx: sqlx.NewNullXXX 方法
- syncx: SegmentKeysLock HOT 3
- slice.ContainsFunc破坏性修改确认 HOT 3
- mapx: 支持 Len 方法
- 重构randx.RandCode以及传入的RandType数值 HOT 3
- slice 转 map HOT 3
- 提供 Must 和 MustT 函数,简化错误处理 HOT 4
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from ekit.