Skip to content

Commit

Permalink
add new functions: Sort/AsArray/AsMap/Unique
Browse files Browse the repository at this point in the history
  • Loading branch information
mylxsw committed Apr 21, 2021
1 parent 4275264 commit 41ed10f
Show file tree
Hide file tree
Showing 3 changed files with 286 additions and 3 deletions.
174 changes: 173 additions & 1 deletion collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"reflect"
"sort"
)

var (
Expand Down Expand Up @@ -86,6 +87,177 @@ func MustNew(data interface{}) *Collection {
return res
}

// Sort sort collection with orderBy function, only support array collection
// compareFunc(val1 interface{}, val2 interface{}) bool
func (collection *Collection) Sort(compareFunc interface{}) *Collection {
if collection.isMapType() {
panic("map not support sort")
}

if !IsFunction(compareFunc, []int{2, 1}) {
panic("invalid callback function")
}

orderByFuncValue := reflect.ValueOf(compareFunc)
if orderByFuncValue.Type().Out(0).Kind() != reflect.Bool {
panic("the return type for compareFunc must be a bool value")
}

sortItems := make(sortStructs, len(collection.dataArray))
for i, v := range collection.dataArray {
sortItems[i] = sortStruct{
Compare: func(v1, v2 interface{}) bool {
arguments := []reflect.Value{reflect.ValueOf(v1), reflect.ValueOf(v2)}
return orderByFuncValue.Call(arguments)[0].Bool()
},
Value: v,
}
}

sort.Sort(sortItems)

results := make([]interface{}, len(sortItems))
for i, v := range sortItems {
results[i] = v.Value
}

return MustNew(results)
}

type sortStruct struct {
Compare func(v1, v2 interface{}) bool
Value interface{}
}

type sortStructs []sortStruct

func (s sortStructs) Len() int {
return len(s)
}

func (s sortStructs) Less(i, j int) bool {
return s[i].Compare(s[i].Value, s[j].Value)
}

func (s sortStructs) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}

// AsArray convert the collection to an array collection
// Remember: the underlying data type is changed to []interface{}
func (collection *Collection) AsArray() *Collection {
if collection.isMapType() {
results := make([]interface{}, len(collection.dataMap))
i := 0
for _, value := range collection.dataMap {
results[i] = value
i++
}

return MustNew(results)
}

results := make([]interface{}, len(collection.dataArray))
for i, value := range collection.dataArray {
results[i] = value
}

return MustNew(results)
}

// AsMap convert collection to a map collection
// keyFunc(value interface{}) interface{}
// keyFunc(value interface{}, key interface{}) interface{}
// Remember: the underlying data type is changed to map[interface{}]interface{}
func (collection *Collection) AsMap(keyFunc interface{}) *Collection {
if !IsFunction(keyFunc, []int{1, 1}, []int{2, 1}) {
panic("invalid callback function")
}

keyFuncValue := reflect.ValueOf(keyFunc)
keyFuncType := keyFuncValue.Type()
argumentCount := keyFuncType.NumIn()

if collection.isMapType() {
results := make(map[interface{}]interface{})
for key, value := range collection.dataMap {
arguments := []reflect.Value{reflect.ValueOf(value), reflect.ValueOf(key)}
uniqID := keyFuncValue.Call(arguments[0:argumentCount])[0].Interface()

if _, ok := results[uniqID]; ok {
continue
}

results[uniqID] = value
}

return MustNew(results)
}

results := make(map[interface{}]interface{})
for index, item := range collection.dataArray {
uniqID := keyFuncValue.Call([]reflect.Value{reflect.ValueOf(item), reflect.ValueOf(index)}[0:argumentCount])[0].Interface()

if _, ok := results[uniqID]; ok {
continue
}

results[uniqID] = item
}

return MustNew(results)
}

// Unique remove duplicated elements from collection
// uniqFunc(value interface{}) interface{}
// uniqFunc(value interface{}, key interface{}) interface{}
// Remember: the return collection type is map[interface{}]interface{} for map, []interface{} for array or slices
func (collection *Collection) Unique(uniqFunc interface{}) *Collection {
if !IsFunction(uniqFunc, []int{1, 1}, []int{2, 1}) {
panic("invalid callback function")
}

uniqFuncValue := reflect.ValueOf(uniqFunc)
uniqFuncType := uniqFuncValue.Type()
argumentCount := uniqFuncType.NumIn()

if collection.isMapType() {
results := make(map[interface{}]interface{})
for key, value := range collection.dataMap {
arguments := []reflect.Value{reflect.ValueOf(value), reflect.ValueOf(key)}
uniqID := uniqFuncValue.Call(arguments[0:argumentCount])[0].Interface()

if _, ok := results[uniqID]; ok {
continue
}

results[uniqID] = value
}

return MustNew(results)
}

results := make(map[interface{}]interface{})
for index, item := range collection.dataArray {
uniqID := uniqFuncValue.Call([]reflect.Value{reflect.ValueOf(item), reflect.ValueOf(index)}[0:argumentCount])[0].Interface()

if _, ok := results[uniqID]; ok {
continue
}

results[uniqID] = item
}

resultsArr := make([]interface{}, len(results))
i := 0
for _, r := range results {
resultsArr[i] = r
i++
}

return MustNew(resultsArr)
}

