diff --git a/README.md b/README.md index 80a1abf..e84f248 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ \_/__/ ``` -# Underscore.go [![GoDoc](https://godoc.org/github.com/ahl5esoft/golang-underscore?status.svg)](https://godoc.org/github.com/ahl5esoft/golang-underscore) [![Go Report Card](https://goreportcard.com/badge/github.com/ahl5esoft/golang-underscore)](https://goreportcard.com/report/github.com/ahl5esoft/golang-underscore) ![Version](https://img.shields.io/badge/version-1.2.0-green.svg) +# Underscore.go [![GoDoc](https://godoc.org/github.com/ahl5esoft/golang-underscore?status.svg)](https://godoc.org/github.com/ahl5esoft/golang-underscore) [![Go Report Card](https://goreportcard.com/badge/github.com/ahl5esoft/golang-underscore)](https://goreportcard.com/report/github.com/ahl5esoft/golang-underscore) ![Version](https://img.shields.io/badge/version-1.4.0-green.svg) like underscore.js, but for Go ## Installation @@ -24,11 +24,11 @@ like underscore.js, but for Go ## Documentation ### API +* [`Aggregate`](#aggregate) * [`All`](#all), [`AllBy`](#allBy) * [`Any`](#any), [`AnyBy`](#anyBy) * [`AsParallel`](#asParallel) * [`Chain`](#chain) -* [`Clone`](#clone) * [`Distinct`](#distinct), [`DistinctBy`](#distinctBy) * [`Each`](#each) * [`Filter`](#where), [`FilterBy`](#whereBy) @@ -46,17 +46,44 @@ like underscore.js, but for Go * [`Object`](#object) * [`Property`](#property), [`PropertyRV`](#propertyRV) * [`Range`](#range) -* [`Reduce`](#reduce) +* [`Reduce`](#aggregate) * [`Reject`](#reject), [`RejectBy`](#rejectBy) * [`Reverse`](#reverse), [`ReverseBy`](#reverseBy) * [`Select`](#select), [`SelectBy`](#selectBy) * [`Size`](#size) +* [`Skip`](#skip) * [`Sort`](#sort), [`SortBy`](#sortBy) * [`Take`](#take) * [`Uniq`](#distinct), [`UniqBy`](#distinctBy) * [`Values`](#values) * [`Where`](#where), [`WhereBy`](#whereBy) + + +### Aggregate(memo, fn) IEnumerable +### Aggregate(source, iterator) IQuery + +__Arguments__ + +* `memo` - anyType +* `iterator` - func(memo, element or value, key or index) memo + +__Examples__ + +```go +dst := make([]int, 0) +Chain2([]int{1, 2}).Aggregate(make([]int, 0), func(memo []int, n, _ int) []int { + memo = append(memo, n) + memo = append(memo, n+10) + return memo +}).Value(&dst) +// dst = [1 11 2 12] +``` + +__Same__ + +* `Reduce` + ### All(predicate) bool @@ -200,28 +227,6 @@ Chain([]int{1, 2, 1, 4, 1, 3}).Uniq(nil).Group(func(n, _ int) string { // len(res) == 2 && ok == true ``` - - -### Clone() - -__Return__ - -* interface{} - -__Examples__ - -```go -arr := []int{1, 2, 3} -var duplicate []int -Chain(arr).Clone().Value(&duplicate) -// or -duplicate := Clone(arr) -ok := All(duplicate, func(n, i int) bool { - return arr[i] == n -}) -// ok == true -``` - ### Distinct(selector) IEnumerable @@ -842,38 +847,6 @@ Range2(0, 3, 2).Value(&res) // res = [0 2] ``` - - -### Reduce(source, iterator) - -__Arguments__ - -* `source` - array -* `iterator` - func(memo, element or value, key or index) memo -* `memo` - anyType - -__Return__ - -* interface{} - memo - -__Examples__ - -```go -var res []int -Chain([]int{1, 2}).Reduce(func(memo []int, n, _ int) []int { - memo = append(memo, n) - memo = append(memo, n+10) - return memo -}, make([]int, 0)).Value(&res) -// or -res := Reduce([]int{1, 2}, func(memo []int, n, _ int) []int { - memo = append(memo, n) - memo = append(memo, n+10) - return memo -}, make([]int, 0)).([]int) -// res = [1 11 2 12] -``` - ### Reject(source, predicate) @@ -1067,6 +1040,23 @@ l := Size(dict) // l = 3 ``` + + +### Skip(count) IEnumerable + +__Arguments__ + +* `count` - int + +__Examples__ + +```go +src := []int{1, 2, 3} +dst := make([]int, 0) +Chain2(src).Skip(2).Value(&dst) +// dst = [3] +``` + ### Sort(source, selector) @@ -1129,25 +1119,19 @@ res := SortBy(arr, "id").([]testModel) -### Take(source, count) +### Take(count) IEnumerable +### Take(count) IQuery __Arguments__ -* `source` - array or map * `count` - int -__Return__ - -* interface{} - __Examples__ ```go -arr := []int{1, 2, 3} -var res []int -Chain(arr).Take(1).Value(&res) -// or -res := Take(arr, 1).([]int) +src := []int{1, 2, 3} +dst := make([]int, 0) +Chain2(src).Take(1).Value(&dst) // res = [1] ``` @@ -1234,6 +1218,14 @@ __Same__ * `FilterBy` ## Release Notes +~~~ +v1.4.0 (2019-06-15) +* Reduce、Take支持IEnumerable +* IEnumerable增加Aggregate、Skip +* IQuery删除Clone +* 优化IEnumerable的First、Index、Values +~~~ + ~~~ v1.3.0 (2019-06-09) * FindIndex、FindIndexBy、Keys、Map、MapBy、Object、Uniq、UniqBy、Values支持IEnumerable diff --git a/aggregate.go b/aggregate.go new file mode 100644 index 0000000..086b1f9 --- /dev/null +++ b/aggregate.go @@ -0,0 +1,17 @@ +package underscore + +import "reflect" + +func (m enumerable) Aggregate(memo interface{}, fn interface{}) IEnumerable { + fnRV := reflect.ValueOf(fn) + iterator := m.GetEnumerator() + memoRV := reflect.ValueOf(memo) + for ok := iterator.MoveNext(); ok; ok = iterator.MoveNext() { + memoRV = fnRV.Call([]reflect.Value{ + memoRV, + iterator.GetValue(), + iterator.GetKey(), + })[0] + } + return chainFromRV(memoRV) +} diff --git a/aggregate_test.go b/aggregate_test.go new file mode 100644 index 0000000..514a66f --- /dev/null +++ b/aggregate_test.go @@ -0,0 +1,38 @@ +package underscore + +import ( + "testing" +) + +func Benchmark_Aggregate(b *testing.B) { + for n := 0; n < b.N; n++ { + total := 0 + Range2(1, 100, 1).Aggregate(make([]int, 0), func(memo []int, r, _ int) []int { + memo = append(memo, r) + memo = append(memo, -r) + return memo + }).Value(&total) + } +} + +func Benchmark_Aggregate_NoValue(b *testing.B) { + for n := 0; n < b.N; n++ { + Range2(1, 100, 1).Aggregate(make([]int, 0), func(memo []int, r, _ int) []int { + memo = append(memo, r) + memo = append(memo, -r) + return memo + }) + } +} + +func Test_Aggregate(t *testing.T) { + dst := make([]int, 0) + Chain2([]int{1, 2}).Aggregate(make([]int, 0), func(memo []int, n, _ int) []int { + memo = append(memo, n) + memo = append(memo, n+10) + return memo + }).Value(&dst) + if !(len(dst) == 4 && dst[0] == 1 && dst[1] == 11 && dst[2] == 2 && dst[3] == 12) { + t.Error(dst) + } +} diff --git a/chain.go b/chain.go index 2a8b4cd..463171c 100644 --- a/chain.go +++ b/chain.go @@ -11,32 +11,12 @@ func Chain(source interface{}) IQuery { // Chain2 is 初始化 func Chain2(src interface{}) IEnumerable { - srcRV := reflect.ValueOf(src) - switch srcRV.Kind() { - case reflect.Array, reflect.Slice: - return chainByArrayOrSlice(srcRV, srcRV.Len()) - case reflect.Map: - return chainByMap(srcRV, srcRV.Len()) - default: - if iterator, ok := src.(IEnumerator); ok { - return enumerable{ - Enumerator: func() IEnumerator { - return iterator - }, - } - } - - return enumerable{ - Enumerator: func() IEnumerator { - return nullEnumerator{ - Src: srcRV, - } - }, - } - } + return chainFromRV( + reflect.ValueOf(src), + ) } -func chainByArrayOrSlice(srcRV reflect.Value, size int) IEnumerable { +func chainFromArrayOrSlice(srcRV reflect.Value, size int) IEnumerable { return enumerable{ Enumerator: func() IEnumerator { index := 0 @@ -56,7 +36,7 @@ func chainByArrayOrSlice(srcRV reflect.Value, size int) IEnumerable { } } -func chainByMap(srcRV reflect.Value, size int) IEnumerable { +func chainFromMap(srcRV reflect.Value, size int) IEnumerable { return enumerable{ Enumerator: func() IEnumerator { index := 0 @@ -76,3 +56,28 @@ func chainByMap(srcRV reflect.Value, size int) IEnumerable { }, } } + +func chainFromRV(rv reflect.Value) IEnumerable { + switch rv.Kind() { + case reflect.Array, reflect.Slice: + return chainFromArrayOrSlice(rv, rv.Len()) + case reflect.Map: + return chainFromMap(rv, rv.Len()) + default: + if iterator, ok := rv.Interface().(IEnumerator); ok { + return enumerable{ + Enumerator: func() IEnumerator { + return iterator + }, + } + } + + return enumerable{ + Enumerator: func() IEnumerator { + return nullEnumerator{ + Src: rv, + } + }, + } + } +} diff --git a/clone.go b/clone.go deleted file mode 100644 index e9ca60f..0000000 --- a/clone.go +++ /dev/null @@ -1,33 +0,0 @@ -package underscore - -import ( - "reflect" - - fjson "github.com/json-iterator/go" -) - -// Clone will create a deep-copied clone of the `src` -func Clone(src, dst interface{}) { - bf, _ := fjson.Marshal(src) - fjson.Unmarshal(bf, dst) -} - -func (m *query) Clone() IQuery { - rt := reflect.TypeOf(m.Source) - if rt.Kind() == reflect.Ptr { - rt = rt.Elem() - } - - rv := reflect.New(rt) - Clone( - m.Source, - rv.Interface(), - ) - - if rt.Kind() == reflect.Array || rt.Kind() == reflect.Map || rt.Kind() == reflect.Slice { - m.Source = rv.Elem().Interface() - } else { - m.Source = rv.Interface() - } - return m -} diff --git a/clone_test.go b/clone_test.go deleted file mode 100644 index 0ca70fb..0000000 --- a/clone_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package underscore - -import ( - "testing" - - fjson "github.com/json-iterator/go" -) - -type cloneModel struct { - ID string - Name string - Age int -} - -func TestClone_Struct(t *testing.T) { - m := cloneModel{ - ID: "id", - Name: "name", - Age: 11, - } - duplicate := new(cloneModel) - Clone(m, &duplicate) - sOld, _ := fjson.MarshalToString(m) - sNew, _ := fjson.MarshalToString(duplicate) - if sOld != sNew { - t.Error(sOld, sNew) - } -} - -func TestChain_Clone_Map(t *testing.T) { - dic := map[string]int{ - "a": 1, - } - duplicate := make(map[string]int) - Chain(dic).Clone().Value(&duplicate) - if duplicate["a"] != 1 { - t.Error(duplicate) - } -} - -func TestChain_Clone_Struct(t *testing.T) { - m := cloneModel{ - ID: "id", - Name: "name", - Age: 11, - } - duplicate := new(cloneModel) - Chain(m).Clone().Value(&duplicate) - - sOld, _ := fjson.MarshalToString(m) - sNew, _ := fjson.MarshalToString(duplicate) - if sOld != sNew { - t.Error(sOld, sNew) - } -} diff --git a/first.go b/first.go index f780b86..b24e713 100644 --- a/first.go +++ b/first.go @@ -19,8 +19,8 @@ func (m *query) First() IQuery { func (m enumerable) First() IEnumerable { iterator := m.GetEnumerator() for ok := iterator.MoveNext(); ok; ok = iterator.MoveNext() { - return Chain2( - iterator.GetValue().Interface(), + return chainFromRV( + iterator.GetValue(), ) } diff --git a/i-enumerable.go b/i-enumerable.go index 274e4ce..2d39a10 100644 --- a/i-enumerable.go +++ b/i-enumerable.go @@ -2,6 +2,7 @@ package underscore // IEnumerable is 迭代器接口 type IEnumerable interface { + Aggregate(memo interface{}, fn interface{}) IEnumerable All(predicate interface{}) bool AllBy(dict map[string]interface{}) bool Any(predicate interface{}) bool @@ -17,12 +18,17 @@ type IEnumerable interface { FindIndexBy(dict map[string]interface{}) int First() IEnumerable GetEnumerator() IEnumerator + Index(keySelector interface{}) IEnumerable + IndexBy(fieldName string) IEnumerable Keys() IEnumerable Map(selector interface{}) IEnumerable MapBy(fieldName string) IEnumerable Object() IEnumerable + Reduce(memo interface{}, fn interface{}) IEnumerable Select(selector interface{}) IEnumerable SelectBy(fieldName string) IEnumerable + Skip(count int) IEnumerable + Take(count int) IEnumerable Uniq(selector interface{}) IEnumerable UniqBy(fieldName string) IEnumerable Value(res interface{}) diff --git a/i-query.go b/i-query.go index 23be3cd..b7ef96b 100644 --- a/i-query.go +++ b/i-query.go @@ -7,7 +7,6 @@ type IQuery interface { Any(interface{}) bool AnyBy(map[string]interface{}) bool AsParallel() IQuery - Clone() IQuery Each(interface{}) Find(interface{}) IQuery FindBy(map[string]interface{}) IQuery diff --git a/index.go b/index.go index 8c66daa..cbd470b 100644 --- a/index.go +++ b/index.go @@ -41,3 +41,36 @@ func (m *query) IndexBy(property string) IQuery { m.Source = IndexBy(m.Source, property) return m } + +func (m enumerable) Index(keySelector interface{}) IEnumerable { + return enumerable{ + Enumerator: func() IEnumerator { + iterator := m.GetEnumerator() + keySelectorRV := reflect.ValueOf(keySelector) + 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], + ) + valueRV = iterator.GetValue() + } + + return + }, + } + }, + } +} + +func (m enumerable) IndexBy(fieldName string) IEnumerable { + getter := PropertyRV(fieldName) + return m.Index(func(value, _ interface{}) facade { + return facade{ + getter(value), + } + }) +} diff --git a/index_test.go b/index_test.go index 77ac7c6..2e44d3e 100644 --- a/index_test.go +++ b/index_test.go @@ -1,50 +1,28 @@ package underscore -import ( - "testing" -) +import "testing" func Test_Index(t *testing.T) { - res := Index([]string{"a", "b"}, func(r string, _ int) string { - return r - }).(map[string]string) - if !(res["a"] == "a" && res["b"] == "b") { - t.Error(res) + src := []string{"a", "b"} + dst := make(map[string]string) + Chain2(src).Index(func(item string, _ int) string { + return item + }).Value(&dst) + if len(dst) != 2 || dst["a"] != "a" || dst["b"] != "b" { + t.Error(dst) } } func Test_IndexBy(t *testing.T) { - arr := []testModel{ + src := []testModel{ {ID: 1, Name: "a"}, {ID: 2, Name: "a"}, {ID: 3, Name: "b"}, {ID: 4, Name: "b"}, } - res := IndexBy(arr, "Name").(map[string]testModel) - if len(res) != 2 { - t.Error(res) - } -} - -func Test_Chain_Index(t *testing.T) { - res := make(map[string]string) - Chain([]string{"a", "b"}).Index(func(item string, _ int) string { - return item - }).Value(&res) - if res["a"] != "a" { - t.Error(res) - } -} - -func Test_Chain_IndexBy(t *testing.T) { - res := make(map[string]testModel) - Chain([]testModel{ - {ID: 1, Name: "a"}, - {ID: 2, Name: "a"}, - {ID: 3, Name: "b"}, - {ID: 4, Name: "b"}, - }).IndexBy("Name").Value(&res) - if len(res) != 2 { - t.Error("wrong") + dst := make(map[string]testModel) + Chain2(src).IndexBy("name").Value(&dst) + if len(dst) != 2 { + t.Error(dst) } } diff --git a/reduce.go b/reduce.go index d12a148..764984a 100644 --- a/reduce.go +++ b/reduce.go @@ -31,3 +31,7 @@ func (m *query) Reduce(iterator, memo interface{}) IQuery { m.Source = Reduce(m.Source, iterator, memo) return m } + +func (m enumerable) Reduce(memo interface{}, fn interface{}) IEnumerable { + return m.Aggregate(memo, fn) +} diff --git a/reduce_test.go b/reduce_test.go index 91cda35..093d551 100644 --- a/reduce_test.go +++ b/reduce_test.go @@ -1,33 +1,15 @@ package underscore -import ( - "testing" -) +import "testing" func Test_Reduce(t *testing.T) { - v := Reduce([]int{1, 2}, func(memo []int, n, _ int) []int { + dst := make([]int, 0) + Chain2([]int{1, 2}).Reduce(make([]int, 0), func(memo []int, n, _ int) []int { memo = append(memo, n) memo = append(memo, n+10) return memo - }, make([]int, 0)) - res, ok := v.([]int) - if !(ok && len(res) == 4) { - t.Error("wrong length") - } - - if !(res[0] == 1 && res[1] == 11 && res[2] == 2 && res[3] == 12) { - t.Error("wrong result") - } -} - -func Test_Chain_Reduce(t *testing.T) { - res := make([]int, 0) - Chain([]int{1, 2}).Reduce(func(memo []int, n, _ int) []int { - memo = append(memo, n) - memo = append(memo, n+10) - return memo - }, make([]int, 0)).Value(&res) - if !(len(res) == 4 && res[0] == 1 && res[1] == 11 && res[2] == 2 && res[3] == 12) { - t.Error(res) + }).Value(&dst) + if !(len(dst) == 4 && dst[0] == 1 && dst[1] == 11 && dst[2] == 2 && dst[3] == 12) { + t.Error(dst) } } diff --git a/skip.go b/skip.go new file mode 100644 index 0000000..5f37b9d --- /dev/null +++ b/skip.go @@ -0,0 +1,26 @@ +package underscore + +import "reflect" + +func (m enumerable) Skip(count int) IEnumerable { + return enumerable{ + Enumerator: func() IEnumerator { + iterator := m.GetEnumerator() + return &enumerator{ + MoveNextFunc: func() (valueRV reflect.Value, keyRV reflect.Value, ok bool) { + for ; count > 0; count-- { + if !iterator.MoveNext() { + return + } + } + + if ok = iterator.MoveNext(); ok { + valueRV = iterator.GetValue() + keyRV = iterator.GetKey() + } + return + }, + } + }, + } +} diff --git a/skip_test.go b/skip_test.go new file mode 100644 index 0000000..a0b8d6f --- /dev/null +++ b/skip_test.go @@ -0,0 +1,12 @@ +package underscore + +import "testing" + +func Test_Skip(t *testing.T) { + src := []int{1, 2, 3} + dst := make([]int, 0) + Chain2(src).Skip(2).Value(&dst) + if len(dst) != 1 || dst[0] != 3 { + t.Fatal(dst) + } +} diff --git a/take.go b/take.go index 8d4bb40..bd526bf 100644 --- a/take.go +++ b/take.go @@ -1,5 +1,7 @@ package underscore +import "reflect" + func (m *query) Take(count int) IQuery { index := 0 return m.Where(func(_, _ interface{}) bool { @@ -7,3 +9,26 @@ func (m *query) Take(count int) IQuery { return index <= count }) } + +func (m enumerable) Take(count int) IEnumerable { + return enumerable{ + Enumerator: func() IEnumerator { + iterator := m.GetEnumerator() + return &enumerator{ + MoveNextFunc: func() (valueRV reflect.Value, keyRV reflect.Value, ok bool) { + if count <= 0 { + return + } + + count-- + if ok = iterator.MoveNext(); ok { + valueRV = iterator.GetValue() + keyRV = iterator.GetKey() + } + + return + }, + } + }, + } +} diff --git a/take_test.go b/take_test.go index aa55978..e8870a8 100644 --- a/take_test.go +++ b/take_test.go @@ -1,20 +1,26 @@ package underscore -import ( - "testing" -) +import "testing" -func Test_Chain_Take(t *testing.T) { - arr := []int{1, 2, 3} - res := make([]int, 0) - Chain(arr).Take(1).Value(&res) - if res[0] != 1 { - t.Fatal("wrong") +func Benchmark_Take(b *testing.B) { + for n := 0; n < b.N; n++ { + dst := make([]int, 0) + Range(1, benchmarkSize, 1).Take(200).Value(&dst) } +} + +func Benchmark_Take_New(b *testing.B) { + for n := 0; n < b.N; n++ { + dst := make([]int, 0) + Range2(1, benchmarkSize, 1).Take(200).Value(&dst) + } +} - res = make([]int, 0) - Chain(nil).Take(1).Value(&res) - if len(res) > 0 { - t.Error("wrong") +func Test_Take(t *testing.T) { + src := []int{1, 2, 3} + dst := make([]int, 0) + Chain2(src).Take(1).Value(&dst) + if len(dst) != 1 || dst[0] != 1 { + t.Fatal(dst) } } diff --git a/values.go b/values.go index b3d9fd2..34ab76f 100644 --- a/values.go +++ b/values.go @@ -18,10 +18,10 @@ func (m *query) Values() IQuery { } func (m enumerable) Values() IEnumerable { - iterator := m.GetEnumerator() return enumerable{ Enumerator: func() IEnumerator { index := 0 + iterator := m.GetEnumerator() return &enumerator{ MoveNextFunc: func() (valueRV reflect.Value, keyRV reflect.Value, ok bool) { if ok = iterator.MoveNext(); ok {