add ListItemValues/ListItemValuesUnique functions and associated unit testing cases for package gutil/gvar

This commit is contained in:
John
2020-07-02 23:40:34 +08:00
parent 72da1642ee
commit 17b91dcad7
6 changed files with 380 additions and 0 deletions

View File

@ -0,0 +1,25 @@
// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gvar
import (
"github.com/gogf/gf/util/gutil"
)
// ListItemValues retrieves and returns the elements of all item struct/map with key <key>.
// Note that the parameter <list> should be type of slice which contains elements of map or struct,
// or else it returns an empty slice.
func (v *Var) ListItemValues(key interface{}) (values []interface{}) {
return gutil.ListItemValues(v.Val(), key)
}
// ListItemValuesUnique retrieves and returns the unique elements of all struct/map with key <key>.
// Note that the parameter <list> should be type of slice which contains elements of map or struct,
// or else it returns an empty slice.
func (v *Var) ListItemValuesUnique(key string) []interface{} {
return gutil.ListItemValuesUnique(v.Val(), key)
}

View File

@ -0,0 +1,115 @@
// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gvar_test
import (
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/test/gtest"
"testing"
)
func Test_ListItemValues_Map(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
listMap := g.List{
g.Map{"id": 1, "score": 100},
g.Map{"id": 2, "score": 99},
g.Map{"id": 3, "score": 99},
}
t.Assert(gvar.New(listMap).ListItemValues("id"), g.Slice{1, 2, 3})
t.Assert(gvar.New(listMap).ListItemValues("score"), g.Slice{100, 99, 99})
})
gtest.C(t, func(t *gtest.T) {
listMap := g.List{
g.Map{"id": 1, "score": 100},
g.Map{"id": 2, "score": nil},
g.Map{"id": 3, "score": 0},
}
t.Assert(gvar.New(listMap).ListItemValues("id"), g.Slice{1, 2, 3})
t.Assert(gvar.New(listMap).ListItemValues("score"), g.Slice{100, nil, 0})
})
}
func Test_ListItemValues_Struct(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
type T struct {
Id int
Score float64
}
listStruct := g.Slice{
T{1, 100},
T{2, 99},
T{3, 0},
}
t.Assert(gvar.New(listStruct).ListItemValues("Id"), g.Slice{1, 2, 3})
t.Assert(gvar.New(listStruct).ListItemValues("Score"), g.Slice{100, 99, 0})
})
// Pointer items.
gtest.C(t, func(t *gtest.T) {
type T struct {
Id int
Score float64
}
listStruct := g.Slice{
&T{1, 100},
&T{2, 99},
&T{3, 0},
}
t.Assert(gvar.New(listStruct).ListItemValues("Id"), g.Slice{1, 2, 3})
t.Assert(gvar.New(listStruct).ListItemValues("Score"), g.Slice{100, 99, 0})
})
// Nil element value.
gtest.C(t, func(t *gtest.T) {
type T struct {
Id int
Score interface{}
}
listStruct := g.Slice{
T{1, 100},
T{2, nil},
T{3, 0},
}
t.Assert(gvar.New(listStruct).ListItemValues("Id"), g.Slice{1, 2, 3})
t.Assert(gvar.New(listStruct).ListItemValues("Score"), g.Slice{100, nil, 0})
})
}
func Test_ListItemValuesUnique(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
listMap := g.List{
g.Map{"id": 1, "score": 100},
g.Map{"id": 2, "score": 100},
g.Map{"id": 3, "score": 100},
g.Map{"id": 4, "score": 100},
g.Map{"id": 5, "score": 100},
}
t.Assert(gvar.New(listMap).ListItemValuesUnique("id"), g.Slice{1, 2, 3, 4, 5})
t.Assert(gvar.New(listMap).ListItemValuesUnique("score"), g.Slice{100})
})
gtest.C(t, func(t *gtest.T) {
listMap := g.List{
g.Map{"id": 1, "score": 100},
g.Map{"id": 2, "score": 100},
g.Map{"id": 3, "score": 100},
g.Map{"id": 4, "score": 100},
g.Map{"id": 5, "score": 99},
}
t.Assert(gvar.New(listMap).ListItemValuesUnique("id"), g.Slice{1, 2, 3, 4, 5})
t.Assert(gvar.New(listMap).ListItemValuesUnique("score"), g.Slice{100, 99})
})
gtest.C(t, func(t *gtest.T) {
listMap := g.List{
g.Map{"id": 1, "score": 100},
g.Map{"id": 2, "score": 100},
g.Map{"id": 3, "score": 0},
g.Map{"id": 4, "score": 100},
g.Map{"id": 5, "score": 99},
}
t.Assert(gvar.New(listMap).ListItemValuesUnique("id"), g.Slice{1, 2, 3, 4, 5})
t.Assert(gvar.New(listMap).ListItemValuesUnique("score"), g.Slice{100, 0, 99})
})
}