// GroupBy iterates over elements of collection, grouping all elements by specified conditions
// groupFunc(interface{}) interface{}
// groupFunc(interface{}, int) interface{}
Expand Down Expand Up @@ -276,7 +448,7 @@ func (collection *Collection) toMap(result interface{}) error {

func (collection *Collection) ToArray() ([]interface{}, error) {
if collection.isMapType() {
return nil, fmt.Errorf("collection is a map")
return collection.AsArray().ToArray()
}

var res []interface{}
Expand Down
111 changes: 109 additions & 2 deletions collection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"testing"

"github.com/mylxsw/coll"
"github.com/stretchr/testify/assert"
)

var testMapData = map[string]string{
Expand Down Expand Up @@ -383,7 +384,113 @@ func TestGroupByForArray(t *testing.T) {
t.Errorf("test failed: %v", err)
}

for k, v := range element2s {
fmt.Printf("key=%v, value=%v\n", k, v)
assert.Equal(t, 3, len(element2s["boy"]))
assert.Equal(t, 5, len(element2s["girl"]))
}

func TestUniqueForArray(t *testing.T) {
col := coll.MustNew([]*Element{
{ID: 11, Name: "guan", Gender: "boy"},
{ID: 12, Name: "yi", Gender: "boy"},
{ID: 13, Name: "yao", Gender: "girl"},
{ID: 15, Name: "a", Gender: "girl"},
{ID: 18, Name: "b", Gender: "boy"},
{ID: 23, Name: "c", Gender: "girl"},
{ID: 24, Name: "dd", Gender: "girl"},
{ID: 26, Name: "e", Gender: "girl"},
})

var rs []interface{}
assert.NoError(t, col.Unique(func(ele *Element) string { return ele.Gender }).All(&rs))
assert.Equal(t, 2, len(rs))
}

func TestUniqueForMap(t *testing.T) {
col := coll.MustNew(map[string]Element{
"a11": {ID: 11, Name: "guan", Gender: "boy"},
"1dc2": {ID: 12, Name: "yi", Gender: "boy"},
"313": {ID: 13, Name: "yao", Gender: "girl"},
"c14": {ID: 15, Name: "a", Gender: "girl"},
"1ff5": {ID: 18, Name: "ee", Gender: "boy"},
"16": {ID: 23, Name: "c", Gender: "girl"},
"1ag7": {ID: 24, Name: "yao", Gender: "girl"},
"189": {ID: 26, Name: "ee", Gender: "girl"},
})

var rs map[string]interface{}
assert.NoError(t, col.Unique(func(ele Element) string { return ele.Name }).All(&rs))
assert.Equal(t, 6, len(rs))
for k, val := range rs {
fmt.Printf("%s - %v\n", k, val)
}
}

func TestAsMap(t *testing.T) {
col := coll.MustNew([]*Element{
{ID: 11, Name: "guan", Gender: "boy"},
{ID: 12, Name: "yi", Gender: "boy"},
{ID: 13, Name: "yao", Gender: "girl"},
{ID: 15, Name: "a", Gender: "girl"},
{ID: 18, Name: "b", Gender: "boy"},
{ID: 23, Name: "c", Gender: "girl"},
{ID: 24, Name: "dd", Gender: "girl"},
{ID: 26, Name: "e", Gender: "girl"},
})

var rs map[string]interface{}
assert.NoError(t, col.AsMap(func(ele *Element) string { return ele.Name }).All(&rs))
assert.Equal(t, 8, len(rs))

for k, val := range rs {
fmt.Printf("key=%s, value=%v\n", k, val)
}
}

func TestAsArray(t *testing.T) {
col := coll.MustNew(map[string]Element{
"a11": {ID: 11, Name: "guan", Gender: "boy"},
"1dc2": {ID: 12, Name: "yi", Gender: "boy"},
"313": {ID: 13, Name: "yao", Gender: "girl"},
"c14": {ID: 15, Name: "a", Gender: "girl"},
"1ff5": {ID: 18, Name: "ee", Gender: "boy"},
"16": {ID: 23, Name: "c", Gender: "girl"},
"1ag7": {ID: 24, Name: "yao", Gender: "girl"},
"189": {ID: 26, Name: "ee", Gender: "girl"},
})

{
var rs []interface{}
assert.NoError(t, col.AsArray().All(&rs))
assert.Equal(t, 8, len(rs))
for i, v := range rs {
fmt.Printf("%d - %v\n", i, v)
}
}

{
rs, err := col.ToArray()
assert.NoError(t, err)
for i, v := range rs {
fmt.Printf("%d - %v\n", i, v)
}
}
}

func TestOrderBy(t *testing.T) {
col := coll.MustNew([]*Element{
{ID: 23, Name: "c", Gender: "girl"},
{ID: 11, Name: "guan", Gender: "boy"},
{ID: 12, Name: "yi", Gender: "boy"},
{ID: 26, Name: "e", Gender: "girl"},
{ID: 15, Name: "a", Gender: "girl"},
{ID: 18, Name: "b", Gender: "boy"},
{ID: 13, Name: "yao", Gender: "girl"},
{ID: 24, Name: "dd", Gender: "girl"},
})

col.Sort(func(val1 *Element, val2 *Element) bool {
return val1.ID < val2.ID
}).Each(func(val *Element) {
fmt.Printf("%d - %v\n", val.ID, val)
})
}
4 changes: 4 additions & 0 deletions helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import (
"unsafe"
)

func Unique(origin interface{}, dest interface{}, uniquer interface{}) error {
return MustNew(origin).Unique(uniquer).All(dest)
}

func Map(origin interface{}, dest interface{}, mapper interface{}) error {
return MustNew(origin).Map(mapper).All(dest)
}
Expand Down

0 comments on commit 41ed10f

Please sign in to comment.