diff --git a/README.md b/README.md index e84f248..f0f679e 100644 --- a/README.md +++ b/README.md @@ -430,65 +430,46 @@ Chain2([][]int{ -### Group(source, keySelector) +### Group(keySelector) IEnumerable +### Group(keySelector) IQuery __Arguments__ -* `source` - array or map * `keySelector` - func(element or value, index or key) anyType -__Return__ - -* interface{} - map[anyType][](element or value) - __Examples__ ```go -src := []int{ 1, 2, 3, 4, 5 } -var res map[string][]int -Chain(src).Group(func (n, _ int) string { - if n % 2 == 0 { - return "even" - } - return "odd" -}).Value(&res) -// or -res := Group(src, func (n, _ int) string { - if n % 2 == 0 { +dst := make(map[string][]int) +Chain2([]int{1, 2, 3, 4, 5}).Group(func(n, _ int) string { + if n%2 == 0 { return "even" } return "odd" -}).(map[string][]int) -// res = map[odd:[1 3 5] even:[2 4]] +}).Value(&dst) +// dst = map[odd:[1 3 5] even:[2 4]] ``` -### GroupBy(source, property) +### GroupBy(fieldName) IEnumerable +### GroupBy(fieldName) IQuery __Arguments__ -* `source` - array or map -* `property` - property name - -__Return__ - -* interface{} - map[property type][](element or value) +* `fieldName` - field name __Examples__ ```go -arr := []testModel{ +dst := make(map[string][]testModel) +Chain2([]testModel{ {ID: 1, Name: "a"}, {ID: 2, Name: "a"}, {ID: 3, Name: "b"}, {ID: 4, Name: "b"}, -} -var res map[string][]testModel -Chain(arr).GroupBy("name").Value(&res) -// or -res := GroupBy(arr, "name").(map[string][]testModel) -// res = map[a:[{{0} 1 a} {{0} 2 a}] b:[{{0} 3 b} {{0} 4 b}]] +}).GroupBy("Name").Value(&dst) +// dst = map[a:[{1 a} {2 a}] b:[{3 b} {4 b}]] ``` @@ -1218,6 +1199,13 @@ __Same__ * `FilterBy` ## Release Notes +~~~ +v1.5.0 (2019-06-18) +* 增加Chain Benchmark +* IEnumerable增加Group、GroupBy +* 优化IEnumerable的Distinct、Enumerator、Index、Property、Select、Where +~~~ + ~~~ v1.4.0 (2019-06-15) * Reduce、Take支持IEnumerable diff --git a/chain_test.go b/chain_test.go index c02d229..7d56893 100644 --- a/chain_test.go +++ b/chain_test.go @@ -1,5 +1,7 @@ package underscore +import "testing" + var ( benchmarkSize = 1000000 ) @@ -14,3 +16,25 @@ type testNestedModel struct { Age int } + +func Benchmark_Chain(b *testing.B) { + for n := 0; n < b.N; n++ { + var dst int + Range(1, benchmarkSize, 1).Map(func(r, _ int) int { + return -r + }).Where(func(r, _ int) bool { + return r < -20 + }).First().Value(&dst) + } +} + +func Benchmark_Chain_New(b *testing.B) { + for n := 0; n < b.N; n++ { + var dst int + Range2(1, benchmarkSize, 1).Select(func(r, _ int) int { + return -r + }).Where(func(r, _ int) bool { + return r < -20 + }).First().Value(&dst) + } +} diff --git a/distinct.go b/distinct.go index e36d507..22e3c7f 100644 --- a/distinct.go +++ b/distinct.go @@ -21,9 +21,7 @@ func (m enumerable) Distinct(selector interface{}) IEnumerable { for ok = iterator.MoveNext(); ok; ok = iterator.MoveNext() { valueRV = iterator.GetValue() keyRV = iterator.GetKey() - v := getRV( - selectorRV.Call([]reflect.Value{valueRV, keyRV})[0], - ).Interface() + v := getFuncReturnRV(selectorRV, iterator).Interface() if _, has := set[v]; !has { set[v] = true return diff --git a/enumerator.go b/enumerator.go index 7baf903..3f5c7ce 100644 --- a/enumerator.go +++ b/enumerator.go @@ -16,7 +16,7 @@ func (m enumerator) GetKey() reflect.Value { } func (m enumerator) GetValue() reflect.Value { - return getRV(m.value) + return getRealRV(m.value) } func (m *enumerator) MoveNext() (ok bool) { diff --git a/group.go b/group.go index f6e2bf1..b22d3e6 100644 --- a/group.go +++ b/group.go @@ -1,13 +1,10 @@ package underscore -import ( - "reflect" -) +import "reflect" -// Group is 分组 -func Group(source, keySelector interface{}) interface{} { +func (m *query) Group(keySelector interface{}) IQuery { var groupRV reflect.Value - each(source, keySelector, func(groupKeyRV, valueRV, _ reflect.Value) bool { + each(m.Source, keySelector, func(groupKeyRV, valueRV, _ reflect.Value) bool { groupValueRT := reflect.SliceOf(valueRV.Type()) if !groupRV.IsValid() { groupRT := reflect.MapOf(groupKeyRV.Type(), groupValueRT) @@ -24,27 +21,67 @@ func Group(source, keySelector interface{}) interface{} { return false }) if groupRV.IsValid() { - return groupRV.Interface() + m.Source = groupRV.Interface() } - return nil + + return m } -// GroupBy is 根据某个属性分组 -func GroupBy(source interface{}, property string) interface{} { +func (m *query) GroupBy(property string) IQuery { getPropertyRV := PropertyRV(property) - return Group(source, func(value, _ interface{}) facade { + return m.Group(func(value, _ interface{}) facade { return facade{ getPropertyRV(value), } }) } -func (m *query) Group(keySelector interface{}) IQuery { - m.Source = Group(m.Source, keySelector) - return m +func (m enumerable) Group(keySelector interface{}) enumerable { + return enumerable{ + Enumerator: func() IEnumerator { + groupRVs := make(map[interface{}]reflect.Value) + iterator := m.GetEnumerator() + keySelectorRV := reflect.ValueOf(keySelector) + keyRVs := make([]reflect.Value, 0) + for ok := iterator.MoveNext(); ok; ok = iterator.MoveNext() { + keyRV := getFuncReturnRV(keySelectorRV, iterator) + key := keyRV.Interface() + groupRV, ok := groupRVs[key] + if !ok { + groupRV = reflect.MakeSlice( + reflect.SliceOf( + iterator.GetValue().Type(), + ), + 0, + 0, + ) + keyRVs = append(keyRVs, keyRV) + } + groupRVs[key] = reflect.Append( + groupRV, + iterator.GetValue(), + ) + } + index := 0 + return &enumerator{ + MoveNextFunc: func() (valueRV reflect.Value, keyRV reflect.Value, ok bool) { + if ok = index < len(keyRVs); ok { + keyRV = keyRVs[index] + valueRV = groupRVs[keyRV.Interface()] + index++ + } + return + }, + } + }, + } } -func (m *query) GroupBy(property string) IQuery { - m.Source = GroupBy(m.Source, property) - return m +func (m enumerable) GroupBy(fieldName string) enumerable { + getter := PropertyRV(fieldName) + return m.Group(func(value, _ interface{}) facade { + return facade{ + getter(value), + } + }) } diff --git a/group_test.go b/group_test.go index d15c3a3..e89a956 100644 --- a/group_test.go +++ b/group_test.go @@ -1,56 +1,53 @@ package underscore -import ( - "testing" -) +import "testing" -func Test_Group(t *testing.T) { - dic := Group([]int{1, 2, 3, 4, 5}, func(n, _ int) string { - if n%2 == 0 { - return "even" - } - return "odd" - }).(map[string][]int) - if len(dic["even"]) != 2 { - t.Error("wrong") +func Benchmark_Group(b *testing.B) { + for n := 0; n < b.N; n++ { + dst := make([]int, 0) + Range(1, benchmarkSize, 1).Group(func(n, _ int) string { + if n%2 == 0 { + return "even" + } + return "odd" + }).Value(&dst) } } -func Test_GroupBy(t *testing.T) { - arr := []testModel{ - {ID: 1, Name: "a"}, - {ID: 2, Name: "a"}, - {ID: 3, Name: "b"}, - {ID: 4, Name: "b"}, - } - dic := GroupBy(arr, "name").(map[string][]testModel) - if len(dic) != 2 { - t.Error("wrong") +func Benchmark_Group_New(b *testing.B) { + for n := 0; n < b.N; n++ { + dst := make([]int, 0) + Range2(1, benchmarkSize, 1).Group(func(n, _ int) string { + if n%2 == 0 { + return "even" + } + return "odd" + }).Value(&dst) } } -func Test_Chain_Group(t *testing.T) { - res := make(map[string][]int) - Chain([]int{1, 2, 3, 4, 5}).Group(func(n, _ int) string { +func Test_Group(t *testing.T) { + dst := make(map[string][]int) + Chain2([]int{1, 2, 3, 4, 5}).Group(func(n, _ int) string { if n%2 == 0 { return "even" } return "odd" - }).Value(&res) - if len(res["even"]) != 2 { + }).Value(&dst) + if len(dst["even"]) != 2 { t.Error("wrong") } } -func Test_Chain_GroupBy(t *testing.T) { - res := make(map[string][]testModel) - Chain([]testModel{ +func Test_GroupBy(t *testing.T) { + dst := make(map[string][]testModel) + Chain2([]testModel{ {ID: 1, Name: "a"}, {ID: 2, Name: "a"}, {ID: 3, Name: "b"}, {ID: 4, Name: "b"}, - }).GroupBy("Name").Value(&res) - if len(res) != 2 { + }).GroupBy("Name").Value(&dst) + if len(dst) != 2 { t.Error("wrong") } } diff --git a/i-enumerable.go b/i-enumerable.go index 2d39a10..c698b6d 100644 --- a/i-enumerable.go +++ b/i-enumerable.go @@ -18,6 +18,8 @@ type IEnumerable interface { FindIndexBy(dict map[string]interface{}) int First() IEnumerable GetEnumerator() IEnumerator + Group(keySelector interface{}) enumerable + GroupBy(fieldName string) enumerable Index(keySelector interface{}) IEnumerable IndexBy(fieldName string) IEnumerable Keys() IEnumerable diff --git a/index.go b/index.go index cbd470b..017b9ad 100644 --- a/index.go +++ b/index.go @@ -50,12 +50,7 @@ func (m enumerable) Index(keySelector interface{}) IEnumerable { return &enumerator{ MoveNextFunc: func() (valueRV reflect.Value, keyRV reflect.Value, ok bool) { if ok = iterator.MoveNext(); ok { - keyRV = getRV( - keySelectorRV.Call([]reflect.Value{ - iterator.GetValue(), - iterator.GetKey(), - })[0], - ) + keyRV = getFuncReturnRV(keySelectorRV, iterator) valueRV = iterator.GetValue() } diff --git a/property.go b/property.go index b2d32a8..28497ae 100644 --- a/property.go +++ b/property.go @@ -20,7 +20,7 @@ func Property(name string) func(interface{}) interface{} { func PropertyRV(name string) GetProeprtyRVFunc { var getter GetProeprtyRVFunc getter = func(item interface{}) reflect.Value { - itemRV := getRV(item) + itemRV := getRealRV(item) itemRT := itemRV.Type() for i := 0; i < itemRT.NumField(); i++ { field := itemRT.Field(i) @@ -42,20 +42,3 @@ func PropertyRV(name string) GetProeprtyRVFunc { } return getter } - -func getRV(v interface{}) reflect.Value { - rv := reflect.ValueOf(v) - if rv.Type() == rtOfRV { - rv = v.(reflect.Value) - } - - if rv.Kind() == reflect.Ptr { - rv = rv.Elem() - } - - if rv.Type() == facadeRT { - rv = rv.Interface().(facade).Real - } - - return rv -} diff --git a/select.go b/select.go index dad895e..e7ecf03 100644 --- a/select.go +++ b/select.go @@ -5,18 +5,13 @@ import "reflect" func (m enumerable) Select(selector interface{}) IEnumerable { return enumerable{ Enumerator: func() IEnumerator { - index := 0 iterator := m.GetEnumerator() selectorRV := reflect.ValueOf(selector) return &enumerator{ MoveNextFunc: func() (valueRV reflect.Value, keyRV reflect.Value, ok bool) { if ok = iterator.MoveNext(); ok { - valueRV = selectorRV.Call([]reflect.Value{ - iterator.GetValue(), - iterator.GetKey(), - })[0] - keyRV = reflect.ValueOf(index) - index++ + keyRV = iterator.GetKey() + valueRV = getFuncReturnRV(selectorRV, iterator) } return diff --git a/util.go b/util.go index 38aa216..762e495 100644 --- a/util.go +++ b/util.go @@ -1,40 +1,29 @@ package underscore -import ( - "reflect" -) +import "reflect" -// ToRealValue is 将反射值转为真实类型的值 -func ToRealValue(rv reflect.Value) interface{} { - var value interface{} - switch rv.Kind() { - case reflect.Bool: - value = rv.Bool() - break - case reflect.Float32, reflect.Float64: - value = rv.Float() - break - case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64: - value = rv.Int() - break - case reflect.String: - value = rv.String() - break - case reflect.Struct: - value = rv.Interface() - break - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - value = rv.Uint() - break - case reflect.Ptr: - return ToRealValue( - reflect.Indirect(rv), - ) - default: - if !rv.IsNil() { - value = rv.Interface() - } - break +func getRealRV(v interface{}) reflect.Value { + rv := reflect.ValueOf(v) + if rv.Type() == rtOfRV { + rv = v.(reflect.Value) } - return value + + if rv.Kind() == reflect.Ptr { + rv = rv.Elem() + } + + if rv.Type() == facadeRT { + rv = rv.Interface().(facade).Real + } + + return rv +} + +func getFuncReturnRV(selectorRV reflect.Value, enumerator IEnumerator) reflect.Value { + return getRealRV( + selectorRV.Call([]reflect.Value{ + enumerator.GetValue(), + enumerator.GetKey(), + })[0], + ) } diff --git a/where.go b/where.go index fe7adb5..1d610f1 100644 --- a/where.go +++ b/where.go @@ -24,8 +24,7 @@ func (m enumerable) Where(predicate interface{}) IEnumerable { for ok = iterator.MoveNext(); ok; ok = iterator.MoveNext() { valueRV = iterator.GetValue() keyRV = iterator.GetKey() - returnRVs := predicateRV.Call([]reflect.Value{valueRV, keyRV}) - if returnRVs[0].Bool() { + if predicateRV.Call([]reflect.Value{valueRV, keyRV})[0].Bool() { return } }