diff --git a/container/gvar/gvar_list.go b/container/gvar/gvar_list.go new file mode 100644 index 000000000..bc6d74188 --- /dev/null +++ b/container/gvar/gvar_list.go @@ -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 . +// Note that the parameter 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 . +// Note that the parameter 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) +} diff --git a/container/gvar/gvar_z_unit_list_test.go b/container/gvar/gvar_z_unit_list_test.go new file mode 100644 index 000000000..fba1fd3bb --- /dev/null +++ b/container/gvar/gvar_z_unit_list_test.go @@ -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}) + }) +} diff --git a/util/gutil/gutil_list.go b/util/gutil/gutil_list.go new file mode 100644 index 000000000..799ef08b0 --- /dev/null +++ b/util/gutil/gutil_list.go @@ -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 . +// Note that the parameter 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 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 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 . +// Note that the parameter 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 +} diff --git a/util/gutil/gutil_slice.go b/util/gutil/gutil_slice.go index 3b6086277..b02106131 100644 --- a/util/gutil/gutil_slice.go +++ b/util/gutil/gutil_slice.go @@ -13,3 +13,21 @@ func SliceCopy(data []interface{}) []interface{} { copy(newData, data) return newData } + +// SliceDelete deletes an element at and returns the new slice. +// It does nothing if the given 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:]...) +} diff --git a/util/gutil/gutil_z_comparator_z_unit_test.go b/util/gutil/gutil_z_unit_comparator_test.go similarity index 100% rename from util/gutil/gutil_z_comparator_z_unit_test.go rename to util/gutil/gutil_z_unit_comparator_test.go diff --git a/util/gutil/gutil_z_unit_list_test.go b/util/gutil/gutil_z_unit_list_test.go new file mode 100755 index 000000000..81cc88b9b --- /dev/null +++ b/util/gutil/gutil_z_unit_list_test.go @@ -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}) + }) +}