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) +} 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) +} 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) + } + +} diff --git a/distinct.go b/distinct.go index aaef37a..cea8a23 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. // @@ -50,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. @@ -74,6 +82,12 @@ func (q Query) DistinctBy(selector func(interface{}) interface{}) Query { } } +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)) + })) +} + // 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..b63452c 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{} @@ -38,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 @@ -54,6 +75,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/example_test.go b/example_test.go index 41e6c00..593dec3 100644 --- a/example_test.go +++ b/example_test.go @@ -2,6 +2,7 @@ package linq import ( "fmt" + "log" "strings" "time" ) @@ -326,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() @@ -360,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} @@ -445,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} @@ -567,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} @@ -583,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} @@ -603,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 @@ -735,8 +736,8 @@ func ExampleOrderedQuery_ThenByDescending() { } // Output: // apPLe - // apPLE // apple + // apPLE // APple // orange // baNanA @@ -1281,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} @@ -1910,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 { @@ -2242,6 +2245,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).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 { + 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 +2398,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. @@ -2405,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/except.go b/except.go index d146992..260783b 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 *Expended[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..e164e5d 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/expander.go b/expander.go new file mode 100644 index 0000000..e6cd87d --- /dev/null +++ b/expander.go @@ -0,0 +1,109 @@ +package linq + +var _ Expander[int] = &Expended[int, int]{} +var _ Expander3[int] = &Expended3[int, int, int]{} +var _ Expander4[int] = &Expended4[int, int, int, int]{} +var _ OrderedExpander[int] = &orderedExtender[int, int]{} +var _ OrderedExpended[int, int] = &orderedExtender[int, int]{} + +func (q QueryG[T]) Expend(e Expander[T]) Expander[T] { + e.Expend(q) + 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 +} + +type OrderedExpander[T any] interface { + Expend(q OrderedQueryG[T]) any +} + +type Expander[T any] interface { + Expend(q QueryG[T]) any + Expander() +} + +type Expander3[T any] interface { + Expend(q QueryG[T]) any + Expander3() +} + +type Expander4[T any] interface { + Expend(q QueryG[T]) any + Expander4() +} + +type OrderedExpended[T1 any, T2 comparable] interface { + ThenBy(selector func(T1) T2) OrderedQueryG[T1] + ThenByDescending(selector func(T1) T2) OrderedQueryG[T1] +} + +type Expended[T1, T2 any] struct { + q QueryG[T1] +} + +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 &Expended[T1, T2]{} +} + +type Expended3[T1, T2, T3 any] struct { + q QueryG[T1] +} + +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]() Expander3[T1] { + return &Expended3[T1, T2, T3]{} +} + +func To4[T1, T2, T3, T4 any]() Expander4[T1] { + return &Expended4[T1, T2, T3, T4]{} +} + +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 +} + +type Expended4[T1, T2, T3, T4 any] struct { + q QueryG[T1] +} + +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/from.go b/from.go index e1c1867..16d6759 100644 --- a/from.go +++ b/from.go @@ -11,6 +11,39 @@ type Query struct { Iterate func() Iterator } +type IteratorG[T any] func() (item T, ok bool) + +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 + } + }, + } +} + +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. @@ -19,12 +52,23 @@ type KeyValue struct { Value interface{} } +type KeyValueG[K comparable, V any] 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 { 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 @@ -88,6 +132,50 @@ func From(source interface{}) Query { } } +func FromSliceG[T any](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 + } + }, + } +} + +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 { @@ -118,6 +206,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 { @@ -141,6 +242,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. @@ -150,6 +271,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{ @@ -172,6 +302,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{ @@ -191,3 +343,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 dbc469d..39bf691 100644 --- a/from_test.go +++ b/from_test.go @@ -1,6 +1,10 @@ package linq -import "testing" +import ( + "github.com/stretchr/testify/assert" + "strconv" + "testing" +) func TestFrom(t *testing.T) { c := make(chan interface{}, 3) @@ -44,6 +48,27 @@ 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 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 @@ -72,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'} @@ -81,6 +118,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"} @@ -90,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} @@ -98,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} @@ -105,3 +163,15 @@ 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) +} + +func TestConvertToQueryG(t *testing.T) { + input := []int{1, 2, 3} + actual := AsQueryG[int](From(input)).ToSlice() + assert.Equal(t, input, actual) +} 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) +} diff --git a/go.mod b/go.mod index e7fab52..b32de7c 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,14 @@ module github.com/ahmetb/go-linq/v3 -go 1.11 +go 1.18 + +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 + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/groupby.go b/groupby.go index face510..6951879 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 *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]] { + 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..612803a 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).Expend3(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 diff --git a/groupjoin.go b/groupjoin.go index f9f8144..5dfd526 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 *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] { + + 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..64b958f 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).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 }, + 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( 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 }) 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 { diff --git a/join.go b/join.go index 68211a2..fca86e7 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 *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] { + + 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..e924ac4 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).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 { + 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 110559d..9c77899 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 *Expended[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 *Expended[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..d10e4e9 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 "" }) diff --git a/result.go b/result.go index 588cbd5..6be666f 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" @@ -58,6 +71,25 @@ 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()() + return ok +} + // AnyWithT is the typed version of AnyWith. // // - predicateFn is of type "func(TSource) bool" @@ -123,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() @@ -136,6 +172,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() @@ -147,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) { @@ -161,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" @@ -189,6 +254,12 @@ func (q Query) First() interface{} { return item } +// First returns the first element of a collection. +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 // specified condition. func (q Query) FirstWith(predicate func(interface{}) bool) interface{} { @@ -203,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" @@ -234,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)" @@ -274,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)" @@ -307,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{}) { @@ -321,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" @@ -363,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() @@ -383,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() @@ -410,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{} { @@ -427,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{}) { @@ -447,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" @@ -468,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 @@ -544,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" @@ -632,6 +842,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. // @@ -659,6 +909,15 @@ func (q Query) ToSlice(v interface{}) { res.Elem().Set(slice.Slice(0, index)) } +func (q QueryG[T]) ToSlice() []T { + var r = make([]T, 0) + 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..c6c6303 100644 --- a/result_test.go +++ b/result_test.go @@ -3,8 +3,11 @@ package linq import ( "math" "reflect" + "strconv" "testing" "unsafe" + + "github.com/stretchr/testify/assert" ) func TestAll(t *testing.T) { @@ -26,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 }) @@ -50,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{} @@ -69,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 }) @@ -92,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()) @@ -116,6 +158,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{} @@ -133,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{} @@ -151,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 }) @@ -173,6 +263,14 @@ func TestFirst(t *testing.T) { } } +func TestFirstG(t *testing.T) { + 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) { tests := []struct { input interface{} @@ -191,6 +289,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 }) @@ -218,6 +331,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 }) @@ -245,6 +377,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 }) @@ -267,6 +418,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{} @@ -285,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 }) @@ -308,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) } } } @@ -352,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{} @@ -369,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{} @@ -389,12 +626,45 @@ 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 }) }) } +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()) + assert.Equal(t, float64(9.), FromSliceG([]float64{1., 2., 2., 3., 1.}).Sum()) +} + func TestSumInts(t *testing.T) { tests := []struct { input interface{} @@ -464,6 +734,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"} @@ -494,6 +780,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 @@ -620,3 +929,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/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()) +} diff --git a/select.go b/select.go index 5dc8cc7..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 { @@ -89,8 +90,49 @@ func (q Query) SelectIndexed(selector func(int, interface{}) interface{}) Query } } +func (e *Expended[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 (e *Expended[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 T2, ok bool) { + item, hasNext := next() + if hasNext { + outItem = selector(index, item) + ok = true + index++ + return + } + ok = false + return + } + }, + } + 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( diff --git a/select_test.go b/select_test.go index a32349b..c3f6d81 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,25 @@ func TestSelect(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 { + return strconv.Itoa(i) + }).ToSlice() + assert.Equal(t, expected, stringSlice) +} + +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 { + assert.Equal(t, index, i) + return strconv.Itoa(i) + }).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/selectmany.go b/selectmany.go index 74fa671..d310b15 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 *Expended[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 *Expended[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 *Expended3[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 *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] { + 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..de1348c 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}}).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"}).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) + "_" + }).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}}).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}) + } + 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"}).Expend3(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/setup_test.go b/setup_test.go index a4f4de5..89c2801 100644 --- a/setup_test.go +++ b/setup_test.go @@ -1,6 +1,9 @@ package linq -import "testing" +import ( + "reflect" + "testing" +) import "fmt" @@ -45,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() @@ -71,6 +103,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/skip.go b/skip.go index 0fb05d3..a09e674 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. // @@ -54,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" @@ -112,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 2dd68c6..b1715a6 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.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()) +} + func TestSkipWhile(t *testing.T) { tests := []struct { input interface{} @@ -47,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 }) @@ -80,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 }) 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 + } +} 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 }) 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()) +} diff --git a/where.go b/where.go index 5da796f..c915755 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" @@ -68,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 8357dda..f9d85c6 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 }) @@ -53,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) {}) diff --git a/zip.go b/zip.go index da3dcd3..8635c74 100644 --- a/zip.go +++ b/zip.go @@ -53,3 +53,23 @@ func (q Query) ZipT(q2 Query, return q.Zip(q2, resultSelectorFunc) } + +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() + 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 335f706..61e6e1d 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).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) +} + 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}