Giter VIP home page Giter VIP logo

Comments (3)

flycash avatar flycash commented on June 16, 2024

我稍微总结一下你预期的东西:

目标

将使用 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.

zoupengyu3517 avatar zoupengyu3517 commented on June 16, 2024

我稍微总结一下你预期的东西:

目标

将使用 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.

flycash avatar flycash commented on June 16, 2024

这给了我启发。我准备设计一些通用的方法和数据结构,支持数据库树形表接口设计:

  • 邻接表设计
  • 闭包表设计
  • Path 设计

你可以后续保持关注 ekit,哈哈哈,我主要是太忙没时间设计和实现

from ekit.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.