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 {