From 6232ae24f1f929018e5e412bb736d782389bd0c5 Mon Sep 17 00:00:00 2001 From: zjhe Date: Fri, 5 Aug 2022 22:02:06 +0800 Subject: [PATCH 01/56] add QueryG type, add FromSliceG and ToSliceG method --- from.go | 28 ++++++++++++++++++++++++++++ from_test.go | 8 ++++++++ go.mod | 10 +++++++++- result.go | 9 +++++++++ result_test.go | 9 +++++++++ setup_test.go | 20 +++++++++++++++++++- where.go | 17 +++++++++++++++++ 7 files changed, 99 insertions(+), 2 deletions(-) diff --git a/from.go b/from.go index e1c1867..440eadf 100644 --- a/from.go +++ b/from.go @@ -11,6 +11,12 @@ type Query struct { Iterate func() Iterator } +type IteratorG[T interface{}] func() (item T, ok bool) + +type QueryG[T interface{}] struct { + Iterate func() IteratorG[T] +} + // KeyValue is a type that is used to iterate over a map (if query is created // from a map). This type is also used by ToMap() method to output result of a // query into a map. @@ -19,6 +25,11 @@ type KeyValue struct { Value interface{} } +type KeyValueG[K, V interface{}] struct { + Key K + Value V +} + // Iterable is an interface that has to be implemented by a custom collection in // order to work with linq. type Iterable interface { @@ -88,6 +99,23 @@ func From(source interface{}) Query { } } +func FromSliceG[T interface{}](source []T) QueryG[T] { + return QueryG[T]{ + Iterate: func() IteratorG[T] { + index := 0 + return func() (item T, ok bool) { + ok = index < len(source) + if ok { + item = source[index] + index++ + return + } + return + } + }, + } +} + // FromChannel initializes a linq query with passed channel, linq iterates over // channel until it is closed. func FromChannel(source <-chan interface{}) Query { diff --git a/from_test.go b/from_test.go index dbc469d..12c658b 100644 --- a/from_test.go +++ b/from_test.go @@ -44,6 +44,14 @@ func TestFrom(t *testing.T) { } } +func TestFromSliceG(t *testing.T) { + slice := []int{1, 2, 3} + q := FromSliceG(slice) + if !validateQueryG(q, slice) { + t.Fatalf("FromSliceG") + } +} + func TestFromChannel(t *testing.T) { c := make(chan interface{}, 3) c <- 10 diff --git a/go.mod b/go.mod index e7fab52..9dd8f80 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,11 @@ module github.com/ahmetb/go-linq/v3 -go 1.11 +go 1.18 + +require github.com/stretchr/testify v1.8.0 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/result.go b/result.go index 588cbd5..6ff0dec 100644 --- a/result.go +++ b/result.go @@ -659,6 +659,15 @@ func (q Query) ToSlice(v interface{}) { res.Elem().Set(slice.Slice(0, index)) } +func (q QueryG[T]) ToSlice() []T { + var r []T + next := q.Iterate() + for item, ok := next(); ok; item, ok = next() { + r = append(r, item) + } + return r +} + // grow grows the slice s by doubling its capacity, then it returns the new // slice (resliced to its full capacity) and the new capacity. func grow(s reflect.Value) (v reflect.Value, newCap int) { diff --git a/result_test.go b/result_test.go index 8faeb6d..c05d4f9 100644 --- a/result_test.go +++ b/result_test.go @@ -5,6 +5,8 @@ import ( "reflect" "testing" "unsafe" + + "github.com/stretchr/testify/assert" ) func TestAll(t *testing.T) { @@ -620,3 +622,10 @@ func TestToSlice(t *testing.T) { } } } + +func TestToSliceG(t *testing.T) { + slice := []int{1, 2, 3} + q := FromSliceG(slice) + to := q.ToSlice() + assert.Equal(t, slice, to) +} diff --git a/setup_test.go b/setup_test.go index a4f4de5..b3cea91 100644 --- a/setup_test.go +++ b/setup_test.go @@ -1,6 +1,9 @@ package linq -import "testing" +import ( + "reflect" + "testing" +) import "fmt" @@ -71,6 +74,21 @@ func validateQuery(q Query, output []interface{}) bool { return !(ok || ok2) } +func validateQueryG[T interface{}](g QueryG[T], output []T) bool { + next := g.Iterate() + for _, o := range output { + q, ok := next() + if !ok { + return false + } + if !reflect.DeepEqual(q, o) { + return false + } + } + _, ok := next() + return !ok +} + func mustPanicWithError(t *testing.T, expectedErr string, f func()) { defer func() { r := recover() diff --git a/where.go b/where.go index 5da796f..8488ca7 100644 --- a/where.go +++ b/where.go @@ -19,6 +19,23 @@ func (q Query) Where(predicate func(interface{}) bool) Query { } } +func (q QueryG[T]) Where(predicate func(T) bool) QueryG[T] { + return QueryG[T]{ + Iterate: func() IteratorG[T] { + next := q.Iterate() + + return func() (item T, ok bool) { + for item, ok = next(); ok; item, ok = next() { + if predicate(item) { + return + } + } + return + } + }, + } +} + // WhereT is the typed version of Where. // // - predicateFn is of type "func(TSource)bool" From 860409aa20ca9edb7660779da4b62c7f2c1eaedd Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 14:43:29 +0800 Subject: [PATCH 02/56] Add SelectG method --- from.go | 6 +++--- select.go | 41 +++++++++++++++++++++++++++++++++++++++++ select_test.go | 19 +++++++++++++++++++ where_test.go | 15 ++++++++++++++- 4 files changed, 77 insertions(+), 4 deletions(-) diff --git a/from.go b/from.go index 440eadf..a997199 100644 --- a/from.go +++ b/from.go @@ -11,9 +11,9 @@ type Query struct { Iterate func() Iterator } -type IteratorG[T interface{}] func() (item T, ok bool) +type IteratorG[T any] func() (item T, ok bool) -type QueryG[T interface{}] struct { +type QueryG[T any] struct { Iterate func() IteratorG[T] } @@ -99,7 +99,7 @@ func From(source interface{}) Query { } } -func FromSliceG[T interface{}](source []T) QueryG[T] { +func FromSliceG[T any](source []T) QueryG[T] { return QueryG[T]{ Iterate: func() IteratorG[T] { index := 0 diff --git a/select.go b/select.go index 5dc8cc7..845cf48 100644 --- a/select.go +++ b/select.go @@ -50,6 +50,47 @@ func (q Query) SelectT(selectorFn interface{}) Query { return q.Select(selectorFunc) } +func (q QueryG[T]) Select(m Mapper[T]) interface{} { + return m.Map(q) +} + +type Mapper[T any] interface { + Map(QueryG[T]) interface{} +} + +type mapper[TIn, TOut any] struct { + selector func(TIn) TOut +} + +func (m mapper[TIn, TOut]) Map(q QueryG[TIn]) interface{} { + return Select(q, m.selector) +} + +func Map[TIn, TOut any](selector func(TIn) TOut) Mapper[TIn] { + return mapper[TIn, TOut]{ + selector: selector, + } +} + +func Select[TIn, TOut any](q QueryG[TIn], selector func(TIn) TOut) QueryG[TOut] { + o := QueryG[TOut]{ + Iterate: func() IteratorG[TOut] { + next := q.Iterate() + return func() (outItem TOut, ok bool) { + item, hasNext := next() + if hasNext { + outItem = selector(item) + ok = true + return + } + ok = false + return + } + }, + } + return o +} + // SelectIndexed projects each element of a collection into a new form by // incorporating the element's index. Returns a query with the result of // invoking the transform function on each element of original source. diff --git a/select_test.go b/select_test.go index a32349b..ea2db12 100644 --- a/select_test.go +++ b/select_test.go @@ -1,6 +1,7 @@ package linq import ( + "github.com/stretchr/testify/assert" "strconv" "testing" ) @@ -26,6 +27,24 @@ func TestSelect(t *testing.T) { } } +func TestSelectGFunc(t *testing.T) { + input := []int{1, 2, 3} + expected := []string{"1", "2", "3"} + stringSlice := Select[int, string](FromSliceG(input), func(i int) string { + return strconv.Itoa(i) + }).ToSlice() + assert.Equal(t, expected, stringSlice) +} + +func TestSelectG(t *testing.T) { + input := []int{1, 2, 3} + expected := []string{"1", "2", "3"} + stringSlice := FromSliceG(input).Select(Map[int, string](func(i int) string { + return strconv.Itoa(i) + })).(QueryG[string]).ToSlice() + assert.Equal(t, expected, stringSlice) +} + func TestSelectT_PanicWhenSelectorFnIsInvalid(t *testing.T) { mustPanicWithError(t, "SelectT: parameter [selectorFn] has a invalid function signature. Expected: 'func(T)T', actual: 'func(int,int)int'", func() { From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).SelectT(func(item, idx int) int { return item + 2 }) diff --git a/where_test.go b/where_test.go index 8357dda..eaba0e8 100644 --- a/where_test.go +++ b/where_test.go @@ -1,6 +1,9 @@ package linq -import "testing" +import ( + "github.com/stretchr/testify/assert" + "testing" +) func TestWhere(t *testing.T) { tests := []struct { @@ -23,6 +26,16 @@ func TestWhere(t *testing.T) { } } +func TestWhereG(t *testing.T) { + inputs := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} + q := FromSliceG(inputs) + greaterThanFive := q.Where(func(item int) bool { + return item > 5 + }).ToSlice() + expected := []int{6, 7, 8, 9, 10} + assert.Equal(t, expected, greaterThanFive) +} + func TestWhereT_PanicWhenPredicateFnIsInvalid(t *testing.T) { mustPanicWithError(t, "WhereT: parameter [predicateFn] has a invalid function signature. Expected: 'func(T)bool', actual: 'func(int)int'", func() { From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).WhereT(func(item int) int { return item + 2 }) From bb014ffdc2d7506415c219cab2e22c4e316180ec Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 15:02:48 +0800 Subject: [PATCH 03/56] Add SelectIndexedG method --- select.go | 43 +++++++++++++++++++++++++++++++++++++++++++ select_test.go | 10 ++++++++++ 2 files changed, 53 insertions(+) diff --git a/select.go b/select.go index 845cf48..dd077dd 100644 --- a/select.go +++ b/select.go @@ -148,3 +148,46 @@ func (q Query) SelectIndexedT(selectorFn interface{}) Query { return q.SelectIndexed(selectorFunc) } + +type MapperWithIndex[T any] interface { + Map(QueryG[T]) interface{} +} + +type mapperWithIndex[TIn, TOut any] struct { + selector func(int, TIn) TOut +} + +func (m mapperWithIndex[TIn, TOut]) Map(q QueryG[TIn]) interface{} { + return SelectIndexedG(q, m.selector) +} + +func MapWithIndex[TIn, TOut any](selector func(int, TIn) TOut) Mapper[TIn] { + return mapperWithIndex[TIn, TOut]{ + selector: selector, + } +} + +func (q QueryG[T]) SelectIndexed(m MapperWithIndex[T]) interface{} { + return m.Map(q) +} + +func SelectIndexedG[TIn, TOut any](q QueryG[TIn], selector func(int, TIn) TOut) QueryG[TOut] { + o := QueryG[TOut]{ + Iterate: func() IteratorG[TOut] { + next := q.Iterate() + index := 0 + return func() (outItem TOut, ok bool) { + item, hasNext := next() + if hasNext { + outItem = selector(index, item) + ok = true + index++ + return + } + ok = false + return + } + }, + } + return o +} diff --git a/select_test.go b/select_test.go index ea2db12..5a13452 100644 --- a/select_test.go +++ b/select_test.go @@ -45,6 +45,16 @@ func TestSelectG(t *testing.T) { assert.Equal(t, expected, stringSlice) } +func TestSelectIndexedG(t *testing.T) { + input := []int{0, 1, 2} + expected := []string{"0", "1", "2"} + stringSlice := FromSliceG(input).SelectIndexed(MapWithIndex[int, string](func(index, i int) string { + assert.Equal(t, index, i) + return strconv.Itoa(i) + })).(QueryG[string]).ToSlice() + assert.Equal(t, expected, stringSlice) +} + func TestSelectT_PanicWhenSelectorFnIsInvalid(t *testing.T) { mustPanicWithError(t, "SelectT: parameter [selectorFn] has a invalid function signature. Expected: 'func(T)T', actual: 'func(int,int)int'", func() { From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).SelectT(func(item, idx int) int { return item + 2 }) From 8db3799ad48ca6e7c46ddb9d8af3c5ddf5858be1 Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 15:30:23 +0800 Subject: [PATCH 04/56] Add aggregate methods --- aggregate.go | 30 ++++++++++++++++++++++++ aggregate_test.go | 60 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/aggregate.go b/aggregate.go index bb1975d..c2b26aa 100644 --- a/aggregate.go +++ b/aggregate.go @@ -46,6 +46,21 @@ func (q Query) AggregateT(f interface{}) interface{} { return q.Aggregate(fFunc) } +func (q QueryG[T]) Aggregate(f func(T, T) T) T { + next := q.Iterate() + + result, any := next() + if !any { + return *new(T) + } + + for current, ok := next(); ok; current, ok = next() { + result = f(result, current) + } + + return result +} + // AggregateWithSeed applies an accumulator function over a sequence. The // specified seed value is used as the initial accumulator value. // @@ -93,6 +108,17 @@ func (q Query) AggregateWithSeedT(seed interface{}, return q.AggregateWithSeed(seed, fFunc) } +func (q QueryG[T]) AggregateWithSeed(seed T, f func(T, T) T) T { + next := q.Iterate() + result := seed + + for current, ok := next(); ok; current, ok = next() { + result = f(result, current) + } + + return result +} + // AggregateWithSeedBy applies an accumulator function over a sequence. The // specified seed value is used as the initial accumulator value, and the // specified function is used to select the result value. @@ -156,3 +182,7 @@ func (q Query) AggregateWithSeedByT(seed interface{}, return q.AggregateWithSeedBy(seed, fFunc, resultSelectorFunc) } + +func (q QueryG[T]) AggregateWithSeedBy(seed T, f func(T, T) T, resultSelector func(T) interface{}) interface{} { + return resultSelector(q.AggregateWithSeed(seed, f)) +} diff --git a/aggregate_test.go b/aggregate_test.go index 0fe1a51..ddd25e5 100644 --- a/aggregate_test.go +++ b/aggregate_test.go @@ -1,6 +1,9 @@ package linq -import "testing" +import ( + "github.com/stretchr/testify/assert" + "testing" +) import "strings" func TestAggregate(t *testing.T) { @@ -26,6 +29,42 @@ func TestAggregate(t *testing.T) { } } +func TestAggregateG(t *testing.T) { + input := []string{"apple", "mango", "orange", "passionfruit", "grape"} + expected := "passionfruit" + actual := FromSliceG(input).Aggregate(func(r, i string) string { + if len(r) > len(i) { + return r + } + return i + }) + assert.Equal(t, expected, actual) +} + +func TestAggregateSumG(t *testing.T) { + input := []int{1, 2, 3, 4} + expected := 10 + actual := FromSliceG(input).Aggregate(func(i1, i2 int) int { + return i1 + i2 + }) + assert.Equal(t, expected, actual) +} + +func TestAggregateWithSeedG(t *testing.T) { + input := []int{1, 2, 3, 4} + expected := 15 + actual := FromSliceG(input).AggregateWithSeed(5, func(i1, i2 int) int { + return i1 + i2 + }) + assert.Equal(t, expected, actual) + input = []int{} + expected = 5 + actual = FromSliceG(input).AggregateWithSeed(5, func(i1, i2 int) int { + return i1 + i2 + }) + assert.Equal(t, expected, actual) +} + func TestAggregateT_PanicWhenFunctionIsInvalid(t *testing.T) { mustPanicWithError(t, "AggregateT: parameter [f] has a invalid function signature. Expected: 'func(T,T)T', actual: 'func(int,string,string)string'", func() { From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).AggregateT(func(x int, r string, i string) string { @@ -117,3 +156,22 @@ func TestAggregateWithSeedByT_PanicWhenResultSelectorFnIsInvalid(t *testing.T) { ) }) } + +func TestAggregateWithSeedByG(t *testing.T) { + input := []string{"apple", "mango", "orange", "passionfruit", "grape"} + expected := "PASSIONFRUIT" + + actual := FromSliceG(input).AggregateWithSeedBy("banana", + func(r, i string) string { + if len(r) > len(i) { + return r + } + return i + }, + func(r string) interface{} { + return strings.ToUpper(r) + }, + ).(string) + + assert.Equal(t, expected, actual) +} From 2abcc650de5464c4f3e7e098ba1f136a2b1cc9bb Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 15:54:36 +0800 Subject: [PATCH 05/56] Add concat methods --- concat.go | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++ concat_test.go | 27 ++++++++++++++++++++- 2 files changed, 90 insertions(+), 1 deletion(-) diff --git a/concat.go b/concat.go index 3101c56..b69dc7c 100644 --- a/concat.go +++ b/concat.go @@ -25,6 +25,29 @@ func (q Query) Append(item interface{}) Query { } } +func (q QueryG[T]) Append(item T) QueryG[T] { + return QueryG[T]{ + Iterate: func() IteratorG[T] { + next := q.Iterate() + appended := false + + return func() (T, bool) { + i, ok := next() + if ok { + return i, ok + } + + if !appended { + appended = true + return item, true + } + + return *new(T), false + } + }, + } +} + // Concat concatenates two collections. // // The Concat method differs from the Union method because the Concat method @@ -53,6 +76,29 @@ func (q Query) Concat(q2 Query) Query { } } +func (q QueryG[T]) Concat(q2 QueryG[T]) QueryG[T] { + return QueryG[T]{ + Iterate: func() IteratorG[T] { + next := q.Iterate() + next2 := q2.Iterate() + use1 := true + + return func() (item T, ok bool) { + if use1 { + item, ok = next() + if ok { + return + } + + use1 = false + } + + return next2() + } + }, + } +} + // Prepend inserts an item to the beginning of a collection, so it becomes the // first item. func (q Query) Prepend(item interface{}) Query { @@ -72,3 +118,21 @@ func (q Query) Prepend(item interface{}) Query { }, } } + +func (q QueryG[T]) Prepend(item T) QueryG[T] { + return QueryG[T]{ + Iterate: func() IteratorG[T] { + next := q.Iterate() + prepended := false + + return func() (T, bool) { + if prepended { + return next() + } + + prepended = true + return item, true + } + }, + } +} diff --git a/concat_test.go b/concat_test.go index 51a36d9..16f59a6 100644 --- a/concat_test.go +++ b/concat_test.go @@ -1,6 +1,9 @@ package linq -import "testing" +import ( + "github.com/stretchr/testify/assert" + "testing" +) func TestAppend(t *testing.T) { input := []int{1, 2, 3, 4} @@ -11,6 +14,13 @@ func TestAppend(t *testing.T) { } } +func TestAppendG(t *testing.T) { + input := []int{1, 2, 3, 4} + expected := []int{1, 2, 3, 4, 5} + actual := FromSliceG(input).Append(5).ToSlice() + assert.Equal(t, expected, actual) +} + func TestConcat(t *testing.T) { input1 := []int{1, 2, 3} input2 := []int{4, 5} @@ -21,6 +31,14 @@ func TestConcat(t *testing.T) { } } +func TestConcatG(t *testing.T) { + input1 := []int{1, 2, 3} + input2 := []int{4, 5} + expected := []int{1, 2, 3, 4, 5} + actual := FromSliceG(input1).Concat(FromSliceG(input2)).ToSlice() + assert.Equal(t, expected, actual) +} + func TestPrepend(t *testing.T) { input := []int{1, 2, 3, 4} want := []interface{}{0, 1, 2, 3, 4} @@ -29,3 +47,10 @@ func TestPrepend(t *testing.T) { t.Errorf("From(%v).Prepend()=%v expected %v", input, toSlice(q), want) } } + +func TestPrependG(t *testing.T) { + input := []int{1, 2, 3, 4} + want := []int{0, 1, 2, 3, 4} + actual := FromSliceG(input).Prepend(0).ToSlice() + assert.Equal(t, want, actual) +} From 8f9ec6ad5dd4231cbec29614c46a194b755296e1 Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 16:02:01 +0800 Subject: [PATCH 06/56] Add RepeatG method --- from.go | 19 +++++++++++++++++++ from_test.go | 11 ++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/from.go b/from.go index a997199..ae120e2 100644 --- a/from.go +++ b/from.go @@ -219,3 +219,22 @@ func Repeat(value interface{}, count int) Query { }, } } + +func RepeatG[T any](value T, count int) QueryG[T] { + return QueryG[T]{ + Iterate: func() IteratorG[T] { + index := 0 + + return func() (item T, ok bool) { + if index >= count { + return *new(T), false + } + + item, ok = value, true + + index++ + return + } + }, + } +} diff --git a/from_test.go b/from_test.go index 12c658b..09ad63b 100644 --- a/from_test.go +++ b/from_test.go @@ -1,6 +1,9 @@ package linq -import "testing" +import ( + "github.com/stretchr/testify/assert" + "testing" +) func TestFrom(t *testing.T) { c := make(chan interface{}, 3) @@ -113,3 +116,9 @@ func TestRepeat(t *testing.T) { t.Errorf("Repeat(1, 5)=%v expected %v", toSlice(q), w) } } + +func TestRepeatG(t *testing.T) { + expected := []int{1, 1, 1, 1, 1} + actual := RepeatG(1, 5).ToSlice() + assert.Equal(t, expected, actual) +} From cd3fb5d970fdf6abeb1e2c3575cbe220f56cbfa2 Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 16:10:05 +0800 Subject: [PATCH 07/56] Add DefaultIfEmptyG method --- defaultifempty.go | 30 ++++++++++++++++++++++++++++++ defaultifempty_test.go | 19 +++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/defaultifempty.go b/defaultifempty.go index 0bf7246..dca4f5a 100644 --- a/defaultifempty.go +++ b/defaultifempty.go @@ -31,3 +31,33 @@ func (q Query) DefaultIfEmpty(defaultValue interface{}) Query { }, } } + +func (q QueryG[T]) DefaultIfEmpty(defaultValue T) QueryG[T] { + return QueryG[T]{ + Iterate: func() IteratorG[T] { + next := q.Iterate() + state := 1 + + return func() (item T, ok bool) { + switch state { + case 1: + item, ok = next() + if ok { + state = 2 + } else { + item = defaultValue + ok = true + state = -1 + } + return + case 2: + for item, ok = next(); ok; item, ok = next() { + return + } + return + } + return + } + }, + } +} diff --git a/defaultifempty_test.go b/defaultifempty_test.go index 55025a0..b52a31a 100644 --- a/defaultifempty_test.go +++ b/defaultifempty_test.go @@ -1,6 +1,7 @@ package linq import ( + "github.com/stretchr/testify/assert" "testing" ) @@ -23,3 +24,21 @@ func TestDefaultIfEmpty(t *testing.T) { } } + +func TestDefaultIfEmptyG(t *testing.T) { + defaultValue := 0 + tests := []struct { + input []int + want []int + }{ + {[]int{}, []int{defaultValue}}, + {[]int{1, 2, 3, 4, 5}, []int{1, 2, 3, 4, 5}}, + } + + for _, test := range tests { + actual := FromSliceG(test.input).DefaultIfEmpty(defaultValue).ToSlice() + + assert.Equal(t, test.want, actual) + } + +} From ad27354e4c82e3c9d195119aa41a9aa84dc3c653 Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 17:00:59 +0800 Subject: [PATCH 08/56] Add FromMapG function --- from.go | 27 +++++++++++++++++++++++++++ from_test.go | 14 ++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/from.go b/from.go index ae120e2..780eeaa 100644 --- a/from.go +++ b/from.go @@ -116,6 +116,33 @@ func FromSliceG[T any](source []T) QueryG[T] { } } +func FromMapG[K comparable, V any](source map[K]V) QueryG[KeyValueG[K, V]] { + return QueryG[KeyValueG[K, V]]{ + Iterate: func() IteratorG[KeyValueG[K, V]] { + index := 0 + length := len(source) + var keys []K + for k, _ := range source { + keys = append(keys, k) + } + return func() (item KeyValueG[K, V], next bool) { + if index == length { + next = false + return + } + key := keys[index] + item = KeyValueG[K, V]{ + Key: key, + Value: source[key], + } + next = true + index++ + return + } + }, + } +} + // FromChannel initializes a linq query with passed channel, linq iterates over // channel until it is closed. func FromChannel(source <-chan interface{}) Query { diff --git a/from_test.go b/from_test.go index 09ad63b..3eafaeb 100644 --- a/from_test.go +++ b/from_test.go @@ -2,6 +2,7 @@ package linq import ( "github.com/stretchr/testify/assert" + "strconv" "testing" ) @@ -55,6 +56,19 @@ func TestFromSliceG(t *testing.T) { } } +func TestFromMapG(t *testing.T) { + source := map[string]int{ + "1": 1, + "2": 2, + "3": 3, + } + + slice := FromMapG(source).ToSlice() + for _, pair := range slice { + assert.Equal(t, pair.Key, strconv.Itoa(pair.Value)) + } +} + func TestFromChannel(t *testing.T) { c := make(chan interface{}, 3) c <- 10 From 28ff32036a99707fb20ef183319e5ace9d6b07b6 Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 17:06:25 +0800 Subject: [PATCH 09/56] Add FromStringG function --- from.go | 20 ++++++++++++++++++++ from_test.go | 7 +++++++ 2 files changed, 27 insertions(+) diff --git a/from.go b/from.go index 780eeaa..b5123ae 100644 --- a/from.go +++ b/from.go @@ -196,6 +196,26 @@ func FromString(source string) Query { } } +func FromStringG(source string) QueryG[rune] { + runes := []rune(source) + length := len(runes) + + return QueryG[rune]{ + Iterate: func() IteratorG[rune] { + index := 0 + + return func() (item rune, ok bool) { + ok = index < length + if ok { + item = runes[index] + index++ + } + return + } + }, + } +} + // FromIterable initializes a linq query with custom collection passed. This // collection has to implement Iterable interface, linq iterates over items, // that has to implement Comparable interface or be basic types. diff --git a/from_test.go b/from_test.go index 3eafaeb..ff56b1f 100644 --- a/from_test.go +++ b/from_test.go @@ -106,6 +106,13 @@ func TestFromString(t *testing.T) { } } +func TestFromStringG(t *testing.T) { + source := "string" + expected := []rune{'s', 't', 'r', 'i', 'n', 'g'} + actual := FromStringG(source).ToSlice() + assert.Equal(t, expected, actual) +} + func TestFromIterable(t *testing.T) { s := foo{f1: 1, f2: true, f3: "string"} w := []interface{}{1, true, "string"} From 5f6298942b11d92ca49281441d69aa71d2a5b998 Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 17:10:26 +0800 Subject: [PATCH 10/56] Add FromChannelG function --- from.go | 13 +++++++++++++ from_test.go | 12 ++++++++++++ 2 files changed, 25 insertions(+) diff --git a/from.go b/from.go index b5123ae..f170f68 100644 --- a/from.go +++ b/from.go @@ -173,6 +173,19 @@ func FromChannelT(source interface{}) Query { } } +// FromChannelG initializes a linq query with passed channel, linq iterates over +// channel until it is closed. +func FromChannelG[T any](source <-chan T) QueryG[T] { + return QueryG[T]{ + Iterate: func() IteratorG[T] { + return func() (item T, ok bool) { + item, ok = <-source + return + } + }, + } +} + // FromString initializes a linq query with passed string, linq iterates over // runes of string. func FromString(source string) Query { diff --git a/from_test.go b/from_test.go index ff56b1f..9b69a74 100644 --- a/from_test.go +++ b/from_test.go @@ -97,6 +97,18 @@ func TestFromChannelT(t *testing.T) { } } +func TestFromChannelG(t *testing.T) { + c := make(chan int, 3) + c <- 10 + c <- 15 + c <- -3 + close(c) + + expected := []int{10, 15, -3} + actual := FromChannelG(c).ToSlice() + assert.Equal(t, expected, actual) +} + func TestFromString(t *testing.T) { s := "string" w := []interface{}{'s', 't', 'r', 'i', 'n', 'g'} From 63807a4b47e099bd53ddc4e1df3727763ed66525 Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 17:18:27 +0800 Subject: [PATCH 11/56] Add FromIterableG function --- from.go | 15 +++++++++++++++ from_test.go | 7 +++++++ setup_test.go | 29 +++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/from.go b/from.go index f170f68..fb83668 100644 --- a/from.go +++ b/from.go @@ -36,6 +36,12 @@ type Iterable interface { Iterate() Iterator } +// IterableG is an interface that has to be implemented by a custom collection in +// order to work with linq. +type IterableG[T any] interface { + Iterate() IteratorG[T] +} + // From initializes a linq query with passed slice, array or map as the source. // String, channel or struct implementing Iterable interface can be used as an // input. In this case From delegates it to FromString, FromChannel and @@ -238,6 +244,15 @@ func FromIterable(source Iterable) Query { } } +// FromIterableG initializes a linq query with custom collection passed. This +// collection has to implement Iterable interface, linq iterates over items, +// that has to implement Comparable interface or be basic types. +func FromIterableG[T any](source IterableG[T]) QueryG[T] { + return QueryG[T]{ + Iterate: source.Iterate, + } +} + // Range generates a sequence of integral numbers within a specified range. func Range(start, count int) Query { return Query{ diff --git a/from_test.go b/from_test.go index 9b69a74..4226361 100644 --- a/from_test.go +++ b/from_test.go @@ -134,6 +134,13 @@ func TestFromIterable(t *testing.T) { } } +func TestFromIterableG(t *testing.T) { + s := fooG{f1: 1, f2: 2, f3: 3} + expected := []int{1, 2, 3} + actual := FromIterableG[int](s).ToSlice() + assert.Equal(t, expected, actual) +} + func TestRange(t *testing.T) { w := []interface{}{-2, -1, 0, 1, 2} diff --git a/setup_test.go b/setup_test.go index b3cea91..89c2801 100644 --- a/setup_test.go +++ b/setup_test.go @@ -48,6 +48,35 @@ func (f foo) CompareTo(c Comparable) int { return 0 } +type fooG struct { + f1 int + f2 int + f3 int +} + +func (f fooG) Iterate() IteratorG[int] { + i := 0 + + return func() (item int, ok bool) { + switch i { + case 0: + item = f.f1 + ok = true + case 1: + item = f.f2 + ok = true + case 2: + item = f.f3 + ok = true + default: + ok = false + } + + i++ + return + } +} + func toSlice(q Query) (result []interface{}) { next := q.Iterate() From b38b9c404ba66f39309317a4c91f20d501d0d6c1 Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 17:20:56 +0800 Subject: [PATCH 12/56] Add RangeG function --- from.go | 22 ++++++++++++++++++++++ from_test.go | 7 +++++++ 2 files changed, 29 insertions(+) diff --git a/from.go b/from.go index fb83668..7055a98 100644 --- a/from.go +++ b/from.go @@ -275,6 +275,28 @@ func Range(start, count int) Query { } } +// RangeG generates a sequence of integral numbers within a specified range. +func RangeG(start, count int) QueryG[int] { + return QueryG[int]{ + Iterate: func() IteratorG[int] { + index := 0 + current := start + + return func() (item int, ok bool) { + if index >= count { + return 0, false + } + + item, ok = current, true + + index++ + current++ + return + } + }, + } +} + // Repeat generates a sequence that contains one repeated value. func Repeat(value interface{}, count int) Query { return Query{ diff --git a/from_test.go b/from_test.go index 4226361..e9ccd3d 100644 --- a/from_test.go +++ b/from_test.go @@ -149,6 +149,13 @@ func TestRange(t *testing.T) { } } +func TestRangeG(t *testing.T) { + expected := []int{-2, -1, 0, 1, 2} + + actual := RangeG(-2, 5).ToSlice() + assert.Equal(t, expected, actual) +} + func TestRepeat(t *testing.T) { w := []interface{}{1, 1, 1, 1, 1} From bfe98cebbd1c98e146ad31d0b0326963f9bc254f Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 18:14:23 +0800 Subject: [PATCH 13/56] Add ToMapG method --- from.go | 2 +- result.go | 40 ++++++++++++++++++++++++++++++++++++++++ result_test.go | 24 ++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/from.go b/from.go index 7055a98..c23fb30 100644 --- a/from.go +++ b/from.go @@ -25,7 +25,7 @@ type KeyValue struct { Value interface{} } -type KeyValueG[K, V interface{}] struct { +type KeyValueG[K comparable, V any] struct { Key K Value V } diff --git a/result.go b/result.go index 6ff0dec..7df3afd 100644 --- a/result.go +++ b/result.go @@ -632,6 +632,46 @@ func (q Query) ToMapByT(result interface{}, q.ToMapBy(result, keySelectorFunc, valueSelectorFunc) } +type T2KVMap[T any] interface { + Map(q QueryG[T]) interface{} +} + +type t2kvMapper[K comparable, V, T any] struct { + keySelector func(T) K + valueSelector func(T) V +} + +func (t t2kvMapper[K, V, T]) Map(q QueryG[T]) interface{} { + return ToMapBy[K, V, T](q, t.keySelector, t.valueSelector) +} + +func T2KV[K comparable, V, T any](keySelector func(T) K, valueSelector func(T) V) T2KVMap[T] { + return t2kvMapper[K, V, T]{ + keySelector: keySelector, + valueSelector: valueSelector, + } +} + +func (q QueryG[T]) ToMapBy(m T2KVMap[T]) interface{} { + return m.Map(q) +} + +// ToMapBy iterates over a collection and populates the result map with +// elements. Functions keySelector and valueSelector are executed for each +// element of the collection to generate key and value for the map. Generated +// key and value types must be assignable to the map's key and value types. +// ToMapBy doesn't empty the result map before populating it. +func ToMapBy[K comparable, V, T any](q QueryG[T], + keySelector func(T) K, + valueSelector func(T) V) map[K]V { + r := make(map[K]V) + next := q.Iterate() + for item, ok := next(); ok; item, ok = next() { + r[keySelector(item)] = valueSelector(item) + } + return r +} + // ToSlice iterates over a collection and saves the results in the slice pointed // by v. It overwrites the existing slice, starting from index 0. // diff --git a/result_test.go b/result_test.go index c05d4f9..78f26b5 100644 --- a/result_test.go +++ b/result_test.go @@ -3,6 +3,7 @@ package linq import ( "math" "reflect" + "strconv" "testing" "unsafe" @@ -496,6 +497,29 @@ func TestToMap(t *testing.T) { } } +func TestToMapG(t *testing.T) { + input := make(map[int]bool) + input[1] = true + input[2] = false + input[3] = true + + expected := map[int]string{ + 1: "true", + 2: "false", + 3: "true", + } + + actual := FromMapG(input).ToMapBy(T2KV[int, string, KeyValueG[int, bool]]( + func(pair KeyValueG[int, bool]) int { + return pair.Key + }, + func(pair KeyValueG[int, bool]) string { + return strconv.FormatBool(pair.Value) + })).(map[int]string) + + assert.Equal(t, expected, actual) +} + func TestToMapBy(t *testing.T) { input := make(map[int]bool) input[1] = true From d6415a50c036983e286b9da3860293d3268f399b Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 18:21:31 +0800 Subject: [PATCH 14/56] Add AllG method --- result.go | 13 +++++++++++++ result_test.go | 12 ++++++++++++ 2 files changed, 25 insertions(+) diff --git a/result.go b/result.go index 7df3afd..f2d7da0 100644 --- a/result.go +++ b/result.go @@ -18,6 +18,19 @@ func (q Query) All(predicate func(interface{}) bool) bool { return true } +// All determines whether all elements of a collection satisfy a condition. +func (q QueryG[T]) All(predicate func(T) bool) bool { + next := q.Iterate() + + for item, ok := next(); ok; item, ok = next() { + if !predicate(item) { + return false + } + } + + return true +} + // AllT is the typed version of All. // // - predicateFn is of type "func(TSource) bool" diff --git a/result_test.go b/result_test.go index 78f26b5..cb628cd 100644 --- a/result_test.go +++ b/result_test.go @@ -29,6 +29,18 @@ func TestAll(t *testing.T) { } } +func TestAllG(t *testing.T) { + input := []int{2, 4, 6, 8} + allEven := FromSliceG(input).All(func(i int) bool { + return i%2 == 0 + }) + allOdd := FromSliceG(input).All(func(i int) bool { + return i%2 != 0 + }) + assert.True(t, allEven) + assert.False(t, allOdd) +} + func TestAllT_PanicWhenPredicateFnIsInvalid(t *testing.T) { mustPanicWithError(t, "AllT: parameter [predicateFn] has a invalid function signature. Expected: 'func(T)bool', actual: 'func(int)int'", func() { From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).AllT(func(item int) int { return item + 2 }) From 83846ff3b948702fbf3aec4a78a29c9197ebac81 Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 18:26:59 +0800 Subject: [PATCH 15/56] Add AnyG method --- result.go | 6 ++++++ result_test.go | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/result.go b/result.go index f2d7da0..a5c2e90 100644 --- a/result.go +++ b/result.go @@ -71,6 +71,12 @@ func (q Query) AnyWith(predicate func(interface{}) bool) bool { return false } +// Any determines whether any element of a collection exists. +func (q QueryG[T]) Any() bool { + _, ok := q.Iterate()() + return ok +} + // AnyWithT is the typed version of AnyWith. // // - predicateFn is of type "func(TSource) bool" diff --git a/result_test.go b/result_test.go index cb628cd..f4a1716 100644 --- a/result_test.go +++ b/result_test.go @@ -65,6 +65,12 @@ func TestAny(t *testing.T) { } } +func TestAnyG(t *testing.T) { + assert.True(t, FromSliceG([]int{1, 2, 3}).Any()) + assert.True(t, FromStringG("string").Any()) + assert.False(t, FromSliceG([]int{}).Any()) +} + func TestAnyWith(t *testing.T) { tests := []struct { input interface{} From a78b1f6263e3aaef5f01acb30ea8223bc310360e Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 18:29:44 +0800 Subject: [PATCH 16/56] Add AnyWithG method --- result.go | 13 +++++++++++++ result_test.go | 16 ++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/result.go b/result.go index a5c2e90..6bc6a81 100644 --- a/result.go +++ b/result.go @@ -71,6 +71,19 @@ func (q Query) AnyWith(predicate func(interface{}) bool) bool { return false } +// AnyWith determines whether any element of a collection satisfies a condition. +func (q QueryG[T]) AnyWith(predicate func(T) bool) bool { + next := q.Iterate() + + for item, ok := next(); ok; item, ok = next() { + if predicate(item) { + return true + } + } + + return false +} + // Any determines whether any element of a collection exists. func (q QueryG[T]) Any() bool { _, ok := q.Iterate()() diff --git a/result_test.go b/result_test.go index f4a1716..589ff71 100644 --- a/result_test.go +++ b/result_test.go @@ -90,6 +90,22 @@ func TestAnyWith(t *testing.T) { } } +func TestAnyWithG(t *testing.T) { + tests := []struct { + input []int + want bool + }{ + {[]int{1, 2, 2, 3, 1}, false}, + {[]int{}, false}, + } + + for _, test := range tests { + assert.Equal(t, test.want, FromSliceG(test.input).AnyWith(func(i int) bool { + return i == 4 + })) + } +} + func TestAnyWithT_PanicWhenPredicateFnIsInvalid(t *testing.T) { mustPanicWithError(t, "AnyWithT: parameter [predicateFn] has a invalid function signature. Expected: 'func(T)bool', actual: 'func(int)int'", func() { From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).AnyWithT(func(item int) int { return item + 2 }) From 57cd3e102e5000754a30aa0ec9ff378732526511 Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 18:46:18 +0800 Subject: [PATCH 17/56] Add ContainsG method --- result.go | 13 +++++++++++++ result_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/result.go b/result.go index 6bc6a81..71b55b6 100644 --- a/result.go +++ b/result.go @@ -168,6 +168,19 @@ func (q Query) Contains(value interface{}) bool { return false } +// Contains determines whether a collection contains a specified element. +func (q QueryG[T]) Contains(value T) bool { + next := q.Iterate() + + for item, ok := next(); ok; item, ok = next() { + if reflect.DeepEqual(item, value) { + return true + } + } + + return false +} + // Count returns the number of elements in a collection. func (q Query) Count() (r int) { next := q.Iterate() diff --git a/result_test.go b/result_test.go index 589ff71..6b3409c 100644 --- a/result_test.go +++ b/result_test.go @@ -153,6 +153,32 @@ func TestContains(t *testing.T) { } } +func TestContainsG(t *testing.T) { + assert.False(t, FromSliceG([]int{1, 2, 2, 3, 1}).Contains(10), false) + assert.True(t, FromSliceG([]uint{1, 2, 5, 7, 10}).Contains(uint(5))) + assert.False(t, FromSliceG([]float32{}).Contains(1.)) + assert.True(t, FromSliceG([]struct { + f1 int + f2 string + }{ + {1, "1"}, + {2, "2"}, + }).Contains(struct { + f1 int + f2 string + }{ + 2, "2", + })) + assert.True(t, FromSliceG([][]int{ + {1, 2, 3}, + {4, 5, 6}, + }).Contains([]int{4, 5, 6})) + assert.False(t, FromSliceG([][]int{ + {1, 2, 3}, + {4, 5, 6}, + }).Contains([]int{4, 5})) +} + func TestCount(t *testing.T) { tests := []struct { input interface{} From 98d1c47378be8aaa04ff128f30d88ebad05f40bf Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 19:37:46 +0800 Subject: [PATCH 18/56] Add AverageG method --- from.go | 12 ++++++++++++ result.go | 4 ++++ result_test.go | 5 +++++ 3 files changed, 21 insertions(+) diff --git a/from.go b/from.go index c23fb30..0a733dc 100644 --- a/from.go +++ b/from.go @@ -17,6 +17,18 @@ type QueryG[T any] struct { Iterate func() IteratorG[T] } +func (q QueryG[T]) AsQuery() Query { + return Query{ + Iterate: func() Iterator { + next := q.Iterate() + return func() (item interface{}, ok bool) { + i, ok := next() + return i, ok + } + }, + } +} + // KeyValue is a type that is used to iterate over a map (if query is created // from a map). This type is also used by ToMap() method to output result of a // query into a map. diff --git a/result.go b/result.go index 71b55b6..69e2e89 100644 --- a/result.go +++ b/result.go @@ -155,6 +155,10 @@ func (q Query) Average() (r float64) { return r / float64(n) } +func (q QueryG[T]) Average() float64 { + return q.AsQuery().Average() +} + // Contains determines whether a collection contains a specified element. func (q Query) Contains(value interface{}) bool { next := q.Iterate() diff --git a/result_test.go b/result_test.go index 6b3409c..5cf0942 100644 --- a/result_test.go +++ b/result_test.go @@ -129,6 +129,11 @@ func TestAverage(t *testing.T) { } } +func TestAverageG(t *testing.T) { + assert.Equal(t, 1.8, FromSliceG([]int{1, 2, 2, 3, 1}).Average()) + assert.Equal(t, 1., FromSliceG([]float32{1., 1}).Average()) +} + func TestAverageForNaN(t *testing.T) { if r := From([]int{}).Average(); !math.IsNaN(r) { t.Errorf("From([]int{}).Average()=%v expected %v", r, math.NaN()) From ee76d8bc1065f6e5da811ef6beb5f5ed8dadd3cc Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 19:40:05 +0800 Subject: [PATCH 19/56] Add CountG method --- result.go | 4 ++++ result_test.go | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/result.go b/result.go index 69e2e89..eeafa8b 100644 --- a/result.go +++ b/result.go @@ -196,6 +196,10 @@ func (q Query) Count() (r int) { return } +func (q QueryG[T]) Count() int { + return q.AsQuery().Count() +} + // CountWith returns a number that represents how many elements in the specified // collection satisfy a condition. func (q Query) CountWith(predicate func(interface{}) bool) (r int) { diff --git a/result_test.go b/result_test.go index 5cf0942..4c6028e 100644 --- a/result_test.go +++ b/result_test.go @@ -201,6 +201,11 @@ func TestCount(t *testing.T) { } } +func TestCountG(t *testing.T) { + assert.Equal(t, 5, FromSliceG([]int{1, 2, 2, 3, 1}).Count()) + assert.Equal(t, 0, FromSliceG([]float32{}).Count()) +} + func TestCountWith(t *testing.T) { tests := []struct { input interface{} From 4d55482d8c42a89aa42dcd310520a29a9bab22f5 Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 19:47:53 +0800 Subject: [PATCH 20/56] Add CountWithG method --- result.go | 12 ++++++++++++ result_test.go | 17 +++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/result.go b/result.go index eeafa8b..b9d22f6 100644 --- a/result.go +++ b/result.go @@ -214,6 +214,18 @@ func (q Query) CountWith(predicate func(interface{}) bool) (r int) { return } +func (q QueryG[T]) CountWith(predicate func(T) bool) (r int) { + next := q.Iterate() + + for item, ok := next(); ok; item, ok = next() { + if predicate(item) { + r++ + } + } + + return +} + // CountWithT is the typed version of CountWith. // // - predicateFn is of type "func(TSource) bool" diff --git a/result_test.go b/result_test.go index 4c6028e..31c9b01 100644 --- a/result_test.go +++ b/result_test.go @@ -224,6 +224,23 @@ func TestCountWith(t *testing.T) { } } +func TestCountWithG(t *testing.T) { + tests := []struct { + input []int + want int + }{ + {[]int{1, 2, 2, 3, 1}, 4}, + {[]int{}, 0}, + } + + for _, test := range tests { + r := From(test.input).CountWith(func(i interface{}) bool { + return i.(int) <= 2 + }) + assert.Equal(t, test.want, r) + } +} + func TestCountWithT_PanicWhenPredicateFnIsInvalid(t *testing.T) { mustPanicWithError(t, "CountWithT: parameter [predicateFn] has a invalid function signature. Expected: 'func(T)bool', actual: 'func(int)int'", func() { From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).CountWithT(func(item int) int { return item + 2 }) From 8ded08d737a1599fd10164a8cf540094debf7cf4 Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 19:51:35 +0800 Subject: [PATCH 21/56] Add FirstG method --- result.go | 6 ++++++ result_test.go | 16 ++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/result.go b/result.go index b9d22f6..20c5238 100644 --- a/result.go +++ b/result.go @@ -254,6 +254,12 @@ func (q Query) First() interface{} { return item } +// First returns the first element of a collection. +func (q QueryG[T]) First() T { + item, _ := q.Iterate()() + return item +} + // FirstWith returns the first element of a collection that satisfies a // specified condition. func (q Query) FirstWith(predicate func(interface{}) bool) interface{} { diff --git a/result_test.go b/result_test.go index 31c9b01..f849c66 100644 --- a/result_test.go +++ b/result_test.go @@ -263,6 +263,22 @@ func TestFirst(t *testing.T) { } } +func TestFirstG(t *testing.T) { + tests := []struct { + input []int + want int + }{ + {[]int{1, 2, 2, 3, 1}, 1}, + {[]int{}, 0}, + } + + for _, test := range tests { + assert.Equal(t, test.want, FromSliceG(test.input).First()) + } + + assert.Equal(t, "", FromSliceG([]string{}).First()) +} + func TestFirstWith(t *testing.T) { tests := []struct { input interface{} From 7e4d2001cee70193837b2b40117208f4d9a36f7d Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 20:00:17 +0800 Subject: [PATCH 22/56] Add FirstWithG --- result.go | 12 ++++++++++++ result_test.go | 15 +++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/result.go b/result.go index 20c5238..b27ad34 100644 --- a/result.go +++ b/result.go @@ -274,6 +274,18 @@ func (q Query) FirstWith(predicate func(interface{}) bool) interface{} { return nil } +func (q QueryG[T]) FirstWithG(predicate func(T) bool) (T, bool) { + next := q.Iterate() + + for item, ok := next(); ok; item, ok = next() { + if predicate(item) { + return item, true + } + } + + return *new(T), false +} + // FirstWithT is the typed version of FirstWith. // // - predicateFn is of type "func(TSource) bool" diff --git a/result_test.go b/result_test.go index f849c66..760c03b 100644 --- a/result_test.go +++ b/result_test.go @@ -297,6 +297,21 @@ func TestFirstWith(t *testing.T) { } } +func TestFirstWithG(t *testing.T) { + item, _ := FromSliceG([]int{1, 2, 2, 3, 1}).FirstWithG(func(i int) bool { + return i > 2 + }) + assert.Equal(t, 3, item) + _, ok := FromSliceG([]int{1, 2, 2, 3, 1}).FirstWithG(func(i int) bool { + return i > 4 + }) + assert.False(t, ok) + _, ok = FromSliceG([]int{}).FirstWithG(func(i int) bool { + return i > 4 + }) + assert.False(t, ok) +} + func TestFirstWithT_PanicWhenPredicateFnIsInvalid(t *testing.T) { mustPanicWithError(t, "FirstWithT: parameter [predicateFn] has a invalid function signature. Expected: 'func(T)bool', actual: 'func(int)int'", func() { From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).FirstWithT(func(item int) int { return item + 2 }) From f3eb23227c907dee783f62636f6f5b6b6614092c Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 20:03:27 +0800 Subject: [PATCH 23/56] Add ForEachG --- result.go | 8 ++++++++ result_test.go | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/result.go b/result.go index b27ad34..b385b70 100644 --- a/result.go +++ b/result.go @@ -317,6 +317,14 @@ func (q Query) ForEach(action func(interface{})) { } } +func (q QueryG[T]) ForEach(action func(T)) { + next := q.Iterate() + + for item, ok := next(); ok; item, ok = next() { + action(item) + } +} + // ForEachT is the typed version of ForEach. // // - actionFn is of type "func(TSource)" diff --git a/result_test.go b/result_test.go index 760c03b..d3689b3 100644 --- a/result_test.go +++ b/result_test.go @@ -339,6 +339,25 @@ func TestForEach(t *testing.T) { } } +func TestForEachG(t *testing.T) { + tests := []struct { + input []int + want []int + }{ + {[]int{1, 2, 2, 35, 111}, []int{2, 4, 4, 70, 222}}, + {[]int{}, []int{}}, + } + + for _, test := range tests { + output := []int{} + FromSliceG(test.input).ForEach(func(item int) { + output = append(output, item*2) + }) + + assert.Equal(t, test.want, output) + } +} + func TestForEachT_PanicWhenActionFnIsInvalid(t *testing.T) { mustPanicWithError(t, "ForEachT: parameter [actionFn] has a invalid function signature. Expected: 'func(T)', actual: 'func(int,int)'", func() { From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).ForEachT(func(item, idx int) { item = item + 2 }) From 1cda6e907171d5a042795e61ca0e22a8adddf172 Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 20:08:28 +0800 Subject: [PATCH 24/56] Add ForEachIndexedG method --- result.go | 10 ++++++++++ result_test.go | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/result.go b/result.go index b385b70..b89bd7a 100644 --- a/result.go +++ b/result.go @@ -365,6 +365,16 @@ func (q Query) ForEachIndexed(action func(int, interface{})) { } } +func (q QueryG[T]) ForEachIndexed(action func(int, T)) { + next := q.Iterate() + index := 0 + + for item, ok := next(); ok; item, ok = next() { + action(index, item) + index++ + } +} + // ForEachIndexedT is the typed version of ForEachIndexed. // // - actionFn is of type "func(int, TSource)" diff --git a/result_test.go b/result_test.go index d3689b3..38f9c5b 100644 --- a/result_test.go +++ b/result_test.go @@ -385,6 +385,25 @@ func TestForEachIndexed(t *testing.T) { } } +func TestForEachIndexedG(t *testing.T) { + tests := []struct { + input []int + want []int + }{ + {[]int{1, 2, 2, 35, 111}, []int{1, 3, 4, 38, 115}}, + {[]int{}, []int{}}, + } + + for _, test := range tests { + output := []int{} + FromSliceG(test.input).ForEachIndexed(func(index int, item int) { + output = append(output, item+index) + }) + + assert.Equal(t, test.want, output) + } +} + func TestForEachIndexedT_PanicWhenActionFnIsInvalid(t *testing.T) { mustPanicWithError(t, "ForEachIndexedT: parameter [actionFn] has a invalid function signature. Expected: 'func(int,T)', actual: 'func(int)'", func() { From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).ForEachIndexedT(func(item int) { item = item + 2 }) From 303de6476f8dcd51f606e9011521560d89819a98 Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 20:15:15 +0800 Subject: [PATCH 25/56] Add LastG method --- result.go | 11 +++++++++++ result_test.go | 8 ++++++++ 2 files changed, 19 insertions(+) diff --git a/result.go b/result.go index b89bd7a..33ed34c 100644 --- a/result.go +++ b/result.go @@ -408,6 +408,17 @@ func (q Query) Last() (r interface{}) { return } +func (q QueryG[T]) Last() (r T, got bool) { + next := q.Iterate() + got = false + for item, ok := next(); ok; item, ok = next() { + got = true + r = item + } + + return +} + // LastWith returns the last element of a collection that satisfies a specified // condition. func (q Query) LastWith(predicate func(interface{}) bool) (r interface{}) { diff --git a/result_test.go b/result_test.go index 38f9c5b..c95300e 100644 --- a/result_test.go +++ b/result_test.go @@ -426,6 +426,14 @@ func TestLast(t *testing.T) { } } +func TestLastG(t *testing.T) { + last, got := FromSliceG([]int{1, 2, 2, 3, 5}).Last() + assert.Equal(t, 5, last) + assert.True(t, got) + _, got = FromSliceG([]int{}).Last() + assert.False(t, got) +} + func TestLastWith(t *testing.T) { tests := []struct { input interface{} From 53ca402807390c2b41fea3ff69dcc7b6ed6618e0 Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 20:18:02 +0800 Subject: [PATCH 26/56] Change FirstG method return list --- result.go | 6 +++--- result_test.go | 18 +++++------------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/result.go b/result.go index 33ed34c..e7228b0 100644 --- a/result.go +++ b/result.go @@ -255,9 +255,9 @@ func (q Query) First() interface{} { } // First returns the first element of a collection. -func (q QueryG[T]) First() T { - item, _ := q.Iterate()() - return item +func (q QueryG[T]) First() (T, bool) { + item, ok := q.Iterate()() + return item, ok } // FirstWith returns the first element of a collection that satisfies a diff --git a/result_test.go b/result_test.go index c95300e..dc9ec83 100644 --- a/result_test.go +++ b/result_test.go @@ -264,19 +264,11 @@ func TestFirst(t *testing.T) { } func TestFirstG(t *testing.T) { - tests := []struct { - input []int - want int - }{ - {[]int{1, 2, 2, 3, 1}, 1}, - {[]int{}, 0}, - } - - for _, test := range tests { - assert.Equal(t, test.want, FromSliceG(test.input).First()) - } - - assert.Equal(t, "", FromSliceG([]string{}).First()) + first, got := FromSliceG([]int{1, 2, 2, 3, 5}).First() + assert.Equal(t, 1, first) + assert.True(t, got) + _, got = FromSliceG([]string{}).First() + assert.False(t, got) } func TestFirstWith(t *testing.T) { From 823d94d7d1af3966c6efca224f53a20d28b6a4e5 Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 20:23:38 +0800 Subject: [PATCH 27/56] Add LastWithG method --- result.go | 15 +++++++++++++++ result_test.go | 13 +++++++++++++ 2 files changed, 28 insertions(+) diff --git a/result.go b/result.go index e7228b0..07ddf14 100644 --- a/result.go +++ b/result.go @@ -433,6 +433,21 @@ func (q Query) LastWith(predicate func(interface{}) bool) (r interface{}) { return } +// LastWith returns the last element of a collection that satisfies a specified +// condition. +func (q QueryG[T]) LastWith(predicate func(T) bool) (r T, got bool) { + next := q.Iterate() + got = false + for item, ok := next(); ok; item, ok = next() { + if predicate(item) { + got = true + r = item + } + } + + return +} + // LastWithT is the typed version of LastWith. // // - predicateFn is of type "func(TSource) bool" diff --git a/result_test.go b/result_test.go index dc9ec83..eecca14 100644 --- a/result_test.go +++ b/result_test.go @@ -444,6 +444,19 @@ func TestLastWith(t *testing.T) { } } +func TestLastWithG(t *testing.T) { + greaterThanTwo := func(i int) bool { + return i > 2 + } + last, got := FromSliceG([]int{1, 2, 2, 3, 1, 4, 2, 5, 1, 1}).LastWith(greaterThanTwo) + assert.Equal(t, 5, last) + assert.True(t, got) + _, got = FromSliceG([]int{}).LastWith(greaterThanTwo) + assert.False(t, got) + _, got = FromSliceG([]int{1, 1, 1, 1, 1}).LastWith(greaterThanTwo) + assert.False(t, got) +} + func TestLastWithT_PanicWhenPredicateFnIsInvalid(t *testing.T) { mustPanicWithError(t, "LastWithT: parameter [predicateFn] has a invalid function signature. Expected: 'func(T)bool', actual: 'func(int)int'", func() { From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).LastWithT(func(item int) int { return item + 2 }) From 36c793ab95befb61e7e58f128b083770d6a231e9 Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 20:32:21 +0800 Subject: [PATCH 28/56] Add MaxG and MinG method --- result.go | 10 ++++++++++ result_test.go | 41 ++++++++++++++++++++++++++++++++++------- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/result.go b/result.go index 07ddf14..ee3ca12 100644 --- a/result.go +++ b/result.go @@ -490,6 +490,11 @@ func (q Query) Max() (r interface{}) { return } +func (q QueryG[T]) Max() (T, bool) { + r, ok := q.AsQuery().Max().(T) + return r, ok +} + // Min returns the minimum value in a collection of values. func (q Query) Min() (r interface{}) { next := q.Iterate() @@ -510,6 +515,11 @@ func (q Query) Min() (r interface{}) { return } +func (q QueryG[T]) Min() (T, bool) { + r, ok := q.AsQuery().Min().(T) + return r, ok +} + // Results iterates over a collection and returnes slice of interfaces func (q Query) Results() (r []interface{}) { next := q.Iterate() diff --git a/result_test.go b/result_test.go index eecca14..b4e34e9 100644 --- a/result_test.go +++ b/result_test.go @@ -480,19 +480,46 @@ func TestMax(t *testing.T) { } } +func TestMaxG(t *testing.T) { + tests := []struct { + input []int + want int + ok bool + }{ + {[]int{1, 2, 2, 3, 1}, 3, true}, + {[]int{1}, 1, true}, + {[]int{}, 0, false}, + } + + for _, test := range tests { + max, ok := FromSliceG(test.input).Max() + if !test.ok { + assert.False(t, ok) + } else { + assert.Equal(t, test.want, max) + assert.True(t, ok) + } + } +} + func TestMin(t *testing.T) { tests := []struct { - input interface{} - want interface{} + input []int + want int + ok bool }{ - {[]int{1, 2, 2, 3, 0}, 0}, - {[]int{1}, 1}, - {[]int{}, nil}, + {[]int{1, 2, 2, 3, 0}, 0, true}, + {[]int{1}, 1, true}, + {[]int{}, 0, false}, } for _, test := range tests { - if r := From(test.input).Min(); r != test.want { - t.Errorf("From(%v).Min()=%v expected %v", test.input, r, test.want) + min, ok := FromSliceG(test.input).Min() + if !test.ok { + assert.False(t, ok) + } else { + assert.Equal(t, test.want, min) + assert.True(t, ok) } } } From 987d87a0e2f9d698c1c098e770dd6a16e9711e94 Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 20:35:01 +0800 Subject: [PATCH 29/56] Add SequenceEqualG method --- result.go | 15 +++++++++++++++ result_test.go | 16 ++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/result.go b/result.go index ee3ca12..8bb1703 100644 --- a/result.go +++ b/result.go @@ -547,6 +547,21 @@ func (q Query) SequenceEqual(q2 Query) bool { return !ok2 } +func (q QueryG[T]) SequenceEqual(q2 QueryG[T]) bool { + next := q.Iterate() + next2 := q2.Iterate() + + for item, ok := next(); ok; item, ok = next() { + item2, ok2 := next2() + if !ok2 || !reflect.DeepEqual(item, item2) { + return false + } + } + + _, ok2 := next2() + return !ok2 +} + // Single returns the only element of a collection, and nil if there is not // exactly one element in the collection. func (q Query) Single() interface{} { diff --git a/result_test.go b/result_test.go index b4e34e9..1e61187 100644 --- a/result_test.go +++ b/result_test.go @@ -551,6 +551,22 @@ func TestSequenceEqual(t *testing.T) { } } +func TestSequenceEqualG(t *testing.T) { + tests := []struct { + input []int + input2 []int + want bool + }{ + {[]int{1, 2, 2, 3, 1}, []int{4, 6}, false}, + {[]int{1, -1, 100}, []int{1, -1, 100}, true}, + {[]int{}, []int{}, true}, + } + + for _, test := range tests { + assert.Equal(t, test.want, FromSliceG(test.input).SequenceEqual(FromSliceG(test.input2))) + } +} + func TestSingle(t *testing.T) { tests := []struct { input interface{} From 96d66ff151f9e49e374ca0ed599449b90527df9d Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 22:02:06 +0800 Subject: [PATCH 30/56] Add SingleWithG method --- result.go | 34 ++++++++++++++++++++++++++++++++++ result_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/result.go b/result.go index 8bb1703..bdc8881 100644 --- a/result.go +++ b/result.go @@ -579,6 +579,23 @@ func (q Query) Single() interface{} { return item } +// Single returns the only element of a collection, and nil if there is not +// exactly one element in the collection. +func (q QueryG[T]) Single() (T, bool) { + next := q.Iterate() + item, ok := next() + if !ok { + return *new(T), false + } + + _, ok = next() + if ok { + return *new(T), false + } + + return item, true +} + // SingleWith returns the only element of a collection that satisfies a // specified condition, and nil if more than one such element exists. func (q Query) SingleWith(predicate func(interface{}) bool) (r interface{}) { @@ -599,6 +616,23 @@ func (q Query) SingleWith(predicate func(interface{}) bool) (r interface{}) { return } +func (q QueryG[T]) SingleWith(predicate func(T) bool) (r T, found bool) { + next := q.Iterate() + found = false + + for item, ok := next(); ok; item, ok = next() { + if predicate(item) { + if found { + return *new(T), false + } + + found = true + r = item + } + } + return +} + // SingleWithT is the typed version of SingleWith. // // - predicateFn is of type "func(TSource) bool" diff --git a/result_test.go b/result_test.go index 1e61187..b62993f 100644 --- a/result_test.go +++ b/result_test.go @@ -584,6 +584,28 @@ func TestSingle(t *testing.T) { } } +func TestSingleG(t *testing.T) { + tests := []struct { + input []int + want int + ok bool + }{ + {[]int{1, 2, 2, 3, 1}, 0, false}, + {[]int{1}, 1, true}, + {[]int{}, 0, false}, + } + + for _, test := range tests { + single, ok := FromSliceG(test.input).Single() + if !test.ok { + assert.False(t, ok) + } else { + assert.True(t, ok) + assert.Equal(t, test.want, single) + } + } +} + func TestSingleWith(t *testing.T) { tests := []struct { input interface{} @@ -604,6 +626,31 @@ func TestSingleWith(t *testing.T) { } } +func TestSingleWithG(t *testing.T) { + tests := []struct { + input []int + want int + ok bool + }{ + {[]int{1, 2, 2, 3, 1}, 3, true}, + {[]int{1, 1, 1}, 0, false}, + {[]int{5, 1, 1, 10, 2, 2}, 0, false}, + {[]int{}, 0, false}, + } + + for _, test := range tests { + item, found := FromSliceG(test.input).SingleWith(func(i int) bool { + return i > 2 + }) + if !test.ok { + assert.False(t, found) + } else { + assert.True(t, found) + assert.Equal(t, test.want, item) + } + } +} + func TestSingleWithT_PanicWhenPredicateFnIsInvalid(t *testing.T) { mustPanicWithError(t, "SingleWithT: parameter [predicateFn] has a invalid function signature. Expected: 'func(T)bool', actual: 'func(int)int'", func() { From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).SingleWithT(func(item int) int { return item + 2 }) From c4739a83cd87c065c01683a26cf1c6b5d69ea208 Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 22:35:33 +0800 Subject: [PATCH 31/56] Add ZipG method --- zip.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ zip_test.go | 17 ++++++++++++++++- 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/zip.go b/zip.go index da3dcd3..839487c 100644 --- a/zip.go +++ b/zip.go @@ -32,6 +32,53 @@ func (q Query) Zip(q2 Query, } } +func (q QueryG[T]) Zip(selector Zipper[T]) interface{} { + return selector.Map(q) +} + +type Zipper[TIn1 any] interface { + Map(q QueryG[TIn1]) interface{} +} + +func ZipWith[TIn1, TIn2, TOut any](q2 QueryG[TIn2], resultSelector func(TIn1, TIn2) TOut) Zipper[TIn1] { + return zipSelector[TIn1, TIn2, TOut]{ + resultSelector: resultSelector, + q2: q2, + } +} + +type zipSelector[TIn1, TIn2, TOut any] struct { + resultSelector func(TIn1, TIn2) TOut + q2 QueryG[TIn2] +} + +var z Zipper[int] = zipSelector[int, int, int]{} + +func (z zipSelector[TIn1, TIn2, TOut]) Map(q QueryG[TIn1]) interface{} { + return ZipG(q, z.q2, z.resultSelector) +} + +func ZipG[TIn1, TIn2, TOut any](q QueryG[TIn1], q2 QueryG[TIn2], + resultSelector func(TIn1, TIn2) TOut) QueryG[TOut] { + return QueryG[TOut]{ + Iterate: func() IteratorG[TOut] { + next1 := q.Iterate() + next2 := q2.Iterate() + + return func() (item TOut, ok bool) { + item1, ok1 := next1() + item2, ok2 := next2() + + if ok1 && ok2 { + return resultSelector(item1, item2), true + } + + return *new(TOut), false + } + }, + } +} + // ZipT is the typed version of Zip. // // - resultSelectorFn is of type "func(TFirst,TSecond)TResult" diff --git a/zip_test.go b/zip_test.go index 335f706..29c564f 100644 --- a/zip_test.go +++ b/zip_test.go @@ -1,6 +1,10 @@ package linq -import "testing" +import ( + "github.com/stretchr/testify/assert" + "strconv" + "testing" +) func TestZip(t *testing.T) { input1 := []int{1, 2, 3} @@ -14,6 +18,17 @@ func TestZip(t *testing.T) { } } +func TestZipG(t *testing.T) { + input1 := []int{1, 2, 3} + input2 := []int{2, 4, 5, 1} + want := []string{"3", "6", "8"} + + slice := FromSliceG(input1).Zip(ZipWith(FromSliceG(input2), func(i1, i2 int) string { + return strconv.Itoa(i1 + i2) + })).(QueryG[string]).ToSlice() + assert.Equal(t, want, slice) +} + func TestZipT_PanicWhenResultSelectorFnIsInvalid(t *testing.T) { mustPanicWithError(t, "ZipT: parameter [resultSelectorFn] has a invalid function signature. Expected: 'func(T,T)T', actual: 'func(int,int,int)int'", func() { input1 := []int{1, 2, 3} From 9456e4990b5edb4e7248ada62a33050756143b30 Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 22:44:35 +0800 Subject: [PATCH 32/56] Add WhereIndexdG method --- where.go | 27 +++++++++++++++++++++++++++ where_test.go | 12 ++++++++++++ 2 files changed, 39 insertions(+) diff --git a/where.go b/where.go index 8488ca7..c915755 100644 --- a/where.go +++ b/where.go @@ -85,6 +85,33 @@ func (q Query) WhereIndexed(predicate func(int, interface{}) bool) Query { } } +// WhereIndexed filters a collection of values based on a predicate. Each +// element's index is used in the logic of the predicate function. +// +// The first argument represents the zero-based index of the element within +// collection. The second argument of predicate represents the element to test. +func (q QueryG[T]) WhereIndexed(predicate func(int, T) bool) QueryG[T] { + return QueryG[T]{ + Iterate: func() IteratorG[T] { + next := q.Iterate() + index := 0 + + return func() (item T, ok bool) { + for item, ok = next(); ok; item, ok = next() { + if predicate(index, item) { + index++ + return + } + + index++ + } + + return + } + }, + } +} + // WhereIndexedT is the typed version of WhereIndexed. // // - predicateFn is of type "func(int,TSource)bool" diff --git a/where_test.go b/where_test.go index eaba0e8..f9d85c6 100644 --- a/where_test.go +++ b/where_test.go @@ -66,6 +66,18 @@ func TestWhereIndexed(t *testing.T) { } } +func TestWhereIndexedG(t *testing.T) { + assert.Equal(t, []int{2, 3, 2}, FromSliceG([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).WhereIndexed(func(i, x int) bool { + return x < 4 && i > 4 + }).ToSlice()) + assert.Equal(t, []rune{'s', 't', 'r'}, FromStringG("sstr").WhereIndexed(func(i int, x rune) bool { + return x != 's' || i == 1 + }).ToSlice()) + assert.Equal(t, []rune{'a', 'b'}, FromStringG("abcde").WhereIndexed(func(i int, x rune) bool { + return i < 2 + }).ToSlice()) +} + func TestWhereIndexedT_PanicWhenPredicateFnIsInvalid(t *testing.T) { mustPanicWithError(t, "WhereIndexedT: parameter [predicateFn] has a invalid function signature. Expected: 'func(int,T)bool', actual: 'func(string)'", func() { From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).WhereIndexedT(func(item string) {}) From 978efbc105a8c89127f94fc5db6fa26e0a8c782c Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 22:49:41 +0800 Subject: [PATCH 33/56] Add UnionG method --- union.go | 39 +++++++++++++++++++++++++++++++++++++++ union_test.go | 40 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/union.go b/union.go index 717b83b..bc02062 100644 --- a/union.go +++ b/union.go @@ -38,3 +38,42 @@ func (q Query) Union(q2 Query) Query { }, } } + +// Union produces the set union of two collections. +// +// This method excludes duplicates from the return set. This is different +// behavior to the Concat method, which returns all the elements in the input +// collection including duplicates. +func (q QueryG[T]) Union(q2 QueryG[T]) QueryG[T] { + return QueryG[T]{ + Iterate: func() IteratorG[T] { + next := q.Iterate() + next2 := q2.Iterate() + + set := make(map[interface{}]bool) + use1 := true + + return func() (item T, ok bool) { + if use1 { + for item, ok = next(); ok; item, ok = next() { + if _, has := set[item]; !has { + set[item] = true + return + } + } + + use1 = false + } + + for item, ok = next2(); ok; item, ok = next2() { + if _, has := set[item]; !has { + set[item] = true + return + } + } + + return + } + }, + } +} diff --git a/union_test.go b/union_test.go index e7aef60..e2af7f3 100644 --- a/union_test.go +++ b/union_test.go @@ -1,6 +1,9 @@ package linq -import "testing" +import ( + "github.com/stretchr/testify/assert" + "testing" +) func TestUnion(t *testing.T) { input1 := []int{1, 2, 3} @@ -11,3 +14,38 @@ func TestUnion(t *testing.T) { t.Errorf("From(%v).Union(%v)=%v expected %v", input1, input2, toSlice(q), want) } } + +func TestUnionG_int(t *testing.T) { + input1 := []int{1, 2, 3} + input2 := []int{2, 4, 5, 1} + want := []int{1, 2, 3, 4, 5} + + assert.Equal(t, want, FromSliceG(input1).Union(FromSliceG(input2)).ToSlice()) +} + +type unionG_test struct { + f1 int + f2 string +} + +func TestUnionG_struct(t *testing.T) { + input1 := []unionG_test{ + {1, "1"}, + {2, "2"}, + } + input2 := []unionG_test{ + {2, "2"}, + {3, "3"}, + {4, "4"}, + {5, "5"}, + } + want := []unionG_test{ + {1, "1"}, + {2, "2"}, + {3, "3"}, + {4, "4"}, + {5, "5"}, + } + + assert.Equal(t, want, FromSliceG(input1).Union(FromSliceG(input2)).ToSlice()) +} From 7c765d9ef14739f6158dcfd8bfc6c82749e77016 Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 23:27:37 +0800 Subject: [PATCH 34/56] Add SumG method --- go.mod | 5 ++++- result.go | 12 ++++++++++++ result_test.go | 7 +++++++ sum_helper.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 sum_helper.go diff --git a/go.mod b/go.mod index 9dd8f80..b32de7c 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,10 @@ module github.com/ahmetb/go-linq/v3 go 1.18 -require github.com/stretchr/testify v1.8.0 +require ( + github.com/stretchr/testify v1.8.0 + golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e +) require ( github.com/davecgh/go-spew v1.1.1 // indirect diff --git a/result.go b/result.go index bdc8881..2cb41e0 100644 --- a/result.go +++ b/result.go @@ -654,6 +654,18 @@ func (q Query) SingleWithT(predicateFn interface{}) interface{} { return q.SingleWith(predicateFunc) } +func (q QueryG[T]) Sum() T { + var sum any + adder := getAdder(*new(T)) + q.ForEach(func(t T) { + sum = adder(t) + }) + if sum == nil { + return *new(T) + } + return sum.(T) +} + // SumInts computes the sum of a collection of numeric values. // // Values can be of any integer type: int, int8, int16, int32, int64. The result diff --git a/result_test.go b/result_test.go index b62993f..ed88ef3 100644 --- a/result_test.go +++ b/result_test.go @@ -657,6 +657,13 @@ func TestSingleWithT_PanicWhenPredicateFnIsInvalid(t *testing.T) { }) } +func TestSumG(t *testing.T) { + assert.Equal(t, 9, FromSliceG([]int{1, 2, 2, 3, 1}).Sum()) + assert.Equal(t, int8(9), FromSliceG([]int8{int8(4), int8(5)}).Sum()) + assert.Equal(t, 1, FromSliceG([]int{1}).Sum()) + assert.Equal(t, 0, FromSliceG([]int{}).Sum()) +} + func TestSumInts(t *testing.T) { tests := []struct { input interface{} diff --git a/sum_helper.go b/sum_helper.go new file mode 100644 index 0000000..aef9e52 --- /dev/null +++ b/sum_helper.go @@ -0,0 +1,46 @@ +package linq + +import "golang.org/x/exp/constraints" + +func getAdder(data any) func(any) any { + switch data.(type) { + case int: + return adder[int]() + case int8: + return adder[int8]() + case int16: + return adder[int16]() + case int32: + return adder[int32]() + case int64: + return adder[int64]() + case uint: + return adder[uint]() + case uint8: + return adder[uint8]() + case uint16: + return adder[uint16]() + case uint32: + return adder[uint32]() + case uint64: + return adder[uint64]() + case float32: + return adder[float32]() + case float64: + return adder[float64]() + default: + return nil + } +} + +type Number interface { + constraints.Integer | constraints.Float +} + +func adder[T Number]() func(any) any { + var sum T = 0 + return func(i any) any { + sum += i.(T) + return sum + } +} From b23b0c1c5f3064367f7ebf76622c8bdfd16e960b Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 23:30:41 +0800 Subject: [PATCH 35/56] Add ToChannelG method --- result.go | 12 ++++++++++++ result_test.go | 16 ++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/result.go b/result.go index 2cb41e0..3ff7d44 100644 --- a/result.go +++ b/result.go @@ -742,6 +742,18 @@ func (q Query) ToChannel(result chan<- interface{}) { close(result) } +// ToChannel iterates over a collection and outputs each element to a channel, +// then closes it. +func (q QueryG[T]) ToChannel(result chan<- T) { + next := q.Iterate() + + for item, ok := next(); ok; item, ok = next() { + result <- item + } + + close(result) +} + // ToChannelT is the typed version of ToChannel. // // - result is of type "chan TSource" diff --git a/result_test.go b/result_test.go index ed88ef3..0135fb1 100644 --- a/result_test.go +++ b/result_test.go @@ -733,6 +733,22 @@ func TestToChannel(t *testing.T) { } } +func TestToChannelG(t *testing.T) { + c := make(chan int) + input := []int{1, 2, 3, 4, 5} + + go func() { + FromSliceG(input).ToChannel(c) + }() + + result := []int{} + for value := range c { + result = append(result, value) + } + + assert.Equal(t, input, result) +} + func TestToChannelT(t *testing.T) { c := make(chan string) input := []string{"1", "2", "3", "4", "5"} From 10256e388164276272cd964349f920b10b2321bf Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 23:34:03 +0800 Subject: [PATCH 36/56] Add test case to ToChannelG method --- result_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/result_test.go b/result_test.go index 0135fb1..c6c6303 100644 --- a/result_test.go +++ b/result_test.go @@ -662,6 +662,7 @@ func TestSumG(t *testing.T) { assert.Equal(t, int8(9), FromSliceG([]int8{int8(4), int8(5)}).Sum()) assert.Equal(t, 1, FromSliceG([]int{1}).Sum()) assert.Equal(t, 0, FromSliceG([]int{}).Sum()) + assert.Equal(t, float64(9.), FromSliceG([]float64{1., 2., 2., 3., 1.}).Sum()) } func TestSumInts(t *testing.T) { From 949571d67e077eee6d41cc578f4235d2e7ab8bce Mon Sep 17 00:00:00 2001 From: zjhe Date: Sat, 6 Aug 2022 23:39:58 +0800 Subject: [PATCH 37/56] Add IndexOfG method --- index.go | 17 +++++++++++++++++ index_test.go | 13 +++++++++++++ 2 files changed, 30 insertions(+) diff --git a/index.go b/index.go index cd348c0..bf9d8db 100644 --- a/index.go +++ b/index.go @@ -17,6 +17,23 @@ func (q Query) IndexOf(predicate func(interface{}) bool) int { return -1 } +// IndexOf searches for an element that matches the conditions defined by a specified predicate +// and returns the zero-based index of the first occurrence within the collection. This method +// returns -1 if an item that matches the conditions is not found. +func (q QueryG[T]) IndexOf(predicate func(T) bool) int { + index := 0 + next := q.Iterate() + + for item, ok := next(); ok; item, ok = next() { + if predicate(item) { + return index + } + index++ + } + + return -1 +} + // IndexOfT is the typed version of IndexOf. // // - predicateFn is of type "func(int,TSource)bool" diff --git a/index_test.go b/index_test.go index 38635e4..7f7791f 100644 --- a/index_test.go +++ b/index_test.go @@ -1,6 +1,7 @@ package linq import ( + "github.com/stretchr/testify/assert" "testing" ) @@ -46,6 +47,18 @@ func TestIndexOf(t *testing.T) { } } +func TestIndexOfG(t *testing.T) { + assert.Equal(t, 2, FromSliceG([]int{1, 2, 3, 4, 5, 6, 7, 8, 9}).IndexOf(func(i int) bool { + return i == 3 + })) + assert.Equal(t, 3, FromStringG("sstr").IndexOf(func(i rune) bool { + return i == 'r' + })) + assert.Equal(t, -1, FromStringG("gadsgsadgsda").IndexOf(func(i rune) bool { + return i == 'z' + })) +} + func TestIndexOfT_PanicWhenPredicateFnIsInvalid(t *testing.T) { mustPanicWithError(t, "IndexOfT: parameter [predicateFn] has a invalid function signature. Expected: 'func(T)bool', actual: 'func(int)int'", func() { From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).IndexOfT(func(item int) int { return item + 2 }) From faa27c2226fe54d0246ee8ebe8e05b0d62eabb4e Mon Sep 17 00:00:00 2001 From: zjhe Date: Sun, 7 Aug 2022 07:55:08 +0800 Subject: [PATCH 38/56] Add IntersectG method --- intersect.go | 78 +++++++++++++++++++++++++++++++++++++++++++++++ intersect_test.go | 23 +++++++++++++- 2 files changed, 100 insertions(+), 1 deletion(-) diff --git a/intersect.go b/intersect.go index 511ed3b..b069886 100644 --- a/intersect.go +++ b/intersect.go @@ -29,6 +29,35 @@ func (q Query) Intersect(q2 Query) Query { } } +// Intersect produces the set intersection of the source collection and the +// provided input collection. The intersection of two sets A and B is defined as +// the set that contains all the elements of A that also appear in B, but no +// other elements. +func (q QueryG[T]) Intersect(q2 QueryG[T]) QueryG[T] { + return QueryG[T]{ + Iterate: func() IteratorG[T] { + next := q.Iterate() + next2 := q2.Iterate() + + set := make(map[interface{}]bool) + for item, ok := next2(); ok; item, ok = next2() { + set[item] = true + } + + return func() (item T, ok bool) { + for item, ok = next(); ok; item, ok = next() { + if _, has := set[item]; has { + delete(set, item) + return + } + } + + return + } + }, + } +} + // IntersectBy produces the set intersection of the source collection and the // provided input collection. The intersection of two sets A and B is defined as // the set that contains all the elements of A that also appear in B, but no @@ -64,6 +93,55 @@ func (q Query) IntersectBy(q2 Query, } } +type Intersect[T any] interface { + Map(q1, q2 QueryG[T]) any +} + +type intersectBy[TIn, TOut any] struct { + selector func(TIn) TOut +} + +func IntersectSelector[TIn, TOut any](selector func(TIn) TOut) Intersect[TIn] { + return intersectBy[TIn, TOut]{ + selector: selector, + } +} + +func (i intersectBy[TIn, TOut]) Map(q1, q2 QueryG[TIn]) any { + return IntersectBy[TIn, TOut](q1, q2, i.selector) +} + +func (q QueryG[T]) IntersectBy(q2 QueryG[T], i Intersect[T]) QueryG[T] { + return i.Map(q, q2).(QueryG[T]) +} + +func IntersectBy[T1, T2 any](q1, q2 QueryG[T1], selector func(T1) T2) QueryG[T1] { + return QueryG[T1]{ + Iterate: func() IteratorG[T1] { + next := q1.Iterate() + next2 := q2.Iterate() + + set := make(map[interface{}]bool) + for item, ok := next2(); ok; item, ok = next2() { + s := selector(item) + set[s] = true + } + + return func() (item T1, ok bool) { + for item, ok = next(); ok; item, ok = next() { + s := selector(item) + if _, has := set[s]; has { + delete(set, s) + return + } + } + + return + } + }, + } +} + // IntersectByT is the typed version of IntersectBy. // // - selectorFn is of type "func(TSource) TSource" diff --git a/intersect_test.go b/intersect_test.go index f68a32e..825c24d 100644 --- a/intersect_test.go +++ b/intersect_test.go @@ -1,6 +1,9 @@ package linq -import "testing" +import ( + "github.com/stretchr/testify/assert" + "testing" +) func TestIntersect(t *testing.T) { input1 := []int{1, 2, 3} @@ -12,6 +15,14 @@ func TestIntersect(t *testing.T) { } } +func TestIntersectG(t *testing.T) { + input1 := []int{1, 2, 3} + input2 := []int{1, 4, 7, 9, 12, 3} + want := []int{1, 3} + + assert.Equal(t, want, FromSliceG(input1).Intersect(FromSliceG(input2)).ToSlice()) +} + func TestIntersectBy(t *testing.T) { input1 := []int{5, 7, 8} input2 := []int{1, 4, 7, 9, 12, 3} @@ -24,6 +35,16 @@ func TestIntersectBy(t *testing.T) { } } +func TestIntersectByG(t *testing.T) { + input1 := []int{5, 7, 8} + input2 := []int{1, 5, 7, 9, 12, 3} + want := []int{5, 8} + actual := FromSliceG(input1).IntersectBy(FromSliceG(input2), IntersectSelector(func(i int) int { + return i % 2 + })).ToSlice() + assert.Equal(t, want, actual) +} + func TestIntersectByT_PanicWhenSelectorFnIsInvalid(t *testing.T) { mustPanicWithError(t, "IntersectByT: parameter [selectorFn] has a invalid function signature. Expected: 'func(T)T', actual: 'func(int,int)int'", func() { From([]int{5, 7, 8}).IntersectByT(From([]int{1, 4, 7, 9, 12, 3}), func(i, x int) int { From a9842bffad6ba79992ab936894db757db6b12c4e Mon Sep 17 00:00:00 2001 From: zjhe Date: Sun, 7 Aug 2022 09:13:06 +0800 Subject: [PATCH 39/56] Add Expender to expend type parameters for method --- expender.go | 50 +++++++++++++++++++++++ select.go | 105 +++++++++++++++++++------------------------------ select_test.go | 8 ++-- zip.go | 67 ++++++++++--------------------- zip_test.go | 4 +- 5 files changed, 116 insertions(+), 118 deletions(-) create mode 100644 expender.go diff --git a/expender.go b/expender.go new file mode 100644 index 0000000..a09063b --- /dev/null +++ b/expender.go @@ -0,0 +1,50 @@ +package linq + +var _ Expender[int] = &expender[int, int]{} +var _ Expended[int, int] = &expender[int, int]{} +var _ Expended3[int, int, int] = &expender3[int, int, int]{} + +func (q QueryG[T]) Expend(e Expender[T]) Expender[T] { + e.Expend(q) + return e +} + +type Expender[T any] interface { + Expend(q QueryG[T]) any +} + +type Expended[T1, T2 any] interface { + Select(selector func(T1) T2) QueryG[T2] + SelectIndexed(selector func(int, T1) T2) QueryG[T2] +} + +type Expended3[T1, T2, T3 any] interface { + Zip(q2 QueryG[T2], + resultSelector func(T1, T2) T3) QueryG[T3] +} + +type expender[T1, T2 any] struct { + q QueryG[T1] +} + +func (e *expender[T1, T2]) Expend(q QueryG[T1]) any { + e.q = q + return e +} + +func Expend[T1, T2 any]() Expender[T1] { + return &expender[T1, T2]{} +} + +type expender3[T1, T2, T3 any] struct { + q QueryG[T1] +} + +func (e *expender3[T1, T2, T3]) Expend(q QueryG[T1]) any { + e.q = q + return e +} + +func Expend3[T1, T2, T3 any]() Expender[T1] { + return &expender3[T1, T2, T3]{} +} diff --git a/select.go b/select.go index dd077dd..4270f5d 100644 --- a/select.go +++ b/select.go @@ -50,28 +50,6 @@ func (q Query) SelectT(selectorFn interface{}) Query { return q.Select(selectorFunc) } -func (q QueryG[T]) Select(m Mapper[T]) interface{} { - return m.Map(q) -} - -type Mapper[T any] interface { - Map(QueryG[T]) interface{} -} - -type mapper[TIn, TOut any] struct { - selector func(TIn) TOut -} - -func (m mapper[TIn, TOut]) Map(q QueryG[TIn]) interface{} { - return Select(q, m.selector) -} - -func Map[TIn, TOut any](selector func(TIn) TOut) Mapper[TIn] { - return mapper[TIn, TOut]{ - selector: selector, - } -} - func Select[TIn, TOut any](q QueryG[TIn], selector func(TIn) TOut) QueryG[TOut] { o := QueryG[TOut]{ Iterate: func() IteratorG[TOut] { @@ -130,53 +108,31 @@ func (q Query) SelectIndexed(selector func(int, interface{}) interface{}) Query } } -// SelectIndexedT is the typed version of SelectIndexed. -// - selectorFn is of type "func(int,TSource)TResult" -// NOTE: SelectIndexed has better performance than SelectIndexedT. -func (q Query) SelectIndexedT(selectorFn interface{}) Query { - selectGenericFunc, err := newGenericFunc( - "SelectIndexedT", "selectorFn", selectorFn, - simpleParamValidator(newElemTypeSlice(new(int), new(genericType)), newElemTypeSlice(new(genericType))), - ) - if err != nil { - panic(err) - } - - selectorFunc := func(index int, item interface{}) interface{} { - return selectGenericFunc.Call(index, item) - } - - return q.SelectIndexed(selectorFunc) -} - -type MapperWithIndex[T any] interface { - Map(QueryG[T]) interface{} -} - -type mapperWithIndex[TIn, TOut any] struct { - selector func(int, TIn) TOut -} - -func (m mapperWithIndex[TIn, TOut]) Map(q QueryG[TIn]) interface{} { - return SelectIndexedG(q, m.selector) -} - -func MapWithIndex[TIn, TOut any](selector func(int, TIn) TOut) Mapper[TIn] { - return mapperWithIndex[TIn, TOut]{ - selector: selector, +func (e *expender[TIn, TOut]) Select(selector func(TIn) TOut) QueryG[TOut] { + o := QueryG[TOut]{ + Iterate: func() IteratorG[TOut] { + next := e.q.Iterate() + return func() (outItem TOut, ok bool) { + item, hasNext := next() + if hasNext { + outItem = selector(item) + ok = true + return + } + ok = false + return + } + }, } + return o } -func (q QueryG[T]) SelectIndexed(m MapperWithIndex[T]) interface{} { - return m.Map(q) -} - -func SelectIndexedG[TIn, TOut any](q QueryG[TIn], selector func(int, TIn) TOut) QueryG[TOut] { - o := QueryG[TOut]{ - Iterate: func() IteratorG[TOut] { - next := q.Iterate() +func (e *expender[T1, T2]) SelectIndexed(selector func(int, T1) T2) QueryG[T2] { + o := QueryG[T2]{ + Iterate: func() IteratorG[T2] { + next := e.q.Iterate() index := 0 - return func() (outItem TOut, ok bool) { + return func() (outItem T2, ok bool) { item, hasNext := next() if hasNext { outItem = selector(index, item) @@ -191,3 +147,22 @@ func SelectIndexedG[TIn, TOut any](q QueryG[TIn], selector func(int, TIn) TOut) } return o } + +// SelectIndexedT is the typed version of SelectIndexed. +// - selectorFn is of type "func(int,TSource)TResult" +// NOTE: SelectIndexed has better performance than SelectIndexedT. +func (q Query) SelectIndexedT(selectorFn interface{}) Query { + selectGenericFunc, err := newGenericFunc( + "SelectIndexedT", "selectorFn", selectorFn, + simpleParamValidator(newElemTypeSlice(new(int), new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + selectorFunc := func(index int, item interface{}) interface{} { + return selectGenericFunc.Call(index, item) + } + + return q.SelectIndexed(selectorFunc) +} diff --git a/select_test.go b/select_test.go index 5a13452..9c48eb0 100644 --- a/select_test.go +++ b/select_test.go @@ -39,19 +39,19 @@ func TestSelectGFunc(t *testing.T) { func TestSelectG(t *testing.T) { input := []int{1, 2, 3} expected := []string{"1", "2", "3"} - stringSlice := FromSliceG(input).Select(Map[int, string](func(i int) string { + stringSlice := FromSliceG(input).Expend(Expend[int, string]()).(Expended[int, string]).Select(func(i int) string { return strconv.Itoa(i) - })).(QueryG[string]).ToSlice() + }).ToSlice() assert.Equal(t, expected, stringSlice) } func TestSelectIndexedG(t *testing.T) { input := []int{0, 1, 2} expected := []string{"0", "1", "2"} - stringSlice := FromSliceG(input).SelectIndexed(MapWithIndex[int, string](func(index, i int) string { + stringSlice := FromSliceG(input).Expend(Expend[int, string]()).(Expended[int, string]).SelectIndexed(func(index, i int) string { assert.Equal(t, index, i) return strconv.Itoa(i) - })).(QueryG[string]).ToSlice() + }).ToSlice() assert.Equal(t, expected, stringSlice) } diff --git a/zip.go b/zip.go index 839487c..d8fffbd 100644 --- a/zip.go +++ b/zip.go @@ -32,53 +32,6 @@ func (q Query) Zip(q2 Query, } } -func (q QueryG[T]) Zip(selector Zipper[T]) interface{} { - return selector.Map(q) -} - -type Zipper[TIn1 any] interface { - Map(q QueryG[TIn1]) interface{} -} - -func ZipWith[TIn1, TIn2, TOut any](q2 QueryG[TIn2], resultSelector func(TIn1, TIn2) TOut) Zipper[TIn1] { - return zipSelector[TIn1, TIn2, TOut]{ - resultSelector: resultSelector, - q2: q2, - } -} - -type zipSelector[TIn1, TIn2, TOut any] struct { - resultSelector func(TIn1, TIn2) TOut - q2 QueryG[TIn2] -} - -var z Zipper[int] = zipSelector[int, int, int]{} - -func (z zipSelector[TIn1, TIn2, TOut]) Map(q QueryG[TIn1]) interface{} { - return ZipG(q, z.q2, z.resultSelector) -} - -func ZipG[TIn1, TIn2, TOut any](q QueryG[TIn1], q2 QueryG[TIn2], - resultSelector func(TIn1, TIn2) TOut) QueryG[TOut] { - return QueryG[TOut]{ - Iterate: func() IteratorG[TOut] { - next1 := q.Iterate() - next2 := q2.Iterate() - - return func() (item TOut, ok bool) { - item1, ok1 := next1() - item2, ok2 := next2() - - if ok1 && ok2 { - return resultSelector(item1, item2), true - } - - return *new(TOut), false - } - }, - } -} - // ZipT is the typed version of Zip. // // - resultSelectorFn is of type "func(TFirst,TSecond)TResult" @@ -100,3 +53,23 @@ func (q Query) ZipT(q2 Query, return q.Zip(q2, resultSelectorFunc) } + +func (e *expender3[T1, T2, T3]) Zip(q2 QueryG[T2], resultSelector func(T1, T2) T3) QueryG[T3] { + return QueryG[T3]{ + Iterate: func() IteratorG[T3] { + next1 := e.q.Iterate() + next2 := q2.Iterate() + + return func() (item T3, ok bool) { + item1, ok1 := next1() + item2, ok2 := next2() + + if ok1 && ok2 { + return resultSelector(item1, item2), true + } + + return *new(T3), false + } + }, + } +} diff --git a/zip_test.go b/zip_test.go index 29c564f..8008d45 100644 --- a/zip_test.go +++ b/zip_test.go @@ -23,9 +23,9 @@ func TestZipG(t *testing.T) { input2 := []int{2, 4, 5, 1} want := []string{"3", "6", "8"} - slice := FromSliceG(input1).Zip(ZipWith(FromSliceG(input2), func(i1, i2 int) string { + slice := FromSliceG(input1).Expend(Expend3[int, int, string]()).(Expended3[int, int, string]).Zip(FromSliceG(input2), func(i1, i2 int) string { return strconv.Itoa(i1 + i2) - })).(QueryG[string]).ToSlice() + }).ToSlice() assert.Equal(t, want, slice) } From 5084caf2ca05dbc16802b81f446bdaef8195344f Mon Sep 17 00:00:00 2001 From: zjhe Date: Sun, 7 Aug 2022 10:53:12 +0800 Subject: [PATCH 40/56] Add SelectMany methods --- example_test.go | 102 ++++++++++++++++++++++++++++++++ expender.go | 10 +++- select_test.go | 4 +- selectmany.go | 141 +++++++++++++++++++++++++++++++++++++++++++++ selectmany_test.go | 57 ++++++++++++++++++ zip_test.go | 2 +- 6 files changed, 311 insertions(+), 5 deletions(-) diff --git a/example_test.go b/example_test.go index 41e6c00..e2071d0 100644 --- a/example_test.go +++ b/example_test.go @@ -2,6 +2,7 @@ package linq import ( "fmt" + "log" "strings" "time" ) @@ -2242,6 +2243,51 @@ func ExampleQuery_SelectManyByT() { // Owner: Weiss, Charlotte, Pet: Whiskers } +// The following code example demonstrates how to use SelectManyT +// to perform a one-to-many projection over a slice +func ExampleQuery_SelectManyByG() { + + type Pet struct { + Name string + } + + type Person struct { + Name string + Pets []Pet + } + + magnus := Person{ + Name: "Hedlund, Magnus", + Pets: []Pet{{Name: "Daisy"}}, + } + + terry := Person{ + Name: "Adams, Terry", + Pets: []Pet{{Name: "Barley"}, {Name: "Boots"}}, + } + charlotte := Person{ + Name: "Weiss, Charlotte", + Pets: []Pet{{Name: "Whiskers"}}, + } + + people := []Person{magnus, terry, charlotte} + results := FromSliceG(people).Expend(To3[Person, Pet, string]()).(Expended3[Person, Pet, string]). + SelectManyBy(func(person Person) QueryG[Pet] { + return FromSliceG(person.Pets) + }, func(pet Pet, person Person) string { + return fmt.Sprintf("Owner: %s, Pet: %s", person.Name, pet.Name) + }).ToSlice() + + for _, result := range results { + fmt.Println(result) + } + // Output: + // Owner: Hedlund, Magnus, Pet: Daisy + // Owner: Adams, Terry, Pet: Barley + // Owner: Adams, Terry, Pet: Boots + // Owner: Weiss, Charlotte, Pet: Whiskers +} + // The following code example demonstrates how to use SelectManyT // to perform a projection over a list of sentences and rank the // top 5 most used words @@ -2350,6 +2396,62 @@ func ExampleQuery_SelectManyIndexedT() { } +// The following code example demonstrates how to use SelectManyIndexedT +// to perform a one-to-many projection over an slice of log files and +// print out their contents. +func ExampleQuery_SelectManyIndexedG() { + type LogFile struct { + Name string + Lines []string + } + + file1 := LogFile{ + Name: "file1.log", + Lines: []string{ + "INFO: 2013/11/05 18:11:01 main.go:44: Special Information", + "WARNING: 2013/11/05 18:11:01 main.go:45: There is something you need to know about", + "ERROR: 2013/11/05 18:11:01 main.go:46: Something has failed", + }, + } + + file2 := LogFile{ + Name: "file2.log", + Lines: []string{ + "INFO: 2013/11/05 18:11:01 main.go:46: Everything is ok", + }, + } + + file3 := LogFile{ + Name: "file3.log", + Lines: []string{ + "2013/11/05 18:42:26 Hello World", + }, + } + + logFiles := []LogFile{file1, file2, file3} + var results []string + + results = FromSliceG(logFiles).Expend(To2[LogFile, string]()).(Expended[LogFile, string]). + SelectManyIndexed(func(fileIndex int, file LogFile) QueryG[string] { + return FromSliceG(file.Lines).Expend(To2[string, string]()).(Expended[string, string]).SelectIndexed( + func(lineIndex int, line string) string { + return fmt.Sprintf("File:[%d] - %s => line: %d - %s", fileIndex+1, file.Name, lineIndex+1, line) + }) + }).ToSlice() + + for _, result := range results { + log.Print(result) + fmt.Println(result) + } + // Output: + // File:[1] - file1.log => line: 1 - INFO: 2013/11/05 18:11:01 main.go:44: Special Information + // File:[1] - file1.log => line: 2 - WARNING: 2013/11/05 18:11:01 main.go:45: There is something you need to know about + // File:[1] - file1.log => line: 3 - ERROR: 2013/11/05 18:11:01 main.go:46: Something has failed + // File:[2] - file2.log => line: 1 - INFO: 2013/11/05 18:11:01 main.go:46: Everything is ok + // File:[3] - file3.log => line: 1 - 2013/11/05 18:42:26 Hello World + +} + // The following code example demonstrates how to use SelectManyByIndexedT // to perform a one-to-many projection over an array and use the index of // each outer element. diff --git a/expender.go b/expender.go index a09063b..8ce8bdd 100644 --- a/expender.go +++ b/expender.go @@ -16,11 +16,17 @@ type Expender[T any] interface { type Expended[T1, T2 any] interface { Select(selector func(T1) T2) QueryG[T2] SelectIndexed(selector func(int, T1) T2) QueryG[T2] + SelectMany(selector func(T1) QueryG[T2]) QueryG[T2] + SelectManyIndexed(selector func(int, T1) QueryG[T2]) QueryG[T2] } type Expended3[T1, T2, T3 any] interface { Zip(q2 QueryG[T2], resultSelector func(T1, T2) T3) QueryG[T3] + SelectManyBy(selector func(T1) QueryG[T2], + resultSelector func(T2, T1) T3) QueryG[T3] + SelectManyByIndexed(selector func(int, T1) QueryG[T2], + resultSelector func(T2, T1) T3) QueryG[T3] } type expender[T1, T2 any] struct { @@ -32,7 +38,7 @@ func (e *expender[T1, T2]) Expend(q QueryG[T1]) any { return e } -func Expend[T1, T2 any]() Expender[T1] { +func To2[T1, T2 any]() Expender[T1] { return &expender[T1, T2]{} } @@ -45,6 +51,6 @@ func (e *expender3[T1, T2, T3]) Expend(q QueryG[T1]) any { return e } -func Expend3[T1, T2, T3 any]() Expender[T1] { +func To3[T1, T2, T3 any]() Expender[T1] { return &expender3[T1, T2, T3]{} } diff --git a/select_test.go b/select_test.go index 9c48eb0..9cc8e51 100644 --- a/select_test.go +++ b/select_test.go @@ -39,7 +39,7 @@ func TestSelectGFunc(t *testing.T) { func TestSelectG(t *testing.T) { input := []int{1, 2, 3} expected := []string{"1", "2", "3"} - stringSlice := FromSliceG(input).Expend(Expend[int, string]()).(Expended[int, string]).Select(func(i int) string { + stringSlice := FromSliceG(input).Expend(To2[int, string]()).(Expended[int, string]).Select(func(i int) string { return strconv.Itoa(i) }).ToSlice() assert.Equal(t, expected, stringSlice) @@ -48,7 +48,7 @@ func TestSelectG(t *testing.T) { func TestSelectIndexedG(t *testing.T) { input := []int{0, 1, 2} expected := []string{"0", "1", "2"} - stringSlice := FromSliceG(input).Expend(Expend[int, string]()).(Expended[int, string]).SelectIndexed(func(index, i int) string { + stringSlice := FromSliceG(input).Expend(To2[int, string]()).(Expended[int, string]).SelectIndexed(func(index, i int) string { assert.Equal(t, index, i) return strconv.Itoa(i) }).ToSlice() diff --git a/selectmany.go b/selectmany.go index 74fa671..ebe9aea 100644 --- a/selectmany.go +++ b/selectmany.go @@ -1,5 +1,7 @@ package linq +import "reflect" + // SelectMany projects each element of a collection to a Query, iterates and // flattens the resulting collection into one collection. func (q Query) SelectMany(selector func(interface{}) Query) Query { @@ -32,6 +34,36 @@ func (q Query) SelectMany(selector func(interface{}) Query) Query { } } +func (e *expender[T1, T2]) SelectMany(selector func(T1) QueryG[T2]) QueryG[T2] { + return QueryG[T2]{ + Iterate: func() IteratorG[T2] { + outernext := e.q.Iterate() + var inner T1 + var innernext IteratorG[T2] + + return func() (item T2, ok bool) { + for !ok { + if reflect.DeepEqual(inner, *new(T1)) { + inner, ok = outernext() + if !ok { + return + } + + innernext = selector(inner).Iterate() + } + + item, ok = innernext() + if !ok { + inner = *new(T1) + } + } + + return + } + }, + } +} + // SelectManyT is the typed version of SelectMany. // // - selectorFn is of type "func(TSource)Query" @@ -95,6 +127,47 @@ func (q Query) SelectManyIndexed(selector func(int, interface{}) Query) Query { } } +// SelectManyIndexed projects each element of a collection to a Query, iterates +// and flattens the resulting collection into one collection. +// +// The first argument to selector represents the zero-based index of that +// element in the source collection. This can be useful if the elements are in a +// known order and you want to do something with an element at a particular +// index, for example. It can also be useful if you want to retrieve the index +// of one or more elements. The second argument to selector represents the +// element to process. +func (e *expender[T1, T2]) SelectManyIndexed(selector func(int, T1) QueryG[T2]) QueryG[T2] { + return QueryG[T2]{ + Iterate: func() IteratorG[T2] { + outernext := e.q.Iterate() + index := 0 + var inner T1 + var innernext IteratorG[T2] + + return func() (item T2, ok bool) { + for !ok { + if reflect.DeepEqual(inner, *new(T1)) { + inner, ok = outernext() + if !ok { + return + } + + innernext = selector(index, inner).Iterate() + index++ + } + + item, ok = innernext() + if !ok { + inner = *new(T1) + } + } + + return + } + }, + } +} + // SelectManyIndexedT is the typed version of SelectManyIndexed. // // - selectorFn is of type "func(int,TSource)Query" @@ -153,6 +226,39 @@ func (q Query) SelectManyBy(selector func(interface{}) Query, } } +func (e *expender3[T1, T2, T3]) SelectManyBy(selector func(T1) QueryG[T2], + resultSelector func(T2, T1) T3) QueryG[T3] { + return QueryG[T3]{ + Iterate: func() IteratorG[T3] { + outernext := e.q.Iterate() + var outer T1 + var innernext IteratorG[T2] + + return func() (item T3, ok bool) { + var product T2 + for !ok { + if reflect.DeepEqual(outer, *new(T1)) { + outer, ok = outernext() + if !ok { + return + } + + innernext = selector(outer).Iterate() + } + + product, ok = innernext() + if !ok { + outer = *new(T1) + } + } + + item = resultSelector(product, outer) + return + } + }, + } +} + // SelectManyByT is the typed version of SelectManyBy. // // - selectorFn is of type "func(TSource)Query" @@ -228,6 +334,41 @@ func (q Query) SelectManyByIndexed(selector func(int, interface{}) Query, } } +func (e *expender3[T1, T2, T3]) SelectManyByIndexed(selector func(int, T1) QueryG[T2], + resultSelector func(T2, T1) T3) QueryG[T3] { + return QueryG[T3]{ + Iterate: func() IteratorG[T3] { + outernext := e.q.Iterate() + index := 0 + var outer T1 + var innernext IteratorG[T2] + + return func() (item T3, ok bool) { + var product T2 + for !ok { + if reflect.DeepEqual(outer, *new(T1)) { + outer, ok = outernext() + if !ok { + return + } + + innernext = selector(index, outer).Iterate() + index++ + } + + product, ok = innernext() + if !ok { + outer = *new(T1) + } + } + + item = resultSelector(product, outer) + return + } + }, + } +} + // SelectManyByIndexedT is the typed version of SelectManyByIndexed. // // - selectorFn is of type "func(int,TSource)Query" diff --git a/selectmany_test.go b/selectmany_test.go index 9a2548c..ce0e9b7 100644 --- a/selectmany_test.go +++ b/selectmany_test.go @@ -1,6 +1,7 @@ package linq import ( + "github.com/stretchr/testify/assert" "strconv" "testing" ) @@ -26,6 +27,15 @@ func TestSelectMany(t *testing.T) { } } +func TestSelectManyG(t *testing.T) { + assert.Equal(t, []int{1, 2, 3, 4, 5, 6, 7}, FromSliceG([][]int{{1, 2, 3}, {4, 5, 6, 7}}).Expend(To2[[]int, int]()).(Expended[[]int, int]).SelectMany(func(s []int) QueryG[int] { + return FromSliceG(s) + }).ToSlice()) + assert.Equal(t, []rune{'s', 't', 'r', 'i', 'n', 'g'}, FromSliceG([]string{"str", "ing"}).Expend(To2[string, rune]()).(Expended[string, rune]).SelectMany(func(s string) QueryG[rune] { + return FromStringG(s) + }).ToSlice()) +} + func TestSelectManyT_PanicWhenSelectorFnIsInvalid(t *testing.T) { mustPanicWithError(t, "SelectManyT: parameter [selectorFn] has a invalid function signature. Expected: 'func(T)linq.Query', actual: 'func(int)int'", func() { From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).SelectManyT(func(item int) int { return item + 2 }) @@ -56,6 +66,18 @@ func TestSelectManyIndexed(t *testing.T) { } } +func TestSelectManyIndexedG(t *testing.T) { + assert.Equal(t, []int{1, 2, 3, 5, 6, 7}, FromSliceG([][]int{{1, 2, 3}, {4, 5, 6, 7}}).Expend(To2[[]int, int]()).(Expended[[]int, int]).SelectManyIndexed(func(i int, s []int) QueryG[int] { + if i > 0 { + return FromSliceG(s[1:]) + } + return FromSliceG(s) + }).ToSlice()) + assert.Equal(t, []rune{'s', 't', 'r', '0', 'i', 'n', 'g', '1'}, FromSliceG([]string{"str", "ing"}).Expend(To2[string, rune]()).(Expended[string, rune]).SelectManyIndexed(func(i int, s string) QueryG[rune] { + return FromStringG(s + strconv.Itoa(i)) + }).ToSlice()) +} + func TestSelectManyIndexedT_PanicWhenSelectorFnIsInvalid(t *testing.T) { mustPanicWithError(t, "SelectManyIndexedT: parameter [selectorFn] has a invalid function signature. Expected: 'func(int,T)linq.Query', actual: 'func(int)int'", func() { From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).SelectManyIndexedT(func(item int) int { return item + 2 }) @@ -88,6 +110,19 @@ func TestSelectManyBy(t *testing.T) { } } +func TestSelectManyByG(t *testing.T) { + assert.Equal(t, []int{2, 3, 4, 5, 6, 7, 8}, FromSliceG([][]int{{1, 2, 3}, {4, 5, 6, 7}}).Expend(To3[[]int, int, int]()).(Expended3[[]int, int, int]).SelectManyBy(func(s []int) QueryG[int] { + return FromSliceG(s) + }, func(i int, _ []int) int { + return i + 1 + }).ToSlice()) + assert.Equal(t, []string{"s_", "t_", "r_", "i_", "n_", "g_"}, FromSliceG([]string{"str", "ing"}).Expend(To3[string, rune, string]()).(Expended3[string, rune, string]).SelectManyBy(func(s string) QueryG[rune] { + return FromStringG(s) + }, func(x rune, _ string) string { + return string(x) + "_" + }).ToSlice()) +} + func TestSelectManyByT_PanicWhenSelectorFnIsInvalid(t *testing.T) { mustPanicWithError(t, "SelectManyByT: parameter [selectorFn] has a invalid function signature. Expected: 'func(T)linq.Query', actual: 'func(int)interface {}'", func() { From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).SelectManyByT(func(item int) interface{} { return item + 2 }, 2) @@ -135,6 +170,28 @@ func TestSelectManyIndexedBy(t *testing.T) { } } +func TestSelectManyIndexedByG(t *testing.T) { + assert.Equal(t, []int{11, 21, 31, 5, 6, 7, 8}, FromSliceG([][]int{{1, 2, 3}, {4, 5, 6, 7}}).Expend(To3[[]int, int, int]()).(Expended3[[]int, int, int]).SelectManyByIndexed( + func(i int, x []int) QueryG[int] { + if i == 0 { + return FromSliceG([]int{10, 20, 30}) + } + return FromSliceG(x) + }, func(x int, _ []int) int { + return x + 1 + }).ToSlice()) + assert.Equal(t, []string{"s_", "t_", "r_", "i_", "n_", "g_"}, + FromSliceG([]string{"st", "ng"}).Expend(To3[string, rune, string]()).(Expended3[string, rune, string]).SelectManyByIndexed( + func(i int, x string) QueryG[rune] { + if i == 0 { + return FromStringG(x + "r") + } + return FromStringG("i" + x) + }, func(x rune, _ string) string { + return string(x) + "_" + }).ToSlice()) +} + func TestSelectManyIndexedByT_PanicWhenSelectorFnIsInvalid(t *testing.T) { mustPanicWithError(t, "SelectManyByIndexedT: parameter [selectorFn] has a invalid function signature. Expected: 'func(int,T)linq.Query', actual: 'func(int)interface {}'", func() { From([][]int{{1, 1, 1, 2}, {1, 2, 3, 4, 2}}).SelectManyByIndexedT( diff --git a/zip_test.go b/zip_test.go index 8008d45..62485b9 100644 --- a/zip_test.go +++ b/zip_test.go @@ -23,7 +23,7 @@ func TestZipG(t *testing.T) { input2 := []int{2, 4, 5, 1} want := []string{"3", "6", "8"} - slice := FromSliceG(input1).Expend(Expend3[int, int, string]()).(Expended3[int, int, string]).Zip(FromSliceG(input2), func(i1, i2 int) string { + slice := FromSliceG(input1).Expend(To3[int, int, string]()).(Expended3[int, int, string]).Zip(FromSliceG(input2), func(i1, i2 int) string { return strconv.Itoa(i1 + i2) }).ToSlice() assert.Equal(t, want, slice) From d9da2de034481c20e4827f852e8634a601d5b8a7 Mon Sep 17 00:00:00 2001 From: zjhe Date: Sun, 7 Aug 2022 11:10:44 +0800 Subject: [PATCH 41/56] Add SkipG methods --- result.go | 2 +- skip.go | 23 +++++++++++++++++++++++ skip_test.go | 12 +++++++++++- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/result.go b/result.go index 3ff7d44..6be666f 100644 --- a/result.go +++ b/result.go @@ -910,7 +910,7 @@ func (q Query) ToSlice(v interface{}) { } func (q QueryG[T]) ToSlice() []T { - var r []T + var r = make([]T, 0) next := q.Iterate() for item, ok := next(); ok; item, ok = next() { r = append(r, item) diff --git a/skip.go b/skip.go index 0fb05d3..1d6d08f 100644 --- a/skip.go +++ b/skip.go @@ -22,6 +22,29 @@ func (q Query) Skip(count int) Query { } } +// Skip bypasses a specified number of elements in a collection and then returns +// the remaining elements. +func (q QueryG[T]) Skip(count int) QueryG[T] { + return QueryG[T]{ + Iterate: func() IteratorG[T] { + next := q.Iterate() + n := count + + return func() (item T, ok bool) { + for ; n > 0; n-- { + item, ok = next() + if !ok { + return + } + } + + item, ok = next() + return + } + }, + } +} + // SkipWhile bypasses elements in a collection as long as a specified condition // is true and then returns the remaining elements. // diff --git a/skip_test.go b/skip_test.go index 2dd68c6..82858ea 100644 --- a/skip_test.go +++ b/skip_test.go @@ -1,6 +1,9 @@ package linq -import "testing" +import ( + "github.com/stretchr/testify/assert" + "testing" +) func TestSkip(t *testing.T) { tests := []struct { @@ -20,6 +23,13 @@ func TestSkip(t *testing.T) { } } +func TestSkipG(t *testing.T) { + assert.Equal(t, []int{}, FromSliceG([]int{1, 2}).Skip(3).ToSlice()) + assert.Equal(t, []int{3, 1}, FromSliceG([]int{1, 2, 2, 3, 1}).Skip(3).ToSlice()) + assert.Equal(t, []int{2, 1, 2, 3, 4, 2}, FromSliceG([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).Skip(3).ToSlice()) + assert.Equal(t, []rune{'r'}, FromStringG("sstr").Skip(3).ToSlice()) +} + func TestSkipWhile(t *testing.T) { tests := []struct { input interface{} From 73b405646bfdd7efa9922d4ef5c4b35f16eac72a Mon Sep 17 00:00:00 2001 From: zjhe Date: Sun, 7 Aug 2022 12:37:54 +0800 Subject: [PATCH 42/56] Add SkipWhileG methods --- skip.go | 32 ++++++++++++++++++++++++++++++++ skip_test.go | 17 ++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/skip.go b/skip.go index 1d6d08f..2cb085a 100644 --- a/skip.go +++ b/skip.go @@ -77,6 +77,38 @@ func (q Query) SkipWhile(predicate func(interface{}) bool) Query { } } +// SkipWhile bypasses elements in a collection as long as a specified condition +// is true and then returns the remaining elements. +// +// This method tests each element by using predicate and skips the element if +// the result is true. After the predicate function returns false for an +// element, that element and the remaining elements in source are returned and +// there are no more invocations of predicate. +func (q QueryG[T]) SkipWhile(predicate func(T) bool) QueryG[T] { + return QueryG[T]{ + Iterate: func() IteratorG[T] { + next := q.Iterate() + ready := false + + return func() (item T, ok bool) { + for !ready { + item, ok = next() + if !ok { + return + } + + ready = !predicate(item) + if ready { + return + } + } + + return next() + } + }, + } +} + // SkipWhileT is the typed version of SkipWhile. // // - predicateFn is of type "func(TSource)bool" diff --git a/skip_test.go b/skip_test.go index 82858ea..807e4fc 100644 --- a/skip_test.go +++ b/skip_test.go @@ -24,7 +24,7 @@ func TestSkip(t *testing.T) { } func TestSkipG(t *testing.T) { - assert.Equal(t, []int{}, FromSliceG([]int{1, 2}).Skip(3).ToSlice()) + assert.Empty(t, FromSliceG([]int{1, 2}).Skip(3).ToSlice()) assert.Equal(t, []int{3, 1}, FromSliceG([]int{1, 2, 2, 3, 1}).Skip(3).ToSlice()) assert.Equal(t, []int{2, 1, 2, 3, 4, 2}, FromSliceG([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).Skip(3).ToSlice()) assert.Equal(t, []rune{'r'}, FromStringG("sstr").Skip(3).ToSlice()) @@ -57,6 +57,21 @@ func TestSkipWhile(t *testing.T) { } } +func TestSkipWhileG(t *testing.T) { + assert.Empty(t, FromSliceG([]int{1, 2}).SkipWhile(func(i int) bool { + return i < 3 + }).ToSlice()) + assert.Equal(t, []int{4, 1, 2}, FromSliceG([]int{4, 1, 2}).SkipWhile(func(i int) bool { + return i < 3 + }).ToSlice()) + assert.Equal(t, []int{3, 4, 2}, FromSliceG([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).SkipWhile(func(i int) bool { + return i < 3 + }).ToSlice()) + assert.Equal(t, []rune{'t', 'r'}, FromStringG("sstr").SkipWhile(func(i rune) bool { + return i == 's' + }).ToSlice()) +} + func TestSkipWhileT_PanicWhenPredicateFnIsInvalid(t *testing.T) { mustPanicWithError(t, "SkipWhileT: parameter [predicateFn] has a invalid function signature. Expected: 'func(T)bool', actual: 'func(int,int)bool'", func() { From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).SkipWhileT(func(item int, x int) bool { return item == 1 }) From 87f11f90add5a8095db1be7f749c9847b1730be7 Mon Sep 17 00:00:00 2001 From: zjhe Date: Sun, 7 Aug 2022 14:04:15 +0800 Subject: [PATCH 43/56] Add SkipWhileIndexedG methods --- skip.go | 36 ++++++++++++++++++++++++++++++++++++ skip_test.go | 15 +++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/skip.go b/skip.go index 2cb085a..a09e674 100644 --- a/skip.go +++ b/skip.go @@ -167,6 +167,42 @@ func (q Query) SkipWhileIndexed(predicate func(int, interface{}) bool) Query { } } +// SkipWhileIndexed bypasses elements in a collection as long as a specified +// condition is true and then returns the remaining elements. The element's +// index is used in the logic of the predicate function. +// +// This method tests each element by using predicate and skips the element if +// the result is true. After the predicate function returns false for an +// element, that element and the remaining elements in source are returned and +// there are no more invocations of predicate. +func (q QueryG[T]) SkipWhileIndexed(predicate func(int, T) bool) QueryG[T] { + return QueryG[T]{ + Iterate: func() IteratorG[T] { + next := q.Iterate() + ready := false + index := 0 + + return func() (item T, ok bool) { + for !ready { + item, ok = next() + if !ok { + return + } + + ready = !predicate(index, item) + if ready { + return + } + + index++ + } + + return next() + } + }, + } +} + // SkipWhileIndexedT is the typed version of SkipWhileIndexed. // // - predicateFn is of type "func(int,TSource)bool" diff --git a/skip_test.go b/skip_test.go index 807e4fc..b1715a6 100644 --- a/skip_test.go +++ b/skip_test.go @@ -105,6 +105,21 @@ func TestSkipWhileIndexed(t *testing.T) { } } +func TestSkipWhileIndexedG(t *testing.T) { + assert.Empty(t, FromSliceG([]int{1, 2}).SkipWhileIndexed(func(i int, x int) bool { + return x < 3 + }).ToSlice()) + assert.Equal(t, []int{4, 1, 2}, FromSliceG([]int{4, 1, 2}).SkipWhileIndexed(func(i int, x int) bool { + return x < 3 + }).ToSlice()) + assert.Equal(t, []int{2, 3, 4, 2}, FromSliceG([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).SkipWhileIndexed(func(i int, x int) bool { + return x < 2 || i < 5 + }).ToSlice()) + assert.Equal(t, []rune{'s', 't', 'r'}, FromStringG("sstr").SkipWhileIndexed(func(i int, x rune) bool { + return x == 's' && i < 1 + }).ToSlice()) +} + func TestSkipWhileIndexedT_PanicWhenPredicateFnIsInvalid(t *testing.T) { mustPanicWithError(t, "SkipWhileIndexedT: parameter [predicateFn] has a invalid function signature. Expected: 'func(int,T)bool', actual: 'func(int,int,int)bool'", func() { From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).SkipWhileIndexedT(func(item int, x int, y int) bool { return item == 1 }) From 289fae24046d5bb800f3958ee9a166ee322a6f93 Mon Sep 17 00:00:00 2001 From: zjhe Date: Sun, 7 Aug 2022 14:17:46 +0800 Subject: [PATCH 44/56] Add TakeG methods --- take.go | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++ take_test.go | 35 +++++++++++++++++++++- 2 files changed, 119 insertions(+), 1 deletion(-) diff --git a/take.go b/take.go index c2707e5..0eae5e6 100644 --- a/take.go +++ b/take.go @@ -20,6 +20,26 @@ func (q Query) Take(count int) Query { } } +// Take returns a specified number of contiguous elements from the start of a +// collection. +func (q QueryG[T]) Take(count int) QueryG[T] { + return QueryG[T]{ + Iterate: func() IteratorG[T] { + next := q.Iterate() + n := count + + return func() (item T, ok bool) { + if n <= 0 { + return + } + + n-- + return next() + } + }, + } +} + // TakeWhile returns elements from a collection as long as a specified condition // is true, and then skips the remaining elements. func (q Query) TakeWhile(predicate func(interface{}) bool) Query { @@ -50,6 +70,36 @@ func (q Query) TakeWhile(predicate func(interface{}) bool) Query { } } +// TakeWhile returns elements from a collection as long as a specified condition +// is true, and then skips the remaining elements. +func (q QueryG[T]) TakeWhile(predicate func(T) bool) QueryG[T] { + return QueryG[T]{ + Iterate: func() IteratorG[T] { + next := q.Iterate() + done := false + + return func() (item T, ok bool) { + if done { + return + } + + item, ok = next() + if !ok { + done = true + return + } + + if predicate(item) { + return + } + + done = true + return *new(T), false + } + }, + } +} + // TakeWhileT is the typed version of TakeWhile. // // - predicateFn is of type "func(TSource)bool" @@ -107,6 +157,41 @@ func (q Query) TakeWhileIndexed(predicate func(int, interface{}) bool) Query { } } +// TakeWhileIndexed returns elements from a collection as long as a specified +// condition is true. The element's index is used in the logic of the predicate +// function. The first argument of predicate represents the zero-based index of +// the element within collection. The second argument represents the element to +// test. +func (q QueryG[T]) TakeWhileIndexed(predicate func(int, T) bool) QueryG[T] { + return QueryG[T]{ + Iterate: func() IteratorG[T] { + next := q.Iterate() + done := false + index := 0 + + return func() (item T, ok bool) { + if done { + return + } + + item, ok = next() + if !ok { + done = true + return + } + + if predicate(index, item) { + index++ + return + } + + done = true + return *new(T), false + } + }, + } +} + // TakeWhileIndexedT is the typed version of TakeWhileIndexed. // // - predicateFn is of type "func(int,TSource)bool" diff --git a/take_test.go b/take_test.go index ca4bc63..b4dccc0 100644 --- a/take_test.go +++ b/take_test.go @@ -1,6 +1,9 @@ package linq -import "testing" +import ( + "github.com/stretchr/testify/assert" + "testing" +) func TestTake(t *testing.T) { tests := []struct { @@ -19,6 +22,12 @@ func TestTake(t *testing.T) { } } +func TestTakeG(t *testing.T) { + assert.Equal(t, []int{1, 2, 2}, FromSliceG([]int{1, 2, 2, 3, 1}).Take(3).ToSlice()) + assert.Equal(t, []int{1, 1, 1}, FromSliceG([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).Take(3).ToSlice()) + assert.Equal(t, []rune{'s', 's', 't'}, FromStringG("sstr").Take(3).ToSlice()) +} + func TestTakeWhile(t *testing.T) { tests := []struct { input interface{} @@ -43,6 +52,18 @@ func TestTakeWhile(t *testing.T) { } } +func TestTakeWhileG(t *testing.T) { + assert.Equal(t, []int{1, 1, 1, 2, 1, 2}, FromSliceG([]int{1, 1, 1, 2, 1, 2}).TakeWhile(func(i int) bool { + return i < 3 + }).ToSlice()) + assert.Equal(t, []int{1, 1, 1, 2, 1, 2}, FromSliceG([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).TakeWhile(func(i int) bool { + return i < 3 + }).ToSlice()) + assert.Equal(t, []rune{'s', 's'}, FromStringG("sstr").TakeWhile(func(i rune) bool { + return i == 's' + }).ToSlice()) +} + func TestTakeWhileT_PanicWhenPredicateFnIsInvalid(t *testing.T) { mustPanicWithError(t, "TakeWhileT: parameter [predicateFn] has a invalid function signature. Expected: 'func(T)bool', actual: 'func(int)int'", func() { From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).TakeWhileT(func(item int) int { return item + 2 }) @@ -73,6 +94,18 @@ func TestTakeWhileIndexed(t *testing.T) { } } +func TestTakeWhileIndexedG(t *testing.T) { + assert.Equal(t, []int{1, 1, 1, 2}, FromSliceG([]int{1, 1, 1, 2}).TakeWhileIndexed(func(i int, x int) bool { + return x < 2 || i < 5 + }).ToSlice()) + assert.Equal(t, []int{1, 1, 1, 2, 1}, FromSliceG([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).TakeWhileIndexed(func(i int, x int) bool { + return x < 2 || i < 5 + }).ToSlice()) + assert.Equal(t, []rune{'s'}, FromStringG("sstr").TakeWhileIndexed(func(i int, x rune) bool { + return x == 's' && i < 1 + }).ToSlice()) +} + func TestTakeWhileIndexedT_PanicWhenPredicateFnIsInvalid(t *testing.T) { mustPanicWithError(t, "TakeWhileIndexedT: parameter [predicateFn] has a invalid function signature. Expected: 'func(int,T)bool', actual: 'func(int)int'", func() { From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).TakeWhileIndexedT(func(item int) int { return item + 2 }) From 1e6e9cdc8008b05209d4c6523389f0e74249117f Mon Sep 17 00:00:00 2001 From: zjhe Date: Sun, 7 Aug 2022 14:38:17 +0800 Subject: [PATCH 45/56] Add DistinctG methods(No ordered query) --- distinct.go | 10 ++++++++++ distinct_test.go | 25 ++++++++++++++++++++++++- expender.go | 1 + from.go | 15 +++++++++++++++ from_test.go | 6 ++++++ 5 files changed, 56 insertions(+), 1 deletion(-) diff --git a/distinct.go b/distinct.go index aaef37a..e7df182 100644 --- a/distinct.go +++ b/distinct.go @@ -22,6 +22,10 @@ func (q Query) Distinct() Query { } } +func (q QueryG[T]) Distinct() QueryG[T] { + return AsQueryG[T](q.AsQuery().Distinct()) +} + // Distinct method returns distinct elements from a collection. The result is an // ordered collection that contains no duplicate values. // @@ -74,6 +78,12 @@ func (q Query) DistinctBy(selector func(interface{}) interface{}) Query { } } +func (e *expender[T1, T2]) DistinctBy(selector func(T1) T2) QueryG[T1] { + return AsQueryG[T1](e.q.AsQuery().DistinctBy(func(i interface{}) interface{} { + return selector(i.(T1)) + })) +} + // DistinctByT is the typed version of DistinctBy. // // - selectorFn is of type "func(TSource) TSource". diff --git a/distinct_test.go b/distinct_test.go index e1e6618..37f09c6 100644 --- a/distinct_test.go +++ b/distinct_test.go @@ -1,6 +1,9 @@ package linq -import "testing" +import ( + "github.com/stretchr/testify/assert" + "testing" +) func TestDistinct(t *testing.T) { tests := []struct { @@ -19,6 +22,12 @@ func TestDistinct(t *testing.T) { } } +func TestDistinctG(t *testing.T) { + assert.Equal(t, []int{1, 2, 3}, FromSliceG([]int{1, 2, 2, 3, 1}).Distinct().ToSlice()) + assert.Equal(t, []int{1, 2, 3, 4}, FromSliceG([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).Distinct().ToSlice()) + assert.Equal(t, []rune{'s', 't', 'r'}, FromStringG("sstr").Distinct().ToSlice()) +} + func TestDistinctForOrderedQuery(t *testing.T) { tests := []struct { input interface{} @@ -54,6 +63,20 @@ func TestDistinctBy(t *testing.T) { } } +func TestDistinctByG(t *testing.T) { + type user struct { + id int + name string + } + + users := []user{{1, "Foo"}, {2, "Bar"}, {3, "Foo"}} + want := []user{user{1, "Foo"}, user{2, "Bar"}} + + assert.Equal(t, want, FromSliceG(users).Expend(To2[user, string]()).(Expended[user, string]).DistinctBy(func(u user) string { + return u.name + }).ToSlice()) +} + func TestDistinctByT_PanicWhenSelectorFnIsInvalid(t *testing.T) { mustPanicWithError(t, "DistinctByT: parameter [selectorFn] has a invalid function signature. Expected: 'func(T)T', actual: 'func(string,string)bool'", func() { From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).DistinctByT(func(indice, item string) bool { return item == "2" }) diff --git a/expender.go b/expender.go index 8ce8bdd..88bb4eb 100644 --- a/expender.go +++ b/expender.go @@ -18,6 +18,7 @@ type Expended[T1, T2 any] interface { SelectIndexed(selector func(int, T1) T2) QueryG[T2] SelectMany(selector func(T1) QueryG[T2]) QueryG[T2] SelectManyIndexed(selector func(int, T1) QueryG[T2]) QueryG[T2] + DistinctBy(selector func(T1) T2) QueryG[T1] } type Expended3[T1, T2, T3 any] interface { diff --git a/from.go b/from.go index 0a733dc..16d6759 100644 --- a/from.go +++ b/from.go @@ -29,6 +29,21 @@ func (q QueryG[T]) AsQuery() Query { } } +func AsQueryG[T any](q Query) QueryG[T] { + return QueryG[T]{ + Iterate: func() IteratorG[T] { + next := q.Iterate() + return func() (T, bool) { + item, ok := next() + if ok { + return item.(T), true + } + return *new(T), false + } + }, + } +} + // KeyValue is a type that is used to iterate over a map (if query is created // from a map). This type is also used by ToMap() method to output result of a // query into a map. diff --git a/from_test.go b/from_test.go index e9ccd3d..39bf691 100644 --- a/from_test.go +++ b/from_test.go @@ -169,3 +169,9 @@ func TestRepeatG(t *testing.T) { actual := RepeatG(1, 5).ToSlice() assert.Equal(t, expected, actual) } + +func TestConvertToQueryG(t *testing.T) { + input := []int{1, 2, 3} + actual := AsQueryG[int](From(input)).ToSlice() + assert.Equal(t, input, actual) +} From 697df8b52d718e635757c06390001d023fd9e8ba Mon Sep 17 00:00:00 2001 From: zjhe Date: Sun, 7 Aug 2022 15:58:21 +0800 Subject: [PATCH 46/56] Add OrderedQueryG methods --- distinct.go | 4 ++ distinct_test.go | 12 +++++ expender.go | 41 ++++++++++++++-- orderby.go | 62 ++++++++++++++++++++++++ orderby_test.go | 123 ++++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 236 insertions(+), 6 deletions(-) diff --git a/distinct.go b/distinct.go index e7df182..d62c58e 100644 --- a/distinct.go +++ b/distinct.go @@ -54,6 +54,10 @@ func (oq OrderedQuery) Distinct() OrderedQuery { } } +func (q OrderedQueryG[T]) Distinct() OrderedQueryG[T] { + return asOrderQueryG[T](q.orderedQuery.Distinct()) +} + // DistinctBy method returns distinct elements from a collection. This method // executes selector function for each element to determine a value to compare. // The result is an unordered collection that contains no duplicate values. diff --git a/distinct_test.go b/distinct_test.go index 37f09c6..04de808 100644 --- a/distinct_test.go +++ b/distinct_test.go @@ -47,6 +47,18 @@ func TestDistinctForOrderedQuery(t *testing.T) { } } +func TestDistinctForOrderedQueryG(t *testing.T) { + assert.Equal(t, []int{1, 2, 3}, FromSliceG([]int{1, 2, 2, 3, 1}).Expend(To2[int, int]()).(Expended[int, int]).OrderBy(func(i int) int { + return i + }).Distinct().ToSlice()) + assert.Equal(t, []int{1, 2, 3, 4}, FromSliceG([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).Expend(To2[int, int]()).(Expended[int, int]).OrderBy(func(i int) int { + return i + }).Distinct().ToSlice()) + assert.Equal(t, []rune{'r', 's', 't'}, FromStringG("sstr").Expend(To2[rune, rune]()).(Expended[rune, rune]).OrderBy(func(i rune) rune { + return i + }).Distinct().ToSlice()) +} + func TestDistinctBy(t *testing.T) { type user struct { id int diff --git a/expender.go b/expender.go index 88bb4eb..af00686 100644 --- a/expender.go +++ b/expender.go @@ -1,15 +1,26 @@ package linq -var _ Expender[int] = &expender[int, int]{} +var _ Expander[int] = &expender[int, int]{} var _ Expended[int, int] = &expender[int, int]{} var _ Expended3[int, int, int] = &expender3[int, int, int]{} +var _ OrderedExpander[int] = &orderedExtender[int, int]{} +var _ OrderedExpended[int, int] = &orderedExtender[int, int]{} -func (q QueryG[T]) Expend(e Expender[T]) Expender[T] { +func (q QueryG[T]) Expend(e Expander[T]) Expander[T] { e.Expend(q) return e } -type Expender[T any] interface { +func (q OrderedQueryG[T]) Expend(e OrderedExpander[T]) OrderedExpander[T] { + e.Expend(q) + return e +} + +type OrderedExpander[T any] interface { + Expend(q OrderedQueryG[T]) any +} + +type Expander[T any] interface { Expend(q QueryG[T]) any } @@ -19,6 +30,13 @@ type Expended[T1, T2 any] interface { SelectMany(selector func(T1) QueryG[T2]) QueryG[T2] SelectManyIndexed(selector func(int, T1) QueryG[T2]) QueryG[T2] DistinctBy(selector func(T1) T2) QueryG[T1] + OrderBy(selector func(T1) T2) OrderedQueryG[T1] + OrderByDescending(selector func(T1) T2) OrderedQueryG[T1] +} + +type OrderedExpended[T1 any, T2 comparable] interface { + ThenBy(selector func(T1) T2) OrderedQueryG[T1] + ThenByDescending(selector func(T1) T2) OrderedQueryG[T1] } type Expended3[T1, T2, T3 any] interface { @@ -39,7 +57,7 @@ func (e *expender[T1, T2]) Expend(q QueryG[T1]) any { return e } -func To2[T1, T2 any]() Expender[T1] { +func To2[T1, T2 any]() Expander[T1] { return &expender[T1, T2]{} } @@ -52,6 +70,19 @@ func (e *expender3[T1, T2, T3]) Expend(q QueryG[T1]) any { return e } -func To3[T1, T2, T3 any]() Expender[T1] { +func To3[T1, T2, T3 any]() Expander[T1] { return &expender3[T1, T2, T3]{} } + +func OrderedTo2[T1 any, T2 comparable]() OrderedExpander[T1] { + return &orderedExtender[T1, T2]{} +} + +type orderedExtender[T1 any, T2 comparable] struct { + q OrderedQueryG[T1] +} + +func (o *orderedExtender[T1, T2]) Expend(q OrderedQueryG[T1]) any { + o.q = q + return o +} diff --git a/orderby.go b/orderby.go index 110559d..1d1c83d 100644 --- a/orderby.go +++ b/orderby.go @@ -16,6 +16,11 @@ type OrderedQuery struct { orders []order } +type OrderedQueryG[T any] struct { + QueryG[T] + orderedQuery OrderedQuery +} + // OrderBy sorts the elements of a collection in ascending order. Elements are // sorted according to a key. func (q Query) OrderBy(selector func(interface{}) interface{}) OrderedQuery { @@ -42,6 +47,31 @@ func (q Query) OrderBy(selector func(interface{}) interface{}) OrderedQuery { } } +func asOrderQueryG[T any](q OrderedQuery) OrderedQueryG[T] { + return OrderedQueryG[T]{ + orderedQuery: q, + QueryG: QueryG[T]{ + Iterate: func() IteratorG[T] { + next := q.Iterate() + return func() (T, bool) { + item, ok := next() + if ok { + return item.(T), true + } + return *new(T), false + } + }, + }, + } +} + +func (e *expender[T1, T2]) OrderBy(selector func(T1) T2) OrderedQueryG[T1] { + orderBy := e.q.AsQuery().OrderBy(func(i interface{}) interface{} { + return selector(i.(T1)) + }) + return asOrderQueryG[T1](orderBy) +} + // OrderByT is the typed version of OrderBy. // // - selectorFn is of type "func(TSource) TKey" @@ -89,6 +119,13 @@ func (q Query) OrderByDescending(selector func(interface{}) interface{}) Ordered } } +func (e *expender[T1, T2]) OrderByDescending(selector func(T1) T2) OrderedQueryG[T1] { + orderBy := e.q.AsQuery().OrderByDescending(func(i interface{}) interface{} { + return selector(i.(T1)) + }) + return asOrderQueryG[T1](orderBy) +} + // OrderByDescendingT is the typed version of OrderByDescending. // - selectorFn is of type "func(TSource) TKey" // NOTE: OrderByDescending has better performance than OrderByDescendingT. @@ -136,6 +173,13 @@ func (oq OrderedQuery) ThenBy( } } +func (o *orderedExtender[T1, T2]) ThenBy(selector func(T1) T2) OrderedQueryG[T1] { + thenBy := o.q.orderedQuery.ThenBy(func(i interface{}) interface{} { + return selector(i.(T1)) + }) + return asOrderQueryG[T1](thenBy) +} + // ThenByT is the typed version of ThenBy. // - selectorFn is of type "func(TSource) TKey" // NOTE: ThenBy has better performance than ThenByT. @@ -182,6 +226,13 @@ func (oq OrderedQuery) ThenByDescending(selector func(interface{}) interface{}) } } +func (o *orderedExtender[T1, T2]) ThenByDescending(selector func(T1) T2) OrderedQueryG[T1] { + thenBy := o.q.orderedQuery.ThenBy(func(i interface{}) interface{} { + return selector(i.(T1)) + }) + return asOrderQueryG[T1](thenBy) +} + // ThenByDescendingT is the typed version of ThenByDescending. // - selectorFn is of type "func(TSource) TKey" // NOTE: ThenByDescending has better performance than ThenByDescendingT. @@ -228,6 +279,17 @@ func (q Query) Sort(less func(i, j interface{}) bool) Query { } } +// Sort returns a new query by sorting elements with provided less function in +// ascending order. The comparer function should return true if the parameter i +// is less than j. While this method is uglier than chaining OrderBy, +// OrderByDescending, ThenBy and ThenByDescending methods, it's performance is +// much better. +func (q QueryG[T]) Sort(less func(T, T) bool) QueryG[T] { + return AsQueryG[T](q.AsQuery().Sort(func(i, j interface{}) bool { + return less(i.(T), j.(T)) + })) +} + // SortT is the typed version of Sort. // - lessFn is of type "func(TSource,TSource) bool" // NOTE: Sort has better performance than SortT. diff --git a/orderby_test.go b/orderby_test.go index 3d4c538..32354cf 100644 --- a/orderby_test.go +++ b/orderby_test.go @@ -1,6 +1,9 @@ package linq -import "testing" +import ( + "github.com/stretchr/testify/assert" + "testing" +) func TestEmpty(t *testing.T) { q := From([]string{}).OrderBy(func(in interface{}) interface{} { @@ -13,6 +16,14 @@ func TestEmpty(t *testing.T) { } } +func TestEmptyG(t *testing.T) { + actual := FromSliceG([]string{}).Expend(To2[string, int]()).(Expended[string, int]).OrderBy(func(in string) int { + return 0 + }).ToSlice() + + assert.Empty(t, actual) +} + func TestOrderBy(t *testing.T) { slice := make([]foo, 100) @@ -35,6 +46,28 @@ func TestOrderBy(t *testing.T) { } } +func TestOrderByG(t *testing.T) { + slice := make([]foo, 100) + + for i := len(slice) - 1; i >= 0; i-- { + slice[i].f1 = i + } + + actual := FromSliceG(slice).Expend(To2[foo, int]()).(Expended[foo, int]).OrderBy(func(i foo) int { + return i.f1 + }) + + j := 0 + next := actual.Iterate() + for item, ok := next(); ok; item, ok = next() { + if item.f1 != j { + t.Errorf("OrderBy()[%v]=%v expected %v", j, item, foo{f1: j}) + } + + j++ + } +} + func TestOrderByT_PanicWhenSelectorFnIsInvalid(t *testing.T) { mustPanicWithError(t, "OrderByT: parameter [selectorFn] has a invalid function signature. Expected: 'func(T)T', actual: 'func(int,int)int'", func() { From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).OrderByT(func(item, j int) int { return item + 2 }) @@ -63,6 +96,28 @@ func TestOrderByDescending(t *testing.T) { } } +func TestOrderByDescendingG(t *testing.T) { + slice := make([]foo, 100) + + for i := 0; i < len(slice); i++ { + slice[i].f1 = i + } + + q := FromSliceG(slice).Expend(To2[foo, int]()).(Expended[foo, int]).OrderByDescending(func(i foo) int { + return i.f1 + }) + + j := len(slice) - 1 + next := q.Iterate() + for item, ok := next(); ok; item, ok = next() { + if item.f1 != j { + t.Errorf("OrderByDescending()[%v]=%v expected %v", j, item, foo{f1: j}) + } + + j-- + } +} + func TestOrderByDescendingT_PanicWhenSelectorFnIsInvalid(t *testing.T) { mustPanicWithError(t, "OrderByDescendingT: parameter [selectorFn] has a invalid function signature. Expected: 'func(T)T', actual: 'func(int,int)int'", func() { From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).OrderByDescendingT(func(item, j int) int { return item + 2 }) @@ -91,6 +146,28 @@ func TestThenBy(t *testing.T) { } } +func TestThenByG(t *testing.T) { + slice := make([]foo, 1000) + + for i := len(slice) - 1; i >= 0; i-- { + slice[i].f1 = i + slice[i].f2 = i%2 == 0 + } + + q := FromSliceG(slice).Expend(To2[foo, bool]()).(Expended[foo, bool]).OrderBy(func(i foo) bool { + return i.f2 + }).Expend(OrderedTo2[foo, int]()).(OrderedExpended[foo, int]).ThenBy(func(i foo) int { + return i.f1 + }) + + next := q.Iterate() + for item, ok := next(); ok; item, ok = next() { + if item.f2 != (item.f1%2 == 0) { + t.Errorf("OrderBy().ThenBy()=%v", item) + } + } +} + func TestThenByT_PanicWhenSelectorFnIsInvalid(t *testing.T) { mustPanicWithError(t, "ThenByT: parameter [selectorFn] has a invalid function signature. Expected: 'func(T)T', actual: 'func(int,int)bool'", func() { From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}). @@ -121,6 +198,28 @@ func TestThenByDescending(t *testing.T) { } } +func TestThenByDescendingG(t *testing.T) { + slice := make([]foo, 1000) + + for i := len(slice) - 1; i >= 0; i-- { + slice[i].f1 = i + slice[i].f2 = i%2 == 0 + } + + q := FromSliceG(slice).Expend(To2[foo, bool]()).(Expended[foo, bool]).OrderBy(func(i foo) bool { + return i.f2 + }).Expend(OrderedTo2[foo, int]()).(OrderedExpended[foo, int]).ThenByDescending(func(i foo) int { + return i.f1 + }) + + next := q.Iterate() + for item, ok := next(); ok; item, ok = next() { + if item.f2 != (item.f1%2 == 0) { + t.Errorf("OrderBy().ThenByDescending()=%v", item) + } + } +} + func TestThenByDescendingT_PanicWhenSelectorFnIsInvalid(t *testing.T) { mustPanicWithError(t, "ThenByDescending: parameter [selectorFn] has a invalid function signature. Expected: 'func(T)T', actual: 'func(int,int)bool'", func() { From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}). @@ -151,6 +250,28 @@ func TestSort(t *testing.T) { } } +func TestSortG(t *testing.T) { + slice := make([]foo, 100) + + for i := len(slice) - 1; i >= 0; i-- { + slice[i].f1 = i + } + + q := FromSliceG(slice).Sort(func(i, j foo) bool { + return i.f1 < j.f1 + }) + + j := 0 + next := q.Iterate() + for item, ok := next(); ok; item, ok = next() { + if item.f1 != j { + t.Errorf("Sort()[%v]=%v expected %v", j, item, foo{f1: j}) + } + + j++ + } +} + func TestSortT_PanicWhenLessFnIsInvalid(t *testing.T) { mustPanicWithError(t, "SortT: parameter [lessFn] has a invalid function signature. Expected: 'func(T,T)bool', actual: 'func(int,int)string'", func() { From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).SortT(func(i, j int) string { return "" }) From 781bedf2abd7f3c77fe1bcbd99c1254f68733b6d Mon Sep 17 00:00:00 2001 From: zjhe Date: Sun, 7 Aug 2022 16:05:13 +0800 Subject: [PATCH 47/56] Add ExceptG methods --- except.go | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++ except_test.go | 24 +++++++++++++++++++++- expender.go | 1 + 3 files changed, 80 insertions(+), 1 deletion(-) diff --git a/except.go b/except.go index d146992..5048f52 100644 --- a/except.go +++ b/except.go @@ -26,6 +26,32 @@ func (q Query) Except(q2 Query) Query { } } +// Except produces the set difference of two sequences. The set difference is +// the members of the first sequence that don't appear in the second sequence. +func (q QueryG[T]) Except(q2 QueryG[T]) QueryG[T] { + return QueryG[T]{ + Iterate: func() IteratorG[T] { + next := q.Iterate() + + next2 := q2.Iterate() + set := make(map[interface{}]bool) + for i, ok := next2(); ok; i, ok = next2() { + set[i] = true + } + + return func() (item T, ok bool) { + for item, ok = next(); ok; item, ok = next() { + if _, has := set[item]; !has { + return + } + } + + return + } + }, + } +} + // ExceptBy invokes a transform function on each element of a collection and // produces the set difference of two sequences. The set difference is the // members of the first sequence that don't appear in the second sequence. @@ -56,6 +82,36 @@ func (q Query) ExceptBy(q2 Query, } } +// ExceptBy invokes a transform function on each element of a collection and +// produces the set difference of two sequences. The set difference is the +// members of the first sequence that don't appear in the second sequence. +func (e *expender[T1, T2]) ExceptBy(q2 QueryG[T1], + selector func(T1) T2) QueryG[T1] { + return QueryG[T1]{ + Iterate: func() IteratorG[T1] { + next := e.q.Iterate() + + next2 := q2.Iterate() + set := make(map[interface{}]bool) + for i, ok := next2(); ok; i, ok = next2() { + s := selector(i) + set[s] = true + } + + return func() (item T1, ok bool) { + for item, ok = next(); ok; item, ok = next() { + s := selector(item) + if _, has := set[s]; !has { + return + } + } + + return + } + }, + } +} + // ExceptByT is the typed version of ExceptBy. // // - selectorFn is of type "func(TSource) TSource" diff --git a/except_test.go b/except_test.go index 3513ee5..8cfd66a 100644 --- a/except_test.go +++ b/except_test.go @@ -1,6 +1,9 @@ package linq -import "testing" +import ( + "github.com/stretchr/testify/assert" + "testing" +) func TestExcept(t *testing.T) { input1 := []int{1, 2, 3, 4, 5, 1, 2, 5} @@ -12,6 +15,15 @@ func TestExcept(t *testing.T) { } } +func TestExceptG(t *testing.T) { + input1 := []int{1, 2, 3, 4, 5, 1, 2, 5} + input2 := []int{1, 2} + want := []int{3, 4, 5, 5} + + actual := FromSliceG(input1).Except(FromSliceG(input2)).ToSlice() + assert.Equal(t, want, actual) +} + func TestExceptBy(t *testing.T) { input1 := []int{1, 2, 3, 4, 5, 1, 2, 5} input2 := []int{1} @@ -24,6 +36,16 @@ func TestExceptBy(t *testing.T) { } } +func TestExceptByG(t *testing.T) { + input1 := []int{1, 2, 3, 4, 5, 1, 2, 5} + input2 := []int{1} + want := []int{2, 4, 2} + + assert.Equal(t, want, FromSliceG(input1).Expend(To2[int, int]()).(Expended[int, int]).ExceptBy(FromSliceG(input2), func(i int) int { + return i % 2 + }).ToSlice()) +} + func TestExceptByT_PanicWhenSelectorFnIsInvalid(t *testing.T) { mustPanicWithError(t, "ExceptByT: parameter [selectorFn] has a invalid function signature. Expected: 'func(T)T', actual: 'func(int,int)int'", func() { From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).ExceptByT(From([]int{1}), func(x, item int) int { return item + 2 }) diff --git a/expender.go b/expender.go index af00686..5d868dd 100644 --- a/expender.go +++ b/expender.go @@ -32,6 +32,7 @@ type Expended[T1, T2 any] interface { DistinctBy(selector func(T1) T2) QueryG[T1] OrderBy(selector func(T1) T2) OrderedQueryG[T1] OrderByDescending(selector func(T1) T2) OrderedQueryG[T1] + ExceptBy(q QueryG[T1], selector func(T1) T2) QueryG[T1] } type OrderedExpended[T1 any, T2 comparable] interface { From 2eeb11399e98283eb990e7efbdaa509663ec3a78 Mon Sep 17 00:00:00 2001 From: zjhe Date: Sun, 7 Aug 2022 16:07:32 +0800 Subject: [PATCH 48/56] Add Channel2ChannelG test --- general_test.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/general_test.go b/general_test.go index 8e89ae0..cbf932c 100644 --- a/general_test.go +++ b/general_test.go @@ -1,6 +1,7 @@ package linq import ( + "github.com/stretchr/testify/assert" "reflect" "testing" ) @@ -34,3 +35,31 @@ func TestChannelToChannel(t *testing.T) { t.Errorf("FromChannel().ToChannel()=%v expected %v", result, input) } } + +func TestChannelToChannelG(t *testing.T) { + input := []int{30, 40, 50} + + inpCh := make(chan int) + resCh := make(chan int) + + go func() { + for _, i := range input { + inpCh <- i + } + + close(inpCh) + }() + + go func() { + FromChannelG(inpCh).Where(func(i int) bool { + return i > 20 + }).ToChannel(resCh) + }() + + result := []int{} + for value := range resCh { + result = append(result, value) + } + + assert.Equal(t, input, result) +} From 2f172577baa8c7f9c49453332416c8ef4a961ee8 Mon Sep 17 00:00:00 2001 From: zjhe Date: Sun, 7 Aug 2022 16:24:23 +0800 Subject: [PATCH 49/56] Add GroupByG test --- expender.go | 12 +++++++----- groupby.go | 43 +++++++++++++++++++++++++++++++++++++++++++ groupby_test.go | 27 +++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 5 deletions(-) diff --git a/expender.go b/expender.go index 5d868dd..c1c85bb 100644 --- a/expender.go +++ b/expender.go @@ -35,11 +35,6 @@ type Expended[T1, T2 any] interface { ExceptBy(q QueryG[T1], selector func(T1) T2) QueryG[T1] } -type OrderedExpended[T1 any, T2 comparable] interface { - ThenBy(selector func(T1) T2) OrderedQueryG[T1] - ThenByDescending(selector func(T1) T2) OrderedQueryG[T1] -} - type Expended3[T1, T2, T3 any] interface { Zip(q2 QueryG[T2], resultSelector func(T1, T2) T3) QueryG[T3] @@ -47,6 +42,13 @@ type Expended3[T1, T2, T3 any] interface { resultSelector func(T2, T1) T3) QueryG[T3] SelectManyByIndexed(selector func(int, T1) QueryG[T2], resultSelector func(T2, T1) T3) QueryG[T3] + GroupBy(keySelector func(T1) T2, + elementSelector func(T1) T3) QueryG[GroupG[T2, T3]] +} + +type OrderedExpended[T1 any, T2 comparable] interface { + ThenBy(selector func(T1) T2) OrderedQueryG[T1] + ThenByDescending(selector func(T1) T2) OrderedQueryG[T1] } type expender[T1, T2 any] struct { diff --git a/groupby.go b/groupby.go index face510..ed3ed5b 100644 --- a/groupby.go +++ b/groupby.go @@ -6,6 +6,11 @@ type Group struct { Group []interface{} } +type GroupG[T1, T2 any] struct { + Key T1 + Group []T2 +} + // GroupBy method groups the elements of a collection according to a specified // key selector function and projects the elements for each group by using a // specified function. @@ -44,6 +49,44 @@ func (q Query) GroupBy(keySelector func(interface{}) interface{}, } } +// GroupBy method groups the elements of a collection according to a specified +// key selector function and projects the elements for each group by using a +// specified function. +func (e *expender3[T, TK, TE]) GroupBy(keySelector func(T) TK, + elementSelector func(T) TE) QueryG[GroupG[TK, TE]] { + return QueryG[GroupG[TK, TE]]{ + func() IteratorG[GroupG[TK, TE]] { + next := e.q.Iterate() + set := make(map[interface{}][]TE) + + for item, ok := next(); ok; item, ok = next() { + key := keySelector(item) + set[key] = append(set[key], elementSelector(item)) + } + + length := len(set) + idx := 0 + groups := make([]GroupG[TK, TE], length) + for k, v := range set { + groups[idx] = GroupG[TK, TE]{Key: k.(TK), Group: v} + idx++ + } + + index := 0 + + return func() (item GroupG[TK, TE], ok bool) { + ok = index < length + if ok { + item = groups[index] + index++ + } + + return + } + }, + } +} + // GroupByT is the typed version of GroupBy. // // - keySelectorFn is of type "func(TSource) TKey" diff --git a/groupby_test.go b/groupby_test.go index 7cf7392..b9d49b6 100644 --- a/groupby_test.go +++ b/groupby_test.go @@ -1,6 +1,7 @@ package linq import ( + "github.com/stretchr/testify/assert" "reflect" "testing" ) @@ -38,6 +39,32 @@ func TestGroupBy(t *testing.T) { } } +func TestGroupByG(t *testing.T) { + input := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} + wantEven := []int{2, 4, 6, 8} + wantOdd := []int{1, 3, 5, 7, 9} + + q := FromSliceG(input).Expend(To3[int, int, int]()).(Expended3[int, int, int]).GroupBy( + func(i int) int { + return i % 2 + }, func(i int) int { + return i + }) + + next := q.Iterate() + for item, ok := next(); ok; item, ok = next() { + group := item + switch group.Key { + case 0: + assert.Equal(t, wantEven, group.Group) + case 1: + assert.Equal(t, wantOdd, group.Group) + default: + assert.Fail(t, "Unexpected result") + } + } +} + func TestGroupByT_PanicWhenKeySelectorFnIsInvalid(t *testing.T) { mustPanicWithError(t, "GroupByT: parameter [keySelectorFn] has a invalid function signature. Expected: 'func(T)T', actual: 'func(int,int)bool'", func() { var r []int From cdc9651c5565a9ee8280c928eb235808a471a616 Mon Sep 17 00:00:00 2001 From: zjhe Date: Sun, 7 Aug 2022 16:46:18 +0800 Subject: [PATCH 50/56] Add JoinG test --- distinct.go | 2 +- except.go | 2 +- expender.go | 41 ++++++++++++++++++++++++++++++--------- groupby.go | 2 +- join.go | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++ join_test.go | 32 ++++++++++++++++++++++++++++++- orderby.go | 4 ++-- select.go | 4 ++-- selectmany.go | 8 ++++---- zip.go | 2 +- 10 files changed, 128 insertions(+), 22 deletions(-) diff --git a/distinct.go b/distinct.go index d62c58e..33e13b2 100644 --- a/distinct.go +++ b/distinct.go @@ -82,7 +82,7 @@ func (q Query) DistinctBy(selector func(interface{}) interface{}) Query { } } -func (e *expender[T1, T2]) DistinctBy(selector func(T1) T2) QueryG[T1] { +func (e *expander[T1, T2]) DistinctBy(selector func(T1) T2) QueryG[T1] { return AsQueryG[T1](e.q.AsQuery().DistinctBy(func(i interface{}) interface{} { return selector(i.(T1)) })) diff --git a/except.go b/except.go index 5048f52..2aa4a31 100644 --- a/except.go +++ b/except.go @@ -85,7 +85,7 @@ func (q Query) ExceptBy(q2 Query, // ExceptBy invokes a transform function on each element of a collection and // produces the set difference of two sequences. The set difference is the // members of the first sequence that don't appear in the second sequence. -func (e *expender[T1, T2]) ExceptBy(q2 QueryG[T1], +func (e *expander[T1, T2]) ExceptBy(q2 QueryG[T1], selector func(T1) T2) QueryG[T1] { return QueryG[T1]{ Iterate: func() IteratorG[T1] { diff --git a/expender.go b/expender.go index c1c85bb..7692160 100644 --- a/expender.go +++ b/expender.go @@ -1,8 +1,11 @@ package linq -var _ Expander[int] = &expender[int, int]{} -var _ Expended[int, int] = &expender[int, int]{} -var _ Expended3[int, int, int] = &expender3[int, int, int]{} +var _ Expander[int] = &expander[int, int]{} +var _ Expander[int] = &expander3[int, int, int]{} +var _ Expander[int] = &expander4[int, int, int, int]{} +var _ Expended[int, int] = &expander[int, int]{} +var _ Expended3[int, int, int] = &expander3[int, int, int]{} +var _ Expended4[int, int, int, int] = &expander4[int, int, int, int]{} var _ OrderedExpander[int] = &orderedExtender[int, int]{} var _ OrderedExpended[int, int] = &orderedExtender[int, int]{} @@ -46,35 +49,46 @@ type Expended3[T1, T2, T3 any] interface { elementSelector func(T1) T3) QueryG[GroupG[T2, T3]] } +type Expended4[T1, T2, T3, T4 any] interface { + Join(inner QueryG[T2], + outerKeySelector func(T1) T3, + innerKeySelector func(T2) T3, + resultSelector func(outer T1, inner T2) T4) QueryG[T4] +} + type OrderedExpended[T1 any, T2 comparable] interface { ThenBy(selector func(T1) T2) OrderedQueryG[T1] ThenByDescending(selector func(T1) T2) OrderedQueryG[T1] } -type expender[T1, T2 any] struct { +type expander[T1, T2 any] struct { q QueryG[T1] } -func (e *expender[T1, T2]) Expend(q QueryG[T1]) any { +func (e *expander[T1, T2]) Expend(q QueryG[T1]) any { e.q = q return e } func To2[T1, T2 any]() Expander[T1] { - return &expender[T1, T2]{} + return &expander[T1, T2]{} } -type expender3[T1, T2, T3 any] struct { +type expander3[T1, T2, T3 any] struct { q QueryG[T1] } -func (e *expender3[T1, T2, T3]) Expend(q QueryG[T1]) any { +func (e *expander3[T1, T2, T3]) Expend(q QueryG[T1]) any { e.q = q return e } func To3[T1, T2, T3 any]() Expander[T1] { - return &expender3[T1, T2, T3]{} + return &expander3[T1, T2, T3]{} +} + +func To4[T1, T2, T3, T4 any]() Expander[T1] { + return &expander4[T1, T2, T3, T4]{} } func OrderedTo2[T1 any, T2 comparable]() OrderedExpander[T1] { @@ -89,3 +103,12 @@ func (o *orderedExtender[T1, T2]) Expend(q OrderedQueryG[T1]) any { o.q = q return o } + +type expander4[T1, T2, T3, T4 any] struct { + q QueryG[T1] +} + +func (e *expander4[T1, T2, T3, T4]) Expend(q QueryG[T1]) any { + e.q = q + return e +} diff --git a/groupby.go b/groupby.go index ed3ed5b..ead5a76 100644 --- a/groupby.go +++ b/groupby.go @@ -52,7 +52,7 @@ func (q Query) GroupBy(keySelector func(interface{}) interface{}, // GroupBy method groups the elements of a collection according to a specified // key selector function and projects the elements for each group by using a // specified function. -func (e *expender3[T, TK, TE]) GroupBy(keySelector func(T) TK, +func (e *expander3[T, TK, TE]) GroupBy(keySelector func(T) TK, elementSelector func(T) TE) QueryG[GroupG[TK, TE]] { return QueryG[GroupG[TK, TE]]{ func() IteratorG[GroupG[TK, TE]] { diff --git a/join.go b/join.go index 68211a2..d526a04 100644 --- a/join.go +++ b/join.go @@ -53,6 +53,59 @@ func (q Query) Join(inner Query, } } +// Join correlates the elements of two collection based on matching keys. +// +// A join refers to the operation of correlating the elements of two sources of +// information based on a common key. Join brings the two information sources +// and the keys by which they are matched together in one method call. This +// differs from the use of SelectMany, which requires more than one method call +// to perform the same operation. +// +// Join preserves the order of the elements of outer collection, and for each of +// these elements, the order of the matching elements of inner. +func (e *expander4[TOut, TInner, TKey, TResult]) Join(inner QueryG[TInner], + outerKeySelector func(TOut) TKey, + innerKeySelector func(TInner) TKey, + resultSelector func(outer TOut, inner TInner) TResult) QueryG[TResult] { + + return QueryG[TResult]{ + Iterate: func() IteratorG[TResult] { + outernext := e.q.Iterate() + innernext := inner.Iterate() + + innerLookup := make(map[interface{}][]TInner) + for innerItem, ok := innernext(); ok; innerItem, ok = innernext() { + innerKey := innerKeySelector(innerItem) + innerLookup[innerKey] = append(innerLookup[innerKey], innerItem) + } + + var outerItem TOut + var innerGroup []TInner + innerLen, innerIndex := 0, 0 + + return func() (item TResult, ok bool) { + if innerIndex >= innerLen { + has := false + for !has { + outerItem, ok = outernext() + if !ok { + return + } + + innerGroup, has = innerLookup[outerKeySelector(outerItem)] + innerLen = len(innerGroup) + innerIndex = 0 + } + } + + item = resultSelector(outerItem, innerGroup[innerIndex]) + innerIndex++ + return item, true + } + }, + } +} + // JoinT is the typed version of Join. // // - outerKeySelectorFn is of type "func(TOuter) TKey" diff --git a/join_test.go b/join_test.go index 25dc292..ce0cf2a 100644 --- a/join_test.go +++ b/join_test.go @@ -1,6 +1,9 @@ package linq -import "testing" +import ( + "github.com/stretchr/testify/assert" + "testing" +) func TestJoin(t *testing.T) { outer := []int{0, 1, 2, 3, 4, 5, 8} @@ -26,6 +29,33 @@ func TestJoin(t *testing.T) { } } +func TestJoinG(t *testing.T) { + outer := []int{0, 1, 2, 3, 4, 5, 8} + inner := []uint{1, 2, 1, 4, 7, 6, 7, 2} + want := []KeyValueG[int, uint]{ + {1, 1}, + {1, 1}, + {2, 2}, + {2, 2}, + {4, 4}, + } + + q := FromSliceG(outer).Expend(To4[int, uint, int, KeyValueG[int, uint]]()).(Expended4[int, uint, int, KeyValueG[int, uint]]).Join( + FromSliceG(inner), func(i int) int { + return i + }, func(i uint) int { + return int(i) + }, func(i int, ui uint) KeyValueG[int, uint] { + return KeyValueG[int, uint]{ + Key: i, + Value: ui, + } + }, + ) + + assert.Equal(t, want, q.ToSlice()) +} + func TestJoinT_PanicWhenOuterKeySelectorFnIsInvalid(t *testing.T) { mustPanicWithError(t, "JoinT: parameter [outerKeySelectorFn] has a invalid function signature. Expected: 'func(T)T', actual: 'func(int,int)int'", func() { From([]int{0, 1, 2}).JoinT( diff --git a/orderby.go b/orderby.go index 1d1c83d..1eed09e 100644 --- a/orderby.go +++ b/orderby.go @@ -65,7 +65,7 @@ func asOrderQueryG[T any](q OrderedQuery) OrderedQueryG[T] { } } -func (e *expender[T1, T2]) OrderBy(selector func(T1) T2) OrderedQueryG[T1] { +func (e *expander[T1, T2]) OrderBy(selector func(T1) T2) OrderedQueryG[T1] { orderBy := e.q.AsQuery().OrderBy(func(i interface{}) interface{} { return selector(i.(T1)) }) @@ -119,7 +119,7 @@ func (q Query) OrderByDescending(selector func(interface{}) interface{}) Ordered } } -func (e *expender[T1, T2]) OrderByDescending(selector func(T1) T2) OrderedQueryG[T1] { +func (e *expander[T1, T2]) OrderByDescending(selector func(T1) T2) OrderedQueryG[T1] { orderBy := e.q.AsQuery().OrderByDescending(func(i interface{}) interface{} { return selector(i.(T1)) }) diff --git a/select.go b/select.go index 4270f5d..88d30db 100644 --- a/select.go +++ b/select.go @@ -108,7 +108,7 @@ func (q Query) SelectIndexed(selector func(int, interface{}) interface{}) Query } } -func (e *expender[TIn, TOut]) Select(selector func(TIn) TOut) QueryG[TOut] { +func (e *expander[TIn, TOut]) Select(selector func(TIn) TOut) QueryG[TOut] { o := QueryG[TOut]{ Iterate: func() IteratorG[TOut] { next := e.q.Iterate() @@ -127,7 +127,7 @@ func (e *expender[TIn, TOut]) Select(selector func(TIn) TOut) QueryG[TOut] { return o } -func (e *expender[T1, T2]) SelectIndexed(selector func(int, T1) T2) QueryG[T2] { +func (e *expander[T1, T2]) SelectIndexed(selector func(int, T1) T2) QueryG[T2] { o := QueryG[T2]{ Iterate: func() IteratorG[T2] { next := e.q.Iterate() diff --git a/selectmany.go b/selectmany.go index ebe9aea..0e15a56 100644 --- a/selectmany.go +++ b/selectmany.go @@ -34,7 +34,7 @@ func (q Query) SelectMany(selector func(interface{}) Query) Query { } } -func (e *expender[T1, T2]) SelectMany(selector func(T1) QueryG[T2]) QueryG[T2] { +func (e *expander[T1, T2]) SelectMany(selector func(T1) QueryG[T2]) QueryG[T2] { return QueryG[T2]{ Iterate: func() IteratorG[T2] { outernext := e.q.Iterate() @@ -136,7 +136,7 @@ func (q Query) SelectManyIndexed(selector func(int, interface{}) Query) Query { // index, for example. It can also be useful if you want to retrieve the index // of one or more elements. The second argument to selector represents the // element to process. -func (e *expender[T1, T2]) SelectManyIndexed(selector func(int, T1) QueryG[T2]) QueryG[T2] { +func (e *expander[T1, T2]) SelectManyIndexed(selector func(int, T1) QueryG[T2]) QueryG[T2] { return QueryG[T2]{ Iterate: func() IteratorG[T2] { outernext := e.q.Iterate() @@ -226,7 +226,7 @@ func (q Query) SelectManyBy(selector func(interface{}) Query, } } -func (e *expender3[T1, T2, T3]) SelectManyBy(selector func(T1) QueryG[T2], +func (e *expander3[T1, T2, T3]) SelectManyBy(selector func(T1) QueryG[T2], resultSelector func(T2, T1) T3) QueryG[T3] { return QueryG[T3]{ Iterate: func() IteratorG[T3] { @@ -334,7 +334,7 @@ func (q Query) SelectManyByIndexed(selector func(int, interface{}) Query, } } -func (e *expender3[T1, T2, T3]) SelectManyByIndexed(selector func(int, T1) QueryG[T2], +func (e *expander3[T1, T2, T3]) SelectManyByIndexed(selector func(int, T1) QueryG[T2], resultSelector func(T2, T1) T3) QueryG[T3] { return QueryG[T3]{ Iterate: func() IteratorG[T3] { diff --git a/zip.go b/zip.go index d8fffbd..b2abcb8 100644 --- a/zip.go +++ b/zip.go @@ -54,7 +54,7 @@ func (q Query) ZipT(q2 Query, return q.Zip(q2, resultSelectorFunc) } -func (e *expender3[T1, T2, T3]) Zip(q2 QueryG[T2], resultSelector func(T1, T2) T3) QueryG[T3] { +func (e *expander3[T1, T2, T3]) Zip(q2 QueryG[T2], resultSelector func(T1, T2) T3) QueryG[T3] { return QueryG[T3]{ Iterate: func() IteratorG[T3] { next1 := e.q.Iterate() From 4943821fc2b0173c53063c0aaf11a703abf4b6ba Mon Sep 17 00:00:00 2001 From: zjhe Date: Sun, 7 Aug 2022 16:50:13 +0800 Subject: [PATCH 51/56] Add ReverseG method --- reverse.go | 24 ++++++++++++++++++++++++ reverse_test.go | 10 +++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/reverse.go b/reverse.go index 8716c22..d09559d 100644 --- a/reverse.go +++ b/reverse.go @@ -28,3 +28,27 @@ func (q Query) Reverse() Query { }, } } + +func (q QueryG[T]) Reverse() QueryG[T] { + return QueryG[T]{ + Iterate: func() IteratorG[T] { + next := q.Iterate() + + items := []T{} + for item, ok := next(); ok; item, ok = next() { + items = append(items, item) + } + + index := len(items) - 1 + return func() (item T, ok bool) { + if index < 0 { + return + } + + item, ok = items[index], true + index-- + return + } + }, + } +} diff --git a/reverse_test.go b/reverse_test.go index 91857c7..06234c9 100644 --- a/reverse_test.go +++ b/reverse_test.go @@ -1,6 +1,9 @@ package linq -import "testing" +import ( + "github.com/stretchr/testify/assert" + "testing" +) func TestReverse(t *testing.T) { tests := []struct { @@ -16,3 +19,8 @@ func TestReverse(t *testing.T) { } } } + +func TestReverseG(t *testing.T) { + input := []int{1, 2, 3} + assert.Equal(t, []int{3, 2, 1}, FromSliceG(input).Reverse().ToSlice()) +} From 5c2ecfb3413802973b659df642384f5fbca43d44 Mon Sep 17 00:00:00 2001 From: zjhe Date: Sun, 7 Aug 2022 17:10:36 +0800 Subject: [PATCH 52/56] Add GroupJoinG method --- expender.go | 4 ++++ groupjoin.go | 50 +++++++++++++++++++++++++++++++++++++++++++++++ groupjoin_test.go | 25 +++++++++++++++++++++++- 3 files changed, 78 insertions(+), 1 deletion(-) diff --git a/expender.go b/expender.go index 7692160..b47a961 100644 --- a/expender.go +++ b/expender.go @@ -54,6 +54,10 @@ type Expended4[T1, T2, T3, T4 any] interface { outerKeySelector func(T1) T3, innerKeySelector func(T2) T3, resultSelector func(outer T1, inner T2) T4) QueryG[T4] + GroupJoin(inner QueryG[T2], + outerKeySelector func(T1) T3, + innerKeySelector func(T2) T3, + resultSelector func(outer T1, inners []T2) T4) QueryG[T4] } type OrderedExpended[T1 any, T2 comparable] interface { diff --git a/groupjoin.go b/groupjoin.go index f9f8144..4e036c2 100644 --- a/groupjoin.go +++ b/groupjoin.go @@ -51,6 +51,56 @@ func (q Query) GroupJoin(inner Query, } } +// GroupJoin correlates the elements of two collections based on key equality, +// and groups the results. +// +// This method produces hierarchical results, which means that elements from +// outer query are paired with collections of matching elements from inner. +// GroupJoin enables you to base your results on a whole set of matches for each +// element of outer query. +// +// The resultSelector function is called only one time for each outer element +// together with a collection of all the inner elements that match the outer +// element. This differs from the Join method, in which the result selector +// function is invoked on pairs that contain one element from outer and one +// element from inner. +// +// GroupJoin preserves the order of the elements of outer, and for each element +// of outer, the order of the matching elements from inner. +func (e *expander4[TOut, TInner, TKey, TResult]) GroupJoin(inner QueryG[TInner], + outerKeySelector func(TOut) TKey, + innerKeySelector func(TInner) TKey, + resultSelector func(outer TOut, inners []TInner) TResult) QueryG[TResult] { + + return QueryG[TResult]{ + Iterate: func() IteratorG[TResult] { + outernext := e.q.Iterate() + innernext := inner.Iterate() + + innerLookup := make(map[interface{}][]TInner) + for innerItem, ok := innernext(); ok; innerItem, ok = innernext() { + innerKey := innerKeySelector(innerItem) + innerLookup[innerKey] = append(innerLookup[innerKey], innerItem) + } + + return func() (item TResult, ok bool) { + out, ok := outernext() + if !ok { + return + } + + if group, has := innerLookup[outerKeySelector(out)]; !has { + item = resultSelector(out, []TInner{}) + } else { + item = resultSelector(out, group) + } + + return + } + }, + } +} + // GroupJoinT is the typed version of GroupJoin. // // - inner: The query to join to the outer query. diff --git a/groupjoin_test.go b/groupjoin_test.go index cc23a79..363ced9 100644 --- a/groupjoin_test.go +++ b/groupjoin_test.go @@ -1,6 +1,9 @@ package linq -import "testing" +import ( + "github.com/stretchr/testify/assert" + "testing" +) func TestGroupJoin(t *testing.T) { outer := []int{0, 1, 2} @@ -24,6 +27,26 @@ func TestGroupJoin(t *testing.T) { } } +func TestGroupJoinG(t *testing.T) { + outer := []int{0, 1, 2} + inner := []uint{1, 2, 3, 4, 5, 6, 7, 8, 9} + want := []KeyValueG[int, []uint]{ + {0, []uint{2, 4, 6, 8}}, + {1, []uint{1, 3, 5, 7, 9}}, + {2, []uint{}}, + } + + actual := FromSliceG(outer).Expend(To4[int, uint, int, KeyValueG[int, []uint]]()).(Expended4[int, uint, int, KeyValueG[int, []uint]]).GroupJoin( + FromSliceG(inner), + func(i int) int { return i }, + func(ui uint) int { return int(ui) % 2 }, + func(outer int, inners []uint) KeyValueG[int, []uint] { + return KeyValueG[int, []uint]{outer, inners} + }, + ).ToSlice() + assert.Equal(t, want, actual) +} + func TestGroupJoinT_PanicWhenOuterKeySelectorFnIsInvalid(t *testing.T) { mustPanicWithError(t, "GroupJoinT: parameter [outerKeySelectorFn] has a invalid function signature. Expected: 'func(T)T', actual: 'func(int,int)int'", func() { From([]int{0, 1, 2}).GroupJoinT( From 997cfb6da4807d6f93ee417f450962130c51cbcb Mon Sep 17 00:00:00 2001 From: zjhe Date: Sun, 7 Aug 2022 17:29:02 +0800 Subject: [PATCH 53/56] Rename `enpender.go` to `expander.go` --- expender.go => expander.go | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename expender.go => expander.go (100%) diff --git a/expender.go b/expander.go similarity index 100% rename from expender.go rename to expander.go From 28c4665516d13bde45de670a945ebe29e00fc9d2 Mon Sep 17 00:00:00 2001 From: zjhe Date: Sun, 7 Aug 2022 20:35:00 +0800 Subject: [PATCH 54/56] improve Expander type safe --- distinct.go | 2 +- distinct_test.go | 8 +-- example_test.go | 6 +-- except.go | 2 +- except_test.go | 2 +- expander.go | 125 ++++++++++++++++++++++++++++----------------- groupby.go | 2 +- groupby_test.go | 2 +- groupjoin.go | 2 +- groupjoin_test.go | 2 +- join.go | 2 +- join_test.go | 2 +- orderby.go | 4 +- orderby_test.go | 10 ++-- select.go | 4 +- select_test.go | 4 +- selectmany.go | 8 +-- selectmany_test.go | 16 +++--- zip.go | 2 +- zip_test.go | 2 +- 20 files changed, 119 insertions(+), 88 deletions(-) diff --git a/distinct.go b/distinct.go index 33e13b2..cea8a23 100644 --- a/distinct.go +++ b/distinct.go @@ -82,7 +82,7 @@ func (q Query) DistinctBy(selector func(interface{}) interface{}) Query { } } -func (e *expander[T1, T2]) DistinctBy(selector func(T1) T2) QueryG[T1] { +func (e *Expended[T1, T2]) DistinctBy(selector func(T1) T2) QueryG[T1] { return AsQueryG[T1](e.q.AsQuery().DistinctBy(func(i interface{}) interface{} { return selector(i.(T1)) })) diff --git a/distinct_test.go b/distinct_test.go index 04de808..b63452c 100644 --- a/distinct_test.go +++ b/distinct_test.go @@ -48,13 +48,13 @@ func TestDistinctForOrderedQuery(t *testing.T) { } func TestDistinctForOrderedQueryG(t *testing.T) { - assert.Equal(t, []int{1, 2, 3}, FromSliceG([]int{1, 2, 2, 3, 1}).Expend(To2[int, int]()).(Expended[int, int]).OrderBy(func(i int) int { + assert.Equal(t, []int{1, 2, 3}, FromSliceG([]int{1, 2, 2, 3, 1}).Expend(To2[int, int]()).(*Expended[int, int]).OrderBy(func(i int) int { return i }).Distinct().ToSlice()) - assert.Equal(t, []int{1, 2, 3, 4}, FromSliceG([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).Expend(To2[int, int]()).(Expended[int, int]).OrderBy(func(i int) int { + assert.Equal(t, []int{1, 2, 3, 4}, FromSliceG([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).Expend(To2[int, int]()).(*Expended[int, int]).OrderBy(func(i int) int { return i }).Distinct().ToSlice()) - assert.Equal(t, []rune{'r', 's', 't'}, FromStringG("sstr").Expend(To2[rune, rune]()).(Expended[rune, rune]).OrderBy(func(i rune) rune { + assert.Equal(t, []rune{'r', 's', 't'}, FromStringG("sstr").Expend(To2[rune, rune]()).(*Expended[rune, rune]).OrderBy(func(i rune) rune { return i }).Distinct().ToSlice()) } @@ -84,7 +84,7 @@ func TestDistinctByG(t *testing.T) { users := []user{{1, "Foo"}, {2, "Bar"}, {3, "Foo"}} want := []user{user{1, "Foo"}, user{2, "Bar"}} - assert.Equal(t, want, FromSliceG(users).Expend(To2[user, string]()).(Expended[user, string]).DistinctBy(func(u user) string { + assert.Equal(t, want, FromSliceG(users).Expend(To2[user, string]()).(*Expended[user, string]).DistinctBy(func(u user) string { return u.name }).ToSlice()) } diff --git a/example_test.go b/example_test.go index e2071d0..e740637 100644 --- a/example_test.go +++ b/example_test.go @@ -2271,7 +2271,7 @@ func ExampleQuery_SelectManyByG() { } people := []Person{magnus, terry, charlotte} - results := FromSliceG(people).Expend(To3[Person, Pet, string]()).(Expended3[Person, Pet, string]). + results := FromSliceG(people).Expend3(To3[Person, Pet, string]()).(*Expended3[Person, Pet, string]). SelectManyBy(func(person Person) QueryG[Pet] { return FromSliceG(person.Pets) }, func(pet Pet, person Person) string { @@ -2431,9 +2431,9 @@ func ExampleQuery_SelectManyIndexedG() { logFiles := []LogFile{file1, file2, file3} var results []string - results = FromSliceG(logFiles).Expend(To2[LogFile, string]()).(Expended[LogFile, string]). + results = FromSliceG(logFiles).Expend(To2[LogFile, string]()).(*Expended[LogFile, string]). SelectManyIndexed(func(fileIndex int, file LogFile) QueryG[string] { - return FromSliceG(file.Lines).Expend(To2[string, string]()).(Expended[string, string]).SelectIndexed( + return FromSliceG(file.Lines).Expend(To2[string, string]()).(*Expended[string, string]).SelectIndexed( func(lineIndex int, line string) string { return fmt.Sprintf("File:[%d] - %s => line: %d - %s", fileIndex+1, file.Name, lineIndex+1, line) }) diff --git a/except.go b/except.go index 2aa4a31..260783b 100644 --- a/except.go +++ b/except.go @@ -85,7 +85,7 @@ func (q Query) ExceptBy(q2 Query, // ExceptBy invokes a transform function on each element of a collection and // produces the set difference of two sequences. The set difference is the // members of the first sequence that don't appear in the second sequence. -func (e *expander[T1, T2]) ExceptBy(q2 QueryG[T1], +func (e *Expended[T1, T2]) ExceptBy(q2 QueryG[T1], selector func(T1) T2) QueryG[T1] { return QueryG[T1]{ Iterate: func() IteratorG[T1] { diff --git a/except_test.go b/except_test.go index 8cfd66a..e164e5d 100644 --- a/except_test.go +++ b/except_test.go @@ -41,7 +41,7 @@ func TestExceptByG(t *testing.T) { input2 := []int{1} want := []int{2, 4, 2} - assert.Equal(t, want, FromSliceG(input1).Expend(To2[int, int]()).(Expended[int, int]).ExceptBy(FromSliceG(input2), func(i int) int { + assert.Equal(t, want, FromSliceG(input1).Expend(To2[int, int]()).(*Expended[int, int]).ExceptBy(FromSliceG(input2), func(i int) int { return i % 2 }).ToSlice()) } diff --git a/expander.go b/expander.go index b47a961..baaa000 100644 --- a/expander.go +++ b/expander.go @@ -1,11 +1,12 @@ package linq -var _ Expander[int] = &expander[int, int]{} -var _ Expander[int] = &expander3[int, int, int]{} -var _ Expander[int] = &expander4[int, int, int, int]{} -var _ Expended[int, int] = &expander[int, int]{} -var _ Expended3[int, int, int] = &expander3[int, int, int]{} -var _ Expended4[int, int, int, int] = &expander4[int, int, int, int]{} +var _ Expander[int] = &Expended[int, int]{} +var _ Expander3[int] = &Expended3[int, int, int]{} +var _ Expander4[int] = &Expended4[int, int, int, int]{} + +//var _ Expended[int, int] = &Expended[int, int]{} +//var _ Expended3[int, int, int] = &Expended3[int, int, int]{} +//var _ Expended4[int, int, int, int] = &Expended4[int, int, int, int]{} var _ OrderedExpander[int] = &orderedExtender[int, int]{} var _ OrderedExpended[int, int] = &orderedExtender[int, int]{} @@ -14,6 +15,16 @@ func (q QueryG[T]) Expend(e Expander[T]) Expander[T] { return e } +func (q QueryG[T]) Expend3(e Expander3[T]) Expander3[T] { + e.Expend(q) + return e +} + +func (q QueryG[T]) Expend4(e Expander4[T]) Expander4[T] { + e.Expend(q) + return e +} + func (q OrderedQueryG[T]) Expend(e OrderedExpander[T]) OrderedExpander[T] { e.Expend(q) return e @@ -25,74 +36,92 @@ type OrderedExpander[T any] interface { type Expander[T any] interface { Expend(q QueryG[T]) any + Expander() } -type Expended[T1, T2 any] interface { - Select(selector func(T1) T2) QueryG[T2] - SelectIndexed(selector func(int, T1) T2) QueryG[T2] - SelectMany(selector func(T1) QueryG[T2]) QueryG[T2] - SelectManyIndexed(selector func(int, T1) QueryG[T2]) QueryG[T2] - DistinctBy(selector func(T1) T2) QueryG[T1] - OrderBy(selector func(T1) T2) OrderedQueryG[T1] - OrderByDescending(selector func(T1) T2) OrderedQueryG[T1] - ExceptBy(q QueryG[T1], selector func(T1) T2) QueryG[T1] -} - -type Expended3[T1, T2, T3 any] interface { - Zip(q2 QueryG[T2], - resultSelector func(T1, T2) T3) QueryG[T3] - SelectManyBy(selector func(T1) QueryG[T2], - resultSelector func(T2, T1) T3) QueryG[T3] - SelectManyByIndexed(selector func(int, T1) QueryG[T2], - resultSelector func(T2, T1) T3) QueryG[T3] - GroupBy(keySelector func(T1) T2, - elementSelector func(T1) T3) QueryG[GroupG[T2, T3]] +type Expander3[T any] interface { + Expend(q QueryG[T]) any + Expander3() } -type Expended4[T1, T2, T3, T4 any] interface { - Join(inner QueryG[T2], - outerKeySelector func(T1) T3, - innerKeySelector func(T2) T3, - resultSelector func(outer T1, inner T2) T4) QueryG[T4] - GroupJoin(inner QueryG[T2], - outerKeySelector func(T1) T3, - innerKeySelector func(T2) T3, - resultSelector func(outer T1, inners []T2) T4) QueryG[T4] -} +type Expander4[T any] interface { + Expend(q QueryG[T]) any + Expander4() +} + +//type Expended[T1, T2 any] interface { +// Select(selector func(T1) T2) QueryG[T2] +// SelectIndexed(selector func(int, T1) T2) QueryG[T2] +// SelectMany(selector func(T1) QueryG[T2]) QueryG[T2] +// SelectManyIndexed(selector func(int, T1) QueryG[T2]) QueryG[T2] +// DistinctBy(selector func(T1) T2) QueryG[T1] +// OrderBy(selector func(T1) T2) OrderedQueryG[T1] +// OrderByDescending(selector func(T1) T2) OrderedQueryG[T1] +// ExceptBy(q QueryG[T1], selector func(T1) T2) QueryG[T1] +// Expander() +//} +// +//type Expended3[T1, T2, T3 any] interface { +// Zip(q2 QueryG[T2], +// resultSelector func(T1, T2) T3) QueryG[T3] +// SelectManyBy(selector func(T1) QueryG[T2], +// resultSelector func(T2, T1) T3) QueryG[T3] +// SelectManyByIndexed(selector func(int, T1) QueryG[T2], +// resultSelector func(T2, T1) T3) QueryG[T3] +// GroupBy(keySelector func(T1) T2, +// elementSelector func(T1) T3) QueryG[GroupG[T2, T3]] +// Expander3() +//} +// +//type Expended4[T1, T2, T3, T4 any] interface { +// Join(inner QueryG[T2], +// outerKeySelector func(T1) T3, +// innerKeySelector func(T2) T3, +// resultSelector func(outer T1, inner T2) T4) QueryG[T4] +// GroupJoin(inner QueryG[T2], +// outerKeySelector func(T1) T3, +// innerKeySelector func(T2) T3, +// resultSelector func(outer T1, inners []T2) T4) QueryG[T4] +// Expander4() +//} type OrderedExpended[T1 any, T2 comparable] interface { ThenBy(selector func(T1) T2) OrderedQueryG[T1] ThenByDescending(selector func(T1) T2) OrderedQueryG[T1] } -type expander[T1, T2 any] struct { +type Expended[T1, T2 any] struct { q QueryG[T1] } -func (e *expander[T1, T2]) Expend(q QueryG[T1]) any { +func (*Expended[T1, T2]) Expander() {} + +func (e *Expended[T1, T2]) Expend(q QueryG[T1]) any { e.q = q return e } func To2[T1, T2 any]() Expander[T1] { - return &expander[T1, T2]{} + return &Expended[T1, T2]{} } -type expander3[T1, T2, T3 any] struct { +type Expended3[T1, T2, T3 any] struct { q QueryG[T1] } -func (e *expander3[T1, T2, T3]) Expend(q QueryG[T1]) any { +func (*Expended3[T1, T2, T3]) Expander3() {} + +func (e *Expended3[T1, T2, T3]) Expend(q QueryG[T1]) any { e.q = q return e } -func To3[T1, T2, T3 any]() Expander[T1] { - return &expander3[T1, T2, T3]{} +func To3[T1, T2, T3 any]() Expander3[T1] { + return &Expended3[T1, T2, T3]{} } -func To4[T1, T2, T3, T4 any]() Expander[T1] { - return &expander4[T1, T2, T3, T4]{} +func To4[T1, T2, T3, T4 any]() Expander4[T1] { + return &Expended4[T1, T2, T3, T4]{} } func OrderedTo2[T1 any, T2 comparable]() OrderedExpander[T1] { @@ -108,11 +137,13 @@ func (o *orderedExtender[T1, T2]) Expend(q OrderedQueryG[T1]) any { return o } -type expander4[T1, T2, T3, T4 any] struct { +type Expended4[T1, T2, T3, T4 any] struct { q QueryG[T1] } -func (e *expander4[T1, T2, T3, T4]) Expend(q QueryG[T1]) any { +func (*Expended4[T1, T2, T3, T4]) Expander4() {} + +func (e *Expended4[T1, T2, T3, T4]) Expend(q QueryG[T1]) any { e.q = q return e } diff --git a/groupby.go b/groupby.go index ead5a76..6951879 100644 --- a/groupby.go +++ b/groupby.go @@ -52,7 +52,7 @@ func (q Query) GroupBy(keySelector func(interface{}) interface{}, // GroupBy method groups the elements of a collection according to a specified // key selector function and projects the elements for each group by using a // specified function. -func (e *expander3[T, TK, TE]) GroupBy(keySelector func(T) TK, +func (e *Expended3[T, TK, TE]) GroupBy(keySelector func(T) TK, elementSelector func(T) TE) QueryG[GroupG[TK, TE]] { return QueryG[GroupG[TK, TE]]{ func() IteratorG[GroupG[TK, TE]] { diff --git a/groupby_test.go b/groupby_test.go index b9d49b6..612803a 100644 --- a/groupby_test.go +++ b/groupby_test.go @@ -44,7 +44,7 @@ func TestGroupByG(t *testing.T) { wantEven := []int{2, 4, 6, 8} wantOdd := []int{1, 3, 5, 7, 9} - q := FromSliceG(input).Expend(To3[int, int, int]()).(Expended3[int, int, int]).GroupBy( + q := FromSliceG(input).Expend3(To3[int, int, int]()).(*Expended3[int, int, int]).GroupBy( func(i int) int { return i % 2 }, func(i int) int { diff --git a/groupjoin.go b/groupjoin.go index 4e036c2..5dfd526 100644 --- a/groupjoin.go +++ b/groupjoin.go @@ -67,7 +67,7 @@ func (q Query) GroupJoin(inner Query, // // GroupJoin preserves the order of the elements of outer, and for each element // of outer, the order of the matching elements from inner. -func (e *expander4[TOut, TInner, TKey, TResult]) GroupJoin(inner QueryG[TInner], +func (e *Expended4[TOut, TInner, TKey, TResult]) GroupJoin(inner QueryG[TInner], outerKeySelector func(TOut) TKey, innerKeySelector func(TInner) TKey, resultSelector func(outer TOut, inners []TInner) TResult) QueryG[TResult] { diff --git a/groupjoin_test.go b/groupjoin_test.go index 363ced9..64b958f 100644 --- a/groupjoin_test.go +++ b/groupjoin_test.go @@ -36,7 +36,7 @@ func TestGroupJoinG(t *testing.T) { {2, []uint{}}, } - actual := FromSliceG(outer).Expend(To4[int, uint, int, KeyValueG[int, []uint]]()).(Expended4[int, uint, int, KeyValueG[int, []uint]]).GroupJoin( + actual := FromSliceG(outer).Expend4(To4[int, uint, int, KeyValueG[int, []uint]]()).(*Expended4[int, uint, int, KeyValueG[int, []uint]]).GroupJoin( FromSliceG(inner), func(i int) int { return i }, func(ui uint) int { return int(ui) % 2 }, diff --git a/join.go b/join.go index d526a04..fca86e7 100644 --- a/join.go +++ b/join.go @@ -63,7 +63,7 @@ func (q Query) Join(inner Query, // // Join preserves the order of the elements of outer collection, and for each of // these elements, the order of the matching elements of inner. -func (e *expander4[TOut, TInner, TKey, TResult]) Join(inner QueryG[TInner], +func (e *Expended4[TOut, TInner, TKey, TResult]) Join(inner QueryG[TInner], outerKeySelector func(TOut) TKey, innerKeySelector func(TInner) TKey, resultSelector func(outer TOut, inner TInner) TResult) QueryG[TResult] { diff --git a/join_test.go b/join_test.go index ce0cf2a..e924ac4 100644 --- a/join_test.go +++ b/join_test.go @@ -40,7 +40,7 @@ func TestJoinG(t *testing.T) { {4, 4}, } - q := FromSliceG(outer).Expend(To4[int, uint, int, KeyValueG[int, uint]]()).(Expended4[int, uint, int, KeyValueG[int, uint]]).Join( + q := FromSliceG(outer).Expend4(To4[int, uint, int, KeyValueG[int, uint]]()).(*Expended4[int, uint, int, KeyValueG[int, uint]]).Join( FromSliceG(inner), func(i int) int { return i }, func(i uint) int { diff --git a/orderby.go b/orderby.go index 1eed09e..9c77899 100644 --- a/orderby.go +++ b/orderby.go @@ -65,7 +65,7 @@ func asOrderQueryG[T any](q OrderedQuery) OrderedQueryG[T] { } } -func (e *expander[T1, T2]) OrderBy(selector func(T1) T2) OrderedQueryG[T1] { +func (e *Expended[T1, T2]) OrderBy(selector func(T1) T2) OrderedQueryG[T1] { orderBy := e.q.AsQuery().OrderBy(func(i interface{}) interface{} { return selector(i.(T1)) }) @@ -119,7 +119,7 @@ func (q Query) OrderByDescending(selector func(interface{}) interface{}) Ordered } } -func (e *expander[T1, T2]) OrderByDescending(selector func(T1) T2) OrderedQueryG[T1] { +func (e *Expended[T1, T2]) OrderByDescending(selector func(T1) T2) OrderedQueryG[T1] { orderBy := e.q.AsQuery().OrderByDescending(func(i interface{}) interface{} { return selector(i.(T1)) }) diff --git a/orderby_test.go b/orderby_test.go index 32354cf..d10e4e9 100644 --- a/orderby_test.go +++ b/orderby_test.go @@ -17,7 +17,7 @@ func TestEmpty(t *testing.T) { } func TestEmptyG(t *testing.T) { - actual := FromSliceG([]string{}).Expend(To2[string, int]()).(Expended[string, int]).OrderBy(func(in string) int { + actual := FromSliceG([]string{}).Expend(To2[string, int]()).(*Expended[string, int]).OrderBy(func(in string) int { return 0 }).ToSlice() @@ -53,7 +53,7 @@ func TestOrderByG(t *testing.T) { slice[i].f1 = i } - actual := FromSliceG(slice).Expend(To2[foo, int]()).(Expended[foo, int]).OrderBy(func(i foo) int { + actual := FromSliceG(slice).Expend(To2[foo, int]()).(*Expended[foo, int]).OrderBy(func(i foo) int { return i.f1 }) @@ -103,7 +103,7 @@ func TestOrderByDescendingG(t *testing.T) { slice[i].f1 = i } - q := FromSliceG(slice).Expend(To2[foo, int]()).(Expended[foo, int]).OrderByDescending(func(i foo) int { + q := FromSliceG(slice).Expend(To2[foo, int]()).(*Expended[foo, int]).OrderByDescending(func(i foo) int { return i.f1 }) @@ -154,7 +154,7 @@ func TestThenByG(t *testing.T) { slice[i].f2 = i%2 == 0 } - q := FromSliceG(slice).Expend(To2[foo, bool]()).(Expended[foo, bool]).OrderBy(func(i foo) bool { + q := FromSliceG(slice).Expend(To2[foo, bool]()).(*Expended[foo, bool]).OrderBy(func(i foo) bool { return i.f2 }).Expend(OrderedTo2[foo, int]()).(OrderedExpended[foo, int]).ThenBy(func(i foo) int { return i.f1 @@ -206,7 +206,7 @@ func TestThenByDescendingG(t *testing.T) { slice[i].f2 = i%2 == 0 } - q := FromSliceG(slice).Expend(To2[foo, bool]()).(Expended[foo, bool]).OrderBy(func(i foo) bool { + q := FromSliceG(slice).Expend(To2[foo, bool]()).(*Expended[foo, bool]).OrderBy(func(i foo) bool { return i.f2 }).Expend(OrderedTo2[foo, int]()).(OrderedExpended[foo, int]).ThenByDescending(func(i foo) int { return i.f1 diff --git a/select.go b/select.go index 88d30db..0d59a0f 100644 --- a/select.go +++ b/select.go @@ -108,7 +108,7 @@ func (q Query) SelectIndexed(selector func(int, interface{}) interface{}) Query } } -func (e *expander[TIn, TOut]) Select(selector func(TIn) TOut) QueryG[TOut] { +func (e *Expended[TIn, TOut]) Select(selector func(TIn) TOut) QueryG[TOut] { o := QueryG[TOut]{ Iterate: func() IteratorG[TOut] { next := e.q.Iterate() @@ -127,7 +127,7 @@ func (e *expander[TIn, TOut]) Select(selector func(TIn) TOut) QueryG[TOut] { return o } -func (e *expander[T1, T2]) SelectIndexed(selector func(int, T1) T2) QueryG[T2] { +func (e *Expended[T1, T2]) SelectIndexed(selector func(int, T1) T2) QueryG[T2] { o := QueryG[T2]{ Iterate: func() IteratorG[T2] { next := e.q.Iterate() diff --git a/select_test.go b/select_test.go index 9cc8e51..9fba8ce 100644 --- a/select_test.go +++ b/select_test.go @@ -39,7 +39,7 @@ func TestSelectGFunc(t *testing.T) { func TestSelectG(t *testing.T) { input := []int{1, 2, 3} expected := []string{"1", "2", "3"} - stringSlice := FromSliceG(input).Expend(To2[int, string]()).(Expended[int, string]).Select(func(i int) string { + stringSlice := FromSliceG(input).Expend(To2[int, string]()).(*Expended[int, string]).Select(func(i int) string { return strconv.Itoa(i) }).ToSlice() assert.Equal(t, expected, stringSlice) @@ -48,7 +48,7 @@ func TestSelectG(t *testing.T) { func TestSelectIndexedG(t *testing.T) { input := []int{0, 1, 2} expected := []string{"0", "1", "2"} - stringSlice := FromSliceG(input).Expend(To2[int, string]()).(Expended[int, string]).SelectIndexed(func(index, i int) string { + stringSlice := FromSliceG(input).Expend(To2[int, string]()).(*Expended[int, string]).SelectIndexed(func(index, i int) string { assert.Equal(t, index, i) return strconv.Itoa(i) }).ToSlice() diff --git a/selectmany.go b/selectmany.go index 0e15a56..d310b15 100644 --- a/selectmany.go +++ b/selectmany.go @@ -34,7 +34,7 @@ func (q Query) SelectMany(selector func(interface{}) Query) Query { } } -func (e *expander[T1, T2]) SelectMany(selector func(T1) QueryG[T2]) QueryG[T2] { +func (e *Expended[T1, T2]) SelectMany(selector func(T1) QueryG[T2]) QueryG[T2] { return QueryG[T2]{ Iterate: func() IteratorG[T2] { outernext := e.q.Iterate() @@ -136,7 +136,7 @@ func (q Query) SelectManyIndexed(selector func(int, interface{}) Query) Query { // index, for example. It can also be useful if you want to retrieve the index // of one or more elements. The second argument to selector represents the // element to process. -func (e *expander[T1, T2]) SelectManyIndexed(selector func(int, T1) QueryG[T2]) QueryG[T2] { +func (e *Expended[T1, T2]) SelectManyIndexed(selector func(int, T1) QueryG[T2]) QueryG[T2] { return QueryG[T2]{ Iterate: func() IteratorG[T2] { outernext := e.q.Iterate() @@ -226,7 +226,7 @@ func (q Query) SelectManyBy(selector func(interface{}) Query, } } -func (e *expander3[T1, T2, T3]) SelectManyBy(selector func(T1) QueryG[T2], +func (e *Expended3[T1, T2, T3]) SelectManyBy(selector func(T1) QueryG[T2], resultSelector func(T2, T1) T3) QueryG[T3] { return QueryG[T3]{ Iterate: func() IteratorG[T3] { @@ -334,7 +334,7 @@ func (q Query) SelectManyByIndexed(selector func(int, interface{}) Query, } } -func (e *expander3[T1, T2, T3]) SelectManyByIndexed(selector func(int, T1) QueryG[T2], +func (e *Expended3[T1, T2, T3]) SelectManyByIndexed(selector func(int, T1) QueryG[T2], resultSelector func(T2, T1) T3) QueryG[T3] { return QueryG[T3]{ Iterate: func() IteratorG[T3] { diff --git a/selectmany_test.go b/selectmany_test.go index ce0e9b7..de1348c 100644 --- a/selectmany_test.go +++ b/selectmany_test.go @@ -28,10 +28,10 @@ func TestSelectMany(t *testing.T) { } func TestSelectManyG(t *testing.T) { - assert.Equal(t, []int{1, 2, 3, 4, 5, 6, 7}, FromSliceG([][]int{{1, 2, 3}, {4, 5, 6, 7}}).Expend(To2[[]int, int]()).(Expended[[]int, int]).SelectMany(func(s []int) QueryG[int] { + assert.Equal(t, []int{1, 2, 3, 4, 5, 6, 7}, FromSliceG([][]int{{1, 2, 3}, {4, 5, 6, 7}}).Expend(To2[[]int, int]()).(*Expended[[]int, int]).SelectMany(func(s []int) QueryG[int] { return FromSliceG(s) }).ToSlice()) - assert.Equal(t, []rune{'s', 't', 'r', 'i', 'n', 'g'}, FromSliceG([]string{"str", "ing"}).Expend(To2[string, rune]()).(Expended[string, rune]).SelectMany(func(s string) QueryG[rune] { + assert.Equal(t, []rune{'s', 't', 'r', 'i', 'n', 'g'}, FromSliceG([]string{"str", "ing"}).Expend(To2[string, rune]()).(*Expended[string, rune]).SelectMany(func(s string) QueryG[rune] { return FromStringG(s) }).ToSlice()) } @@ -67,13 +67,13 @@ func TestSelectManyIndexed(t *testing.T) { } func TestSelectManyIndexedG(t *testing.T) { - assert.Equal(t, []int{1, 2, 3, 5, 6, 7}, FromSliceG([][]int{{1, 2, 3}, {4, 5, 6, 7}}).Expend(To2[[]int, int]()).(Expended[[]int, int]).SelectManyIndexed(func(i int, s []int) QueryG[int] { + assert.Equal(t, []int{1, 2, 3, 5, 6, 7}, FromSliceG([][]int{{1, 2, 3}, {4, 5, 6, 7}}).Expend(To2[[]int, int]()).(*Expended[[]int, int]).SelectManyIndexed(func(i int, s []int) QueryG[int] { if i > 0 { return FromSliceG(s[1:]) } return FromSliceG(s) }).ToSlice()) - assert.Equal(t, []rune{'s', 't', 'r', '0', 'i', 'n', 'g', '1'}, FromSliceG([]string{"str", "ing"}).Expend(To2[string, rune]()).(Expended[string, rune]).SelectManyIndexed(func(i int, s string) QueryG[rune] { + assert.Equal(t, []rune{'s', 't', 'r', '0', 'i', 'n', 'g', '1'}, FromSliceG([]string{"str", "ing"}).Expend(To2[string, rune]()).(*Expended[string, rune]).SelectManyIndexed(func(i int, s string) QueryG[rune] { return FromStringG(s + strconv.Itoa(i)) }).ToSlice()) } @@ -111,12 +111,12 @@ func TestSelectManyBy(t *testing.T) { } func TestSelectManyByG(t *testing.T) { - assert.Equal(t, []int{2, 3, 4, 5, 6, 7, 8}, FromSliceG([][]int{{1, 2, 3}, {4, 5, 6, 7}}).Expend(To3[[]int, int, int]()).(Expended3[[]int, int, int]).SelectManyBy(func(s []int) QueryG[int] { + assert.Equal(t, []int{2, 3, 4, 5, 6, 7, 8}, FromSliceG([][]int{{1, 2, 3}, {4, 5, 6, 7}}).Expend3(To3[[]int, int, int]()).(*Expended3[[]int, int, int]).SelectManyBy(func(s []int) QueryG[int] { return FromSliceG(s) }, func(i int, _ []int) int { return i + 1 }).ToSlice()) - assert.Equal(t, []string{"s_", "t_", "r_", "i_", "n_", "g_"}, FromSliceG([]string{"str", "ing"}).Expend(To3[string, rune, string]()).(Expended3[string, rune, string]).SelectManyBy(func(s string) QueryG[rune] { + assert.Equal(t, []string{"s_", "t_", "r_", "i_", "n_", "g_"}, FromSliceG([]string{"str", "ing"}).Expend3(To3[string, rune, string]()).(*Expended3[string, rune, string]).SelectManyBy(func(s string) QueryG[rune] { return FromStringG(s) }, func(x rune, _ string) string { return string(x) + "_" @@ -171,7 +171,7 @@ func TestSelectManyIndexedBy(t *testing.T) { } func TestSelectManyIndexedByG(t *testing.T) { - assert.Equal(t, []int{11, 21, 31, 5, 6, 7, 8}, FromSliceG([][]int{{1, 2, 3}, {4, 5, 6, 7}}).Expend(To3[[]int, int, int]()).(Expended3[[]int, int, int]).SelectManyByIndexed( + assert.Equal(t, []int{11, 21, 31, 5, 6, 7, 8}, FromSliceG([][]int{{1, 2, 3}, {4, 5, 6, 7}}).Expend3(To3[[]int, int, int]()).(*Expended3[[]int, int, int]).SelectManyByIndexed( func(i int, x []int) QueryG[int] { if i == 0 { return FromSliceG([]int{10, 20, 30}) @@ -181,7 +181,7 @@ func TestSelectManyIndexedByG(t *testing.T) { return x + 1 }).ToSlice()) assert.Equal(t, []string{"s_", "t_", "r_", "i_", "n_", "g_"}, - FromSliceG([]string{"st", "ng"}).Expend(To3[string, rune, string]()).(Expended3[string, rune, string]).SelectManyByIndexed( + FromSliceG([]string{"st", "ng"}).Expend3(To3[string, rune, string]()).(*Expended3[string, rune, string]).SelectManyByIndexed( func(i int, x string) QueryG[rune] { if i == 0 { return FromStringG(x + "r") diff --git a/zip.go b/zip.go index b2abcb8..8635c74 100644 --- a/zip.go +++ b/zip.go @@ -54,7 +54,7 @@ func (q Query) ZipT(q2 Query, return q.Zip(q2, resultSelectorFunc) } -func (e *expander3[T1, T2, T3]) Zip(q2 QueryG[T2], resultSelector func(T1, T2) T3) QueryG[T3] { +func (e *Expended3[T1, T2, T3]) Zip(q2 QueryG[T2], resultSelector func(T1, T2) T3) QueryG[T3] { return QueryG[T3]{ Iterate: func() IteratorG[T3] { next1 := e.q.Iterate() diff --git a/zip_test.go b/zip_test.go index 62485b9..61e6e1d 100644 --- a/zip_test.go +++ b/zip_test.go @@ -23,7 +23,7 @@ func TestZipG(t *testing.T) { input2 := []int{2, 4, 5, 1} want := []string{"3", "6", "8"} - slice := FromSliceG(input1).Expend(To3[int, int, string]()).(Expended3[int, int, string]).Zip(FromSliceG(input2), func(i1, i2 int) string { + slice := FromSliceG(input1).Expend3(To3[int, int, string]()).(*Expended3[int, int, string]).Zip(FromSliceG(input2), func(i1, i2 int) string { return strconv.Itoa(i1 + i2) }).ToSlice() assert.Equal(t, want, slice) From 214febd4295f04026ef42ac9c514cb2caf6fcf05 Mon Sep 17 00:00:00 2001 From: zjhe Date: Sun, 7 Aug 2022 20:37:12 +0800 Subject: [PATCH 55/56] remove code in comment --- expander.go | 40 ---------------------------------------- 1 file changed, 40 deletions(-) diff --git a/expander.go b/expander.go index baaa000..e6cd87d 100644 --- a/expander.go +++ b/expander.go @@ -3,10 +3,6 @@ package linq var _ Expander[int] = &Expended[int, int]{} var _ Expander3[int] = &Expended3[int, int, int]{} var _ Expander4[int] = &Expended4[int, int, int, int]{} - -//var _ Expended[int, int] = &Expended[int, int]{} -//var _ Expended3[int, int, int] = &Expended3[int, int, int]{} -//var _ Expended4[int, int, int, int] = &Expended4[int, int, int, int]{} var _ OrderedExpander[int] = &orderedExtender[int, int]{} var _ OrderedExpended[int, int] = &orderedExtender[int, int]{} @@ -49,42 +45,6 @@ type Expander4[T any] interface { Expander4() } -//type Expended[T1, T2 any] interface { -// Select(selector func(T1) T2) QueryG[T2] -// SelectIndexed(selector func(int, T1) T2) QueryG[T2] -// SelectMany(selector func(T1) QueryG[T2]) QueryG[T2] -// SelectManyIndexed(selector func(int, T1) QueryG[T2]) QueryG[T2] -// DistinctBy(selector func(T1) T2) QueryG[T1] -// OrderBy(selector func(T1) T2) OrderedQueryG[T1] -// OrderByDescending(selector func(T1) T2) OrderedQueryG[T1] -// ExceptBy(q QueryG[T1], selector func(T1) T2) QueryG[T1] -// Expander() -//} -// -//type Expended3[T1, T2, T3 any] interface { -// Zip(q2 QueryG[T2], -// resultSelector func(T1, T2) T3) QueryG[T3] -// SelectManyBy(selector func(T1) QueryG[T2], -// resultSelector func(T2, T1) T3) QueryG[T3] -// SelectManyByIndexed(selector func(int, T1) QueryG[T2], -// resultSelector func(T2, T1) T3) QueryG[T3] -// GroupBy(keySelector func(T1) T2, -// elementSelector func(T1) T3) QueryG[GroupG[T2, T3]] -// Expander3() -//} -// -//type Expended4[T1, T2, T3, T4 any] interface { -// Join(inner QueryG[T2], -// outerKeySelector func(T1) T3, -// innerKeySelector func(T2) T3, -// resultSelector func(outer T1, inner T2) T4) QueryG[T4] -// GroupJoin(inner QueryG[T2], -// outerKeySelector func(T1) T3, -// innerKeySelector func(T2) T3, -// resultSelector func(outer T1, inners []T2) T4) QueryG[T4] -// Expander4() -//} - type OrderedExpended[T1 any, T2 comparable] interface { ThenBy(selector func(T1) T2) OrderedQueryG[T1] ThenByDescending(selector func(T1) T2) OrderedQueryG[T1] From e8bd19997cb30c55ae283783cff33d3e957d43ad Mon Sep 17 00:00:00 2001 From: zjhe Date: Mon, 8 Aug 2022 10:53:20 +0800 Subject: [PATCH 56/56] Remove unused Select function. Fix failed test in example_test file --- example_test.go | 32 +++++++++++++++++--------------- select.go | 21 ++------------------- select_test.go | 9 --------- 3 files changed, 19 insertions(+), 43 deletions(-) diff --git a/example_test.go b/example_test.go index e740637..593dec3 100644 --- a/example_test.go +++ b/example_test.go @@ -327,8 +327,8 @@ func ExampleQuery_Append() { // 5 } -//The following code example demonstrates how to use Average -//to calculate the average of a slice of values. +// The following code example demonstrates how to use Average +// to calculate the average of a slice of values. func ExampleQuery_Average() { grades := []int{78, 92, 100, 37, 81} average := From(grades).Average() @@ -361,8 +361,8 @@ func ExampleQuery_Contains() { // Does the slice contains 5? true } -//The following code example demonstrates how to use CountWith -//to count the even numbers in an array. +// The following code example demonstrates how to use CountWith +// to count the even numbers in an array. func ExampleQuery_CountWith() { slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10} @@ -446,8 +446,8 @@ func ExampleQuery_DefaultIfEmpty() { } -//The following code example demonstrates how to use Distinct -//to return distinct elements from a slice of integers. +// The following code example demonstrates how to use Distinct +// to return distinct elements from a slice of integers. func ExampleQuery_Distinct() { ages := []int{21, 46, 46, 55, 17, 21, 55, 55} @@ -568,7 +568,7 @@ func ExampleQuery_First() { } -//The following code example demonstrates how to use FirstWith +// The following code example demonstrates how to use FirstWith // to return the first element of an array that satisfies a condition. func ExampleQuery_FirstWith() { numbers := []int{9, 34, 65, 92, 87, 435, 3, 54, 83, 23, 87, 435, 67, 12, 19} @@ -584,8 +584,8 @@ func ExampleQuery_FirstWith() { } -//The following code example demonstrates how to use Intersect -//to return the elements that appear in each of two slices of integers. +// The following code example demonstrates how to use Intersect +// to return the elements that appear in each of two slices of integers. func ExampleQuery_Intersect() { id1 := []int{44, 26, 92, 30, 71, 38} id2 := []int{39, 59, 83, 47, 26, 4, 30} @@ -604,8 +604,8 @@ func ExampleQuery_Intersect() { } -//The following code example demonstrates how to use IntersectBy -//to return the elements that appear in each of two slices of products with same Code. +// The following code example demonstrates how to use IntersectBy +// to return the elements that appear in each of two slices of products with same Code. func ExampleQuery_IntersectBy() { type Product struct { Name string @@ -736,8 +736,8 @@ func ExampleOrderedQuery_ThenByDescending() { } // Output: // apPLe - // apPLE // apple + // apPLE // APple // orange // baNanA @@ -1282,7 +1282,8 @@ func ExampleQuery_SumUInts() { } // The following code example demonstrates how to use Take -// to return elements from the start of a slice. +// +// to return elements from the start of a slice. func ExampleQuery_Take() { grades := []int{59, 82, 70, 56, 92, 98, 85} @@ -1911,7 +1912,8 @@ func ExampleQuery_GroupByT() { } // The following code example demonstrates how to use GroupJoinT -// to perform a grouped join on two slices. +// +// to perform a grouped join on two slices. func ExampleQuery_GroupJoinT() { type Person struct { @@ -2507,7 +2509,7 @@ func ExampleQuery_SelectManyByIndexedT() { } -//The following code example demonstrates how to use SingleWithT +// The following code example demonstrates how to use SingleWithT // to select the only element of a slice that satisfies a condition. func ExampleQuery_SingleWithT() { fruits := []string{"apple", "banana", "mango", "orange", "passionfruit", "grape"} diff --git a/select.go b/select.go index 0d59a0f..ab3335a 100644 --- a/select.go +++ b/select.go @@ -32,6 +32,7 @@ func (q Query) Select(selector func(interface{}) interface{}) Query { // SelectT is the typed version of Select. // - selectorFn is of type "func(TSource)TResult" +// // NOTE: Select has better performance than SelectT. func (q Query) SelectT(selectorFn interface{}) Query { @@ -50,25 +51,6 @@ func (q Query) SelectT(selectorFn interface{}) Query { return q.Select(selectorFunc) } -func Select[TIn, TOut any](q QueryG[TIn], selector func(TIn) TOut) QueryG[TOut] { - o := QueryG[TOut]{ - Iterate: func() IteratorG[TOut] { - next := q.Iterate() - return func() (outItem TOut, ok bool) { - item, hasNext := next() - if hasNext { - outItem = selector(item) - ok = true - return - } - ok = false - return - } - }, - } - return o -} - // SelectIndexed projects each element of a collection into a new form by // incorporating the element's index. Returns a query with the result of // invoking the transform function on each element of original source. @@ -150,6 +132,7 @@ func (e *Expended[T1, T2]) SelectIndexed(selector func(int, T1) T2) QueryG[T2] { // SelectIndexedT is the typed version of SelectIndexed. // - selectorFn is of type "func(int,TSource)TResult" +// // NOTE: SelectIndexed has better performance than SelectIndexedT. func (q Query) SelectIndexedT(selectorFn interface{}) Query { selectGenericFunc, err := newGenericFunc( diff --git a/select_test.go b/select_test.go index 9fba8ce..c3f6d81 100644 --- a/select_test.go +++ b/select_test.go @@ -27,15 +27,6 @@ func TestSelect(t *testing.T) { } } -func TestSelectGFunc(t *testing.T) { - input := []int{1, 2, 3} - expected := []string{"1", "2", "3"} - stringSlice := Select[int, string](FromSliceG(input), func(i int) string { - return strconv.Itoa(i) - }).ToSlice() - assert.Equal(t, expected, stringSlice) -} - func TestSelectG(t *testing.T) { input := []int{1, 2, 3} expected := []string{"1", "2", "3"}