From 29ec548b649d9eccf58ee3f3c56b836d6f39bf94 Mon Sep 17 00:00:00 2001 From: ccpwcn Date: Mon, 27 Nov 2023 09:48:19 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=BB=93=E6=9E=84=E4=BD=93?= =?UTF-8?q?=E5=88=87=E7=89=87=E5=88=86=E7=BB=84=E5=8A=9F=E8=83=BD=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=83=A8=E5=88=86bug=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E7=BB=86=E8=8A=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 +-- slice.go | 3 ++ struct.go | 35 +++++++++++++++++++++ struct_test.go | 85 ++++++++++++++++++++++++++++++++++++++++++++------ 4 files changed, 117 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 16793f0..85049ee 100644 --- a/README.md +++ b/README.md @@ -56,8 +56,9 @@ - ContainsAll 包含指定元素 - ContainsAny 包含任意元素 - [x] 结构体相关操作 - - JoinStructsField 将任意结构体数组中的指定字段的值使用英文逗号拼接成一个字符串,例如:用户列表中,所有用户ID拼成一个字符串 - - PickStructsField 将任意结构体数组中的指定字段的值提取出来形成一个保持原类型的数组,例如:用户列表中,所有用户ID提取成一个用户ID数组 + - JoinStructsField 将任意结构体切片中的指定字段的值使用英文逗号拼接成一个字符串,例如:用户列表中,所有用户ID拼成一个字符串 + - PickStructsField 将任意结构体切片中的指定字段的值提取出来形成一个保持原类型的数组,例如:用户列表中,所有用户ID提取成一个用户ID数组 + - SliceGroupBy 将任何结构体切片切片中按指定字段的值提取出来进行分组,形成一个Map,例如对用户按类型分组,类型的值为Map的key,对应类型的所有用户集合为Map的Value - [x] 雪花算法 - 通用实现方法,初始化一次,到处随时获得ID,多goroutine并发安全 - [x] UUID 高性能UUID diff --git a/slice.go b/slice.go index bdeeab8..c2be61d 100644 --- a/slice.go +++ b/slice.go @@ -32,6 +32,7 @@ func SlicePagination[T any](data []T, pageSize int) (paged [][]T) { return paged } +// Contains 一个切片是否包含指定元素 func Contains[T any](data []T, element T) bool { for _, datum := range data { if reflect.DeepEqual(datum, element) { @@ -41,6 +42,7 @@ func Contains[T any](data []T, element T) bool { return false } +// ContainsAll 一个切片是否包含所有指定元素 func ContainsAll[T any](data []T, elements []T) bool { for _, element := range elements { if !Contains(data, element) { @@ -50,6 +52,7 @@ func ContainsAll[T any](data []T, elements []T) bool { return true } +// ContainsAny 一个切片是否包含任意指定元素 func ContainsAny[T any](data []T, elements []T) bool { for _, element := range elements { if Contains(data, element) { diff --git a/struct.go b/struct.go index 056fb4b..bd19a0d 100644 --- a/struct.go +++ b/struct.go @@ -60,3 +60,38 @@ func PickStructsField[T interface{}, FT comparable](elements []T, fieldName stri return s } } + +// SliceGroupBy 指定一个结构体切片,按指定的字段对其进行分组 +// 例如: +// +// [{UserName: "zhang", Type: 1}, {UserName: "wang", Type: 1}, {UserName: "li", Type: 2}] +// +// 按Type这个字段对切片进行分组的结果是一个Map: +// +// { +// 1: [{UserName: "zhang", Type: 1}, {UserName: "wang", Type: 1}], +// 2: [{UserName: "li", Type: 2}], +// } +// +// T是结构体类型,KT是字段类型,字段类型必须是可比较的,即:comparable(因为它是Map的Key) +func SliceGroupBy[T any, KT comparable](data []T, fieldName string) map[KT][]T { + // 只处理导出了的字段,如果给定的字段没有导出,那么什么也不做 + if fieldName[0] < 'A' || fieldName[0] > 'Z' { + return map[KT][]T{} + } + if data == nil || len(data) == 0 { + return map[KT][]T{} + } + // 遍历处理分组 + m := make(map[KT][]T) + for _, datum := range data { + v := reflect.ValueOf(datum) + rv := v.FieldByName(fieldName) + if rv.Kind() != reflect.Invalid { // 判断字段是否存在 + if fv, ok := rv.Interface().(KT); ok { + m[fv] = append(m[fv], datum) + } + } + } + return m +} diff --git a/struct_test.go b/struct_test.go index 09eb528..bea02cc 100644 --- a/struct_test.go +++ b/struct_test.go @@ -114,19 +114,19 @@ func TestPickStructsField(t *testing.T) { } for _, testCase := range testCases { switch testCase.usingFieldName { - case "id": - if actual := PickStructsField[User, int](testCase.elements, testCase.usingFieldName); reflect.DeepEqual(actual, testCase.exceptedId) { - t.Errorf("字符串清理,输入:%+v,预期:%+v,实际:%+v", testCase.elements, testCase.exceptedId, actual) + case "Id": + if actual := PickStructsField[User, int](testCase.elements, testCase.usingFieldName); !reflect.DeepEqual(actual, testCase.exceptedId) { + t.Errorf("PickStructsField,输入:%+v,预期:%+v,实际:%+v", testCase.elements, testCase.exceptedId, actual) } break - case "name": - if actual := PickStructsField[User, string](testCase.elements, testCase.usingFieldName); reflect.DeepEqual(actual, testCase.exceptedName) { - t.Errorf("字符串清理,输入:%+v,预期:%+v,实际:%+v", testCase.elements, testCase.exceptedName, actual) + case "Name": + if actual := PickStructsField[User, string](testCase.elements, testCase.usingFieldName); !reflect.DeepEqual(actual, testCase.exceptedName) { + t.Errorf("PickStructsField,输入:%+v,预期:%+v,实际:%+v", testCase.elements, testCase.exceptedName, actual) } break - case "score": - if actual := PickStructsField[User, float64](testCase.elements, testCase.usingFieldName); reflect.DeepEqual(actual, testCase.exceptedScore) { - t.Errorf("字符串清理,输入:%+v,预期:%+v,实际:%+v", testCase.elements, testCase.exceptedScore, actual) + case "Score": + if actual := PickStructsField[User, float64](testCase.elements, testCase.usingFieldName); !reflect.DeepEqual(actual, testCase.exceptedScore) { + t.Errorf("PickStructsField,输入:%+v,预期:%+v,实际:%+v", testCase.elements, testCase.exceptedScore, actual) } break default: @@ -134,3 +134,70 @@ func TestPickStructsField(t *testing.T) { } } } + +func TestSliceGroupBy(t *testing.T) { + type User struct { + Id int + Gender string + Score float64 + Type int + } + var users = []User{ + { + Id: 1, + Gender: "male", + Score: 1.1, + Type: 1, + }, + { + Id: 2, + Gender: "male", + Score: 2.2, + Type: 2, + }, + { + Id: 3, + Gender: "female", + Score: 3.3, + Type: 1, + }, + } + type TestCase[KT string | int] struct { + name string + elements []User + usingFieldName string + exceptedGroupBy map[KT][]User + } + testCases1 := []TestCase[string]{ + { + name: "SliceGroupByStringField-Gender", + elements: users, + usingFieldName: "Gender", + exceptedGroupBy: map[string][]User{ + "female": {{Id: 3, Gender: "female", Score: 3.3, Type: 1}}, + "male": {{Id: 1, Gender: "male", Score: 1.1, Type: 1}, {Id: 2, Gender: "male", Score: 2.2, Type: 2}}, + }, + }, + } + testCases2 := []TestCase[int]{ + { + name: "SliceGroupByIntField-Type", + elements: users, + usingFieldName: "Type", + exceptedGroupBy: map[int][]User{ + 1: {{Id: 1, Gender: "male", Score: 1.1, Type: 1}, {Id: 3, Gender: "female", Score: 3.3, Type: 1}}, + 2: {{Id: 2, Gender: "male", Score: 2.2, Type: 2}}, + }, + }, + } + for _, testCase := range testCases1 { + if actual := SliceGroupBy[User, string](testCase.elements, testCase.usingFieldName); !reflect.DeepEqual(actual, testCase.exceptedGroupBy) { + t.Errorf("SliceGroupBy,输入:%+v,预期:%+v,实际:%+v", testCase.elements, testCase.exceptedGroupBy, actual) + } + } + for _, testCase := range testCases2 { + if actual := SliceGroupBy[User, int](testCase.elements, testCase.usingFieldName); !reflect.DeepEqual(actual, testCase.exceptedGroupBy) { + t.Errorf("SliceGroupBy,输入:%+v,预期:%+v,实际:%+v", testCase.elements, testCase.exceptedGroupBy, actual) + } + } +}