106
util/gutil/gutil_list.go Normal file
View File

@ -0,0 +1,106 @@
// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gutil
import (
"reflect"
)
// ListItemValues retrieves and returns the elements of all item struct/map with key <key>.
// Note that the parameter <list> should be type of slice which contains elements of map or struct,
// or else it returns an empty slice.
func ListItemValues(list interface{}, key interface{}) (values []interface{}) {
// If the given <list> is the most common used slice type []map[string]interface{},
// it enhances the performance using type assertion.
if l, ok := list.([]map[string]interface{}); ok {
if mapKey, ok := key.(string); ok {
if len(l) == 0 {
return
}
if _, ok := l[0][mapKey]; !ok {
return
}
values = make([]interface{}, len(l))
for k, m := range l {
values[k] = m[mapKey]
}
return
}
}
// It uses reflect for common checks and converting.
var (
reflectValue = reflect.ValueOf(list)
reflectKind = reflectValue.Kind()
)
for reflectKind == reflect.Ptr {
reflectValue = reflectValue.Elem()
reflectKind = reflectValue.Kind()
}
values = []interface{}{}
switch reflectKind {
case reflect.Slice, reflect.Array:
if reflectValue.Len() == 0 {
return
}
var (
itemValue reflect.Value
givenKeyValue = reflect.ValueOf(key)
)
for i := 0; i < reflectValue.Len(); i++ {
itemValue = reflectValue.Index(i)
// If the items are type of interface{}.
if itemValue.Kind() == reflect.Interface {
itemValue = itemValue.Elem()
}
if itemValue.Kind() == reflect.Ptr {
itemValue = itemValue.Elem()
}
switch itemValue.Kind() {
case reflect.Map:
v := itemValue.MapIndex(givenKeyValue)
if v.IsValid() {
values = append(values, v.Interface())
}
case reflect.Struct:
// The <mapKey> must be type of string.
v := itemValue.FieldByName(givenKeyValue.String())
if v.IsValid() {
values = append(values, v.Interface())
}
default:
return
}
}
return
default:
return
}
}
// ListItemValuesUnique retrieves and returns the unique elements of all struct/map with key <key>.
// Note that the parameter <list> should be type of slice which contains elements of map or struct,
// or else it returns an empty slice.
func ListItemValuesUnique(list interface{}, key string) []interface{} {
values := ListItemValues(list, key)
if len(values) > 0 {
var (
ok bool
m = make(map[interface{}]struct{}, len(values))
)
for i := 0; i < len(values); {
if _, ok = m[values[i]]; ok {
values = SliceDelete(values, i)
} else {
m[values[i]] = struct{}{}
i++
}
}
}
return values
}

View File

@ -13,3 +13,21 @@ func SliceCopy(data []interface{}) []interface{} {
copy(newData, data)
return newData
}
// SliceDelete deletes an element at <index> and returns the new slice.
// It does nothing if the given <index> is invalid.
func SliceDelete(data []interface{}, index int) (newSlice []interface{}) {
if index < 0 || index >= len(data) {
return data
}
// Determine array boundaries when deleting to improve deletion efficiency.
if index == 0 {
return data[1:]
} else if index == len(data)-1 {
return data[:index]
}
// If it is a non-boundary delete,
// it will involve the creation of an array,
// then the deletion is less efficient.
return append(data[:index], data[index+1:]...)
}

