8 Commits

4 changed files with 233 additions and 0 deletions

1
go.mod
View File

@ -27,6 +27,7 @@ require (
github.com/smarty/assertions v1.15.0 // indirect github.com/smarty/assertions v1.15.0 // indirect
github.com/tidwall/match v1.2.0 // indirect github.com/tidwall/match v1.2.0 // indirect
github.com/tidwall/pretty v1.2.1 // 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/mod v0.9.0 // indirect
golang.org/x/sys v0.6.0 // indirect golang.org/x/sys v0.6.0 // indirect
golang.org/x/tools v0.7.0 // indirect golang.org/x/tools v0.7.0 // indirect

3
go.sum
View File

@ -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/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 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 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 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= 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.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= 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/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 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=

159
op_array/util.go Normal file
View File

@ -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
}

70
op_array/util_test.go Normal file
View File

@ -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)
}