diff --git a/go.mod b/go.mod index 34a1761..32b8bf4 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,7 @@ require ( github.com/smarty/assertions v1.15.0 // indirect github.com/tidwall/match v1.2.0 // indirect github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect golang.org/x/mod v0.9.0 // indirect golang.org/x/sys v0.6.0 // indirect golang.org/x/tools v0.7.0 // indirect diff --git a/go.sum b/go.sum index 31ccf8c..07c7879 100644 --- a/go.sum +++ b/go.sum @@ -44,6 +44,7 @@ github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0b github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= @@ -52,6 +53,8 @@ github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= diff --git a/op_array/util.go b/op_array/util.go new file mode 100644 index 0000000..2c3627b --- /dev/null +++ b/op_array/util.go @@ -0,0 +1,159 @@ +// Package op_array ... +// +// Description : op_array ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2025-11-25 11:30 +package op_array + +import ( + "fmt" + "reflect" +) + +// ToMap 数组转map +func ToMap[Key comparable, Value any](dataList []Value, keyFormat func(item Value) Key) map[Key]Value { + res := make(map[Key]Value) + for _, item := range dataList { + key := keyFormat(item) + res[key] = item + } + return res +} + +// ToCustomMap 数组转map +func ToCustomMap[Key comparable, Value any, CustomValue any](dataList []Value, keyFormat func(item Value) Key, formatCustomValue func(item Value) CustomValue) map[Key]CustomValue { + res := make(map[Key]CustomValue) + for _, item := range dataList { + key := keyFormat(item) + res[key] = formatCustomValue(item) + } + return res +} + +// ExtractField 提取数组指定字段, 并构建成一个新的数组 +func ExtractField[FieldValue any, Value any](dataList []Value, fieldValue func(item Value) FieldValue) []FieldValue { + res := make([]FieldValue, 0) + for _, item := range dataList { + v := fieldValue(item) + res = append(res, v) + } + return res +} + +// Filter 过滤列表数据 +func Filter[Value any](dataList []Value, filterValue func(item Value) bool) []Value { + res := make([]Value, 0) + for _, item := range dataList { + if filterValue(item) { + res = append(res, item) + } + } + return res +} + +// Group 按照指定字段进行分组 +func Group[Key comparable, Value any](dataList []Value, keyFormat func(item Value) Key) [][]Value { + keyList := make([]string, 0) + dataTable := make(map[string][]Value) + res := make([][]Value, 0) + for _, item := range dataList { + key := fmt.Sprintf("%v", keyFormat(item)) + if _, ok := dataTable[key]; !ok { + keyList = append(keyList, key) + } + dataTable[key] = append(dataTable[key], item) + } + formatList := make([][]Value, 0) + for _, key := range keyList { + formatList = append(formatList, dataTable[key]) + } + return res +} + +// TreeItem 数据结构 +type TreeItem[ID comparable, Value any] struct { + ID ID `json:"id" dc:"数据ID"` + ParentID ID `json:"parent_id" dc:"父级ID"` + Value Value `json:"value" dc:"数据值"` + ChildList []*TreeItem[ID, Value] `json:"child_list" dc:"子级数据列表"` +} + +// Tree 将列表数据转为属性结构 +func Tree[ID comparable, Value any](dataList []Value, formatParentID func(v Value) ID, formatID func(v Value) ID) *TreeItem[ID, Value] { + // list 转table + dataTable := make(map[ID]*TreeItem[ID, Value]) + for _, item := range dataList { + dataID := formatID(item) + dataTable[dataID] = &TreeItem[ID, Value]{ + ID: dataID, + ParentID: formatParentID(item), + Value: item, + ChildList: make([]*TreeItem[ID, Value], 0), + } + } + // 构建树(保证一定是一棵树) + rootData := &TreeItem[ID, Value]{ + ID: *new(ID), + ParentID: *new(ID), + Value: *new(Value), + ChildList: make([]*TreeItem[ID, Value], 0), // 子数据列表 + } + for _, item := range dataList { + dataID := formatID(item) + parentID := formatParentID(item) + if _, exist := dataTable[parentID]; exist { + // 归属某一个父级节点 + dataTable[parentID].ChildList = append(dataTable[parentID].ChildList, dataTable[dataID]) + } else { + // 部署于任何父节点, 则归属于虚拟的根节点 + rootData.ChildList = append(rootData.ChildList, dataTable[dataID]) + } + } + return rootData +} + +// DeepClone 深度克隆一个数组, 每一个元素的地址均重新分配 +func DeepClone[Value any](dataList []Value) []Value { + v := *new(Value) + vType := reflect.TypeOf(v) + res := make([]Value, len(dataList)) + for i := 0; i < len(dataList); i++ { + newV := new(Value) + switch vType.Kind() { + case reflect.Ptr: + // 解引用, 并单独读取值后重新分配地址 + copyV := reflect.New(vType.Elem()).Elem() + copyV.Set(reflect.ValueOf(dataList[i]).Elem()) + *newV = copyV.Addr().Interface().(Value) + case reflect.Map: + mapVal := reflect.ValueOf(dataList[i]) + mapKeyList := mapVal.MapKeys() + newMap := reflect.MakeMap(vType) + for _, key := range mapKeyList { + // 通过键获取对应的值 + value := mapVal.MapIndex(key) + newMap.SetMapIndex(key, value) + } + *newV = newMap.Interface().(Value) + default: + *newV = dataList[i] + } + res[i] = *newV + } + return res +} + +// Shard 将数组按照指定数量进行分片 +func Shard[Value any](dataList []Value, itemShardCount int) [][]Value { + res := make([][]Value, 0) + for i := 0; i < len(dataList); i += itemShardCount { + tmpList := make([]Value, 0) + for j := 0; j < itemShardCount; j++ { + tmpList = append(tmpList, dataList[i+j]) + } + res = append(res, tmpList) + } + return res +} diff --git a/op_array/util_test.go b/op_array/util_test.go new file mode 100644 index 0000000..d1848d0 --- /dev/null +++ b/op_array/util_test.go @@ -0,0 +1,70 @@ +// Package op_array ... +// +// Description : op_array ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2025-11-27 12:40 +package op_array + +import ( + "fmt" + "testing" + + "git.zhangdeman.cn/zhangdeman/serialize" +) + +func TestTree(t *testing.T) { + type Data struct { + ID int `json:"id" dc:"数据ID"` + ParentID int `json:"parent_id" dc:"父级ID"` + Value string `json:"value" dc:"数据值"` + } + + dataList := []Data{ + {ID: 1, ParentID: 0, Value: "1"}, + {ID: 2, ParentID: 1, Value: "2"}, + {ID: 3, ParentID: 1, Value: "3"}, + {ID: 4, ParentID: 2, Value: "4"}, + {ID: 5, ParentID: 2, Value: "5"}, + {ID: 6, ParentID: 3, Value: "6"}, + {ID: 7, ParentID: 3, Value: "7"}, + } + res := Tree(dataList, func(v Data) int { return v.ParentID }, func(v Data) int { return v.ID }) + serialize.JSON.ConsoleOutput(res) +} + +func TestClone(t *testing.T) { + type Data struct { + ID int `json:"id" dc:"数据ID"` + } + + // 字面结构体 + dataList := []Data{ + {ID: 1}, + {ID: 2}, + } + res := DeepClone(dataList) + fmt.Println(fmt.Sprintf("%p", &res[0]), fmt.Sprintf("%p", &dataList[0])) + fmt.Println(fmt.Sprintf("%p", &res[1]), fmt.Sprintf("%p", &dataList[1])) + + // 结构体指针 + dataList1 := []*Data{ + {ID: 1}, + {ID: 2}, + } + res1 := DeepClone(dataList1) + fmt.Println(fmt.Sprintf("%p", &res1[0]), fmt.Sprintf("%p", &dataList1[0])) + fmt.Println(fmt.Sprintf("%p", &res1[1]), fmt.Sprintf("%p", &dataList1[1])) + + // Map数据 + dataList2 := []map[string]any{ + {"id": 1}, + {"id": 2}, + } + res2 := DeepClone(dataList2) + fmt.Println(fmt.Sprintf("%p", &res2[0]), fmt.Sprintf("%p", &dataList2[0])) + fmt.Println(fmt.Sprintf("%p", &res2[1]), fmt.Sprintf("%p", &dataList2[1])) + serialize.JSON.ConsoleOutput(res2) + serialize.JSON.ConsoleOutput(dataList2) +}