View File

@ -0,0 +1,116 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gutil_test
import (
"github.com/gogf/gf/frame/g"
"testing"
"github.com/gogf/gf/test/gtest"
"github.com/gogf/gf/util/gutil"
)
func Test_ListItemValues_Map(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
listMap := g.List{
g.Map{"id": 1, "score": 100},
g.Map{"id": 2, "score": 99},
g.Map{"id": 3, "score": 99},
}
t.Assert(gutil.ListItemValues(listMap, "id"), g.Slice{1, 2, 3})
t.Assert(gutil.ListItemValues(listMap, "score"), g.Slice{100, 99, 99})
})
gtest.C(t, func(t *gtest.T) {
listMap := g.List{
g.Map{"id": 1, "score": 100},
g.Map{"id": 2, "score": nil},
g.Map{"id": 3, "score": 0},
}
t.Assert(gutil.ListItemValues(listMap, "id"), g.Slice{1, 2, 3})
t.Assert(gutil.ListItemValues(listMap, "score"), g.Slice{100, nil, 0})
})
}
func Test_ListItemValues_Struct(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
type T struct {
Id int
Score float64
}
listStruct := g.Slice{
T{1, 100},
T{2, 99},
T{3, 0},
}
t.Assert(gutil.ListItemValues(listStruct, "Id"), g.Slice{1, 2, 3})
t.Assert(gutil.ListItemValues(listStruct, "Score"), g.Slice{100, 99, 0})
})
// Pointer items.
gtest.C(t, func(t *gtest.T) {
type T struct {
Id int
Score float64
}
listStruct := g.Slice{
&T{1, 100},
&T{2, 99},
&T{3, 0},
}
t.Assert(gutil.ListItemValues(listStruct, "Id"), g.Slice{1, 2, 3})
t.Assert(gutil.ListItemValues(listStruct, "Score"), g.Slice{100, 99, 0})
})
// Nil element value.
gtest.C(t, func(t *gtest.T) {
type T struct {
Id int
Score interface{}
}
listStruct := g.Slice{
T{1, 100},
T{2, nil},
T{3, 0},
}
t.Assert(gutil.ListItemValues(listStruct, "Id"), g.Slice{1, 2, 3})
t.Assert(gutil.ListItemValues(listStruct, "Score"), g.Slice{100, nil, 0})
})
}
func Test_ListItemValuesUnique(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
listMap := g.List{
g.Map{"id": 1, "score": 100},
g.Map{"id": 2, "score": 100},
g.Map{"id": 3, "score": 100},
g.Map{"id": 4, "score": 100},
g.Map{"id": 5, "score": 100},
}
t.Assert(gutil.ListItemValuesUnique(listMap, "id"), g.Slice{1, 2, 3, 4, 5})
t.Assert(gutil.ListItemValuesUnique(listMap, "score"), g.Slice{100})
})
gtest.C(t, func(t *gtest.T) {
listMap := g.List{
g.Map{"id": 1, "score": 100},
g.Map{"id": 2, "score": 100},
g.Map{"id": 3, "score": 100},
g.Map{"id": 4, "score": 100},
g.Map{"id": 5, "score": 99},
}
t.Assert(gutil.ListItemValuesUnique(listMap, "id"), g.Slice{1, 2, 3, 4, 5})
t.Assert(gutil.ListItemValuesUnique(listMap, "score"), g.Slice{100, 99})
})
gtest.C(t, func(t *gtest.T) {
listMap := g.List{
g.Map{"id": 1, "score": 100},
g.Map{"id": 2, "score": 100},
g.Map{"id": 3, "score": 0},
g.Map{"id": 4, "score": 100},
g.Map{"id": 5, "score": 99},
}
t.Assert(gutil.ListItemValuesUnique(listMap, "id"), g.Slice{1, 2, 3, 4, 5})
t.Assert(gutil.ListItemValuesUnique(listMap, "score"), g.Slice{100, 0, 99})
})
}