Skip to content

Commit

Permalink
Merge pull request #33 from ccpwcn/dev
Browse files Browse the repository at this point in the history
新增结构体切片分组功能
  • Loading branch information
ccpwcn authored Nov 27, 2023
2 parents b4ce8fa + 29ec548 commit 1174533
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 11 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions slice.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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) {
Expand All @@ -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) {
Expand Down
35 changes: 35 additions & 0 deletions struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
85 changes: 76 additions & 9 deletions struct_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,23 +114,90 @@ 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:
break
}
}
}

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

0 comments on commit 1174533

Please sign in to comment.