From cb921f1d48cc871e7641ecc1f9cb5a22ccc95450 Mon Sep 17 00:00:00 2001 From: Cleiton Marques Souza Date: Mon, 5 Dec 2016 14:43:12 -0800 Subject: [PATCH 1/9] Generic functions support --- README.md | 43 +- aggregate.go | 28 + aggregate_test.go | 22 + aggregatetyped.go | 88 +++ aggregatetyped_test.go | 157 +++++ benchmark_test.go | 63 ++ distincttyped.go | 25 + distincttyped_test.go | 42 ++ doc.go | 5 +- example_test.go | 1292 +++++++++++++++++++++++++++++++++++++-- exampletyped_test.go | 1083 ++++++++++++++++++++++++++++++++ excepttyped.go | 24 + excepttyped_test.go | 38 ++ genericfunc.go | 137 +++++ genericfunc_test.go | 132 ++++ groupbytyped.go | 39 ++ groupbytyped_test.go | 92 +++ groupjointyped.go | 60 ++ groupjointyped_test.go | 82 +++ intersecttyped.go | 24 + intersecttyped_test.go | 45 ++ jointyped.go | 57 ++ jointyped_test.go | 111 ++++ orderbytyped.go | 119 ++++ orderbytyped_test.go | 182 ++++++ resulttyped.go | 175 ++++++ resulttyped_test.go | 249 ++++++++ selectmany.go | 4 +- selectmany_test.go | 8 +- selectmanytyped.go | 121 ++++ selectmanytyped_test.go | 199 ++++++ selecttyped.go | 47 ++ selecttyped_test.go | 78 +++ skiptyped.go | 47 ++ skiptyped_test.go | 83 +++ taketyped.go | 47 ++ taketyped_test.go | 83 +++ wheretyped.go | 47 ++ wheretyped_test.go | 102 ++++ ziptyped.go | 42 ++ ziptyped_test.go | 40 ++ 41 files changed, 5304 insertions(+), 58 deletions(-) create mode 100644 aggregatetyped.go create mode 100644 aggregatetyped_test.go create mode 100644 benchmark_test.go create mode 100644 distincttyped.go create mode 100644 distincttyped_test.go create mode 100644 exampletyped_test.go create mode 100644 excepttyped.go create mode 100644 excepttyped_test.go create mode 100644 genericfunc.go create mode 100644 genericfunc_test.go create mode 100644 groupbytyped.go create mode 100644 groupbytyped_test.go create mode 100644 groupjointyped.go create mode 100644 groupjointyped_test.go create mode 100644 intersecttyped.go create mode 100644 intersecttyped_test.go create mode 100644 jointyped.go create mode 100644 jointyped_test.go create mode 100644 orderbytyped.go create mode 100644 orderbytyped_test.go create mode 100644 resulttyped.go create mode 100644 resulttyped_test.go create mode 100644 selectmanytyped.go create mode 100644 selectmanytyped_test.go create mode 100644 selecttyped.go create mode 100644 selecttyped_test.go create mode 100644 skiptyped.go create mode 100644 skiptyped_test.go create mode 100644 taketyped.go create mode 100644 taketyped_test.go create mode 100644 wheretyped.go create mode 100644 wheretyped_test.go create mode 100644 ziptyped.go create mode 100644 ziptyped_test.go diff --git a/README.md b/README.md index 475464c..dc1a392 100644 --- a/README.md +++ b/README.md @@ -95,12 +95,53 @@ func (q MyQuery) GreaterThan(threshold int) Query { result := MyQuery(Range(1,10)).GreaterThan(5).Results() ``` +## Generic functions + +All we know that Go doesn't implement generics. On the other hand, Go offer a nice collection of reflection functions +that can be used to emulate some generic behaviors. +Although there are some [performance loss](../master/benchmark_test.go) when using reflection to parse the functions +and parameters types, the use of generic functions will bring you more code readability removing the type casting. +All the generic functions have a T suffix (eg. WhereT, SelectT, etc.). + +**Example: Implement "MapReduce" in a slice of string sentences to list the top 5 most used words using *generic functions*** +```go +var results []string +From(sentences). + //Split the sentences in a slice of words + SelectManyT(func(sentence string) Query { + return From(strings.Split(sentence, " ")) + }). + //Grouping the words + GroupByT( + func(word string) string { return word }, + func(word string) string { return word }, + ). + //Ordering by word counts + OrderByDescendingT(func(wordGroup Group) int { + return len(wordGroup.Group) + }). + //Then order by word + ThenByT(func(wordGroup Group) string { + return wordGroup.Key.(string) + }). + //Take the top 5 + Take(5). + //Project the words using the index as rank + SelectIndexedT(func(index int, wordGroup Group) string { + return fmt.Sprintf("Rank: #%d, Word: %s, Counts: %d", index+1, wordGroup.Key, len(wordGroup.Group)) + }). + ToSlice(&results) +``` **More examples** can be found in [documentation](https://godoc.org/github.com/ahmetalpbalkan/go-linq). ## Release Notes ~~~ - +v3.0.0 (2016-10-19) +* Generic methods + - Added generic methods support to all functions + - Totally backward compatible with v2.0.0 functions +* AggregateWithSeedBy() v2.0.0 (2016-09-02) * IMPORTANT: This release is a BREAKING CHANGE. The old version is archived at the 'archive/0.9' branch or the 0.9 tags. diff --git a/aggregate.go b/aggregate.go index a8658c5..bd2fc7b 100644 --- a/aggregate.go +++ b/aggregate.go @@ -51,3 +51,31 @@ func (q Query) AggregateWithSeed( 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. +// +// Aggregate method makes it simple to perform a calculation over a sequence of values. +// This method works by calling f() one time for each element in source. +// Each time func is called, Aggregate passes both the element from the sequence and an +// aggregated value (as the first argument to func). The value of the seed parameter is used +// as the initial aggregate value. The result of func replaces the previous aggregated value. +// +// The final result of func is passed to resultSelector to obtain the final result of Aggregate. +// +func (q Query) AggregateWithSeedBy( + seed interface{}, + f func(interface{}, interface{}) interface{}, + resultSelector func(interface{}) interface{}, +) interface{} { + + next := q.Iterate() + result := seed + + for current, ok := next(); ok; current, ok = next() { + result = f(result, current) + } + + return resultSelector(result) +} diff --git a/aggregate_test.go b/aggregate_test.go index 816b6ab..4bfc136 100644 --- a/aggregate_test.go +++ b/aggregate_test.go @@ -1,6 +1,7 @@ package linq import "testing" +import "strings" func TestAggregate(t *testing.T) { tests := []struct { @@ -41,3 +42,24 @@ func TestAggregateWithSeed(t *testing.T) { t.Errorf("From(%v).AggregateWithSeed()=%v expected %v", input, r, want) } } + +func TestAggregateWithSeedBy(t *testing.T) { + input := []string{"apple", "mango", "orange", "passionfruit", "grape"} + want := "PASSIONFRUIT" + + r := From(input).AggregateWithSeedBy("banana", + func(r interface{}, i interface{}) interface{} { + if len(r.(string)) > len(i.(string)) { + return r + } + return i + }, + func(r interface{}) interface{} { + return strings.ToUpper(r.(string)) + }, + ) + + if r != want { + t.Errorf("From(%v).AggregateWithSeed()=%v expected %v", input, r, want) + } +} diff --git a/aggregatetyped.go b/aggregatetyped.go new file mode 100644 index 0000000..34c89de --- /dev/null +++ b/aggregatetyped.go @@ -0,0 +1,88 @@ +package linq + +// AggregateT is the typed version of Aggregate. +// +// NOTE: Aggregate method has better performance than AggregateT +// +// f is of type: func(TSource, TSource) TSource +// +func (q Query) AggregateT(f interface{}) interface{} { + + fGenericFunc, err := newGenericFunc( + "AggregateT", "f", f, + simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + fFunc := func(result interface{}, current interface{}) interface{} { + return fGenericFunc.Call(result, current) + } + + return q.Aggregate(fFunc) +} + +// AggregateWithSeedT is the typed version of AggregateWithSeed. +// +// NOTE: AggregateWithSeed method has better performance than AggregateWithSeedT +// +// f is of a type "func(TAccumulate, TSource) TAccumulate" +// +func (q Query) AggregateWithSeedT(seed interface{}, f interface{}) interface{} { + + fGenericFunc, err := newGenericFunc( + "AggregateWithSeed", "f", f, + simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + fFunc := func(result interface{}, current interface{}) interface{} { + return fGenericFunc.Call(result, current) + } + + return q.AggregateWithSeed(seed, fFunc) +} + +// AggregateWithSeedByT is the typed version of AggregateWithSeedBy. +// +// NOTE: AggregateWithSeedBy method has better performance than AggregateWithSeedByT +// +// f is of a type "func(TAccumulate, TSource) TAccumulate" +// +// resultSelectorFn is of type "func(TAccumulate) TResult" +// +func (q Query) AggregateWithSeedByT( + seed interface{}, + f interface{}, + resultSelectorFn interface{}, +) interface{} { + + fGenericFunc, err := newGenericFunc( + "AggregateWithSeedByT", "f", f, + simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + fFunc := func(result interface{}, current interface{}) interface{} { + return fGenericFunc.Call(result, current) + } + + resultSelectorGenericFunc, err := newGenericFunc( + "AggregateWithSeedByT", "resultSelectorFn", resultSelectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + resultSelectorFunc := func(result interface{}) interface{} { + return resultSelectorGenericFunc.Call(result) + } + + return q.AggregateWithSeedBy(seed, fFunc, resultSelectorFunc) +} diff --git a/aggregatetyped_test.go b/aggregatetyped_test.go new file mode 100644 index 0000000..fb203ef --- /dev/null +++ b/aggregatetyped_test.go @@ -0,0 +1,157 @@ +package linq + +import "testing" +import "strings" + +func TestAggregateT(t *testing.T) { + tests := []struct { + input interface{} + predicate interface{} + want interface{} + }{ + {[]string{"apple", "mango", "orange", "passionfruit", "grape"}, func(r string, i string) string { + if len(r) > len(i) { + return r + } + return i + }, "passionfruit"}, + } + + for _, test := range tests { + r := From(test.input).AggregateT(test.predicate) + if r != test.want { + t.Errorf("From(%v).AggregateT()=%v expected %v", test.input, r, test.want) + } + } +} + +func TestAggregateT_PanicWhenFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).AggregateT(func(x int, r string, i string) string { + if len(r) > len(i) { + return r + } + return i + }) +} + +func TestAggregateWithSeedT(t *testing.T) { + tests := []struct { + input interface{} + predicate interface{} + want interface{} + }{ + {[]string{"apple", "mango", "orange", "grape"}, func(r string, i string) string { + if len(r) > len(i) { + return r + } + return i + }, "passionfruit"}, + } + + for _, test := range tests { + r := From(test.input).AggregateWithSeedT(test.want, test.predicate) + if r != test.want { + t.Errorf("From(%v).AggregateWithSeedT()=%v expected %v", test.input, r, test.want) + } + } +} + +func TestAggregateWithSeedT_PanicWhenFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).AggregateWithSeedT(3, func(x int, r string, i string) string { + if len(r) > len(i) { + return r + } + return i + }) + +} + +func TestAggregateWithSeedByT(t *testing.T) { + + input := []string{"apple", "mango", "orange", "passionfruit", "grape"} + + want := "PASSIONFRUIT" + + r := From(input).AggregateWithSeedByT("banana", + func(r string, i string) string { + if len(r) > len(i) { + return r + } + return i + }, + func(r string) string { + return strings.ToUpper(r) + }, + ) + + if r != want { + t.Errorf("From(%v).AggregateWithSeedT()=%v expected %v", input, r, want) + } + +} + +func TestAggregateWithSeedByT_PanicWhenFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).AggregateWithSeedByT(3, + func(x int, r string, i string) string { + if len(r) > len(i) { + return r + } + return i + }, + func(r string) string { + return r + }, + ) + +} + +func TestAggregateWithSeedByT_PanicWhenResultSelectorIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).AggregateWithSeedByT(3, + func(x int, r int) int { + if x > r { + return x + } + return r + }, + func(r string, t int) string { + return r + }, + ) + +} diff --git a/benchmark_test.go b/benchmark_test.go new file mode 100644 index 0000000..55eb296 --- /dev/null +++ b/benchmark_test.go @@ -0,0 +1,63 @@ +package linq + +import "testing" + +const ( + size = 1000000 +) + +func BenchmarkSelectWhereFirst(b *testing.B) { + for n := 0; n < b.N; n++ { + Range(1, size).Select(func(i interface{}) interface{} { + return -i.(int) + }).Where(func(i interface{}) bool { + return i.(int) > -1000 + }).First() + } +} + +func BenchmarkSelectWhereFirst_generics(b *testing.B) { + for n := 0; n < b.N; n++ { + Range(1, size).SelectT(func(i int) int { + return -i + }).WhereT(func(i int) bool { + return i > -1000 + }).First() + } +} + +func BenchmarkSum(b *testing.B) { + for n := 0; n < b.N; n++ { + Range(1, size).Where(func(i interface{}) bool { + return i.(int)%2 == 0 + }).SumInts() + } +} + +func BenchmarkSum_generics(b *testing.B) { + for n := 0; n < b.N; n++ { + Range(1, size).WhereT(func(i int) bool { + return i%2 == 0 + }).SumInts() + } +} + +func BenchmarkZipSkipTake(b *testing.B) { + for n := 0; n < b.N; n++ { + Range(1, size).Zip(Range(1, size).Select(func(i interface{}) interface{} { + return i.(int) * 2 + }), func(i, j interface{}) interface{} { + return i.(int) + j.(int) + }).Skip(2).Take(5) + } +} + +func BenchmarkZipSkipTake_generics(b *testing.B) { + for n := 0; n < b.N; n++ { + Range(1, size).ZipT(Range(1, size).SelectT(func(i int) int { + return i * 2 + }), func(i, j int) int { + return i + j + }).Skip(2).Take(5) + } +} diff --git a/distincttyped.go b/distincttyped.go new file mode 100644 index 0000000..577feb6 --- /dev/null +++ b/distincttyped.go @@ -0,0 +1,25 @@ +package linq + +// DistinctByT is the typed version of DistinctBy. +// +// NOTE: DistinctBy method has better performance than DistinctByT +// +// selectorFn is of type "func(TSource) TSource". +// +func (q Query) DistinctByT(selectorFn interface{}) Query { + selectorFunc, ok := selectorFn.(func(interface{}) interface{}) + if !ok { + selectorGenericFunc, err := newGenericFunc( + "DistinctBy", "selectorFn", selectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + selectorFunc = func(item interface{}) interface{} { + return selectorGenericFunc.Call(item) + } + } + return q.DistinctBy(selectorFunc) +} diff --git a/distincttyped_test.go b/distincttyped_test.go new file mode 100644 index 0000000..d934299 --- /dev/null +++ b/distincttyped_test.go @@ -0,0 +1,42 @@ +package linq + +import "testing" + +func TestDistinctByT(t *testing.T) { + type user struct { + id int + name string + } + + tests := []struct { + input interface{} + selector interface{} + output []interface{} + }{ + {[]user{{1, "Foo"}, {2, "Bar"}, {3, "Foo"}}, func(u interface{}) interface{} { + return u.(user).name + }, []interface{}{user{1, "Foo"}, user{2, "Bar"}}}, + {[]user{{1, "Foo"}, {2, "Bar"}, {3, "Foo"}}, func(u user) string { + return u.name + }, []interface{}{user{1, "Foo"}, user{2, "Bar"}}}, + } + + for _, test := range tests { + if q := From(test.input).DistinctByT(test.selector); !validateQuery(q, test.output) { + t.Errorf("From(%v).WhereIndexed()=%v expected %v", test.input, toSlice(q), test.output) + } + } +} + +func TestDistinctBy_PanicWhenFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).DistinctByT(func(indice, item string) bool { return item == "2" }) +} diff --git a/doc.go b/doc.go index 8231f70..c2fe778 100644 --- a/doc.go +++ b/doc.go @@ -1,5 +1,8 @@ // Package linq provides methods for querying and manipulating // slices, arrays, maps, strings, channels and collections. // -// Authors: Alexander Kalankhodzhaev (kalan), Ahmet Alp Balkan +// +// Authors: Alexander Kalankhodzhaev (kalan), Ahmet Alp Balkan, Cleiton Marques Souza +// +// package linq diff --git a/example_test.go b/example_test.go index 8e26fbd..5c07a37 100644 --- a/example_test.go +++ b/example_test.go @@ -1,6 +1,9 @@ package linq -import "fmt" +import ( + "fmt" + "strings" +) func ExampleKeyValue() { m := make(map[int]bool) @@ -18,13 +21,62 @@ func ExampleKeyValue_second() { } m := make(map[int]bool) - From(input).ToMap(&m) + From(input). + ToMap(&m) + fmt.Println(m) // Output: // map[10:true] } +// The following code example demonstrates how +// to use Range to generate a slice of values. +func ExampleRange() { + // Generate a slice of integers from 1 to 10 + // and then select their squares. + var squares []int + Range(1, 10). + SelectT( + func(x int) int { return x * x }, + ). + ToSlice(&squares) + + for _, num := range squares { + fmt.Println(num) + } + //Output: + //1 + //4 + //9 + //16 + //25 + //36 + //49 + //64 + //81 + //100 +} + +// The following code example demonstrates how to use Repeat +// to generate a slice of a repeated value. +func ExampleRepeat() { + var slice []string + Repeat("I like programming.", 5). + ToSlice(&slice) + + for _, str := range slice { + fmt.Println(str) + } + //Output: + //I like programming. + //I like programming. + //I like programming. + //I like programming. + //I like programming. + +} + func ExampleQuery() { query := From([]int{1, 2, 3, 4, 5}).Where(func(i interface{}) bool { return i.(int) <= 3 @@ -41,24 +93,615 @@ func ExampleQuery() { // 3 } +// The following code example demonstrates how to use Aggregate function func ExampleQuery_Aggregate() { + fruits := []string{"apple", "mango", "orange", "passionfruit", "grape"} + + // Determine which string in the slice is the longest. + longestName := From(fruits). + Aggregate( + func(r interface{}, i interface{}) interface{} { + if len(r.(string)) > len(i.(string)) { + return r + } + return i + }, + ) + + fmt.Println(longestName) + + // Output: + // passionfruit +} + +// The following code example demonstrates how to use AggregateWithSeed function +func ExampleQuery_AggregateWithSeed() { + ints := []int{4, 8, 8, 3, 9, 0, 7, 8, 2} + + // Count the even numbers in the array, using a seed value of 0. + numEven := From(ints). + AggregateWithSeed(0, + func(total, next interface{}) interface{} { + if next.(int)%2 == 0 { + return total.(int) + 1 + } + return total + }, + ) + + fmt.Printf("The number of even integers is: %d", numEven) + // Output: + // The number of even integers is: 6 +} + +// The following code example demonstrates how to use AggregateWithSeedBy function +func ExampleQuery_AggregateWithSeedBy() { input := []string{"apple", "mango", "orange", "passionfruit", "grape"} - result := From(input).Aggregate(func(r interface{}, i interface{}) interface{} { - if len(r.(string)) > len(i.(string)) { - return r - } - return i - }) + // Determine whether any string in the array is longer than "banana". + longestName := From(input). + AggregateWithSeedBy("banana", + func(longest interface{}, next interface{}) interface{} { + if len(longest.(string)) > len(next.(string)) { + return longest + } + return next - fmt.Println(result) + }, + // Return the final result + func(result interface{}) interface{} { + return fmt.Sprintf("The fruit with the longest name is %s.", result) + }, + ) + + fmt.Println(longestName) + // Output: + // The fruit with the longest name is passionfruit. +} + +// The following code example demonstrates how to +// use Distinct to return distinct elements from a slice of integers. +func ExampleOrderedQuery_Distinct() { + ages := []int{21, 46, 46, 55, 17, 21, 55, 55} + + var distinctAges []int + From(ages). + OrderBy( + func(item interface{}) interface{} { return item }, + ). + Distinct(). + ToSlice(&distinctAges) + + fmt.Println(distinctAges) + // Output: + // [17 21 46 55] +} + +// The following code example demonstrates how to +// use DistinctBy to return distinct elements from a ordered slice of elements. +func ExampleOrderedQuery_DistinctBy() { + type Product struct { + Name string + Code int + } + + products := []Product{ + {Name: "orange", Code: 4}, + {Name: "apple", Code: 9}, + {Name: "lemon", Code: 12}, + {Name: "apple", Code: 9}, + } + + //Order and exclude duplicates. + var noduplicates []Product + From(products). + OrderBy( + func(item interface{}) interface{} { return item.(Product).Name }, + ). + DistinctBy( + func(item interface{}) interface{} { return item.(Product).Code }, + ). + ToSlice(&noduplicates) + + for _, product := range noduplicates { + fmt.Printf("%s %d\n", product.Name, product.Code) + } // Output: + // apple 9 + // lemon 12 + // orange 4 +} + +// The following code example demonstrates how to use ThenBy to perform +// a secondary ordering of the elements in a slice. +func ExampleOrderedQuery_ThenBy() { + fruits := []string{"grape", "passionfruit", "banana", "mango", "orange", "raspberry", "apple", "blueberry"} + + // Sort the strings first by their length and then + //alphabetically by passing the identity selector function. + var query []string + From(fruits). + OrderBy( + func(fruit interface{}) interface{} { return len(fruit.(string)) }, + ). + ThenBy( + func(fruit interface{}) interface{} { return fruit }, + ). + ToSlice(&query) + + for _, fruit := range query { + fmt.Println(fruit) + } + + // Output: + // apple + // grape + // mango + // banana + // orange + // blueberry + // raspberry // passionfruit } +// The following code example demonstrates how to use All to determine +// whether all the elements in a slice satisfy a condition. +// Variable allStartWithB is true if all the pet names start with "B" +// or if the pets array is empty. +func ExampleQuery_All() { + + type Pet struct { + Name string + Age int + } + + pets := []Pet{ + {Name: "Barley", Age: 10}, + {Name: "Boots", Age: 4}, + {Name: "Whiskers", Age: 6}, + } + + // Determine whether all pet names + // in the array start with 'B'. + allStartWithB := From(pets). + All( + func(pet interface{}) bool { return strings.HasPrefix(pet.(Pet).Name, "B") }, + ) + + fmt.Printf("All pet names start with 'B'? %t", allStartWithB) + + // Output: + // + // All pet names start with 'B'? false +} + +// The following code example demonstrates how to use Any to determine +// whether a slice contains any elements. +func ExampleQuery_Any() { + + numbers := []int{1, 2} + hasElements := From(numbers).Any() + + fmt.Printf("Are there any element in the list? %t", hasElements) + + // Output: + // Are there any element in the list? true +} + +// The following code example demonstrates how to use AnyWith +// to determine whether any element in a slice satisfies a condition. +func ExampleQuery_AnyWith() { + + type Pet struct { + Name string + Age int + Vaccinated bool + } + + pets := []Pet{ + {Name: "Barley", Age: 8, Vaccinated: true}, + {Name: "Boots", Age: 4, Vaccinated: false}, + {Name: "Whiskers", Age: 1, Vaccinated: false}, + } + + // Determine whether any pets over age 1 are also unvaccinated. + unvaccinated := From(pets). + AnyWith( + func(p interface{}) bool { + return p.(Pet).Age > 1 && p.(Pet).Vaccinated == false + }, + ) + + fmt.Printf("Are there any unvaccinated animals over age one? %t", unvaccinated) + + // Output: + // + // Are there any unvaccinated animals over age one? true +} + +// The following code example demonstrates how to use Append +// to include an elements in the last position of a slice. +func ExampleQuery_Append() { + input := []int{1, 2, 3, 4} + + q := From(input).Append(5) + + last := q.Last() + + fmt.Println(last) + + // Output: + // 5 +} + +//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() + + fmt.Println(average) + + // Output: + // 77.6 +} + +// The following code example demonstrates how to use Count +// to count the elements in an array. +func ExampleQuery_Count() { + fruits := []string{"apple", "banana", "mango", "orange", "passionfruit", "grape"} + numberOfFruits := From(fruits).Count() + + fmt.Println(numberOfFruits) + + // Output: + // 6 +} + +// The following code example demonstrates how to use Contains +// to determine whether a slice contains a specific element. +func ExampleQuery_Contains() { + slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10} + + has5 := From(slice).Contains(5) + + fmt.Printf("Does the slice contains 5? %t", has5) + + // Output: + // Does the slice contains 5? true +} + +//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} + + evenCount := From(slice). + CountWith( + func(item interface{}) bool { return item.(int)%2 == 0 }, + ) + + fmt.Println(evenCount) + + // Output: + // 6 +} + +//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} + + var distinctAges []int + From(ages). + Distinct(). + ToSlice(&distinctAges) + + fmt.Println(distinctAges) + + // Output: + // [21 46 55 17] +} + +// The following code example demonstrates how to +// use DistinctBy to return distinct elements from a ordered slice of elements. +func ExampleQuery_DistinctBy() { + type Product struct { + Name string + Code int + } + + products := []Product{ + {Name: "orange", Code: 4}, + {Name: "apple", Code: 9}, + {Name: "lemon", Code: 12}, + {Name: "apple", Code: 9}, + } + + //Order and exclude duplicates. + var noduplicates []Product + From(products). + DistinctBy( + func(item interface{}) interface{} { return item.(Product).Code }, + ). + ToSlice(&noduplicates) + + for _, product := range noduplicates { + fmt.Printf("%s %d\n", product.Name, product.Code) + } + + // Output: + // orange 4 + // apple 9 + // lemon 12 + +} + +// The following code example demonstrates how to use the Except +// method to compare two slices of numbers and return elements +// that appear only in the first slice. +func ExampleQuery_Except() { + numbers1 := []float32{2.0, 2.1, 2.2, 2.3, 2.4, 2.5} + numbers2 := []float32{2.2} + + var onlyInFirstSet []float32 + From(numbers1). + Except(From(numbers2)). + ToSlice(&onlyInFirstSet) + + for _, number := range onlyInFirstSet { + fmt.Println(number) + } + + // Output: + //2 + //2.1 + //2.3 + //2.4 + //2.5 + +} + +// The following code example demonstrates how to use the Except +// method to compare two slices of numbers and return elements +// that appear only in the first slice. +func ExampleQuery_ExceptBy() { + type Product struct { + Name string + Code int + } + + fruits1 := []Product{ + {Name: "orange", Code: 4}, + {Name: "apple", Code: 9}, + {Name: "lemon", Code: 12}, + {Name: "apple", Code: 9}, + } + + fruits2 := []Product{ + {Name: "apple", Code: 9}, + } + + //Order and exclude duplicates. + var except []Product + From(fruits1). + ExceptBy(From(fruits2), + func(item interface{}) interface{} { return item.(Product).Code }, + ). + ToSlice(&except) + + for _, product := range except { + fmt.Printf("%s %d\n", product.Name, product.Code) + } + + // Output: + // orange 4 + // lemon 12 + +} + +// The following code example demonstrates how to use First +// to return the first element of an array. +func ExampleQuery_First() { + numbers := []int{9, 34, 65, 92, 87, 435, 3, 54, 83, 23, 87, 435, 67, 12, 19} + + first := From(numbers).First() + + fmt.Println(first) + + // Output: + // 9 + +} + +//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} + + first := From(numbers). + FirstWith( + func(item interface{}) bool { return item.(int) > 80 }, + ) + + fmt.Println(first) + + // Output: + // 92 + +} + +//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} + + var both []int + From(id1). + Intersect(From(id2)). + ToSlice(&both) + + for _, id := range both { + fmt.Println(id) + } + + // Output: + // 26 + // 30 + +} + +//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 + Code int + } + + store1 := []Product{ + {Name: "orange", Code: 4}, + {Name: "apple", Code: 9}, + } + + store2 := []Product{ + {Name: "lemon", Code: 12}, + {Name: "apple", Code: 9}, + } + + var duplicates []Product + From(store1). + IntersectBy(From(store2), + func(p interface{}) interface{} { return p.(Product).Code }, + ). + ToSlice(&duplicates) + + for _, p := range duplicates { + fmt.Println(p.Name, "", p.Code) + } + + // Output: + // apple 9 + +} + +// The following code example demonstrates how to use Last +// to return the last element of an array. +func ExampleQuery_Last() { + numbers := []int{9, 34, 65, 92, 87, 435, 3, 54, + 83, 23, 87, 67, 12, 19} + + last := From(numbers).Last() + + fmt.Println(last) + + //Output: + //19 + +} + +// The following code example demonstrates how to use LastWith +// to return the last element of an array. +func ExampleQuery_LastWith() { + numbers := []int{9, 34, 65, 92, 87, 435, 3, 54, + 83, 23, 87, 67, 12, 19} + + last := From(numbers). + LastWith( + func(n interface{}) bool { return n.(int) > 80 }, + ) + + fmt.Println(last) + + //Output: + //87 + +} + +// The following code example demonstrates how to use Max +// to determine the maximum value in a slice. +func ExampleQuery_Max() { + numbers := []int64{4294967296, 466855135, 81125} + + last := From(numbers).Max() + + fmt.Println(last) + + //Output: + //4294967296 + +} + +// The following code example demonstrates how to use Min +// to determine the minimum value in a slice. +func ExampleQuery_Min() { + grades := []int{78, 92, 99, 37, 81} + + min := From(grades).Min() + + fmt.Println(min) + + //Output: + //37 + +} + +// The following code example demonstrates how to use OrderByDescending +// to sort the elements of a slice in descending order by using a selector function +func ExampleQuery_OrderByDescending() { + names := []string{"Ned", "Ben", "Susan"} + + var result []string + From(names). + OrderByDescending( + func(n interface{}) interface{} { return n }, + ).ToSlice(&result) + + fmt.Println(result) + // Output: + // [Susan Ned Ben] +} + +// The following code example demonstrates how to use ThenByDescending to perform +// a secondary ordering of the elements in a slice in descending order. +func ExampleOrderedQuery_ThenByDescending() { + fruits := []string{"apPLe", "baNanA", "apple", "APple", "orange", "BAnana", "ORANGE", "apPLE"} + + // Sort the strings first ascending by their length and + // then descending using a custom case insensitive comparer. + var query []string + From(fruits). + OrderBy( + func(fruit interface{}) interface{} { return len(fruit.(string)) }, + ). + ThenByDescending( + func(fruit interface{}) interface{} { return fruit.(string)[0] }, + ). + ToSlice(&query) + + for _, fruit := range query { + fmt.Println(fruit) + } + // Output: + // apPLe + // apPLE + // apple + // APple + // orange + // baNanA + // ORANGE + // BAnana + +} + +// The following code example demonstrates how to use Concat +// to concatenate two slices. func ExampleQuery_Concat() { - q := From([]int{1, 2, 3}).Concat(From([]int{4, 5, 6})) + q := From([]int{1, 2, 3}). + Concat(From([]int{4, 5, 6})) + fmt.Println(q.Results()) // Output: @@ -80,6 +723,8 @@ func ExampleQuery_GroupBy() { // [{0 [2 4 6 8]} {1 [1 3 5 7 9]}] } +// The following code example demonstrates how to use GroupJoin +// to perform a grouped join on two slices func ExampleQuery_GroupJoin() { fruits := []string{ "apple", @@ -89,13 +734,14 @@ func ExampleQuery_GroupJoin() { "clementine", } - q := FromString("abc").GroupJoin( - From(fruits), - func(i interface{}) interface{} { return i }, - func(i interface{}) interface{} { return []rune(i.(string))[0] }, - func(outer interface{}, inners []interface{}) interface{} { - return KeyValue{string(outer.(rune)), inners} - }) + q := FromString("abc"). + GroupJoin(From(fruits), + func(i interface{}) interface{} { return i }, + func(i interface{}) interface{} { return []rune(i.(string))[0] }, + func(outer interface{}, inners []interface{}) interface{} { + return KeyValue{string(outer.(rune)), inners} + }, + ) fmt.Println(q.Results()) @@ -103,6 +749,8 @@ func ExampleQuery_GroupJoin() { // [{a [apple apricot]} {b [banana]} {c [cherry clementine]}] } +// The following code example demonstrates how to use Join +// to perform an inner join of two slices based on a common key. func ExampleQuery_Join() { fruits := []string{ "apple", @@ -112,12 +760,14 @@ func ExampleQuery_Join() { "clementine", } - q := Range(1, 10).Join(From(fruits), - func(i interface{}) interface{} { return i }, - func(i interface{}) interface{} { return len(i.(string)) }, - func(outer interface{}, inner interface{}) interface{} { - return KeyValue{outer, inner} - }) + q := Range(1, 10). + Join(From(fruits), + func(i interface{}) interface{} { return i }, + func(i interface{}) interface{} { return len(i.(string)) }, + func(outer interface{}, inner interface{}) interface{} { + return KeyValue{outer, inner} + }, + ) fmt.Println(q.Results()) @@ -125,12 +775,16 @@ func ExampleQuery_Join() { // [{5 apple} {6 banana} {6 cherry} {7 apricot} {10 clementine}] } +// The following code example demonstrates how to use OrderBy +// to sort the elements of a slice. func ExampleQuery_OrderBy() { - q := Range(1, 10).OrderBy(func(i interface{}) interface{} { - return i.(int) % 2 - }).ThenByDescending(func(i interface{}) interface{} { - return i - }) + q := Range(1, 10). + OrderBy( + func(i interface{}) interface{} { return i.(int) % 2 }, + ). + ThenByDescending( + func(i interface{}) interface{} { return i }, + ) fmt.Println(q.Results()) @@ -138,12 +792,59 @@ func ExampleQuery_OrderBy() { // [10 8 6 4 2 9 7 5 3 1] } +// The following code example demonstrates how to use Prepend +// to include an elements in the first position of a slice. +func ExampleQuery_Prepend() { + input := []int{2, 3, 4, 5} + + q := From(input).Prepend(1) + first := q.First() + + fmt.Println(first) + + // Output: + // 1 +} + +// The following code example demonstrates how to use Reverse +// to reverse the order of elements in a string. +func ExampleQuery_Reverse() { + input := "apple" + + var output []rune + From(input). + Reverse(). + ToSlice(&output) + + fmt.Println(string(output)) + + // Output: + // elppa +} + +// The following code example demonstrates how to use Select +// to project over a slice of values. +func ExampleQuery_Select() { + squares := []int{} + + Range(1, 10). + Select( + func(x interface{}) interface{} { return x.(int) * x.(int) }, + ). + ToSlice(&squares) + + fmt.Println(squares) + // Output: + // [1 4 9 16 25 36 49 64 81 100] +} + func ExampleQuery_SelectMany() { input := [][]int{{1, 2, 3}, {4, 5, 6, 7}} - q := From(input).SelectMany(func(i interface{}) Query { - return From(i) - }) + q := From(input). + SelectMany( + func(i interface{}) Query { return From(i) }, + ) fmt.Println(q.Results()) @@ -151,29 +852,420 @@ func ExampleQuery_SelectMany() { // [1 2 3 4 5 6 7] } -func ExampleQuery_Union() { - q := Range(1, 10).Union(Range(6, 10)) +// The following code example demonstrates how to use Select +// to project over a slice of values and use the index of each element. +func ExampleQuery_SelectIndexed() { + fruits := []string{"apple", "banana", "mango", "orange", "passionfruit", "grape"} - fmt.Println(q.Results()) + result := []string{} + From(fruits). + SelectIndexed( + func(index int, fruit interface{}) interface{} { return fruit.(string)[:index] }, + ). + ToSlice(&result) + fmt.Println(result) // Output: - // [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15] + // [ b ma ora pass grape] + } -func ExampleQuery_Zip() { - number := []int{1, 2, 3, 4, 5} - words := []string{"one", "two", "three"} +// The following code example demonstrates how to use SelectManyByIndexed +// to perform a one-to-many projection over an array and use the index of each outer element. +func ExampleQuery_SelectManyByIndexed() { + type Pet struct { + Name string + } - q := From(number).Zip(From(words), func(a interface{}, b interface{}) interface{} { - return []interface{}{a, b} - }) + type Person struct { + Name string + Pets []Pet + } - fmt.Println(q.Results()) + 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} + var results []string + + From(people). + SelectManyByIndexed( + func(index int, person interface{}) Query { + return From(person.(Person).Pets). + Select(func(pet interface{}) interface{} { + return fmt.Sprintf("%d - %s", index, pet.(Pet).Name) + }) + }, + func(indexedPet, person interface{}) interface{} { + return fmt.Sprintf("Pet: %s, Owner: %s", indexedPet, person.(Person).Name) + }, + ). + ToSlice(&results) + + for _, result := range results { + fmt.Println(result) + } // Output: - // [[1 one] [2 two] [3 three]] + // Pet: 0 - Daisy, Owner: Hedlund, Magnus + // Pet: 1 - Barley, Owner: Adams, Terry + // Pet: 1 - Boots, Owner: Adams, Terry + // Pet: 2 - Whiskers, Owner: Weiss, Charlotte + +} + +// The following code example demonstrates how to use SelectManyIndexed +// to perform a one-to-many projection over an slice of log data and print out their contents. +func ExampleQuery_SelectManyIndexed() { + 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 + + From(logFiles). + SelectManyIndexedT(func(fileIndex int, file LogFile) Query { + return From(file.Lines). + SelectIndexedT(func(lineIndex int, line string) string { + return fmt.Sprintf("File:[%d] - %s => line: %d - %s", fileIndex+1, file.Name, lineIndex+1, line) + }) + }). + ToSlice(&results) + + for _, result := range results { + 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 SelectMany +// to perform a one-to-many projection over a slice +func ExampleQuery_SelectManyBy() { + + 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} + var results []string + From(people). + SelectManyBy( + func(person interface{}) Query { return From(person.(Person).Pets) }, + func(pet, person interface{}) interface{} { + return fmt.Sprintf("Owner: %s, Pet: %s", person.(Person).Name, pet.(Pet).Name) + }, + ). + ToSlice(&results) + + 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 SequenceEqual +// to determine whether two slices are equal. +func ExampleQuery_SequenceEqual() { + type Pet struct { + Name string + Age int + } + + pets1 := []Pet{ + {Name: "Barley", Age: 8}, + {Name: "Boots", Age: 4}, + {Name: "Whiskers", Age: 1}, + {Name: "Daisy", Age: 4}, + } + + pets2 := []Pet{ + {Name: "Barley", Age: 8}, + {Name: "Boots", Age: 4}, + {Name: "Whiskers", Age: 1}, + {Name: "Daisy", Age: 4}, + } + + equal := From(pets1).SequenceEqual(From(pets2)) + + fmt.Printf("Are the lists equals? %t", equal) + + // Output: + // Are the lists equals? true } +// The following code example demonstrates how to use Single +// to select the only element of a slice. +func ExampleQuery_Single() { + fruits1 := []string{"orange"} + + fruit1 := From(fruits1).Single() + + fmt.Println(fruit1) + // Output: + // orange +} + +// The following code example demonstrates how to use SingleWith +// to select the only element of a slice that satisfies a condition. +func ExampleQuery_SingleWith() { + fruits := []string{"apple", "banana", "mango", "orange", "passionfruit", "grape"} + + fruit := From(fruits). + SingleWith( + func(f interface{}) bool { return len(f.(string)) > 10 }, + ) + + fmt.Println(fruit) + // Output: + // passionfruit +} + +// The following code example demonstrates how to use Skip +// to skip a specified number of elements in a sorted array +// and return the remaining elements. +func ExampleQuery_Skip() { + grades := []int{59, 82, 70, 56, 92, 98, 85} + var lowerGrades []int + From(grades). + OrderByDescending( + func(g interface{}) interface{} { return g }, + ). + Skip(3). + ToSlice(&lowerGrades) + + //All grades except the top three are: + fmt.Println(lowerGrades) + // Output: + // [82 70 59 56] +} + +// The following code example demonstrates how to use SkipWhile +// to skip elements of an array as long as a condition is true. +func ExampleQuery_SkipWhile() { + grades := []int{59, 82, 70, 56, 92, 98, 85} + var lowerGrades []int + From(grades). + OrderByDescending( + func(g interface{}) interface{} { return g }, + ). + SkipWhile( + func(g interface{}) bool { return g.(int) >= 80 }, + ). + ToSlice(&lowerGrades) + + // All grades below 80: + fmt.Println(lowerGrades) + // Output: + // [70 59 56] +} + +// The following code example demonstrates how to use SkipWhileIndexed +// to skip elements of an array as long as a condition that depends +// on the element's index is true. +func ExampleQuery_SkipWhileIndexed() { + amounts := []int{5000, 2500, 9000, 8000, 6500, 4000, 1500, 5500} + + var query []int + From(amounts). + SkipWhileIndexed( + func(index int, amount interface{}) bool { return amount.(int) > index*1000 }, + ). + ToSlice(&query) + + fmt.Println(query) + // Output: + // [4000 1500 5500] + +} + +// The following code example demonstrates how to use Sort +// to order elements of an slice. +func ExampleQuery_Sort() { + amounts := []int{5000, 2500, 9000, 8000, 6500, 4000, 1500, 5500} + + var query []int + From(amounts). + Sort( + func(i interface{}, j interface{}) bool { return i.(int) < j.(int) }, + ). + ToSlice(&query) + + fmt.Println(query) + // Output: + // [1500 2500 4000 5000 5500 6500 8000 9000] + +} + +// The following code example demonstrates how to use SumFloats +// to sum the values of a slice. +func ExampleQuery_SumFloats() { + numbers := []float64{43.68, 1.25, 583.7, 6.5} + + sum := From(numbers).SumFloats() + + fmt.Printf("The sum of the numbers is %f.", sum) + + // Output: + // The sum of the numbers is 635.130000. + +} + +// The following code example demonstrates how to use SumInts +// to sum the values of a slice. +func ExampleQuery_SumInts() { + numbers := []int{43, 1, 583, 6} + + sum := From(numbers).SumInts() + + fmt.Printf("The sum of the numbers is %d.", sum) + + // Output: + // The sum of the numbers is 633. + +} + +// The following code example demonstrates how to use SumUInts +// to sum the values of a slice. +func ExampleQuery_SumUInts() { + numbers := []uint{43, 1, 583, 6} + + sum := From(numbers).SumUInts() + + fmt.Printf("The sum of the numbers is %d.", sum) + + // Output: + // The sum of the numbers is 633. + +} + +// The following code example demonstrates how to use Take +// to return elements from the start of a slice. +func ExampleQuery_Take() { + grades := []int{59, 82, 70, 56, 92, 98, 85} + + var topThreeGrades []int + From(grades). + OrderByDescending( + func(grade interface{}) interface{} { return grade }, + ). + Take(3). + ToSlice(&topThreeGrades) + + fmt.Printf("The top three grades are: %v", topThreeGrades) + + // Output: + // The top three grades are: [98 92 85] + +} + +// The following code example demonstrates how to use TakeWhile +// to return elements from the start of a slice. +func ExampleQuery_TakeWhile() { + fruits := []string{"apple", "banana", "mango", "orange", "passionfruit", "grape"} + + var query []string + From(fruits). + TakeWhile( + func(fruit interface{}) bool { return fruit.(string) != "orange" }, + ). + ToSlice(&query) + + fmt.Println(query) + + // Output: + // [apple banana mango] +} + +// The following code example demonstrates how to use TakeWhileIndexed +// to return elements from the start of a slice as long as +// a condition that uses the element's index is true. +func ExampleQuery_TakeWhileIndexed() { + + fruits := []string{"apple", "passionfruit", "banana", "mango", + "orange", "blueberry", "grape", "strawberry"} + + var query []string + From(fruits). + TakeWhileIndexed( + func(index int, fruit interface{}) bool { return len(fruit.(string)) >= index }, + ). + ToSlice(&query) + + fmt.Println(query) + + // Output: + // [apple passionfruit banana mango orange blueberry] +} + +// The following code example demonstrates how to use ToChannel +// to send a slice to a channel. func ExampleQuery_ToChannel() { c := make(chan interface{}) @@ -191,17 +1283,52 @@ func ExampleQuery_ToChannel() { // 10 } +// The following code example demonstrates how to use ToMap to populate a map. +func ExampleQuery_ToMap() { + type Product struct { + Name string + Code int + } + + products := []Product{ + {Name: "orange", Code: 4}, + {Name: "apple", Code: 9}, + {Name: "lemon", Code: 12}, + {Name: "apple", Code: 9}, + } + + map1 := map[int]string{} + From(products). + SelectT( + func(item Product) KeyValue { return KeyValue{Key: item.Code, Value: item.Name} }, + ). + ToMap(&map1) + + fmt.Println(map1[4]) + fmt.Println(map1[9]) + fmt.Println(map1[12]) + + // Output: + // orange + // apple + // lemon +} + +// The following code example demonstrates how to use ToMapBy +// by using a key and value selectors to populate a map. func ExampleQuery_ToMapBy() { input := [][]interface{}{{1, true}} result := make(map[int]bool) - From(input).ToMapBy(&result, - func(i interface{}) interface{} { - return i.([]interface{})[0] - }, - func(i interface{}) interface{} { - return i.([]interface{})[1] - }) + From(input). + ToMapBy(&result, + func(i interface{}) interface{} { + return i.([]interface{})[0] + }, + func(i interface{}) interface{} { + return i.([]interface{})[1] + }, + ) fmt.Println(result) @@ -209,6 +1336,7 @@ func ExampleQuery_ToMapBy() { // map[1:true] } +// The following code example demonstrates how to use ToSlice to populate a slice. func ExampleQuery_ToSlice() { result := []int{} Range(1, 10).ToSlice(&result) @@ -218,3 +1346,67 @@ func ExampleQuery_ToSlice() { // Output: // [1 2 3 4 5 6 7 8 9 10] } + +// The following code example demonstrates how to use Union +// to obtain the union of two slices of integers. +func ExampleQuery_Union() { + q := Range(1, 10).Union(Range(6, 10)) + + fmt.Println(q.Results()) + + // Output: + // [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15] +} + +// The following code example demonstrates how to use Where +// to filter a slices. +func ExampleQuery_Where() { + fruits := []string{"apple", "passionfruit", "banana", "mango", + "orange", "blueberry", "grape", "strawberry"} + var query []string + From(fruits). + Where( + func(f interface{}) bool { return len(f.(string)) > 6 }, + ). + ToSlice(&query) + + fmt.Println(query) + + // Output: + // [passionfruit blueberry strawberry] +} + +// The following code example demonstrates how to use WhereIndexed +// to filter a slice based on a predicate that involves the index of each element. +func ExampleQuery_WhereIndexed() { + numbers := []int{0, 30, 20, 15, 90, 85, 40, 75} + + var query []int + From(numbers). + WhereIndexed( + func(index int, number interface{}) bool { return number.(int) <= index*10 }, + ). + ToSlice(&query) + + fmt.Println(query) + + // Output: + // [0 15 40] +} + +// The following code example demonstrates how to use the Zip +// method to merge two slices. +func ExampleQuery_Zip() { + number := []int{1, 2, 3, 4, 5} + words := []string{"one", "two", "three"} + + q := From(number). + Zip(From(words), + func(a interface{}, b interface{}) interface{} { return []interface{}{a, b} }, + ) + + fmt.Println(q.Results()) + + // Output: + // [[1 one] [2 two] [3 three]] +} diff --git a/exampletyped_test.go b/exampletyped_test.go new file mode 100644 index 0000000..409a62e --- /dev/null +++ b/exampletyped_test.go @@ -0,0 +1,1083 @@ +package linq + +import "fmt" +import "strings" +import "time" + +// The following code example demonstrates how to use ThenByDescendingT to perform +// a order in a slice of dates by year, and then by month descending. +func ExampleOrderedQuery_ThenByDescendingT() { + dates := []time.Time{ + time.Date(2015, 3, 23, 0, 0, 0, 0, time.Local), + time.Date(2014, 7, 11, 0, 0, 0, 0, time.Local), + time.Date(2013, 5, 4, 0, 0, 0, 0, time.Local), + time.Date(2015, 1, 2, 0, 0, 0, 0, time.Local), + time.Date(2015, 7, 10, 0, 0, 0, 0, time.Local), + } + + var orderedDates []time.Time + From(dates). + OrderByT( + func(date time.Time) int { + return date.Year() + }). + ThenByDescendingT( + func(date time.Time) int { return int(date.Month()) }, + ). + ToSlice(&orderedDates) + + for _, date := range orderedDates { + fmt.Println(date.Format("2006-Jan-02")) + } + // Output: + // 2013-May-04 + // 2014-Jul-11 + // 2015-Jul-10 + // 2015-Mar-23 + // 2015-Jan-02 + +} + +// The following code example demonstrates how to use ThenByT to perform +// a orders in a slice of dates by year, and then by day. +func ExampleOrderedQuery_ThenByT() { + dates := []time.Time{ + time.Date(2015, 3, 23, 0, 0, 0, 0, time.Local), + time.Date(2014, 7, 11, 0, 0, 0, 0, time.Local), + time.Date(2013, 5, 4, 0, 0, 0, 0, time.Local), + time.Date(2015, 1, 2, 0, 0, 0, 0, time.Local), + time.Date(2015, 7, 10, 0, 0, 0, 0, time.Local), + } + + var orderedDates []time.Time + From(dates). + OrderByT( + func(date time.Time) int { return date.Year() }, + ). + ThenByT( + func(date time.Time) int { return int(date.Day()) }, + ). + ToSlice(&orderedDates) + + for _, date := range orderedDates { + fmt.Println(date.Format("2006-Jan-02")) + } + // Output: + // 2013-May-04 + // 2014-Jul-11 + // 2015-Jan-02 + // 2015-Jul-10 + // 2015-Mar-23 + +} + +// The following code example demonstrates how to reverse +// the order of words in a string using AggregateT. +func ExampleQuery_AggregateT() { + sentence := "the quick brown fox jumps over the lazy dog" + // Split the string into individual words. + words := strings.Split(sentence, " ") + + // Prepend each word to the beginning of the + // new sentence to reverse the word order. + reversed := From(words).AggregateT( + func(workingSentence string, next string) string { return next + " " + workingSentence }, + ) + + fmt.Println(reversed) + + // Output: + // dog lazy the over jumps fox brown quick the +} + +// The following code example demonstrates how to use AggregateWithSeed function +func ExampleQuery_AggregateWithSeedT() { + + fruits := []string{"apple", "mango", "orange", "passionfruit", "grape"} + + // Determine whether any string in the array is longer than "banana". + longestName := From(fruits). + AggregateWithSeedT("banana", + func(longest, next string) string { + if len(next) > len(longest) { + return next + } + return longest + }, + ) + + fmt.Printf("The fruit with the longest name is %s.", longestName) + + // Output: + // + // The fruit with the longest name is passionfruit. + +} + +// The following code example demonstrates how to use AggregateWithSeedByT function +func ExampleQuery_AggregateWithSeedByT() { + input := []string{"apple", "mango", "orange", "passionfruit", "grape"} + + // Determine whether any string in the array is longer than "banana". + longestName := From(input).AggregateWithSeedByT("banana", + func(longest string, next string) string { + if len(longest) > len(next) { + return longest + } + return next + + }, + // Return the final result + func(result string) string { + return fmt.Sprintf("The fruit with the longest name is %s.", result) + }, + ) + + fmt.Println(longestName) + // Output: + // The fruit with the longest name is passionfruit. +} + +// The following code example demonstrates how to use AllT +// to get the students having all marks greater than 70. +func ExampleQuery_AllT() { + + type Student struct { + Name string + Marks []int + } + + students := []Student{ + {Name: "Hugo", Marks: []int{91, 88, 76, 93}}, + {Name: "Rick", Marks: []int{70, 73, 66, 90}}, + {Name: "Michael", Marks: []int{73, 80, 75, 88}}, + {Name: "Fadi", Marks: []int{82, 75, 66, 84}}, + {Name: "Peter", Marks: []int{67, 78, 70, 82}}, + } + + var approvedStudents []Student + From(students). + WhereT( + func(student Student) bool { + return From(student.Marks). + AllT( + func(mark int) bool { return mark > 70 }, + ) + }, + ). + ToSlice(&approvedStudents) + + //List of approved students + for _, student := range approvedStudents { + fmt.Println(student.Name) + } + + // Output: + // Hugo + // Michael +} + +// The following code example demonstrates how to use AnyWithT +// to get the students with any mark lower than 70. +func ExampleQuery_AnyWithT() { + type Student struct { + Name string + Marks []int + } + + students := []Student{ + {Name: "Hugo", Marks: []int{91, 88, 76, 93}}, + {Name: "Rick", Marks: []int{70, 73, 66, 90}}, + {Name: "Michael", Marks: []int{73, 80, 75, 88}}, + {Name: "Fadi", Marks: []int{82, 75, 66, 84}}, + {Name: "Peter", Marks: []int{67, 78, 70, 82}}, + } + + var studentsWithAnyMarkLt70 []Student + From(students). + WhereT( + func(student Student) bool { + return From(student.Marks). + AnyWithT( + func(mark int) bool { return mark < 70 }, + ) + }, + ). + ToSlice(&studentsWithAnyMarkLt70) + + //List of students with any mark lower than 70 + for _, student := range studentsWithAnyMarkLt70 { + fmt.Println(student.Name) + } + + // Output: + // Rick + // Fadi + // Peter + +} + +// The following code example demonstrates how to use CountWithT +// to count the elements in an slice that satisfy a condition. +func ExampleQuery_CountWithT() { + type Pet struct { + Name string + Vaccinated bool + } + + pets := []Pet{ + {Name: "Barley", Vaccinated: true}, + {Name: "Boots", Vaccinated: false}, + {Name: "Whiskers", Vaccinated: false}, + } + + numberUnvaccinated := From(pets). + CountWithT( + func(p Pet) bool { return p.Vaccinated == false }, + ) + + fmt.Printf("There are %d unvaccinated animals.", numberUnvaccinated) + + //Output: + //There are 2 unvaccinated animals. +} + +// The following code example demonstrates how to +// use DistinctByT to return distinct elements from a slice of structs. +func ExampleQuery_DistinctByT() { + type Product struct { + Name string + Code int + } + + products := []Product{ + {Name: "apple", Code: 9}, + {Name: "orange", Code: 4}, + {Name: "apple", Code: 9}, + {Name: "lemon", Code: 12}, + } + + //Exclude duplicates. + var noduplicates []Product + From(products). + DistinctByT( + func(item Product) int { return item.Code }, + ). + ToSlice(&noduplicates) + + for _, product := range noduplicates { + fmt.Printf("%s %d\n", product.Name, product.Code) + } + + // Output: + // apple 9 + // orange 4 + // lemon 12 +} + +// The following code example demonstrates how to use ExceptByT +func ExampleQuery_ExceptByT() { + type Product struct { + Name string + Code int + } + + fruits1 := []Product{ + {Name: "orange", Code: 4}, + {Name: "apple", Code: 9}, + {Name: "lemon", Code: 12}, + {Name: "apple", Code: 9}, + } + + fruits2 := []Product{ + {Name: "apple", Code: 9}, + } + + //Order and exclude duplicates. + var except []Product + From(fruits1). + ExceptByT(From(fruits2), + func(item Product) int { return item.Code }, + ). + ToSlice(&except) + + for _, product := range except { + fmt.Printf("%s %d\n", product.Name, product.Code) + } + + // Output: + // orange 4 + // lemon 12 + +} + +// The following code example demonstrates how to use FirstWithT +// to return the first element of an array that satisfies a condition. +func ExampleQuery_FirstWithT() { + numbers := []int{9, 34, 65, 92, 87, 435, 3, 54, 83, 23, 87, 435, 67, 12, 19} + + first := From(numbers). + FirstWithT( + func(item int) bool { return item > 80 }, + ) + + fmt.Println(first) + + // Output: + // 92 + +} + +// The following code example demonstrates how to use GroupByT +// to group the elements of a slice. +func ExampleQuery_GroupByT() { + + type Pet struct { + Name string + Age int + } + // Create a list of pets. + pets := []Pet{ + {Name: "Barley", Age: 8}, + {Name: "Boots", Age: 4}, + {Name: "Whiskers", Age: 1}, + {Name: "Daisy", Age: 4}, + } + + // Group the pets using Age as the key value + // and selecting only the pet's Name for each value. + var query []Group + From(pets).GroupByT( + func(p Pet) int { return p.Age }, + func(p Pet) string { return p.Name }, + ).OrderByT( + func(g Group) int { return g.Key.(int) }, + ).ToSlice(&query) + + for _, petGroup := range query { + fmt.Printf("%d\n", petGroup.Key) + for _, petName := range petGroup.Group { + fmt.Printf(" %s\n", petName) + } + + } + + // Output: + // 1 + // Whiskers + // 4 + // Boots + // Daisy + // 8 + // Barley +} + +// The following code example demonstrates how to use GroupJoinT +// to perform a grouped join on two slices. +func ExampleQuery_GroupJoinT() { + + type Person struct { + Name string + } + + type Pet struct { + Name string + Owner Person + } + + magnus := Person{Name: "Hedlund, Magnus"} + terry := Person{Name: "Adams, Terry"} + charlotte := Person{Name: "Weiss, Charlotte"} + + barley := Pet{Name: "Barley", Owner: terry} + boots := Pet{Name: "Boots", Owner: terry} + whiskers := Pet{Name: "Whiskers", Owner: charlotte} + daisy := Pet{Name: "Daisy", Owner: magnus} + + people := []Person{magnus, terry, charlotte} + pets := []Pet{barley, boots, whiskers, daisy} + + // Create a slice where each element is a KeyValue + // that contains a person's name as the key and a slice of strings + // of names of the pets they own as a value. + + q := []KeyValue{} + From(people). + GroupJoinT(From(pets), + func(p Person) Person { return p }, + func(p Pet) Person { return p.Owner }, + func(person Person, pets []Pet) KeyValue { + var petNames []string + From(pets). + SelectT( + func(pet Pet) string { return pet.Name }, + ). + ToSlice(&petNames) + return KeyValue{person.Name, petNames} + }, + ).ToSlice(&q) + + for _, obj := range q { + // Output the owner's name. + fmt.Printf("%s:\n", obj.Key) + // Output each of the owner's pet's names. + for _, petName := range obj.Value.([]string) { + fmt.Printf(" %s\n", petName) + } + } + + // Output: + // Hedlund, Magnus: + // Daisy + // Adams, Terry: + // Barley + // Boots + // Weiss, Charlotte: + // Whiskers +} + +// The following code example demonstrates how to use IntersectByT +// to return the elements that appear in each of two slices of products with same Code. +func ExampleQuery_IntersectByT() { + type Product struct { + Name string + Code int + } + + store1 := []Product{ + {Name: "orange", Code: 4}, + {Name: "apple", Code: 9}, + } + + store2 := []Product{ + {Name: "lemon", Code: 12}, + {Name: "apple", Code: 9}, + } + + var duplicates []Product + From(store1). + IntersectByT(From(store2), + func(p Product) int { return p.Code }, + ). + ToSlice(&duplicates) + + for _, p := range duplicates { + fmt.Println(p.Name, "", p.Code) + } + + // Output: + // apple 9 + +} + +// The following code example demonstrates how to use JoinT +// to perform an inner join of two slices based on a common key. +func ExampleQuery_JoinT() { + type Person struct { + Name string + } + + type Pet struct { + Name string + Owner Person + } + + magnus := Person{Name: "Hedlund, Magnus"} + terry := Person{Name: "Adams, Terry"} + charlotte := Person{Name: "Weiss, Charlotte"} + + barley := Pet{Name: "Barley", Owner: terry} + boots := Pet{Name: "Boots", Owner: terry} + whiskers := Pet{Name: "Whiskers", Owner: charlotte} + daisy := Pet{Name: "Daisy", Owner: magnus} + + people := []Person{magnus, terry, charlotte} + pets := []Pet{barley, boots, whiskers, daisy} + + // Create a list of Person-Pet pairs where + // each element is an anonymous type that contains a + // Pet's name and the name of the Person that owns the Pet. + + query := []string{} + From(people). + JoinT(From(pets), + func(person Person) Person { return person }, + func(pet Pet) Person { return pet.Owner }, + func(person Person, pet Pet) string { return fmt.Sprintf("%s - %s", person.Name, pet.Name) }, + ).ToSlice(&query) + + for _, line := range query { + fmt.Println(line) + } + //Output: + //Hedlund, Magnus - Daisy + //Adams, Terry - Barley + //Adams, Terry - Boots + //Weiss, Charlotte - Whiskers +} + +// The following code example demonstrates how to use LastWithT +// to return the last element of an array. +func ExampleQuery_LastWithT() { + numbers := []int{9, 34, 65, 92, 87, 435, 3, 54, + 83, 23, 87, 67, 12, 19} + + last := From(numbers). + LastWithT( + func(n int) bool { return n > 80 }, + ) + + fmt.Println(last) + + //Output: + //87 + +} + +// The following code example demonstrates how to use OrderByDescendingT to order an slice. +func ExampleQuery_OrderByDescendingT() { + type Player struct { + Name string + Points int64 + } + + players := []Player{ + {Name: "Hugo", Points: 4757}, + {Name: "Rick", Points: 7365}, + {Name: "Michael", Points: 2857}, + {Name: "Fadi", Points: 85897}, + {Name: "Peter", Points: 48576}, + } + + //Order and get the top 3 players + var top3Players []KeyValue + From(players). + OrderByDescendingT( + func(p Player) int64 { return p.Points }, + ). + Take(3). + SelectIndexedT( + func(i int, p Player) KeyValue { return KeyValue{Key: i + 1, Value: p} }, + ). + ToSlice(&top3Players) + + for _, rank := range top3Players { + fmt.Printf( + "Rank: #%d - Player: %s - Points: %d\n", + rank.Key, + rank.Value.(Player).Name, + rank.Value.(Player).Points, + ) + + } + // Output: + // Rank: #1 - Player: Fadi - Points: 85897 + // Rank: #2 - Player: Peter - Points: 48576 + // Rank: #3 - Player: Rick - Points: 7365 +} + +// The following code example demonstrates how to use OrderByT +// to sort the elements of a slice. +func ExampleQuery_OrderByT() { + type Pet struct { + Name string + Age int + } + // Create a list of pets. + pets := []Pet{ + {Name: "Barley", Age: 8}, + {Name: "Boots", Age: 4}, + {Name: "Whiskers", Age: 1}, + {Name: "Daisy", Age: 4}, + } + + var orderedPets []Pet + From(pets). + OrderByT( + func(pet Pet) int { return pet.Age }, + ). + ToSlice(&orderedPets) + + for _, pet := range orderedPets { + fmt.Println(pet.Name, "-", pet.Age) + } + + // Output: + // Whiskers - 1 + // Boots - 4 + // Daisy - 4 + // Barley - 8 +} + +// The following code example demonstrates how to use SelectT +// to project over a slice. +func ExampleQuery_SelectT() { + squares := []int{} + + Range(1, 10). + SelectT( + func(x int) int { return x * x }, + ). + ToSlice(&squares) + + fmt.Println(squares) + // Output: + // [1 4 9 16 25 36 49 64 81 100] +} + +// The following code example demonstrates how to use SelectIndexedT +// to determine if the value in a slice of int match their position in the slice. +func ExampleQuery_SelectIndexedT() { + numbers := []int{5, 4, 1, 3, 9, 8, 6, 7, 2, 0} + + var numsInPlace []KeyValue + + From(numbers). + SelectIndexedT( + func(index, num int) KeyValue { return KeyValue{Key: num, Value: (num == index)} }, + ). + ToSlice(&numsInPlace) + + fmt.Println("Number: In-place?") + for _, n := range numsInPlace { + fmt.Printf("%d: %t\n", n.Key, n.Value) + } + + // Output: + // Number: In-place? + // 5: false + // 4: false + // 1: false + // 3: true + // 9: false + // 8: false + // 6: true + // 7: true + // 2: false + // 0: false + +} + +// The following code example demonstrates how to use SelectManyT +// to perform a one-to-many projection over a slice +func ExampleQuery_SelectManyByT() { + + 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} + var results []string + From(people). + SelectManyByT( + func(person Person) Query { return From(person.Pets) }, + func(pet Pet, person Person) interface{} { + return fmt.Sprintf("Owner: %s, Pet: %s", person.Name, pet.Name) + }, + ). + ToSlice(&results) + + 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 +func ExampleQuery_SelectManyT() { + sentences := []string{ + "the quick brown fox jumps over the lazy dog", + "pack my box with five dozen liquor jugs", + "several fabulous dixieland jazz groups played with quick tempo", + "back in my quaint garden jaunty zinnias vie with flaunting phlox", + "five or six big jet planes zoomed quickly by the new tower", + "I quickly explained that many big jobs involve few hazards", + "The wizard quickly jinxed the gnomes before they vaporized", + } + + var results []string + From(sentences). + //Split the sentences in words + SelectManyT(func(sentence string) Query { + return From(strings.Split(sentence, " ")) + }). + //Grouping by word + GroupByT( + func(word string) string { return word }, + func(word string) string { return word }, + ). + //Ordering by word counts + OrderByDescendingT(func(wordGroup Group) int { + return len(wordGroup.Group) + }). + //Then order by word + ThenByT(func(wordGroup Group) string { + return wordGroup.Key.(string) + }). + //Take the top 5 + Take(5). + //Project the words using the index as rank + SelectIndexedT(func(index int, wordGroup Group) string { + return fmt.Sprintf("Rank: #%d, Word: %s, Counts: %d", index+1, wordGroup.Key, len(wordGroup.Group)) + }). + ToSlice(&results) + + for _, result := range results { + fmt.Println(result) + } + + // Output: + // Rank: #1, Word: the, Counts: 4 + // Rank: #2, Word: quickly, Counts: 3 + // Rank: #3, Word: with, Counts: 3 + // Rank: #4, Word: big, Counts: 2 + // Rank: #5, Word: five, Counts: 2 +} + +// 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_SelectManyIndexedT() { + 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 + + From(logFiles). + SelectManyIndexedT(func(fileIndex int, file LogFile) Query { + return From(file.Lines). + SelectIndexedT(func(lineIndex int, line string) string { + return fmt.Sprintf("File:[%d] - %s => line: %d - %s", fileIndex+1, file.Name, lineIndex+1, line) + }) + }). + ToSlice(&results) + + for _, result := range results { + 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. +func ExampleQuery_SelectManyByIndexedT() { + 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} + var results []string + + From(people). + SelectManyByIndexedT( + func(index int, person Person) Query { + return From(person.Pets). + SelectT(func(pet Pet) string { + return fmt.Sprintf("%d - %s", index, pet.Name) + }) + }, + func(indexedPet string, person Person) string { + return fmt.Sprintf("Pet: %s, Owner: %s", indexedPet, person.Name) + }, + ). + ToSlice(&results) + + for _, result := range results { + fmt.Println(result) + } + + // Output: + // Pet: 0 - Daisy, Owner: Hedlund, Magnus + // Pet: 1 - Barley, Owner: Adams, Terry + // Pet: 1 - Boots, Owner: Adams, Terry + // Pet: 2 - Whiskers, Owner: Weiss, Charlotte + +} + +//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"} + + fruit := From(fruits). + SingleWithT( + func(f string) bool { return len(f) > 10 }, + ) + + fmt.Println(fruit) + // Output: + // passionfruit +} + +// The following code example demonstrates how to use SkipWhileT +// to skip elements of an array as long as a condition is true. +func ExampleQuery_SkipWhileT() { + grades := []int{59, 82, 70, 56, 92, 98, 85} + var lowerGrades []int + From(grades). + OrderByDescendingT( + func(g int) int { return g }, + ). + SkipWhileT( + func(g int) bool { return g >= 80 }, + ). + ToSlice(&lowerGrades) + + //"All grades below 80: + fmt.Println(lowerGrades) + // Output: + // [70 59 56] +} + +// The following code example demonstrates how to use SkipWhileIndexedT +// to skip elements of an array as long as a condition that depends +// on the element's index is true. +func ExampleQuery_SkipWhileIndexedT() { + amounts := []int{5000, 2500, 9000, 8000, 6500, 4000, 1500, 5500} + + var query []int + From(amounts). + SkipWhileIndexedT( + func(index int, amount int) bool { return amount > index*1000 }, + ). + ToSlice(&query) + + fmt.Println(query) + // Output: + // [4000 1500 5500] + +} + +// The following code example demonstrates how to use SortT +// to order elements of an slice. +func ExampleQuery_SortT() { + type Pet struct { + Name string + Age int + } + // Create a list of pets. + pets := []Pet{ + {Name: "Barley", Age: 8}, + {Name: "Boots", Age: 4}, + {Name: "Whiskers", Age: 1}, + {Name: "Daisy", Age: 4}, + } + + orderedPets := []Pet{} + From(pets). + SortT( + func(pet1 Pet, pet2 Pet) bool { return pet1.Age < pet2.Age }, + ). + ToSlice(&orderedPets) + + for _, pet := range orderedPets { + fmt.Println(pet.Name, "-", pet.Age) + } + + // Output: + // Whiskers - 1 + // Boots - 4 + // Daisy - 4 + // Barley - 8 + +} + +// The following code example demonstrates how to use TakeWhileT +// to return elements from the start of a slice. +func ExampleQuery_TakeWhileT() { + fruits := []string{"apple", "banana", "mango", "orange", "passionfruit", "grape"} + + var query []string + From(fruits). + TakeWhileT( + func(fruit string) bool { return fruit != "orange" }, + ). + ToSlice(&query) + + fmt.Println(query) + + // Output: + // [apple banana mango] +} + +// The following code example demonstrates how to use TakeWhileIndexedT +// to return elements from the start of a slice as long as +// a condition that uses the element's index is true. +func ExampleQuery_TakeWhileIndexedT() { + + fruits := []string{"apple", "passionfruit", "banana", "mango", + "orange", "blueberry", "grape", "strawberry"} + + var query []string + From(fruits). + TakeWhileIndexedT( + func(index int, fruit string) bool { return len(fruit) >= index }, + ). + ToSlice(&query) + + fmt.Println(query) + + // Output: + // [apple passionfruit banana mango orange blueberry] +} + +// The following code example demonstrates how to use ToMapBy +// by using a key and value selectors to populate a map. +func ExampleQuery_ToMapByT() { + type Product struct { + Name string + Code int + } + + products := []Product{ + {Name: "orange", Code: 4}, + {Name: "apple", Code: 9}, + {Name: "lemon", Code: 12}, + {Name: "apple", Code: 9}, + } + + map1 := map[int]string{} + From(products). + ToMapByT(&map1, + func(item Product) int { return item.Code }, + func(item Product) string { return item.Name }, + ) + + fmt.Println(map1[4]) + fmt.Println(map1[9]) + fmt.Println(map1[12]) + + // Output: + // orange + // apple + // lemon +} + +// The following code example demonstrates how to use WhereT +// to filter a slices. +func ExampleQuery_WhereT() { + fruits := []string{"apple", "passionfruit", "banana", "mango", + "orange", "blueberry", "grape", "strawberry"} + var query []string + From(fruits). + WhereT( + func(f string) bool { return len(f) > 6 }, + ). + ToSlice(&query) + + fmt.Println(query) + + // Output: + // [passionfruit blueberry strawberry] +} + +// The following code example demonstrates how to use WhereIndexedT +// to filter a slice based on a predicate that involves the index of each element. +func ExampleQuery_WhereIndexedT() { + numbers := []int{0, 30, 20, 15, 90, 85, 40, 75} + + var query []int + From(numbers). + WhereIndexedT( + func(index int, number int) bool { return number <= index*10 }, + ). + ToSlice(&query) + + fmt.Println(query) + + // Output: + // [0 15 40] +} + +// The following code example demonstrates how to use the Zip +// method to merge two slices. +func ExampleQuery_ZipT() { + number := []int{1, 2, 3, 4, 5} + words := []string{"one", "two", "three"} + + q := From(number). + ZipT(From(words), + func(a int, b string) []interface{} { return []interface{}{a, b} }, + ) + + fmt.Println(q.Results()) + + // Output: + // [[1 one] [2 two] [3 three]] +} diff --git a/excepttyped.go b/excepttyped.go new file mode 100644 index 0000000..4d8be73 --- /dev/null +++ b/excepttyped.go @@ -0,0 +1,24 @@ +package linq + +// ExceptByT is the typed version of ExceptByT. +// +// NOTE: ExceptBy method has better performance than ExceptByT +// +// selectorFn is of a type "func(TSource) TSource" +// +func (q Query) ExceptByT(q2 Query, selectorFn interface{}) Query { + + selectorGenericFunc, err := newGenericFunc( + "ExceptByT", "selectorFn", selectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + selectorFunc := func(item interface{}) interface{} { + return selectorGenericFunc.Call(item) + } + + return q.ExceptBy(q2, selectorFunc) +} diff --git a/excepttyped_test.go b/excepttyped_test.go new file mode 100644 index 0000000..9415e82 --- /dev/null +++ b/excepttyped_test.go @@ -0,0 +1,38 @@ +package linq + +import "testing" + +func TestExceptByT(t *testing.T) { + tests := []struct { + input1 interface{} + input2 interface{} + selector interface{} + want []interface{} + }{ + {[]int{1, 2, 3, 4, 5, 1, 2, 5}, []int{1}, func(i interface{}) interface{} { + return i.(int) % 2 + }, []interface{}{2, 4, 2}}, + {[]int{1, 2, 3, 4, 5, 1, 2, 5}, []int{1}, func(i int) int { + return i % 2 + }, []interface{}{2, 4, 2}}, + } + + for _, test := range tests { + if q := From(test.input1).ExceptByT(From(test.input2), test.selector); !validateQuery(q, test.want) { + t.Errorf("From(%v).ExceptBy(%v)=%v expected %v", test.input1, test.input2, toSlice(q), test.want) + } + } +} + +func TestExceptByT_PanicWhenFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + + 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/genericfunc.go b/genericfunc.go new file mode 100644 index 0000000..ba1255d --- /dev/null +++ b/genericfunc.go @@ -0,0 +1,137 @@ +package linq + +import ( + "fmt" + "reflect" + "strings" +) + +// genericType represents a any reflect.Type +type genericType int + +var genericTp = reflect.TypeOf(new(genericType)).Elem() + +// functionCache keeps genericFunc reflection objects in cache +type functionCache struct { + MethodName string + ParamName string + FnValue reflect.Value + FnType reflect.Type + TypesIn []reflect.Type + TypesOut []reflect.Type +} + +// genericFunc is a type used to validate and call dynamic functions +type genericFunc struct { + Cache *functionCache +} + +// Call calls a dynamic function +func (g *genericFunc) Call(params ...interface{}) interface{} { + paramsIn := make([]reflect.Value, len(params)) + for i, param := range params { + paramsIn[i] = reflect.ValueOf(param) + } + paramsOut := g.Cache.FnValue.Call(paramsIn) + if len(paramsOut) >= 1 { + return paramsOut[0].Interface() + } + return nil +} + +// newGenericFunc instantiates a new genericFunc pointer +func newGenericFunc(methodName, paramName string, fn interface{}, validateFunc func(*functionCache) error) (*genericFunc, error) { + cache := &functionCache{} + cache.FnValue = reflect.ValueOf(fn) + + if cache.FnValue.Kind() != reflect.Func { + return nil, fmt.Errorf("%s: parameter [%s] is not a function type. It is a '%s'", methodName, paramName, cache.FnValue.Type()) + } + cache.MethodName = methodName + cache.ParamName = paramName + cache.FnType = cache.FnValue.Type() + numTypesIn := cache.FnType.NumIn() + cache.TypesIn = make([]reflect.Type, numTypesIn) + for i := 0; i < numTypesIn; i++ { + cache.TypesIn[i] = cache.FnType.In(i) + } + + numTypesOut := cache.FnType.NumOut() + cache.TypesOut = make([]reflect.Type, numTypesOut) + for i := 0; i < numTypesOut; i++ { + cache.TypesOut[i] = cache.FnType.Out(i) + } + if err := validateFunc(cache); err != nil { + return nil, err + } + + return &genericFunc{Cache: cache}, nil +} + +// simpleParamValidator creates a function to validate genericFunc based in the In and Out function parameters +func simpleParamValidator(In []reflect.Type, Out []reflect.Type) func(cache *functionCache) error { + return func(cache *functionCache) error { + var isValid = func() bool { + if In != nil { + if len(In) != len(cache.TypesIn) { + return false + } + for i, paramIn := range In { + if paramIn != genericTp && paramIn != cache.TypesIn[i] { + return false + } + } + } + if Out != nil { + if len(Out) != len(cache.TypesOut) { + return false + } + for i, paramOut := range Out { + if paramOut != genericTp && paramOut != cache.TypesOut[i] { + return false + } + } + } + return true + } + + if !isValid() { + return fmt.Errorf("%s: parameter [%s] has a invalid function signature. Expected: '%s', actual: '%s'", cache.MethodName, cache.ParamName, formatFnSignature(In, Out), formatFnSignature(cache.TypesIn, cache.TypesOut)) + } + return nil + } +} + +// newElemTypeSlice creates a slice of items elem types +func newElemTypeSlice(items ...interface{}) []reflect.Type { + typeList := make([]reflect.Type, len(items)) + for i, item := range items { + typeItem := reflect.TypeOf(item) + if typeItem.Kind() == reflect.Ptr { + typeList[i] = typeItem.Elem() + } + } + return typeList +} + +// formatFnSignature formats the func signature based in the parameters types +func formatFnSignature(In []reflect.Type, Out []reflect.Type) string { + paramInNames := make([]string, len(In)) + for i, typeIn := range In { + if typeIn == genericTp { + paramInNames[i] = "T" + } else { + paramInNames[i] = typeIn.String() + } + + } + paramOutNames := make([]string, len(Out)) + for i, typeOut := range Out { + if typeOut == genericTp { + paramOutNames[i] = "T" + } else { + paramOutNames[i] = typeOut.String() + } + } + return fmt.Sprintf("func(%s)%s", strings.Join(paramInNames, ","), strings.Join(paramOutNames, ",")) +} diff --git a/genericfunc_test.go b/genericfunc_test.go new file mode 100644 index 0000000..20c36f1 --- /dev/null +++ b/genericfunc_test.go @@ -0,0 +1,132 @@ +package linq + +import ( + "errors" + "reflect" + "testing" +) + +func TestNewGenericFunc(t *testing.T) { + + tests := []struct { + methodName string + paramName string + function interface{} + validationFunc func(*functionCache) error + exception error + }{ + { // A valid function + "TestNewGenericFunc", "test1", + func(item int) bool { return item > 10 }, + simpleParamValidator(newElemTypeSlice(new(int)), newElemTypeSlice(new(bool))), + nil, + }, + { // A valid generic function + "TestNewGenericFunc", "test1", + func(item int) bool { return item > 10 }, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))), + nil, + }, + { //returns error when the function parameter has not the function kind + "TestNewGenericFunc", "test2", + "Not a function", + simpleParamValidator(nil, []reflect.Type{}), + errors.New("TestNewGenericFunc: parameter [test2] is not a function type. It is a 'string'"), + }, + { // Returns error when expected parameters number are not equal + "TestNewGenericFunc", "test3", + func(idx, item int) {}, + simpleParamValidator(newElemTypeSlice(new(int)), []reflect.Type{}), + errors.New("TestNewGenericFunc: parameter [test3] has a invalid function signature. Expected: 'func(int)', actual: 'func(int,int)'"), + }, + { // Returns error when expected parameters types are not equal + "TestNewGenericFunc", "test4", + func(items ...int) bool { return false }, + simpleParamValidator(newElemTypeSlice(new([]bool)), newElemTypeSlice(new(bool))), + errors.New("TestNewGenericFunc: parameter [test4] has a invalid function signature. Expected: 'func([]bool)bool', actual: 'func([]int)bool'"), + }, + { // Returns error when expected returns number are not equal + "TestNewGenericFunc", "test5", + func(item int) bool { return item > 10 }, + simpleParamValidator(newElemTypeSlice(new(int)), []reflect.Type{}), + errors.New("TestNewGenericFunc: parameter [test5] has a invalid function signature. Expected: 'func(int)', actual: 'func(int)bool'"), + }, + { // Returns error when expected return types are not equal + "TestNewGenericFunc", "test6", + func(items ...int) bool { return false }, + simpleParamValidator(newElemTypeSlice(new([]int)), newElemTypeSlice(new(int64))), + errors.New("TestNewGenericFunc: parameter [test6] has a invalid function signature. Expected: 'func([]int)int64', actual: 'func([]int)bool'"), + }, + { // Returns error when expected return types are not equal + "TestNewGenericFunc", "test7", + func(items ...int) bool { return false }, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(int64))), + errors.New("TestNewGenericFunc: parameter [test7] has a invalid function signature. Expected: 'func(T)int64', actual: 'func([]int)bool'"), + }, + } + + for _, test := range tests { + _, err := newGenericFunc(test.methodName, test.paramName, test.function, test.validationFunc) + if !(err == test.exception || err.Error() == test.exception.Error()) { + t.Errorf("Validate expect error: %s, actual: %s", test.exception, err) + } + } +} + +func TestCall(t *testing.T) { + tests := []struct { + methodName string + paramName string + function interface{} + validationFunc func(*functionCache) error + fnParameter interface{} + result interface{} + exception error + }{ + { // A valid function and parameters + "TestCall", "test1", + func(i int) int { return i * 3 }, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(int))), + 3, + 9, + nil, + }, + { // Returns error when the required type doesn't match with the specification + "TestCall", "test2", + func(i int) int { return i * 3 }, + simpleParamValidator(newElemTypeSlice(new(int)), newElemTypeSlice(new(int))), + "not a int", + 9, + errors.New("reflect: Call using string as type int"), + }, + { // A valid function and parameters + "TestCall", "test3", + func(i int) {}, + simpleParamValidator(newElemTypeSlice(new(genericType)), []reflect.Type{}), + 3, + nil, + nil, + }, + } + + for _, test := range tests { + func() { + defer func() { + r := recover() + if !(r == test.exception || r == test.exception.Error()) { + t.Errorf("expect error: nil, actual: %s", r) + } + }() + dynaFunc, err := newGenericFunc(test.methodName, test.paramName, test.function, test.validationFunc) + if err != nil { + t.Errorf("expect error: nil, actual: %s", err) + } + result := dynaFunc.Call(test.fnParameter) + + if result != nil && result != test.result { + t.Errorf("expect result: %d, actual: %d", test.result, result) + } + }() + } + +} diff --git a/groupbytyped.go b/groupbytyped.go new file mode 100644 index 0000000..b1fc36b --- /dev/null +++ b/groupbytyped.go @@ -0,0 +1,39 @@ +package linq + +// GroupByT is the typed version of GroupBy. +// +// NOTE: GroupBy method has better performance than GroupByT +// +// keySelectorFn is of a type "func(TSource) TKey" +// +// elementSelectorFn is of a type "func(TSource) TElement" +// +func (q Query) GroupByT(keySelectorFn interface{}, elementSelectorFn interface{}) Query { + + keySelectorGenericFunc, err := newGenericFunc( + "GroupByT", "keySelectorFn", keySelectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + keySelectorFunc := func(item interface{}) interface{} { + return keySelectorGenericFunc.Call(item) + } + + elementSelectorGenericFunc, err := newGenericFunc( + "GroupByT", "elementSelectorFn", elementSelectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + elementSelectorFunc := func(item interface{}) interface{} { + return elementSelectorGenericFunc.Call(item) + + } + + return q.GroupBy(keySelectorFunc, elementSelectorFunc) +} diff --git a/groupbytyped_test.go b/groupbytyped_test.go new file mode 100644 index 0000000..91531ad --- /dev/null +++ b/groupbytyped_test.go @@ -0,0 +1,92 @@ +package linq + +import ( + "reflect" + "testing" +) + +func TestGroupByT(t *testing.T) { + + tests := []struct { + input interface{} + keySelector interface{} + elementSelector interface{} + wantEven []interface{} + wantOdd []interface{} + }{ + { + []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, + func(i interface{}) interface{} { return i.(int) % 2 }, + func(i interface{}) interface{} { return i.(int) }, + []interface{}{2, 4, 6, 8}, []interface{}{1, 3, 5, 7, 9}, + }, + { + []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, + func(i int) int { return i % 2 }, + func(i int) int { return i }, + []interface{}{2, 4, 6, 8}, []interface{}{1, 3, 5, 7, 9}, + }, + } + for _, test := range tests { + q := From(test.input).GroupByT( + test.keySelector, + test.elementSelector, + ) + + next := q.Iterate() + eq := true + for item, ok := next(); ok; item, ok = next() { + group := item.(Group) + switch group.Key.(int) { + case 0: + if !reflect.DeepEqual(group.Group, test.wantEven) { + eq = false + } + case 1: + if !reflect.DeepEqual(group.Group, test.wantOdd) { + eq = false + } + default: + eq = false + } + } + + if !eq { + t.Errorf("From(%v).GroupByT()=%v", test.input, toSlice(q)) + } + } +} + +func TestGroupByT_PanicWhenKeyFunctionParameterInTypeDoesntMatch(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + + var r []int + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).GroupByT( + func(i, j int) bool { return true }, + func(i int) int { return i }, + ).ToSlice(&r) +} + +func TestGroupByT_PanicWhenElementSelectorFunctionParameterInTypeDoesntMatch(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + + var r []int + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).GroupByT( + func(i int) bool { return true }, + func(i, j int) int { return i }, + ).ToSlice(&r) +} diff --git a/groupjointyped.go b/groupjointyped.go new file mode 100644 index 0000000..edbe36c --- /dev/null +++ b/groupjointyped.go @@ -0,0 +1,60 @@ +package linq + +import "reflect" + +// GroupJoinT is the typed version of GroupJoin. +// +// NOTE: GroupJoin method has better performance than GroupJoinT +// +// inner: The query to join to the outer query. +// +// outerKeySelectorFn is of a type "func(TOuter) TKey" +// +// innerKeySelectorFn is of a type "func(TInner) TKey" +// +// resultSelectorFn: is of a type "func(TOuter, inners []TInner) TResult" +// +func (q Query) GroupJoinT(inner Query, outerKeySelectorFn interface{}, innerKeySelectorFn interface{}, resultSelectorFn interface{}) Query { + + outerKeySelectorGenericFunc, err := newGenericFunc( + "GroupJoinT", "outerKeySelectorFn", outerKeySelectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + outerKeySelectorFunc := func(item interface{}) interface{} { + return outerKeySelectorGenericFunc.Call(item) + } + + innerKeySelectorFuncGenericFunc, err := newGenericFunc( + "GroupJoinT", "innerKeySelectorFn", innerKeySelectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + innerKeySelectorFunc := func(item interface{}) interface{} { + return innerKeySelectorFuncGenericFunc.Call(item) + } + + resultSelectorGenericFunc, err := newGenericFunc( + "GroupJoinT", "resultSelectorFn", resultSelectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + resultSelectorFunc := func(outer interface{}, inners []interface{}) interface{} { + innerSliceType := reflect.MakeSlice(resultSelectorGenericFunc.Cache.TypesIn[1], 0, 0) + innersSlicePointer := reflect.New(innerSliceType.Type()) + From(inners).ToSlice(innersSlicePointer.Interface()) + innersTyped := reflect.Indirect(innersSlicePointer).Interface() + return resultSelectorGenericFunc.Call(outer, innersTyped) + } + + return q.GroupJoin(inner, outerKeySelectorFunc, innerKeySelectorFunc, resultSelectorFunc) +} diff --git a/groupjointyped_test.go b/groupjointyped_test.go new file mode 100644 index 0000000..58e4004 --- /dev/null +++ b/groupjointyped_test.go @@ -0,0 +1,82 @@ +package linq + +import "testing" + +func TestGroupJoinT(t *testing.T) { + tests := []struct { + outer interface{} + inner interface{} + outerKeySelector interface{} + innerKeySelector interface{} + resultSelector interface{} + want []interface{} + }{ + { + []int{0, 1, 2}, []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, + func(i int) int { return i }, + func(i int) int { return i % 2 }, + func(outer int, inners []int) KeyValue { return KeyValue{outer, len(inners)} }, + []interface{}{KeyValue{0, 4}, KeyValue{1, 5}, KeyValue{2, 0}}, + }, + } + + for _, test := range tests { + if q := From(test.outer).GroupJoinT(From(test.inner), test.outerKeySelector, test.innerKeySelector, test.resultSelector); !validateQuery(q, test.want) { + t.Errorf("From().GroupJoin()=%v expected %v", toSlice(q), test.want) + } + } +} + +func TestGroupJoinT_PanicWhenOuterKeySelectorFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + + From([]int{0, 1, 2}).GroupJoinT( + From([]int{1, 2, 3, 4, 5, 6, 7, 8, 9}), + func(i, j int) int { return i }, + func(i int) int { return i % 2 }, + func(outer int, inners []int) KeyValue { return KeyValue{outer, len(inners)} }, + ) +} + +func TestGroupJoinT_PanicWhenInnerKeySelectorFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + + From([]int{0, 1, 2}).GroupJoinT( + From([]int{1, 2, 3, 4, 5, 6, 7, 8, 9}), + func(i int) int { return i }, + func(i, j int) int { return i % 2 }, + func(outer int, inners []int) KeyValue { return KeyValue{outer, len(inners)} }, + ) +} + +func TestGroupJoinT_PanicWhenResultSelectorFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + + From([]int{0, 1, 2}).GroupJoinT( + From([]int{1, 2, 3, 4, 5, 6, 7, 8, 9}), + func(i int) int { return i }, + func(i int) int { return i % 2 }, + func(outer, j int, inners []int) KeyValue { return KeyValue{outer, len(inners)} }, + ) +} diff --git a/intersecttyped.go b/intersecttyped.go new file mode 100644 index 0000000..359f1bc --- /dev/null +++ b/intersecttyped.go @@ -0,0 +1,24 @@ +package linq + +// IntersectByT is the typed version of IntersectBy. +// +// NOTE: IntersectBy method has better performance than IntersectByT +// +// selectorFn is of a type "func(TSource) TSource" +// +func (q Query) IntersectByT(q2 Query, selectorFn interface{}) Query { + + selectorGenericFunc, err := newGenericFunc( + "IntersectByT", "selectorFn", selectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + selectorFunc := func(item interface{}) interface{} { + return selectorGenericFunc.Call(item) + } + + return q.IntersectBy(q2, selectorFunc) +} diff --git a/intersecttyped_test.go b/intersecttyped_test.go new file mode 100644 index 0000000..6dec0fe --- /dev/null +++ b/intersecttyped_test.go @@ -0,0 +1,45 @@ +package linq + +import "testing" + +func TestIntersectByT(t *testing.T) { + tests := []struct { + input1 interface{} + input2 interface{} + selector interface{} + want []interface{} + }{ + { + []int{5, 7, 8}, []int{1, 4, 7, 9, 12, 3}, func(i interface{}) interface{} { + return i.(int) % 2 + }, []interface{}{5, 8}, + }, + + { + []int{5, 7, 8}, []int{1, 4, 7, 9, 12, 3}, func(i int) int { + return i % 2 + }, []interface{}{5, 8}, + }, + } + + for _, test := range tests { + if q := From(test.input1).IntersectByT(From(test.input2), test.selector); !validateQuery(q, test.want) { + t.Errorf("From(%v).IntersectBy(%v)=%v expected %v", test.input1, test.input2, toSlice(q), test.want) + } + } +} + +func TestIntersectByT_PanicWhenFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + + From([]int{5, 7, 8}).IntersectByT(From([]int{1, 4, 7, 9, 12, 3}), func(i, x int) int { + return i % 2 + }) +} diff --git a/jointyped.go b/jointyped.go new file mode 100644 index 0000000..ae02c0b --- /dev/null +++ b/jointyped.go @@ -0,0 +1,57 @@ +package linq + +// JoinT is the typed version of Join. +// +// NOTE: Join method has better performance than JoinT +// +// outerKeySelectorFn is of a type "func(TOuter) TKey" +// +// innerKeySelectorFn is of a type "func(TInner) TKey" +// +// resultSelectorFn is of a type "func(TOuter,TInner) TResult" +// +func (q Query) JoinT(inner Query, + outerKeySelectorFn interface{}, + innerKeySelectorFn interface{}, + resultSelectorFn interface{}, +) Query { + + outerKeySelectorGenericFunc, err := newGenericFunc( + "JoinT", "outerKeySelectorFn", outerKeySelectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + outerKeySelectorFunc := func(item interface{}) interface{} { + return outerKeySelectorGenericFunc.Call(item) + } + + innerKeySelectorFuncGenericFunc, err := newGenericFunc( + "JoinT", "innerKeySelectorFn", + innerKeySelectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + innerKeySelectorFunc := func(item interface{}) interface{} { + return innerKeySelectorFuncGenericFunc.Call(item) + } + + resultSelectorGenericFunc, err := newGenericFunc( + "JoinT", "resultSelectorFn", resultSelectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + resultSelectorFunc := func(outer interface{}, inner interface{}) interface{} { + return resultSelectorGenericFunc.Call(outer, inner) + } + + return q.Join(inner, outerKeySelectorFunc, innerKeySelectorFunc, resultSelectorFunc) +} diff --git a/jointyped_test.go b/jointyped_test.go new file mode 100644 index 0000000..187eaa7 --- /dev/null +++ b/jointyped_test.go @@ -0,0 +1,111 @@ +package linq + +import "testing" + +func TestJoinT(t *testing.T) { + outer := []int{0, 1, 2, 3, 4, 5, 8} + inner := []int{1, 2, 1, 4, 7, 6, 7, 2} + want := []interface{}{ + KeyValue{1, 1}, + KeyValue{1, 1}, + KeyValue{2, 2}, + KeyValue{2, 2}, + KeyValue{4, 4}, + } + + q := From(outer).JoinT( + From(inner), + func(i interface{}) interface{} { return i }, + func(i interface{}) interface{} { return i }, + func(outer interface{}, inner interface{}) interface{} { + return KeyValue{outer, inner} + }) + + if !validateQuery(q, want) { + t.Errorf("From().Join()=%v expected %v", toSlice(q), want) + } + + tests := []struct { + outer interface{} + inner interface{} + outerKeySelector interface{} + innerKeySelector interface{} + resultSelector interface{} + want []interface{} + }{ + { + []int{0, 1, 2, 3, 4, 5, 8}, []int{1, 2, 1, 4, 7, 6, 7, 2}, + func(i interface{}) interface{} { return i }, + func(i interface{}) interface{} { return i }, + func(outer interface{}, inner interface{}) interface{} { return KeyValue{outer, inner} }, + []interface{}{KeyValue{1, 1}, KeyValue{1, 1}, KeyValue{2, 2}, KeyValue{2, 2}, KeyValue{4, 4}}, + }, + { + []int{0, 1, 2, 3, 4, 5, 8}, []int{1, 2, 1, 4, 7, 6, 7, 2}, + func(i int) int { return i }, + func(i int) int { return i }, + func(outer int, inner int) KeyValue { return KeyValue{outer, inner} }, + []interface{}{KeyValue{1, 1}, KeyValue{1, 1}, KeyValue{2, 2}, KeyValue{2, 2}, KeyValue{4, 4}}, + }, + } + + for _, test := range tests { + if q := From(test.outer).JoinT(From(test.inner), test.outerKeySelector, test.innerKeySelector, test.resultSelector); !validateQuery(q, test.want) { + t.Errorf("From().Join()=%v expected %v", toSlice(q), test.want) + } + } +} + +func TestJoinT_PanicWhenOuterKeySelectorFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + + From([]int{0, 1, 2}).JoinT( + From([]int{1, 2, 3, 4, 5, 6, 7, 8, 9}), + func(i, j int) int { return i }, + func(i int) int { return i % 2 }, + func(outer int, inner int) KeyValue { return KeyValue{outer, inner} }, + ) +} + +func TestJoinT_PanicWhenInnerKeySelectorFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + + From([]int{0, 1, 2}).JoinT( + From([]int{1, 2, 3, 4, 5, 6, 7, 8, 9}), + func(i int) int { return i }, + func(i, j int) int { return i % 2 }, + func(outer int, inners []int) KeyValue { return KeyValue{outer, len(inners)} }, + ) +} + +func TestJoinT_PanicWhenResultSelectorFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + + From([]int{0, 1, 2}).JoinT( + From([]int{1, 2, 3, 4, 5, 6, 7, 8, 9}), + func(i int) int { return i }, + func(i int) int { return i % 2 }, + func(outer int, inner, j int) KeyValue { return KeyValue{outer, inner} }, + ) +} diff --git a/orderbytyped.go b/orderbytyped.go new file mode 100644 index 0000000..29b2db3 --- /dev/null +++ b/orderbytyped.go @@ -0,0 +1,119 @@ +package linq + +// OrderByT is the typed version of OrderBy. +// +// NOTE: OrderBy method has better performance than OrderByT +// +// selectorFn is of a type "func(TSource) TKey" +// +func (q Query) OrderByT(selectorFn interface{}) OrderedQuery { + + selectorGenericFunc, err := newGenericFunc( + "OrderByT", "selectorFn", selectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + selectorFunc := func(item interface{}) interface{} { + return selectorGenericFunc.Call(item) + } + + return q.OrderBy(selectorFunc) + +} + +// OrderByDescendingT is the typed version of OrderByDescending. +// +// NOTE: OrderByDescending method has better performance than OrderByDescendingT +// +// selectorFn is of a type "func(TSource) TKey" +// +func (q Query) OrderByDescendingT(selectorFn interface{}) OrderedQuery { + + selectorGenericFunc, err := newGenericFunc( + "OrderByDescendingT", "selectorFn", selectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + selectorFunc := func(item interface{}) interface{} { + return selectorGenericFunc.Call(item) + } + + return q.OrderByDescending(selectorFunc) +} + +// ThenByT is the typed version of ThenBy. +// +// NOTE: ThenBy method has better performance than ThenByT +// +// selectorFn is of a type "func(TSource) TKey" +// +func (oq OrderedQuery) ThenByT(selectorFn interface{}) OrderedQuery { + selectorGenericFunc, err := newGenericFunc( + "ThenByT", "selectorFn", selectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + selectorFunc := func(item interface{}) interface{} { + return selectorGenericFunc.Call(item) + } + + return oq.ThenBy(selectorFunc) +} + +// ThenByDescendingT is the typed version of ThenByDescending. +// +// NOTE: ThenByDescending method has better performance than ThenByDescendingT +// +// selectorFn is of a type "func(TSource) TKey" +// +func (oq OrderedQuery) ThenByDescendingT(selectorFn interface{}) OrderedQuery { + + selectorFunc, ok := selectorFn.(func(interface{}) interface{}) + if !ok { + selectorGenericFunc, err := newGenericFunc( + "ThenByDescending", "selectorFn", selectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + selectorFunc = func(item interface{}) interface{} { + return selectorGenericFunc.Call(item) + } + } + + return oq.ThenByDescending(selectorFunc) +} + +// SortT is the typed version of Sort. +// +// NOTE: Sort method has better performance than SortT +// +// lessFn is of a type "func(TSource,TSource) bool" +// +func (q Query) SortT(lessFn interface{}) Query { + + lessGenericFunc, err := newGenericFunc( + "SortT", "lessFn", lessFn, + simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(bool))), + ) + if err != nil { + panic(err) + } + + lessFunc := func(i, j interface{}) bool { + return lessGenericFunc.Call(i, j).(bool) + } + + return q.Sort(lessFunc) +} diff --git a/orderbytyped_test.go b/orderbytyped_test.go new file mode 100644 index 0000000..e562e67 --- /dev/null +++ b/orderbytyped_test.go @@ -0,0 +1,182 @@ +package linq + +import "testing" + +func TestOrderByT(t *testing.T) { + slice := make([]foo, 100) + + for i := len(slice) - 1; i >= 0; i-- { + slice[i].f1 = i + } + + q := From(slice).OrderByT(func(i interface{}) interface{} { + return i.(foo).f1 + }) + + j := 0 + next := q.Iterate() + for item, ok := next(); ok; item, ok = next() { + if item.(foo).f1 != j { + t.Errorf("OrderBy()[%v]=%v expected %v", j, item, foo{f1: j}) + } + + j++ + } +} + +func TestOrderByT_PanicWhenFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).OrderByT(func(item, j int) int { return item + 2 }) +} + +func TestOrderByDescendingT(t *testing.T) { + slice := make([]foo, 100) + + for i := 0; i < len(slice); i++ { + slice[i].f1 = i + } + + q := From(slice).OrderByDescendingT(func(i foo) int { + return i.f1 + }) + + j := len(slice) - 1 + next := q.Iterate() + for item, ok := next(); ok; item, ok = next() { + if item.(foo).f1 != j { + t.Errorf("OrderByDescendingT()[%v]=%v expected %v", j, item, foo{f1: j}) + } + + j-- + } +} + +func TestOrderByDescendingT_PanicWhenFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).OrderByDescendingT(func(item, j int) int { return item + 2 }) +} + +func TestThenByT(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 := From(slice).OrderByT(func(i foo) bool { + return i.f2 + }).ThenByT(func(i foo) int { + return i.f1 + }) + + next := q.Iterate() + for item, ok := next(); ok; item, ok = next() { + if item.(foo).f2 != (item.(foo).f1%2 == 0) { + t.Errorf("OrderBy().ThenBy()=%v", item) + } + } +} + +func TestThenByT_PanicWhenFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}). + OrderByT(func(item int) int { return item }). + ThenByT(func(item, j int) bool { return true }) +} + +func TestThenByTDescending(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 := From(slice).OrderByT(func(i foo) bool { + return i.f2 + }).ThenByDescendingT(func(i foo) int { + return i.f1 + }) + + next := q.Iterate() + for item, ok := next(); ok; item, ok = next() { + if item.(foo).f2 != (item.(foo).f1%2 == 0) { + t.Errorf("OrderByT().ThenByDescendingT()=%v", item) + } + } +} + +func TestThenByDescendingT_PanicWhenFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}). + OrderByT(func(item int) int { return item }). + ThenByDescendingT(func(item, j int) bool { return true }) +} + +func TestSortT(t *testing.T) { + slice := make([]foo, 100) + + for i := len(slice) - 1; i >= 0; i-- { + slice[i].f1 = i + } + + q := From(slice).SortT(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.(foo).f1 != j { + t.Errorf("Sort()[%v]=%v expected %v", j, item, foo{f1: j}) + } + + j++ + } +} + +func TestSortT_PanicWhenFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).SortT(func(i, j int) string { return "" }) +} diff --git a/resulttyped.go b/resulttyped.go new file mode 100644 index 0000000..f41238d --- /dev/null +++ b/resulttyped.go @@ -0,0 +1,175 @@ +package linq + +// AllT is the typed version of All. +// +// NOTE: All method has better performance than AllT +// +// predicateFn is of a type "func(TSource) bool" +// +func (q Query) AllT(predicateFn interface{}) bool { + + predicateGenericFunc, err := newGenericFunc( + "AllT", "predicateFn", predicateFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))), + ) + if err != nil { + panic(err) + } + predicateFunc := func(item interface{}) bool { + return predicateGenericFunc.Call(item).(bool) + } + + return q.All(predicateFunc) +} + +// AnyWithT is the typed version of AnyWith. +// +// NOTE: AnyWith method has better performance than AnyWithT +// +// predicateFn is of a type "func(TSource) bool" +// +func (q Query) AnyWithT(predicateFn interface{}) bool { + + predicateGenericFunc, err := newGenericFunc( + "AnyWithT", "predicateFn", predicateFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))), + ) + if err != nil { + panic(err) + } + + predicateFunc := func(item interface{}) bool { + return predicateGenericFunc.Call(item).(bool) + } + + return q.AnyWith(predicateFunc) +} + +// CountWithT is the typed version of CountWith. +// +// NOTE: CountWith method has better performance than CountWithT +// +// predicateFn is of a type "func(TSource) bool" +// +func (q Query) CountWithT(predicateFn interface{}) int { + + predicateGenericFunc, err := newGenericFunc( + "CountWithT", "predicateFn", predicateFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))), + ) + if err != nil { + panic(err) + } + + predicateFunc := func(item interface{}) bool { + return predicateGenericFunc.Call(item).(bool) + } + + return q.CountWith(predicateFunc) +} + +// FirstWithT is the typed version of FirstWith. +// +// NOTE: FirstWith method has better performance than FirstWithT +// +// predicateFn is of a type "func(TSource) bool" +// +func (q Query) FirstWithT(predicateFn interface{}) interface{} { + + predicateGenericFunc, err := newGenericFunc( + "FirstWithT", "predicateFn", predicateFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))), + ) + if err != nil { + panic(err) + } + + predicateFunc := func(item interface{}) bool { + return predicateGenericFunc.Call(item).(bool) + } + + return q.FirstWith(predicateFunc) +} + +// LastWithT is the typed version of LastWith. +// +// NOTE: LastWith method has better performance than LastWithT +// +// predicateFn is of a type "func(TSource) bool" +// +func (q Query) LastWithT(predicateFn interface{}) interface{} { + + predicateGenericFunc, err := newGenericFunc( + "LastWithT", "predicateFn", predicateFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))), + ) + if err != nil { + panic(err) + } + + predicateFunc := func(item interface{}) bool { + return predicateGenericFunc.Call(item).(bool) + } + + return q.LastWith(predicateFunc) +} + +// SingleWithT is the typed version of SingleWith. +// +// NOTE: SingleWith method has better performance than SingleWithT +// +// predicateFn is of a type "func(TSource) bool" +// +func (q Query) SingleWithT(predicateFn interface{}) interface{} { + + predicateGenericFunc, err := newGenericFunc( + "SingleWithT", "predicateFn", predicateFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))), + ) + if err != nil { + panic(err) + } + + predicateFunc := func(item interface{}) bool { + return predicateGenericFunc.Call(item).(bool) + } + + return q.SingleWith(predicateFunc) +} + +// ToMapByT is the typed version of ToMapBy. +// +// NOTE: ToMapBy method has better performance than ToMapByT +// +// keySelectorFn is of a type "func(TSource)TKey" +// +// valueSelectorFn is of a type "func(TSource)TValue" +// +func (q Query) ToMapByT(result interface{}, keySelectorFn interface{}, valueSelectorFn interface{}) { + + keySelectorGenericFunc, err := newGenericFunc( + "ToMapByT", "keySelectorFn", keySelectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + keySelectorFunc := func(item interface{}) interface{} { + return keySelectorGenericFunc.Call(item) + } + + valueSelectorGenericFunc, err := newGenericFunc( + "ToMapByT", "valueSelectorFn", valueSelectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + valueSelectorFunc := func(item interface{}) interface{} { + return valueSelectorGenericFunc.Call(item) + } + + q.ToMapBy(result, keySelectorFunc, valueSelectorFunc) +} diff --git a/resulttyped_test.go b/resulttyped_test.go new file mode 100644 index 0000000..8922b1b --- /dev/null +++ b/resulttyped_test.go @@ -0,0 +1,249 @@ +package linq + +import ( + "reflect" + "testing" +) + +func TestAllT(t *testing.T) { + input := []int{2, 4, 6, 8} + + r1 := From(input).AllT(func(i int) bool { + return i%2 == 0 + }) + r2 := From(input).AllT(func(i int) bool { + return i%2 != 0 + }) + + if !r1 { + t.Errorf("From(%v).AllT()=%v", input, r1) + } + + if r2 { + t.Errorf("From(%v).AllT()=%v", input, r2) + } +} + +func TestAllT_PanicWhenFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).AllT(func(item int) int { return item + 2 }) +} + +func TestAnyWithT(t *testing.T) { + tests := []struct { + input interface{} + predicate interface{} + want bool + }{ + {[]int{1, 2, 2, 3, 1}, func(i int) bool { return i == 4 }, false}, + {[]int{1, 2, 2, 3, 1}, func(i int) bool { return i == 4 }, false}, + {[9]int{1, 1, 1, 2, 1, 2, 3, 4, 2}, func(i int) bool { return i == 4 }, true}, + {[]int{}, func(i int) bool { return i == 4 }, false}, + } + + for _, test := range tests { + if r := From(test.input).AnyWithT(test.predicate); r != test.want { + t.Errorf("From(%v).AnyWithT()=%v expected %v", test.input, r, test.want) + } + } +} + +func TestAnyWithT_PanicWhenFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).AnyWithT(func(item int) int { return item + 2 }) +} + +func TestCountWithT(t *testing.T) { + tests := []struct { + input interface{} + predicate interface{} + want int + }{ + {[]int{1, 2, 2, 3, 1}, func(i interface{}) bool { return i.(int) <= 2 }, 4}, + {[]int{1, 2, 2, 3, 1}, func(i int) bool { return i <= 2 }, 4}, + {[]int{}, func(i int) bool { return i <= 2 }, 0}, + } + + for _, test := range tests { + if r := From(test.input).CountWithT(test.predicate); r != test.want { + t.Errorf("From(%v).CountWithT()=%v expected %v", test.input, r, test.want) + } + } +} + +func TestCountWithT_PanicWhenFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).CountWithT(func(item int) int { return item + 2 }) +} + +func TestFirstWithT(t *testing.T) { + tests := []struct { + input interface{} + predicate interface{} + want interface{} + }{ + {[]int{1, 2, 2, 3, 1}, func(i interface{}) bool { return i.(int) > 2 }, 3}, + {[]int{1, 2, 2, 3, 1}, func(i int) bool { return i > 2 }, 3}, + {[]int{}, func(i interface{}) bool { return i.(int) > 2 }, nil}, + } + + for _, test := range tests { + if r := From(test.input).FirstWithT(test.predicate); r != test.want { + t.Errorf("From(%v).FirstWithT()=%v expected %v", test.input, r, test.want) + } + } +} + +func TestFirstWithT_PanicWhenFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).FirstWithT(func(item int) int { return item + 2 }) +} + +func TestLastWithT(t *testing.T) { + tests := []struct { + input interface{} + predicate interface{} + want interface{} + }{ + {[]int{1, 2, 2, 3, 1, 4, 2, 5, 1, 1}, func(i interface{}) bool { return i.(int) > 2 }, 5}, + {[]int{1, 2, 2, 3, 1, 4, 2, 5, 1, 1}, func(i int) bool { return i > 2 }, 5}, + {[]int{}, func(i interface{}) bool { return i.(int) > 2 }, nil}, + } + + for _, test := range tests { + if r := From(test.input).LastWithT(test.predicate); r != test.want { + t.Errorf("From(%v).LastWith()=%v expected %v", test.input, r, test.want) + } + } +} + +func TestLastWithT_PanicWhenFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).LastWithT(func(item int) int { return item + 2 }) +} + +func TestSingleWithT(t *testing.T) { + tests := []struct { + input interface{} + predicate interface{} + want interface{} + }{ + {[]int{1, 2, 2, 3, 1}, func(i interface{}) bool { return i.(int) > 2 }, 3}, + {[]int{1, 1, 1}, func(i int) bool { return i > 2 }, nil}, + {[]int{5, 1, 1, 10, 2, 2}, func(i interface{}) bool { return i.(int) > 2 }, nil}, + {[]int{}, func(i interface{}) bool { return i.(int) > 2 }, nil}, + } + + for _, test := range tests { + if r := From(test.input).SingleWithT(test.predicate); r != test.want { + t.Errorf("From(%v).SingleWithT()=%v expected %v", test.input, r, test.want) + } + } +} + +func TestSingleWithT_PanicWhenFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).SingleWithT(func(item int) int { return item + 2 }) +} + +func TestToMapByT(t *testing.T) { + input := make(map[int]bool) + input[1] = true + input[2] = false + input[3] = true + + result := make(map[int]bool) + From(input).ToMapByT(&result, + func(i KeyValue) interface{} { + return i.Key + }, + func(i KeyValue) interface{} { + return i.Value + }) + + if !reflect.DeepEqual(result, input) { + t.Errorf("From(%v).ToMapByT()=%v expected %v", input, result, input) + } +} + +func TestToMapByT_PanicWhenKeySelectorFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + result := make(map[int]bool) + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).ToMapByT( + &result, + func(item, j int) int { return item + 2 }, + func(item int) int { return item + 2 }, + ) +} + +func TestToMapByT_PanicWhenValueSelectorFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + result := make(map[int]bool) + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).ToMapByT( + &result, + func(item int) int { return item + 2 }, + func(item, j int) int { return item + 2 }, + ) +} diff --git a/selectmany.go b/selectmany.go index 25ea8c3..269cf84 100644 --- a/selectmany.go +++ b/selectmany.go @@ -103,7 +103,7 @@ func (q Query) SelectManyBy( } } - item = resultSelector(outer, item) + item = resultSelector(item, outer) return } }, @@ -143,7 +143,7 @@ func (q Query) SelectManyByIndexed(selector func(int, interface{}) Query, } } - item = resultSelector(outer, item) + item = resultSelector(item, outer) return } }, diff --git a/selectmany_test.go b/selectmany_test.go index c1218a9..c390ce3 100644 --- a/selectmany_test.go +++ b/selectmany_test.go @@ -60,12 +60,12 @@ func TestSelectManyBy(t *testing.T) { {[][]int{{1, 2, 3}, {4, 5, 6, 7}}, func(i interface{}) Query { return From(i) }, func(x interface{}, y interface{}) interface{} { - return y.(int) + 1 + return x.(int) + 1 }, []interface{}{2, 3, 4, 5, 6, 7, 8}}, {[]string{"str", "ing"}, func(i interface{}) Query { return FromString(i.(string)) }, func(x interface{}, y interface{}) interface{} { - return string(y.(rune)) + "_" + return string(x.(rune)) + "_" }, []interface{}{"s_", "t_", "r_", "i_", "n_", "g_"}}, } @@ -89,7 +89,7 @@ func TestSelectManyIndexedBy(t *testing.T) { } return From(x) }, func(x interface{}, y interface{}) interface{} { - return y.(int) + 1 + return x.(int) + 1 }, []interface{}{11, 21, 31, 5, 6, 7, 8}}, {[]string{"st", "ng"}, func(i int, x interface{}) Query { if i == 0 { @@ -97,7 +97,7 @@ func TestSelectManyIndexedBy(t *testing.T) { } return FromString("i" + x.(string)) }, func(x interface{}, y interface{}) interface{} { - return string(y.(rune)) + "_" + return string(x.(rune)) + "_" }, []interface{}{"s_", "t_", "r_", "i_", "n_", "g_"}}, } diff --git a/selectmanytyped.go b/selectmanytyped.go new file mode 100644 index 0000000..04a89e9 --- /dev/null +++ b/selectmanytyped.go @@ -0,0 +1,121 @@ +package linq + +// SelectManyT is the typed version of SelectMany. +// +// NOTE: SelectMany method has better performance than SelectManyT +// +// selectorFn is of a type "func(TSource)Query" +// +func (q Query) SelectManyT(selectorFn interface{}) Query { + + selectManyGenericFunc, err := newGenericFunc( + "SelectManyT", "selectorFn", selectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(Query))), + ) + if err != nil { + panic(err) + } + + selectorFunc := func(inner interface{}) Query { + return selectManyGenericFunc.Call(inner).(Query) + } + return q.SelectMany(selectorFunc) + +} + +// SelectManyIndexedT is the typed version of SelectManyIndexed. +// +// NOTE: SelectManyIndexed method has better performance than SelectManyIndexedT +// +// selectorFn is of a type "func(int,TSource)Query" +// +func (q Query) SelectManyIndexedT(selectorFn interface{}) Query { + + selectManyIndexedGenericFunc, err := newGenericFunc( + "SelectManyIndexedT", "selectorFn", selectorFn, + simpleParamValidator(newElemTypeSlice(new(int), new(genericType)), newElemTypeSlice(new(Query))), + ) + if err != nil { + panic(err) + } + + selectorFunc := func(index int, inner interface{}) Query { + return selectManyIndexedGenericFunc.Call(index, inner).(Query) + } + + return q.SelectManyIndexed(selectorFunc) +} + +// SelectManyByT is the typed version of SelectManyBy. +// +// NOTE: SelectManyBy method has better performance than SelectManyByT +// +// selectorFn is of a type "func(TSource)Query" +// +// resultSelectorFn is of a type "func(TSource,TCollection)TResult" +// +func (q Query) SelectManyByT(selectorFn interface{}, resultSelectorFn interface{}) Query { + + selectorGenericFunc, err := newGenericFunc( + "SelectManyByT", "selectorFn", selectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(Query))), + ) + if err != nil { + panic(err) + } + + selectorFunc := func(outer interface{}) Query { + return selectorGenericFunc.Call(outer).(Query) + } + + resultSelectorGenericFunc, err := newGenericFunc( + "SelectManyByT", "resultSelectorFn", resultSelectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + resultSelectorFunc := func(outer interface{}, item interface{}) interface{} { + return resultSelectorGenericFunc.Call(outer, item) + } + + return q.SelectManyBy(selectorFunc, resultSelectorFunc) +} + +// SelectManyByIndexedT is the typed version of SelectManyByIndexed. +// +// NOTE: SelectManyByIndexed method has better performance than SelectManyByIndexedT +// +// selectorFn is of a type "func(int,TSource)Query" +// +// resultSelectorFn is of a type "func(TSource,TCollection)TResult" +// +func (q Query) SelectManyByIndexedT(selectorFn interface{}, resultSelectorFn interface{}) Query { + + selectorGenericFunc, err := newGenericFunc( + "SelectManyByIndexedT", "selectorFn", selectorFn, + simpleParamValidator(newElemTypeSlice(new(int), new(genericType)), newElemTypeSlice(new(Query))), + ) + if err != nil { + panic(err) + } + + selectorFunc := func(index int, outer interface{}) Query { + return selectorGenericFunc.Call(index, outer).(Query) + } + + resultSelectorGenericFunc, err := newGenericFunc( + "SelectManyByIndexedT", "resultSelectorFn", resultSelectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + resultSelectorFunc := func(outer interface{}, item interface{}) interface{} { + return resultSelectorGenericFunc.Call(outer, item) + } + + return q.SelectManyByIndexed(selectorFunc, resultSelectorFunc) +} diff --git a/selectmanytyped_test.go b/selectmanytyped_test.go new file mode 100644 index 0000000..7638f2f --- /dev/null +++ b/selectmanytyped_test.go @@ -0,0 +1,199 @@ +package linq + +import ( + "strconv" + "testing" +) + +func TestSelectManyT(t *testing.T) { + tests := []struct { + input interface{} + selector interface{} + output []interface{} + }{ + {[][]int{{1, 2, 3}, {4, 5, 6, 7}}, func(i interface{}) Query { + return From(i) + }, []interface{}{1, 2, 3, 4, 5, 6, 7}}, + {[][]int{{1, 2, 3}, {4, 5, 6, 7}}, func(i []int) Query { + return From(i) + }, []interface{}{1, 2, 3, 4, 5, 6, 7}}, + {[]string{"str", "ing"}, func(i interface{}) Query { + return FromString(i.(string)) + }, []interface{}{'s', 't', 'r', 'i', 'n', 'g'}}, + } + + for _, test := range tests { + if q := From(test.input).SelectManyT(test.selector); !validateQuery(q, test.output) { + t.Errorf("From(%v).SelectManyT()=%v expected %v", test.input, toSlice(q), test.output) + } + } +} + +func TestSelectManyT_PanicWhenFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + }() + + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).SelectManyT(func(item int) int { return item + 2 }) +} + +func TestSelectManyIndexedT(t *testing.T) { + tests := []struct { + input interface{} + selector interface{} + output []interface{} + }{ + {[][]int{{1, 2, 3}, {4, 5, 6, 7}}, func(i int, x interface{}) Query { + if i > 0 { + return From(x.([]int)[1:]) + } + return From(x) + }, []interface{}{1, 2, 3, 5, 6, 7}}, + {[][]int{{1, 2, 3}, {4, 5, 6, 7}}, func(i int, x []int) Query { + if i > 0 { + return From(x[1:]) + } + return From(x) + }, []interface{}{1, 2, 3, 5, 6, 7}}, + {[]string{"str", "ing"}, func(i int, x string) Query { + return FromString(x + strconv.Itoa(i)) + }, []interface{}{'s', 't', 'r', '0', 'i', 'n', 'g', '1'}}, + } + + for _, test := range tests { + if q := From(test.input).SelectManyIndexedT(test.selector); !validateQuery(q, test.output) { + t.Errorf("From(%v).SelectManyIndexedT()=%v expected %v", test.input, toSlice(q), test.output) + } + } +} + +func TestSelectManyIndexedT_PanicWhenFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + }() + + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).SelectManyIndexedT(func(item int) int { return item + 2 }) +} + +func TestSelectManyByT(t *testing.T) { + tests := []struct { + input interface{} + selector interface{} + resultSelector interface{} + output []interface{} + }{ + {[][]int{{1, 2, 3}, {4, 5, 6, 7}}, func(i []int) Query { + return From(i) + }, func(x int, y []int) int { + return x + 1 + }, []interface{}{2, 3, 4, 5, 6, 7, 8}}, + {[]string{"str", "ing"}, func(i string) Query { + return FromString(i) + }, func(x rune, y string) string { + return string(x) + "_" + }, []interface{}{"s_", "t_", "r_", "i_", "n_", "g_"}}, + } + + for _, test := range tests { + if q := From(test.input).SelectManyByT(test.selector, test.resultSelector); !validateQuery(q, test.output) { + t.Errorf("From(%v).SelectManyBy()=%v expected %v", test.input, toSlice(q), test.output) + } + } +} + +func TestSelectManyByT_PanicWhenSelectorFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + }() + + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).SelectManyByT(func(item int) interface{} { return item + 2 }, 2) +} + +func TestSelectManyByT_PanicWhenResultSelectorFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + }() + + From([][]int{{1, 1, 1, 2}, {1, 2, 3, 4, 2}}).SelectManyByT( + func(item interface{}) Query { return From(item) }, + func() {}, + ) +} + +func TestSelectManyIndexedByT(t *testing.T) { + tests := []struct { + input interface{} + selector interface{} + resultSelector interface{} + output []interface{} + }{ + {[][]int{{1, 2, 3}, {4, 5, 6, 7}}, func(i int, x []int) Query { + if i == 0 { + return From([]int{10, 20, 30}) + } + return From(x) + }, func(x int, y []int) int { + return x + 1 + }, []interface{}{11, 21, 31, 5, 6, 7, 8}}, + {[]string{"st", "ng"}, func(i int, x string) Query { + if i == 0 { + return FromString(x + "r") + } + return FromString("i" + x) + }, func(x rune, y string) string { + return string(x) + "_" + }, []interface{}{"s_", "t_", "r_", "i_", "n_", "g_"}}, + } + + for _, test := range tests { + if q := From(test.input).SelectManyByIndexedT(test.selector, test.resultSelector); !validateQuery(q, test.output) { + t.Errorf("From(%v).SelectManyIndexedByT()=%v expected %v", test.input, toSlice(q), test.output) + } + } +} + +func TestSelectManyIndexedByT_PanicWhenSelectorFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + }() + + From([][]int{{1, 1, 1, 2}, {1, 2, 3, 4, 2}}).SelectManyByIndexedT( + func(item int) interface{} { return item + 2 }, + 2, + ) +} + +func TestSelectManyIndexedByT_PanicWhenResultSelectorFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + }() + + From([][]int{{1, 1, 1, 2}, {1, 2, 3, 4, 2}}).SelectManyByIndexedT( + func(index int, item interface{}) Query { return From(item) }, + func() {}, + ) +} diff --git a/selecttyped.go b/selecttyped.go new file mode 100644 index 0000000..1ae36a1 --- /dev/null +++ b/selecttyped.go @@ -0,0 +1,47 @@ +package linq + +// SelectT is the typed version of Select. +// +// NOTE: Select method has better performance than SelectT +// +// selectorFn is of a type "func(TSource)TResult" +// +func (q Query) SelectT(selectorFn interface{}) Query { + + selectGenericFunc, err := newGenericFunc( + "SelectT", "selectorFn", selectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + selectorFunc := func(item interface{}) interface{} { + return selectGenericFunc.Call(item) + } + + return q.Select(selectorFunc) +} + +// SelectIndexedT is the typed version of SelectIndexed. +// +// NOTE: SelectIndexed method has better performance than SelectIndexedT +// +// selectorFn is of a type "func(int,TSource)TResult" +// +func (q Query) SelectIndexedT(selectorFn interface{}) Query { + + selectGenericFunc, err := newGenericFunc( + "SelectIndexedT", "selectorFn", selectorFn, + simpleParamValidator(newElemTypeSlice(new(int), new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + selectorFunc := func(index int, item interface{}) interface{} { + return selectGenericFunc.Call(index, item) + } + + return q.SelectIndexed(selectorFunc) +} diff --git a/selecttyped_test.go b/selecttyped_test.go new file mode 100644 index 0000000..bdcac85 --- /dev/null +++ b/selecttyped_test.go @@ -0,0 +1,78 @@ +package linq + +import ( + "strconv" + "testing" +) + +func TestSelectT(t *testing.T) { + tests := []struct { + input interface{} + selector interface{} + output []interface{} + }{ + {[]int{1, 2, 3}, func(i interface{}) interface{} { + return i.(int) * 2 + }, []interface{}{2, 4, 6}}, + {[]int{1, 2, 3}, func(i int) int { + return i * 2 + }, []interface{}{2, 4, 6}}, + {"str", func(i interface{}) interface{} { + return string(i.(rune)) + "1" + }, []interface{}{"s1", "t1", "r1"}}, + } + + for _, test := range tests { + if q := From(test.input).SelectT(test.selector); !validateQuery(q, test.output) { + t.Errorf("From(%v).Select()=%v expected %v", test.input, toSlice(q), test.output) + } + } +} + +func TestSelectT_PanicWhenFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + }() + + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).SelectT(func(item, idx int) int { return item + 2 }) +} + +func TestSelectIndexedT(t *testing.T) { + tests := []struct { + input interface{} + selector interface{} + output []interface{} + }{ + {[]int{1, 2, 3}, func(i int, x interface{}) interface{} { + return x.(int) * i + }, []interface{}{0, 2, 6}}, + {[]int{1, 2, 3}, func(i int, x int) int { + return x * i + }, []interface{}{0, 2, 6}}, + {"str", func(i int, x interface{}) interface{} { + return string(x.(rune)) + strconv.Itoa(i) + }, []interface{}{"s0", "t1", "r2"}}, + } + + for _, test := range tests { + if q := From(test.input).SelectIndexedT(test.selector); !validateQuery(q, test.output) { + t.Errorf("From(%v).SelectIndexed()=%v expected %v", test.input, toSlice(q), test.output) + } + } +} + +func TestSelectIndexedT_PanicWhenFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + }() + + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).SelectIndexedT(func(index string, item int) int { return item + 2 }) +} diff --git a/skiptyped.go b/skiptyped.go new file mode 100644 index 0000000..77eb6da --- /dev/null +++ b/skiptyped.go @@ -0,0 +1,47 @@ +package linq + +// SkipWhileT is the typed version of SkipWhile. +// +// NOTE: SkipWhile method has better performance than SkipWhileT +// +// predicateFn is of a type "func(TSource)bool" +// +func (q Query) SkipWhileT(predicateFn interface{}) Query { + + predicateGenericFunc, err := newGenericFunc( + "SkipWhileT", "predicateFn", predicateFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))), + ) + if err != nil { + panic(err) + } + + predicateFunc := func(item interface{}) bool { + return predicateGenericFunc.Call(item).(bool) + } + + return q.SkipWhile(predicateFunc) +} + +// SkipWhileIndexedT is the typed version of SkipWhileIndexed. +// +// NOTE: SkipWhileIndexed method has better performance than SkipWhileIndexedT +// +// predicateFn is of a type "func(int,TSource)bool" +// +func (q Query) SkipWhileIndexedT(predicateFn interface{}) Query { + + predicateGenericFunc, err := newGenericFunc( + "SkipWhileIndexedT", "predicateFn", predicateFn, + simpleParamValidator(newElemTypeSlice(new(int), new(genericType)), newElemTypeSlice(new(bool))), + ) + if err != nil { + panic(err) + } + + predicateFunc := func(index int, item interface{}) bool { + return predicateGenericFunc.Call(index, item).(bool) + } + + return q.SkipWhileIndexed(predicateFunc) +} diff --git a/skiptyped_test.go b/skiptyped_test.go new file mode 100644 index 0000000..1be6b83 --- /dev/null +++ b/skiptyped_test.go @@ -0,0 +1,83 @@ +package linq + +import "testing" + +func TestSkipWhileT(t *testing.T) { + tests := []struct { + input interface{} + predicate interface{} + output []interface{} + }{ + {[]int{1, 2}, func(i interface{}) bool { + return i.(int) < 3 + }, []interface{}{}}, + {[]int{4, 1, 2}, func(i interface{}) bool { + return i.(int) < 3 + }, []interface{}{4, 1, 2}}, + {[9]int{1, 1, 1, 2, 1, 2, 3, 4, 2}, func(i int) bool { + return i < 3 + }, []interface{}{3, 4, 2}}, + {"sstr", func(i interface{}) bool { + return i.(rune) == 's' + }, []interface{}{'t', 'r'}}, + } + + for _, test := range tests { + if q := From(test.input).SkipWhileT(test.predicate); !validateQuery(q, test.output) { + t.Errorf("From(%v).SkipWhile()=%v expected %v", test.input, toSlice(q), test.output) + } + } +} + +func TestSkipWhileT_PanicWhenFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).SkipWhileT(func(item int, x int) bool { return item == 1 }) +} + +func TestSkipWhileIndexedT(t *testing.T) { + tests := []struct { + input interface{} + predicate interface{} + output []interface{} + }{ + {[]int{1, 2}, func(i int, x interface{}) bool { + return x.(int) < 3 + }, []interface{}{}}, + {[]int{4, 1, 2}, func(i int, x interface{}) bool { + return x.(int) < 3 + }, []interface{}{4, 1, 2}}, + {[9]int{1, 1, 1, 2, 1, 2, 3, 4, 2}, func(i int, x int) bool { + return x < 2 || i < 5 + }, []interface{}{2, 3, 4, 2}}, + {"sstr", func(i int, x interface{}) bool { + return x.(rune) == 's' && i < 1 + }, []interface{}{'s', 't', 'r'}}, + } + + for _, test := range tests { + if q := From(test.input).SkipWhileIndexedT(test.predicate); !validateQuery(q, test.output) { + t.Errorf("From(%v).SkipWhileIndexed()=%v expected %v", test.input, toSlice(q), test.output) + } + } +} + +func TestSkipWhileIndexedT_PanicWhenFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + + 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/taketyped.go b/taketyped.go new file mode 100644 index 0000000..4c1c14b --- /dev/null +++ b/taketyped.go @@ -0,0 +1,47 @@ +package linq + +// TakeWhileT is the typed version of TakeWhile. +// +// NOTE: TakeWhile method has better performance than TakeWhileT +// +// predicateFn is of a type "func(TSource)bool" +// +func (q Query) TakeWhileT(predicateFn interface{}) Query { + + predicateGenericFunc, err := newGenericFunc( + "TakeWhileT", "predicateFn", predicateFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))), + ) + if err != nil { + panic(err) + } + + predicateFunc := func(item interface{}) bool { + return predicateGenericFunc.Call(item).(bool) + } + + return q.TakeWhile(predicateFunc) +} + +// TakeWhileIndexedT is the typed version of TakeWhileIndexed. +// +// NOTE: TakeWhileIndexed method has better performance than TakeWhileIndexedT +// +// predicateFn is of a type "func(int,TSource)bool" +// +func (q Query) TakeWhileIndexedT(predicateFn interface{}) Query { + + whereFunc, err := newGenericFunc( + "TakeWhileIndexedT", "predicateFn", predicateFn, + simpleParamValidator(newElemTypeSlice(new(int), new(genericType)), newElemTypeSlice(new(bool))), + ) + if err != nil { + panic(err) + } + + predicateFunc := func(index int, item interface{}) bool { + return whereFunc.Call(index, item).(bool) + } + + return q.TakeWhileIndexed(predicateFunc) +} diff --git a/taketyped_test.go b/taketyped_test.go new file mode 100644 index 0000000..8fc3a05 --- /dev/null +++ b/taketyped_test.go @@ -0,0 +1,83 @@ +package linq + +import "testing" + +func TestTakeWhileT(t *testing.T) { + tests := []struct { + input interface{} + predicate interface{} + output []interface{} + }{ + {[]int{1, 1, 1, 2, 1, 2}, func(i interface{}) bool { + return i.(int) < 3 + }, []interface{}{1, 1, 1, 2, 1, 2}}, + {[9]int{1, 1, 1, 2, 1, 2, 3, 4, 2}, func(i interface{}) bool { + return i.(int) < 3 + }, []interface{}{1, 1, 1, 2, 1, 2}}, + {"sstr", func(i interface{}) bool { + return i.(rune) == 's' + }, []interface{}{'s', 's'}}, + {"sstr", func(i rune) bool { + return i == 's' + }, []interface{}{'s', 's'}}, + } + + for _, test := range tests { + if q := From(test.input).TakeWhileT(test.predicate); !validateQuery(q, test.output) { + t.Errorf("From(%v).TakeWhile()=%v expected %v", test.input, toSlice(q), test.output) + } + } +} + +func TestTakeWhileT_PanicWhenFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).TakeWhileT(func(item int) int { return item + 2 }) +} + +func TestTakeWhileIndexedT(t *testing.T) { + tests := []struct { + input interface{} + predicate interface{} + output []interface{} + }{ + {[]int{1, 1, 1, 2}, func(i int, x interface{}) bool { + return x.(int) < 2 || i < 5 + }, []interface{}{1, 1, 1, 2}}, + {[9]int{1, 1, 1, 2, 1, 2, 3, 4, 2}, func(i int, x interface{}) bool { + return x.(int) < 2 || i < 5 + }, []interface{}{1, 1, 1, 2, 1}}, + {"sstr", func(i int, x interface{}) bool { + return x.(rune) == 's' && i < 1 + }, []interface{}{'s'}}, + {"sstr", func(i int, x rune) bool { + return x == 's' && i < 1 + }, []interface{}{'s'}}, + } + + for _, test := range tests { + if q := From(test.input).TakeWhileIndexedT(test.predicate); !validateQuery(q, test.output) { + t.Errorf("From(%v).TakeWhileIndexed()=%v expected %v", test.input, toSlice(q), test.output) + } + } +} + +func TestTakeWhileIndexedT_PanicWhenFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).TakeWhileIndexedT(func(item int) int { return item + 2 }) +} diff --git a/wheretyped.go b/wheretyped.go new file mode 100644 index 0000000..b49b894 --- /dev/null +++ b/wheretyped.go @@ -0,0 +1,47 @@ +package linq + +// WhereT is the typed version of Where. +// +// NOTE: Where method has better performance than WhereT +// +// predicateFn is of a type "func(TSource)bool" +// +func (q Query) WhereT(predicateFn interface{}) Query { + + predicateGenericFunc, err := newGenericFunc( + "WhereT", "predicateFn", predicateFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))), + ) + if err != nil { + panic(err) + } + + predicateFunc := func(item interface{}) bool { + return predicateGenericFunc.Call(item).(bool) + } + + return q.Where(predicateFunc) +} + +// WhereIndexedT is the typed version of WhereIndexed. +// +// NOTE: WhereIndexed method has better performance than WhereIndexedT +// +// predicateFn is of a type "func(int,TSource)bool" +// +func (q Query) WhereIndexedT(predicateFn interface{}) Query { + + predicateGenericFunc, err := newGenericFunc( + "WhereIndexedT", "predicateFn", predicateFn, + simpleParamValidator(newElemTypeSlice(new(int), new(genericType)), newElemTypeSlice(new(bool))), + ) + if err != nil { + panic(err) + } + + predicateFunc := func(index int, item interface{}) bool { + return predicateGenericFunc.Call(index, item).(bool) + } + + return q.WhereIndexed(predicateFunc) +} diff --git a/wheretyped_test.go b/wheretyped_test.go new file mode 100644 index 0000000..f68ec08 --- /dev/null +++ b/wheretyped_test.go @@ -0,0 +1,102 @@ +package linq + +import "testing" + +func TestWhereT(t *testing.T) { + tests := []struct { + input interface{} + predicate interface{} + output []interface{} + }{ + {[9]int{1, 1, 1, 2, 1, 2, 3, 4, 2}, func(i interface{}) bool { + return i.(int) >= 3 + }, []interface{}{3, 4}}, + {"sstr", func(i interface{}) bool { + return i.(rune) != 's' + }, []interface{}{'t', 'r'}}, + {"sstr", func(i rune) bool { + return i != 's' + }, []interface{}{'t', 'r'}}, + } + + for _, test := range tests { + if q := From(test.input).WhereT(test.predicate); !validateQuery(q, test.output) { + t.Errorf("From(%v).WhereT()=%v expected %v", test.input, toSlice(q), test.output) + } + } +} + +func TestWhereT_PanicWhenFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).WhereT(func(item int) int { return item + 2 }) +} + +func TestWhereIndexedT(t *testing.T) { + tests := []struct { + input interface{} + predicate interface{} + output []interface{} + }{ + {[9]int{1, 1, 1, 2, 1, 2, 3, 4, 2}, func(i int, x interface{}) bool { + return x.(int) < 4 && i > 4 + }, []interface{}{2, 3, 2}}, + {"sstr", func(i int, x interface{}) bool { + return x.(rune) != 's' || i == 1 + }, []interface{}{'s', 't', 'r'}}, + {"sstr", func(i int, x rune) bool { + return x != 's' || i == 1 + }, []interface{}{'s', 't', 'r'}}, + } + + for _, test := range tests { + if q := From(test.input).WhereIndexedT(test.predicate); !validateQuery(q, test.output) { + t.Errorf("From(%v).WhereIndexed()=%v expected %v", test.input, toSlice(q), test.output) + } + } +} + +func TestWhereT_PanicWhenParameterInTypeDoesntMatch(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + + var r []int + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).WhereT(func(item string) bool { return len(item) > 0 }).ToSlice(&r) +} + +func TestWhereIndexedT_PanicWhenFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic") + } + }() + + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).WhereIndexedT(func(item string) {}) +} + +func TestWhereIndexed_PanicWhenParameterTypesDoesntMatch(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic") + } + }() + var result []int + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).WhereIndexedT(func(index int, item string) bool { return true }).ToSlice(&result) +} diff --git a/ziptyped.go b/ziptyped.go new file mode 100644 index 0000000..fe99661 --- /dev/null +++ b/ziptyped.go @@ -0,0 +1,42 @@ +package linq + +// ZipT is the typed version of Zip. +// +// NOTE: Zip method has better performance than ZipT +// +// resultSelectorFn is of a type "func(TFirst,TSecond)TResult" +// +func (q Query) ZipT(q2 Query, resultSelectorFn interface{}) Query { + + resultSelectorFunc, ok := resultSelectorFn.(func(interface{}, interface{}) interface{}) + if !ok { + resultSelectorGenericFunc, err := newGenericFunc( + "ZipT", "resultSelectorFn", resultSelectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + resultSelectorFunc = func(item1 interface{}, item2 interface{}) interface{} { + return resultSelectorGenericFunc.Call(item1, item2) + } + } + return Query{ + Iterate: func() Iterator { + next1 := q.Iterate() + next2 := q2.Iterate() + + return func() (item interface{}, ok bool) { + item1, ok1 := next1() + item2, ok2 := next2() + + if ok1 && ok2 { + return resultSelectorFunc(item1, item2), true + } + + return nil, false + } + }, + } +} diff --git a/ziptyped_test.go b/ziptyped_test.go new file mode 100644 index 0000000..66f2449 --- /dev/null +++ b/ziptyped_test.go @@ -0,0 +1,40 @@ +package linq + +import "testing" + +func TestZipT(t *testing.T) { + input1 := []int{1, 2, 3} + input2 := []int{2, 4, 5, 1} + want := []interface{}{3, 6, 8} + + if q := From(input1).ZipT(From(input2), func(i, j interface{}) interface{} { + return i.(int) + j.(int) + }); !validateQuery(q, want) { + t.Errorf("From(%v).Zip(%v)=%v expected %v", input1, input2, toSlice(q), want) + } + + if q := From(input1).ZipT(From(input2), func(i, j int) interface{} { + return i + j + }); !validateQuery(q, want) { + t.Errorf("From(%v).Zip(%v)=%v expected %v", input1, input2, toSlice(q), want) + } +} + +func TestZipT_PanicWhenFunctionIsNotValid(t *testing.T) { + defer func() { + r := recover() + t.Log(r) + if r == nil { + t.Error("This execution should panic", r) + } + + }() + + input1 := []int{1, 2, 3} + input2 := []int{2, 4, 5, 1} + + From(input1).ZipT(From(input2), func(i, j, k int) int { + return i + j + }) + +} From fdda798fb99258e073d46176d34c9a6d60ec04aa Mon Sep 17 00:00:00 2001 From: Cleiton Marques Date: Wed, 14 Dec 2016 10:30:27 -0800 Subject: [PATCH 2/9] Removed typed files (#47) * Removed typed files * Removed unnecessary tests * Added mustPanicWithError utility function for tests --- aggregate.go | 79 ++- aggregate_test.go | 54 ++ aggregatetyped.go | 88 ---- aggregatetyped_test.go | 157 ------ distinct.go | 23 + distinct_test.go | 6 + distincttyped.go | 25 - distincttyped_test.go | 42 -- example_test.go | 1084 +++++++++++++++++++++++++++++++++++++++ exampletyped_test.go | 1083 -------------------------------------- except.go | 21 + except_test.go | 6 + excepttyped.go | 24 - excepttyped_test.go | 38 -- genericfunc_test.go | 2 - groupby.go | 36 ++ groupby_test.go | 20 + groupbytyped.go | 39 -- groupbytyped_test.go | 92 ---- groupjoin.go | 57 ++ groupjoin_test.go | 33 ++ groupjointyped.go | 60 --- groupjointyped_test.go | 82 --- intersect.go | 21 + intersect_test.go | 8 + intersecttyped.go | 24 - intersecttyped_test.go | 45 -- join.go | 54 ++ join_test.go | 33 ++ jointyped.go | 57 -- jointyped_test.go | 111 ---- orderby.go | 107 ++++ orderby_test.go | 34 ++ orderbytyped.go | 119 ----- orderbytyped_test.go | 182 ------- result.go | 165 ++++++ result_test.go | 58 +++ resulttyped.go | 175 ------- resulttyped_test.go | 249 --------- select.go | 43 ++ select_test.go | 12 + selectmany.go | 115 +++++ selectmany_test.go | 45 ++ selectmanytyped.go | 121 ----- selectmanytyped_test.go | 199 ------- selecttyped.go | 47 -- selecttyped_test.go | 78 --- setup_test.go | 15 + skip.go | 43 ++ skip_test.go | 12 + skiptyped.go | 47 -- skiptyped_test.go | 83 --- take.go | 43 ++ take_test.go | 12 + taketyped.go | 47 -- taketyped_test.go | 83 --- where.go | 43 ++ where_test.go | 12 + wheretyped.go | 47 -- wheretyped_test.go | 102 ---- zip.go | 39 ++ zip_test.go | 11 + ziptyped.go | 42 -- ziptyped_test.go | 40 -- 64 files changed, 2343 insertions(+), 3631 deletions(-) delete mode 100644 aggregatetyped.go delete mode 100644 aggregatetyped_test.go delete mode 100644 distincttyped.go delete mode 100644 distincttyped_test.go delete mode 100644 exampletyped_test.go delete mode 100644 excepttyped.go delete mode 100644 excepttyped_test.go delete mode 100644 groupbytyped.go delete mode 100644 groupbytyped_test.go delete mode 100644 groupjointyped.go delete mode 100644 groupjointyped_test.go delete mode 100644 intersecttyped.go delete mode 100644 intersecttyped_test.go delete mode 100644 jointyped.go delete mode 100644 jointyped_test.go delete mode 100644 orderbytyped.go delete mode 100644 orderbytyped_test.go delete mode 100644 resulttyped.go delete mode 100644 resulttyped_test.go delete mode 100644 selectmanytyped.go delete mode 100644 selectmanytyped_test.go delete mode 100644 selecttyped.go delete mode 100644 selecttyped_test.go delete mode 100644 skiptyped.go delete mode 100644 skiptyped_test.go delete mode 100644 taketyped.go delete mode 100644 taketyped_test.go delete mode 100644 wheretyped.go delete mode 100644 wheretyped_test.go delete mode 100644 ziptyped.go delete mode 100644 ziptyped_test.go diff --git a/aggregate.go b/aggregate.go index bd2fc7b..87b382f 100644 --- a/aggregate.go +++ b/aggregate.go @@ -26,6 +26,28 @@ func (q Query) Aggregate( return result } +// AggregateT is the typed version of Aggregate. +// +// NOTE: Aggregate method has better performance than AggregateT +// +// f is of type: func(TSource, TSource) TSource +func (q Query) AggregateT(f interface{}) interface{} { + + fGenericFunc, err := newGenericFunc( + "AggregateT", "f", f, + simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + fFunc := func(result interface{}, current interface{}) interface{} { + return fGenericFunc.Call(result, current) + } + + return q.Aggregate(fFunc) +} + // AggregateWithSeed applies an accumulator function over a sequence. // The specified seed value is used as the initial accumulator value. // @@ -52,6 +74,27 @@ func (q Query) AggregateWithSeed( return result } +// AggregateWithSeedT is the typed version of AggregateWithSeed. +// +// NOTE: AggregateWithSeed method has better performance than AggregateWithSeedT +// +// f is of a type "func(TAccumulate, TSource) TAccumulate" +func (q Query) AggregateWithSeedT(seed interface{}, f interface{}) interface{} { + fGenericFunc, err := newGenericFunc( + "AggregateWithSeed", "f", f, + simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + fFunc := func(result interface{}, current interface{}) interface{} { + return fGenericFunc.Call(result, current) + } + + return q.AggregateWithSeed(seed, fFunc) +} + // 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. @@ -63,7 +106,6 @@ func (q Query) AggregateWithSeed( // as the initial aggregate value. The result of func replaces the previous aggregated value. // // The final result of func is passed to resultSelector to obtain the final result of Aggregate. -// func (q Query) AggregateWithSeedBy( seed interface{}, f func(interface{}, interface{}) interface{}, @@ -79,3 +121,38 @@ func (q Query) AggregateWithSeedBy( return resultSelector(result) } + +// AggregateWithSeedByT is the typed version of AggregateWithSeedBy. +// +// NOTE: AggregateWithSeedBy method has better performance than AggregateWithSeedByT +// +// f is of a type "func(TAccumulate, TSource) TAccumulate" +// +// resultSelectorFn is of type "func(TAccumulate) TResult" +func (q Query) AggregateWithSeedByT(seed interface{}, f interface{}, resultSelectorFn interface{}) interface{} { + fGenericFunc, err := newGenericFunc( + "AggregateWithSeedByT", "f", f, + simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + fFunc := func(result interface{}, current interface{}) interface{} { + return fGenericFunc.Call(result, current) + } + + resultSelectorGenericFunc, err := newGenericFunc( + "AggregateWithSeedByT", "resultSelectorFn", resultSelectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + resultSelectorFunc := func(result interface{}) interface{} { + return resultSelectorGenericFunc.Call(result) + } + + return q.AggregateWithSeedBy(seed, fFunc, resultSelectorFunc) +} diff --git a/aggregate_test.go b/aggregate_test.go index 4bfc136..0fe1a51 100644 --- a/aggregate_test.go +++ b/aggregate_test.go @@ -26,6 +26,17 @@ func TestAggregate(t *testing.T) { } } +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 { + if len(r) > len(i) { + return r + } + return i + }) + }) +} + func TestAggregateWithSeed(t *testing.T) { input := []string{"apple", "mango", "orange", "banana", "grape"} want := "passionfruit" @@ -43,6 +54,17 @@ func TestAggregateWithSeed(t *testing.T) { } } +func TestAggregateWithSeedT_PanicWhenFunctionIsInvalid(t *testing.T) { + mustPanicWithError(t, "AggregateWithSeed: 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}).AggregateWithSeedT(3, func(x int, r string, i string) string { + if len(r) > len(i) { + return r + } + return i + }) + }) +} + func TestAggregateWithSeedBy(t *testing.T) { input := []string{"apple", "mango", "orange", "passionfruit", "grape"} want := "PASSIONFRUIT" @@ -63,3 +85,35 @@ func TestAggregateWithSeedBy(t *testing.T) { t.Errorf("From(%v).AggregateWithSeed()=%v expected %v", input, r, want) } } + +func TestAggregateWithSeedByT_PanicWhenFunctionIsInvalid(t *testing.T) { + mustPanicWithError(t, "AggregateWithSeedByT: 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}).AggregateWithSeedByT(3, + func(x int, r string, i string) string { + if len(r) > len(i) { + return r + } + return i + }, + func(r string) string { + return r + }, + ) + }) +} + +func TestAggregateWithSeedByT_PanicWhenResultSelectorFnIsInvalid(t *testing.T) { + mustPanicWithError(t, "AggregateWithSeedByT: parameter [resultSelectorFn] has a invalid function signature. Expected: 'func(T)T', actual: 'func(string,int)string'", func() { + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).AggregateWithSeedByT(3, + func(x int, r int) int { + if x > r { + return x + } + return r + }, + func(r string, t int) string { + return r + }, + ) + }) +} diff --git a/aggregatetyped.go b/aggregatetyped.go deleted file mode 100644 index 34c89de..0000000 --- a/aggregatetyped.go +++ /dev/null @@ -1,88 +0,0 @@ -package linq - -// AggregateT is the typed version of Aggregate. -// -// NOTE: Aggregate method has better performance than AggregateT -// -// f is of type: func(TSource, TSource) TSource -// -func (q Query) AggregateT(f interface{}) interface{} { - - fGenericFunc, err := newGenericFunc( - "AggregateT", "f", f, - simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))), - ) - if err != nil { - panic(err) - } - - fFunc := func(result interface{}, current interface{}) interface{} { - return fGenericFunc.Call(result, current) - } - - return q.Aggregate(fFunc) -} - -// AggregateWithSeedT is the typed version of AggregateWithSeed. -// -// NOTE: AggregateWithSeed method has better performance than AggregateWithSeedT -// -// f is of a type "func(TAccumulate, TSource) TAccumulate" -// -func (q Query) AggregateWithSeedT(seed interface{}, f interface{}) interface{} { - - fGenericFunc, err := newGenericFunc( - "AggregateWithSeed", "f", f, - simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))), - ) - if err != nil { - panic(err) - } - - fFunc := func(result interface{}, current interface{}) interface{} { - return fGenericFunc.Call(result, current) - } - - return q.AggregateWithSeed(seed, fFunc) -} - -// AggregateWithSeedByT is the typed version of AggregateWithSeedBy. -// -// NOTE: AggregateWithSeedBy method has better performance than AggregateWithSeedByT -// -// f is of a type "func(TAccumulate, TSource) TAccumulate" -// -// resultSelectorFn is of type "func(TAccumulate) TResult" -// -func (q Query) AggregateWithSeedByT( - seed interface{}, - f interface{}, - resultSelectorFn interface{}, -) interface{} { - - fGenericFunc, err := newGenericFunc( - "AggregateWithSeedByT", "f", f, - simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))), - ) - if err != nil { - panic(err) - } - - fFunc := func(result interface{}, current interface{}) interface{} { - return fGenericFunc.Call(result, current) - } - - resultSelectorGenericFunc, err := newGenericFunc( - "AggregateWithSeedByT", "resultSelectorFn", resultSelectorFn, - simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), - ) - if err != nil { - panic(err) - } - - resultSelectorFunc := func(result interface{}) interface{} { - return resultSelectorGenericFunc.Call(result) - } - - return q.AggregateWithSeedBy(seed, fFunc, resultSelectorFunc) -} diff --git a/aggregatetyped_test.go b/aggregatetyped_test.go deleted file mode 100644 index fb203ef..0000000 --- a/aggregatetyped_test.go +++ /dev/null @@ -1,157 +0,0 @@ -package linq - -import "testing" -import "strings" - -func TestAggregateT(t *testing.T) { - tests := []struct { - input interface{} - predicate interface{} - want interface{} - }{ - {[]string{"apple", "mango", "orange", "passionfruit", "grape"}, func(r string, i string) string { - if len(r) > len(i) { - return r - } - return i - }, "passionfruit"}, - } - - for _, test := range tests { - r := From(test.input).AggregateT(test.predicate) - if r != test.want { - t.Errorf("From(%v).AggregateT()=%v expected %v", test.input, r, test.want) - } - } -} - -func TestAggregateT_PanicWhenFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - - From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).AggregateT(func(x int, r string, i string) string { - if len(r) > len(i) { - return r - } - return i - }) -} - -func TestAggregateWithSeedT(t *testing.T) { - tests := []struct { - input interface{} - predicate interface{} - want interface{} - }{ - {[]string{"apple", "mango", "orange", "grape"}, func(r string, i string) string { - if len(r) > len(i) { - return r - } - return i - }, "passionfruit"}, - } - - for _, test := range tests { - r := From(test.input).AggregateWithSeedT(test.want, test.predicate) - if r != test.want { - t.Errorf("From(%v).AggregateWithSeedT()=%v expected %v", test.input, r, test.want) - } - } -} - -func TestAggregateWithSeedT_PanicWhenFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - - From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).AggregateWithSeedT(3, func(x int, r string, i string) string { - if len(r) > len(i) { - return r - } - return i - }) - -} - -func TestAggregateWithSeedByT(t *testing.T) { - - input := []string{"apple", "mango", "orange", "passionfruit", "grape"} - - want := "PASSIONFRUIT" - - r := From(input).AggregateWithSeedByT("banana", - func(r string, i string) string { - if len(r) > len(i) { - return r - } - return i - }, - func(r string) string { - return strings.ToUpper(r) - }, - ) - - if r != want { - t.Errorf("From(%v).AggregateWithSeedT()=%v expected %v", input, r, want) - } - -} - -func TestAggregateWithSeedByT_PanicWhenFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - - From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).AggregateWithSeedByT(3, - func(x int, r string, i string) string { - if len(r) > len(i) { - return r - } - return i - }, - func(r string) string { - return r - }, - ) - -} - -func TestAggregateWithSeedByT_PanicWhenResultSelectorIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - - From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).AggregateWithSeedByT(3, - func(x int, r int) int { - if x > r { - return x - } - return r - }, - func(r string, t int) string { - return r - }, - ) - -} diff --git a/distinct.go b/distinct.go index 7a32c31..5b64109 100644 --- a/distinct.go +++ b/distinct.go @@ -73,3 +73,26 @@ func (q Query) DistinctBy(selector func(interface{}) interface{}) Query { }, } } + +// DistinctByT is the typed version of DistinctBy. +// +// NOTE: DistinctBy method has better performance than DistinctByT +// +// selectorFn is of type "func(TSource) TSource". +func (q Query) DistinctByT(selectorFn interface{}) Query { + selectorFunc, ok := selectorFn.(func(interface{}) interface{}) + if !ok { + selectorGenericFunc, err := newGenericFunc( + "DistinctByT", "selectorFn", selectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + selectorFunc = func(item interface{}) interface{} { + return selectorGenericFunc.Call(item) + } + } + return q.DistinctBy(selectorFunc) +} diff --git a/distinct_test.go b/distinct_test.go index a71254e..e1e6618 100644 --- a/distinct_test.go +++ b/distinct_test.go @@ -53,3 +53,9 @@ func TestDistinctBy(t *testing.T) { t.Errorf("From(%v).DistinctBy()=%v expected %v", users, toSlice(q), want) } } + +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/distincttyped.go b/distincttyped.go deleted file mode 100644 index 577feb6..0000000 --- a/distincttyped.go +++ /dev/null @@ -1,25 +0,0 @@ -package linq - -// DistinctByT is the typed version of DistinctBy. -// -// NOTE: DistinctBy method has better performance than DistinctByT -// -// selectorFn is of type "func(TSource) TSource". -// -func (q Query) DistinctByT(selectorFn interface{}) Query { - selectorFunc, ok := selectorFn.(func(interface{}) interface{}) - if !ok { - selectorGenericFunc, err := newGenericFunc( - "DistinctBy", "selectorFn", selectorFn, - simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), - ) - if err != nil { - panic(err) - } - - selectorFunc = func(item interface{}) interface{} { - return selectorGenericFunc.Call(item) - } - } - return q.DistinctBy(selectorFunc) -} diff --git a/distincttyped_test.go b/distincttyped_test.go deleted file mode 100644 index d934299..0000000 --- a/distincttyped_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package linq - -import "testing" - -func TestDistinctByT(t *testing.T) { - type user struct { - id int - name string - } - - tests := []struct { - input interface{} - selector interface{} - output []interface{} - }{ - {[]user{{1, "Foo"}, {2, "Bar"}, {3, "Foo"}}, func(u interface{}) interface{} { - return u.(user).name - }, []interface{}{user{1, "Foo"}, user{2, "Bar"}}}, - {[]user{{1, "Foo"}, {2, "Bar"}, {3, "Foo"}}, func(u user) string { - return u.name - }, []interface{}{user{1, "Foo"}, user{2, "Bar"}}}, - } - - for _, test := range tests { - if q := From(test.input).DistinctByT(test.selector); !validateQuery(q, test.output) { - t.Errorf("From(%v).WhereIndexed()=%v expected %v", test.input, toSlice(q), test.output) - } - } -} - -func TestDistinctBy_PanicWhenFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - - 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 5c07a37..5cd6983 100644 --- a/example_test.go +++ b/example_test.go @@ -3,6 +3,7 @@ package linq import ( "fmt" "strings" + "time" ) func ExampleKeyValue() { @@ -1410,3 +1411,1086 @@ func ExampleQuery_Zip() { // Output: // [[1 one] [2 two] [3 three]] } + +// The following code example demonstrates how to use ThenByDescendingT to perform +// a order in a slice of dates by year, and then by month descending. +func ExampleOrderedQuery_ThenByDescendingT() { + dates := []time.Time{ + time.Date(2015, 3, 23, 0, 0, 0, 0, time.Local), + time.Date(2014, 7, 11, 0, 0, 0, 0, time.Local), + time.Date(2013, 5, 4, 0, 0, 0, 0, time.Local), + time.Date(2015, 1, 2, 0, 0, 0, 0, time.Local), + time.Date(2015, 7, 10, 0, 0, 0, 0, time.Local), + } + + var orderedDates []time.Time + From(dates). + OrderByT( + func(date time.Time) int { + return date.Year() + }). + ThenByDescendingT( + func(date time.Time) int { return int(date.Month()) }, + ). + ToSlice(&orderedDates) + + for _, date := range orderedDates { + fmt.Println(date.Format("2006-Jan-02")) + } + // Output: + // 2013-May-04 + // 2014-Jul-11 + // 2015-Jul-10 + // 2015-Mar-23 + // 2015-Jan-02 + +} + +// The following code example demonstrates how to use ThenByT to perform +// a orders in a slice of dates by year, and then by day. +func ExampleOrderedQuery_ThenByT() { + dates := []time.Time{ + time.Date(2015, 3, 23, 0, 0, 0, 0, time.Local), + time.Date(2014, 7, 11, 0, 0, 0, 0, time.Local), + time.Date(2013, 5, 4, 0, 0, 0, 0, time.Local), + time.Date(2015, 1, 2, 0, 0, 0, 0, time.Local), + time.Date(2015, 7, 10, 0, 0, 0, 0, time.Local), + } + + var orderedDates []time.Time + From(dates). + OrderByT( + func(date time.Time) int { return date.Year() }, + ). + ThenByT( + func(date time.Time) int { return int(date.Day()) }, + ). + ToSlice(&orderedDates) + + for _, date := range orderedDates { + fmt.Println(date.Format("2006-Jan-02")) + } + // Output: + // 2013-May-04 + // 2014-Jul-11 + // 2015-Jan-02 + // 2015-Jul-10 + // 2015-Mar-23 + +} + +// The following code example demonstrates how to reverse +// the order of words in a string using AggregateT. +func ExampleQuery_AggregateT() { + sentence := "the quick brown fox jumps over the lazy dog" + // Split the string into individual words. + words := strings.Split(sentence, " ") + + // Prepend each word to the beginning of the + // new sentence to reverse the word order. + reversed := From(words).AggregateT( + func(workingSentence string, next string) string { return next + " " + workingSentence }, + ) + + fmt.Println(reversed) + + // Output: + // dog lazy the over jumps fox brown quick the +} + +// The following code example demonstrates how to use AggregateWithSeed function +func ExampleQuery_AggregateWithSeedT() { + + fruits := []string{"apple", "mango", "orange", "passionfruit", "grape"} + + // Determine whether any string in the array is longer than "banana". + longestName := From(fruits). + AggregateWithSeedT("banana", + func(longest, next string) string { + if len(next) > len(longest) { + return next + } + return longest + }, + ) + + fmt.Printf("The fruit with the longest name is %s.", longestName) + + // Output: + // The fruit with the longest name is passionfruit. + +} + +// The following code example demonstrates how to use AggregateWithSeedByT function +func ExampleQuery_AggregateWithSeedByT() { + input := []string{"apple", "mango", "orange", "passionfruit", "grape"} + + // Determine whether any string in the array is longer than "banana". + longestName := From(input).AggregateWithSeedByT("banana", + func(longest string, next string) string { + if len(longest) > len(next) { + return longest + } + return next + + }, + // Return the final result + func(result string) string { + return fmt.Sprintf("The fruit with the longest name is %s.", result) + }, + ) + + fmt.Println(longestName) + // Output: + // The fruit with the longest name is passionfruit. +} + +// The following code example demonstrates how to use AllT +// to get the students having all marks greater than 70. +func ExampleQuery_AllT() { + + type Student struct { + Name string + Marks []int + } + + students := []Student{ + {Name: "Hugo", Marks: []int{91, 88, 76, 93}}, + {Name: "Rick", Marks: []int{70, 73, 66, 90}}, + {Name: "Michael", Marks: []int{73, 80, 75, 88}}, + {Name: "Fadi", Marks: []int{82, 75, 66, 84}}, + {Name: "Peter", Marks: []int{67, 78, 70, 82}}, + } + + var approvedStudents []Student + From(students). + WhereT( + func(student Student) bool { + return From(student.Marks). + AllT( + func(mark int) bool { return mark > 70 }, + ) + }, + ). + ToSlice(&approvedStudents) + + //List of approved students + for _, student := range approvedStudents { + fmt.Println(student.Name) + } + + // Output: + // Hugo + // Michael +} + +// The following code example demonstrates how to use AnyWithT +// to get the students with any mark lower than 70. +func ExampleQuery_AnyWithT() { + type Student struct { + Name string + Marks []int + } + + students := []Student{ + {Name: "Hugo", Marks: []int{91, 88, 76, 93}}, + {Name: "Rick", Marks: []int{70, 73, 66, 90}}, + {Name: "Michael", Marks: []int{73, 80, 75, 88}}, + {Name: "Fadi", Marks: []int{82, 75, 66, 84}}, + {Name: "Peter", Marks: []int{67, 78, 70, 82}}, + } + + var studentsWithAnyMarkLt70 []Student + From(students). + WhereT( + func(student Student) bool { + return From(student.Marks). + AnyWithT( + func(mark int) bool { return mark < 70 }, + ) + }, + ). + ToSlice(&studentsWithAnyMarkLt70) + + //List of students with any mark lower than 70 + for _, student := range studentsWithAnyMarkLt70 { + fmt.Println(student.Name) + } + + // Output: + // Rick + // Fadi + // Peter + +} + +// The following code example demonstrates how to use CountWithT +// to count the elements in an slice that satisfy a condition. +func ExampleQuery_CountWithT() { + type Pet struct { + Name string + Vaccinated bool + } + + pets := []Pet{ + {Name: "Barley", Vaccinated: true}, + {Name: "Boots", Vaccinated: false}, + {Name: "Whiskers", Vaccinated: false}, + } + + numberUnvaccinated := From(pets). + CountWithT( + func(p Pet) bool { return p.Vaccinated == false }, + ) + + fmt.Printf("There are %d unvaccinated animals.", numberUnvaccinated) + + //Output: + //There are 2 unvaccinated animals. +} + +// The following code example demonstrates how to use DistinctByT +// to return distinct elements from a slice of structs. +func ExampleQuery_DistinctByT() { + type Product struct { + Name string + Code int + } + + products := []Product{ + {Name: "apple", Code: 9}, + {Name: "orange", Code: 4}, + {Name: "apple", Code: 9}, + {Name: "lemon", Code: 12}, + } + + //Exclude duplicates. + var noduplicates []Product + From(products). + DistinctByT( + func(item Product) int { return item.Code }, + ). + ToSlice(&noduplicates) + + for _, product := range noduplicates { + fmt.Printf("%s %d\n", product.Name, product.Code) + } + + // Output: + // apple 9 + // orange 4 + // lemon 12 +} + +// The following code example demonstrates how to use ExceptByT +func ExampleQuery_ExceptByT() { + type Product struct { + Name string + Code int + } + + fruits1 := []Product{ + {Name: "orange", Code: 4}, + {Name: "apple", Code: 9}, + {Name: "lemon", Code: 12}, + {Name: "apple", Code: 9}, + } + + fruits2 := []Product{ + {Name: "apple", Code: 9}, + } + + //Order and exclude duplicates. + var except []Product + From(fruits1). + ExceptByT(From(fruits2), + func(item Product) int { return item.Code }, + ). + ToSlice(&except) + + for _, product := range except { + fmt.Printf("%s %d\n", product.Name, product.Code) + } + + // Output: + // orange 4 + // lemon 12 + +} + +// The following code example demonstrates how to use FirstWithT +// to return the first element of an array that satisfies a condition. +func ExampleQuery_FirstWithT() { + numbers := []int{9, 34, 65, 92, 87, 435, 3, 54, 83, 23, 87, 435, 67, 12, 19} + + first := From(numbers). + FirstWithT( + func(item int) bool { return item > 80 }, + ) + + fmt.Println(first) + + // Output: + // 92 + +} + +// The following code example demonstrates how to use GroupByT +// to group the elements of a slice. +func ExampleQuery_GroupByT() { + + type Pet struct { + Name string + Age int + } + // Create a list of pets. + pets := []Pet{ + {Name: "Barley", Age: 8}, + {Name: "Boots", Age: 4}, + {Name: "Whiskers", Age: 1}, + {Name: "Daisy", Age: 4}, + } + + // Group the pets using Age as the key value + // and selecting only the pet's Name for each value. + var query []Group + From(pets).GroupByT( + func(p Pet) int { return p.Age }, + func(p Pet) string { return p.Name }, + ).OrderByT( + func(g Group) int { return g.Key.(int) }, + ).ToSlice(&query) + + for _, petGroup := range query { + fmt.Printf("%d\n", petGroup.Key) + for _, petName := range petGroup.Group { + fmt.Printf(" %s\n", petName) + } + + } + + // Output: + // 1 + // Whiskers + // 4 + // Boots + // Daisy + // 8 + // Barley +} + +// The following code example demonstrates how to use GroupJoinT +// to perform a grouped join on two slices. +func ExampleQuery_GroupJoinT() { + + type Person struct { + Name string + } + + type Pet struct { + Name string + Owner Person + } + + magnus := Person{Name: "Hedlund, Magnus"} + terry := Person{Name: "Adams, Terry"} + charlotte := Person{Name: "Weiss, Charlotte"} + + barley := Pet{Name: "Barley", Owner: terry} + boots := Pet{Name: "Boots", Owner: terry} + whiskers := Pet{Name: "Whiskers", Owner: charlotte} + daisy := Pet{Name: "Daisy", Owner: magnus} + + people := []Person{magnus, terry, charlotte} + pets := []Pet{barley, boots, whiskers, daisy} + + // Create a slice where each element is a KeyValue + // that contains a person's name as the key and a slice of strings + // of names of the pets they own as a value. + + q := []KeyValue{} + From(people). + GroupJoinT(From(pets), + func(p Person) Person { return p }, + func(p Pet) Person { return p.Owner }, + func(person Person, pets []Pet) KeyValue { + var petNames []string + From(pets). + SelectT( + func(pet Pet) string { return pet.Name }, + ). + ToSlice(&petNames) + return KeyValue{person.Name, petNames} + }, + ).ToSlice(&q) + + for _, obj := range q { + // Output the owner's name. + fmt.Printf("%s:\n", obj.Key) + // Output each of the owner's pet's names. + for _, petName := range obj.Value.([]string) { + fmt.Printf(" %s\n", petName) + } + } + + // Output: + // Hedlund, Magnus: + // Daisy + // Adams, Terry: + // Barley + // Boots + // Weiss, Charlotte: + // Whiskers +} + +// The following code example demonstrates how to use IntersectByT +// to return the elements that appear in each of two slices of products +// with same Code. +func ExampleQuery_IntersectByT() { + type Product struct { + Name string + Code int + } + + store1 := []Product{ + {Name: "orange", Code: 4}, + {Name: "apple", Code: 9}, + } + + store2 := []Product{ + {Name: "lemon", Code: 12}, + {Name: "apple", Code: 9}, + } + + var duplicates []Product + From(store1). + IntersectByT(From(store2), + func(p Product) int { return p.Code }, + ). + ToSlice(&duplicates) + + for _, p := range duplicates { + fmt.Println(p.Name, "", p.Code) + } + + // Output: + // apple 9 + +} + +// The following code example demonstrates how to use JoinT +// to perform an inner join of two slices based on a common key. +func ExampleQuery_JoinT() { + type Person struct { + Name string + } + + type Pet struct { + Name string + Owner Person + } + + magnus := Person{Name: "Hedlund, Magnus"} + terry := Person{Name: "Adams, Terry"} + charlotte := Person{Name: "Weiss, Charlotte"} + + barley := Pet{Name: "Barley", Owner: terry} + boots := Pet{Name: "Boots", Owner: terry} + whiskers := Pet{Name: "Whiskers", Owner: charlotte} + daisy := Pet{Name: "Daisy", Owner: magnus} + + people := []Person{magnus, terry, charlotte} + pets := []Pet{barley, boots, whiskers, daisy} + + // Create a list of Person-Pet pairs where + // each element is an anonymous type that contains a + // Pet's name and the name of the Person that owns the Pet. + + query := []string{} + From(people). + JoinT(From(pets), + func(person Person) Person { return person }, + func(pet Pet) Person { return pet.Owner }, + func(person Person, pet Pet) string { return fmt.Sprintf("%s - %s", person.Name, pet.Name) }, + ).ToSlice(&query) + + for _, line := range query { + fmt.Println(line) + } + //Output: + //Hedlund, Magnus - Daisy + //Adams, Terry - Barley + //Adams, Terry - Boots + //Weiss, Charlotte - Whiskers +} + +// The following code example demonstrates how to use LastWithT +// to return the last element of an array. +func ExampleQuery_LastWithT() { + numbers := []int{9, 34, 65, 92, 87, 435, 3, 54, + 83, 23, 87, 67, 12, 19} + + last := From(numbers). + LastWithT( + func(n int) bool { return n > 80 }, + ) + + fmt.Println(last) + + //Output: + //87 + +} + +// The following code example demonstrates how to use OrderByDescendingT +// to order an slice. +func ExampleQuery_OrderByDescendingT() { + type Player struct { + Name string + Points int64 + } + + players := []Player{ + {Name: "Hugo", Points: 4757}, + {Name: "Rick", Points: 7365}, + {Name: "Michael", Points: 2857}, + {Name: "Fadi", Points: 85897}, + {Name: "Peter", Points: 48576}, + } + + //Order and get the top 3 players + var top3Players []KeyValue + From(players). + OrderByDescendingT( + func(p Player) int64 { return p.Points }, + ). + Take(3). + SelectIndexedT( + func(i int, p Player) KeyValue { return KeyValue{Key: i + 1, Value: p} }, + ). + ToSlice(&top3Players) + + for _, rank := range top3Players { + fmt.Printf( + "Rank: #%d - Player: %s - Points: %d\n", + rank.Key, + rank.Value.(Player).Name, + rank.Value.(Player).Points, + ) + + } + // Output: + // Rank: #1 - Player: Fadi - Points: 85897 + // Rank: #2 - Player: Peter - Points: 48576 + // Rank: #3 - Player: Rick - Points: 7365 +} + +// The following code example demonstrates how to use OrderByT +// to sort the elements of a slice. +func ExampleQuery_OrderByT() { + type Pet struct { + Name string + Age int + } + // Create a list of pets. + pets := []Pet{ + {Name: "Barley", Age: 8}, + {Name: "Boots", Age: 4}, + {Name: "Whiskers", Age: 1}, + {Name: "Daisy", Age: 4}, + } + + var orderedPets []Pet + From(pets). + OrderByT( + func(pet Pet) int { return pet.Age }, + ). + ToSlice(&orderedPets) + + for _, pet := range orderedPets { + fmt.Println(pet.Name, "-", pet.Age) + } + + // Output: + // Whiskers - 1 + // Boots - 4 + // Daisy - 4 + // Barley - 8 +} + +// The following code example demonstrates how to use SelectT +// to project over a slice. +func ExampleQuery_SelectT() { + squares := []int{} + + Range(1, 10). + SelectT( + func(x int) int { return x * x }, + ). + ToSlice(&squares) + + fmt.Println(squares) + // Output: + // [1 4 9 16 25 36 49 64 81 100] +} + +// The following code example demonstrates how to use SelectIndexedT +// to determine if the value in a slice of int match their position +// in the slice. +func ExampleQuery_SelectIndexedT() { + numbers := []int{5, 4, 1, 3, 9, 8, 6, 7, 2, 0} + + var numsInPlace []KeyValue + + From(numbers). + SelectIndexedT( + func(index, num int) KeyValue { return KeyValue{Key: num, Value: (num == index)} }, + ). + ToSlice(&numsInPlace) + + fmt.Println("Number: In-place?") + for _, n := range numsInPlace { + fmt.Printf("%d: %t\n", n.Key, n.Value) + } + + // Output: + // Number: In-place? + // 5: false + // 4: false + // 1: false + // 3: true + // 9: false + // 8: false + // 6: true + // 7: true + // 2: false + // 0: false + +} + +// The following code example demonstrates how to use SelectManyT +// to perform a one-to-many projection over a slice +func ExampleQuery_SelectManyByT() { + + 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} + var results []string + From(people). + SelectManyByT( + func(person Person) Query { return From(person.Pets) }, + func(pet Pet, person Person) interface{} { + return fmt.Sprintf("Owner: %s, Pet: %s", person.Name, pet.Name) + }, + ). + ToSlice(&results) + + 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 +func ExampleQuery_SelectManyT() { + sentences := []string{ + "the quick brown fox jumps over the lazy dog", + "pack my box with five dozen liquor jugs", + "several fabulous dixieland jazz groups played with quick tempo", + "back in my quaint garden jaunty zinnias vie with flaunting phlox", + "five or six big jet planes zoomed quickly by the new tower", + "I quickly explained that many big jobs involve few hazards", + "The wizard quickly jinxed the gnomes before they vaporized", + } + + var results []string + From(sentences). + //Split the sentences in words + SelectManyT(func(sentence string) Query { + return From(strings.Split(sentence, " ")) + }). + //Grouping by word + GroupByT( + func(word string) string { return word }, + func(word string) string { return word }, + ). + //Ordering by word counts + OrderByDescendingT(func(wordGroup Group) int { + return len(wordGroup.Group) + }). + //Then order by word + ThenByT(func(wordGroup Group) string { + return wordGroup.Key.(string) + }). + //Take the top 5 + Take(5). + //Project the words using the index as rank + SelectIndexedT(func(index int, wordGroup Group) string { + return fmt.Sprintf("Rank: #%d, Word: %s, Counts: %d", index+1, wordGroup.Key, len(wordGroup.Group)) + }). + ToSlice(&results) + + for _, result := range results { + fmt.Println(result) + } + + // Output: + // Rank: #1, Word: the, Counts: 4 + // Rank: #2, Word: quickly, Counts: 3 + // Rank: #3, Word: with, Counts: 3 + // Rank: #4, Word: big, Counts: 2 + // Rank: #5, Word: five, Counts: 2 +} + +// 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_SelectManyIndexedT() { + 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 + + From(logFiles). + SelectManyIndexedT(func(fileIndex int, file LogFile) Query { + return From(file.Lines). + SelectIndexedT(func(lineIndex int, line string) string { + return fmt.Sprintf("File:[%d] - %s => line: %d - %s", fileIndex+1, file.Name, lineIndex+1, line) + }) + }). + ToSlice(&results) + + for _, result := range results { + 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. +func ExampleQuery_SelectManyByIndexedT() { + 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} + var results []string + + From(people). + SelectManyByIndexedT( + func(index int, person Person) Query { + return From(person.Pets). + SelectT(func(pet Pet) string { + return fmt.Sprintf("%d - %s", index, pet.Name) + }) + }, + func(indexedPet string, person Person) string { + return fmt.Sprintf("Pet: %s, Owner: %s", indexedPet, person.Name) + }, + ). + ToSlice(&results) + + for _, result := range results { + fmt.Println(result) + } + + // Output: + // Pet: 0 - Daisy, Owner: Hedlund, Magnus + // Pet: 1 - Barley, Owner: Adams, Terry + // Pet: 1 - Boots, Owner: Adams, Terry + // Pet: 2 - Whiskers, Owner: Weiss, Charlotte + +} + +//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"} + + fruit := From(fruits). + SingleWithT( + func(f string) bool { return len(f) > 10 }, + ) + + fmt.Println(fruit) + // Output: + // passionfruit +} + +// The following code example demonstrates how to use SkipWhileT +// to skip elements of an array as long as a condition is true. +func ExampleQuery_SkipWhileT() { + grades := []int{59, 82, 70, 56, 92, 98, 85} + var lowerGrades []int + From(grades). + OrderByDescendingT( + func(g int) int { return g }, + ). + SkipWhileT( + func(g int) bool { return g >= 80 }, + ). + ToSlice(&lowerGrades) + + //"All grades below 80: + fmt.Println(lowerGrades) + // Output: + // [70 59 56] +} + +// The following code example demonstrates how to use SkipWhileIndexedT +// to skip elements of an array as long as a condition that depends +// on the element's index is true. +func ExampleQuery_SkipWhileIndexedT() { + amounts := []int{5000, 2500, 9000, 8000, 6500, 4000, 1500, 5500} + + var query []int + From(amounts). + SkipWhileIndexedT( + func(index int, amount int) bool { return amount > index*1000 }, + ). + ToSlice(&query) + + fmt.Println(query) + // Output: + // [4000 1500 5500] + +} + +// The following code example demonstrates how to use SortT +// to order elements of an slice. +func ExampleQuery_SortT() { + type Pet struct { + Name string + Age int + } + // Create a list of pets. + pets := []Pet{ + {Name: "Barley", Age: 8}, + {Name: "Boots", Age: 4}, + {Name: "Whiskers", Age: 1}, + {Name: "Daisy", Age: 4}, + } + + orderedPets := []Pet{} + From(pets). + SortT( + func(pet1 Pet, pet2 Pet) bool { return pet1.Age < pet2.Age }, + ). + ToSlice(&orderedPets) + + for _, pet := range orderedPets { + fmt.Println(pet.Name, "-", pet.Age) + } + + // Output: + // Whiskers - 1 + // Boots - 4 + // Daisy - 4 + // Barley - 8 + +} + +// The following code example demonstrates how to use TakeWhileT +// to return elements from the start of a slice. +func ExampleQuery_TakeWhileT() { + fruits := []string{"apple", "banana", "mango", "orange", "passionfruit", "grape"} + + var query []string + From(fruits). + TakeWhileT( + func(fruit string) bool { return fruit != "orange" }, + ). + ToSlice(&query) + + fmt.Println(query) + + // Output: + // [apple banana mango] +} + +// The following code example demonstrates how to use TakeWhileIndexedT +// to return elements from the start of a slice as long asa condition +// that uses the element's index is true. +func ExampleQuery_TakeWhileIndexedT() { + + fruits := []string{"apple", "passionfruit", "banana", "mango", + "orange", "blueberry", "grape", "strawberry"} + + var query []string + From(fruits). + TakeWhileIndexedT( + func(index int, fruit string) bool { return len(fruit) >= index }, + ). + ToSlice(&query) + + fmt.Println(query) + + // Output: + // [apple passionfruit banana mango orange blueberry] +} + +// The following code example demonstrates how to use ToMapBy +// by using a key and value selectors to populate a map. +func ExampleQuery_ToMapByT() { + type Product struct { + Name string + Code int + } + + products := []Product{ + {Name: "orange", Code: 4}, + {Name: "apple", Code: 9}, + {Name: "lemon", Code: 12}, + {Name: "apple", Code: 9}, + } + + map1 := map[int]string{} + From(products). + ToMapByT(&map1, + func(item Product) int { return item.Code }, + func(item Product) string { return item.Name }, + ) + + fmt.Println(map1[4]) + fmt.Println(map1[9]) + fmt.Println(map1[12]) + + // Output: + // orange + // apple + // lemon +} + +// The following code example demonstrates how to use WhereT +// to filter a slices. +func ExampleQuery_WhereT() { + fruits := []string{"apple", "passionfruit", "banana", "mango", + "orange", "blueberry", "grape", "strawberry"} + var query []string + From(fruits). + WhereT( + func(f string) bool { return len(f) > 6 }, + ). + ToSlice(&query) + + fmt.Println(query) + + // Output: + // [passionfruit blueberry strawberry] +} + +// The following code example demonstrates how to use WhereIndexedT +// to filter a slice based on a predicate that involves the index of each element. +func ExampleQuery_WhereIndexedT() { + numbers := []int{0, 30, 20, 15, 90, 85, 40, 75} + + var query []int + From(numbers). + WhereIndexedT( + func(index int, number int) bool { return number <= index*10 }, + ). + ToSlice(&query) + + fmt.Println(query) + + // Output: + // [0 15 40] +} + +// The following code example demonstrates how to use the Zip +// method to merge two slices. +func ExampleQuery_ZipT() { + number := []int{1, 2, 3, 4, 5} + words := []string{"one", "two", "three"} + + q := From(number). + ZipT(From(words), + func(a int, b string) []interface{} { return []interface{}{a, b} }, + ) + + fmt.Println(q.Results()) + + // Output: + // [[1 one] [2 two] [3 three]] +} diff --git a/exampletyped_test.go b/exampletyped_test.go deleted file mode 100644 index 409a62e..0000000 --- a/exampletyped_test.go +++ /dev/null @@ -1,1083 +0,0 @@ -package linq - -import "fmt" -import "strings" -import "time" - -// The following code example demonstrates how to use ThenByDescendingT to perform -// a order in a slice of dates by year, and then by month descending. -func ExampleOrderedQuery_ThenByDescendingT() { - dates := []time.Time{ - time.Date(2015, 3, 23, 0, 0, 0, 0, time.Local), - time.Date(2014, 7, 11, 0, 0, 0, 0, time.Local), - time.Date(2013, 5, 4, 0, 0, 0, 0, time.Local), - time.Date(2015, 1, 2, 0, 0, 0, 0, time.Local), - time.Date(2015, 7, 10, 0, 0, 0, 0, time.Local), - } - - var orderedDates []time.Time - From(dates). - OrderByT( - func(date time.Time) int { - return date.Year() - }). - ThenByDescendingT( - func(date time.Time) int { return int(date.Month()) }, - ). - ToSlice(&orderedDates) - - for _, date := range orderedDates { - fmt.Println(date.Format("2006-Jan-02")) - } - // Output: - // 2013-May-04 - // 2014-Jul-11 - // 2015-Jul-10 - // 2015-Mar-23 - // 2015-Jan-02 - -} - -// The following code example demonstrates how to use ThenByT to perform -// a orders in a slice of dates by year, and then by day. -func ExampleOrderedQuery_ThenByT() { - dates := []time.Time{ - time.Date(2015, 3, 23, 0, 0, 0, 0, time.Local), - time.Date(2014, 7, 11, 0, 0, 0, 0, time.Local), - time.Date(2013, 5, 4, 0, 0, 0, 0, time.Local), - time.Date(2015, 1, 2, 0, 0, 0, 0, time.Local), - time.Date(2015, 7, 10, 0, 0, 0, 0, time.Local), - } - - var orderedDates []time.Time - From(dates). - OrderByT( - func(date time.Time) int { return date.Year() }, - ). - ThenByT( - func(date time.Time) int { return int(date.Day()) }, - ). - ToSlice(&orderedDates) - - for _, date := range orderedDates { - fmt.Println(date.Format("2006-Jan-02")) - } - // Output: - // 2013-May-04 - // 2014-Jul-11 - // 2015-Jan-02 - // 2015-Jul-10 - // 2015-Mar-23 - -} - -// The following code example demonstrates how to reverse -// the order of words in a string using AggregateT. -func ExampleQuery_AggregateT() { - sentence := "the quick brown fox jumps over the lazy dog" - // Split the string into individual words. - words := strings.Split(sentence, " ") - - // Prepend each word to the beginning of the - // new sentence to reverse the word order. - reversed := From(words).AggregateT( - func(workingSentence string, next string) string { return next + " " + workingSentence }, - ) - - fmt.Println(reversed) - - // Output: - // dog lazy the over jumps fox brown quick the -} - -// The following code example demonstrates how to use AggregateWithSeed function -func ExampleQuery_AggregateWithSeedT() { - - fruits := []string{"apple", "mango", "orange", "passionfruit", "grape"} - - // Determine whether any string in the array is longer than "banana". - longestName := From(fruits). - AggregateWithSeedT("banana", - func(longest, next string) string { - if len(next) > len(longest) { - return next - } - return longest - }, - ) - - fmt.Printf("The fruit with the longest name is %s.", longestName) - - // Output: - // - // The fruit with the longest name is passionfruit. - -} - -// The following code example demonstrates how to use AggregateWithSeedByT function -func ExampleQuery_AggregateWithSeedByT() { - input := []string{"apple", "mango", "orange", "passionfruit", "grape"} - - // Determine whether any string in the array is longer than "banana". - longestName := From(input).AggregateWithSeedByT("banana", - func(longest string, next string) string { - if len(longest) > len(next) { - return longest - } - return next - - }, - // Return the final result - func(result string) string { - return fmt.Sprintf("The fruit with the longest name is %s.", result) - }, - ) - - fmt.Println(longestName) - // Output: - // The fruit with the longest name is passionfruit. -} - -// The following code example demonstrates how to use AllT -// to get the students having all marks greater than 70. -func ExampleQuery_AllT() { - - type Student struct { - Name string - Marks []int - } - - students := []Student{ - {Name: "Hugo", Marks: []int{91, 88, 76, 93}}, - {Name: "Rick", Marks: []int{70, 73, 66, 90}}, - {Name: "Michael", Marks: []int{73, 80, 75, 88}}, - {Name: "Fadi", Marks: []int{82, 75, 66, 84}}, - {Name: "Peter", Marks: []int{67, 78, 70, 82}}, - } - - var approvedStudents []Student - From(students). - WhereT( - func(student Student) bool { - return From(student.Marks). - AllT( - func(mark int) bool { return mark > 70 }, - ) - }, - ). - ToSlice(&approvedStudents) - - //List of approved students - for _, student := range approvedStudents { - fmt.Println(student.Name) - } - - // Output: - // Hugo - // Michael -} - -// The following code example demonstrates how to use AnyWithT -// to get the students with any mark lower than 70. -func ExampleQuery_AnyWithT() { - type Student struct { - Name string - Marks []int - } - - students := []Student{ - {Name: "Hugo", Marks: []int{91, 88, 76, 93}}, - {Name: "Rick", Marks: []int{70, 73, 66, 90}}, - {Name: "Michael", Marks: []int{73, 80, 75, 88}}, - {Name: "Fadi", Marks: []int{82, 75, 66, 84}}, - {Name: "Peter", Marks: []int{67, 78, 70, 82}}, - } - - var studentsWithAnyMarkLt70 []Student - From(students). - WhereT( - func(student Student) bool { - return From(student.Marks). - AnyWithT( - func(mark int) bool { return mark < 70 }, - ) - }, - ). - ToSlice(&studentsWithAnyMarkLt70) - - //List of students with any mark lower than 70 - for _, student := range studentsWithAnyMarkLt70 { - fmt.Println(student.Name) - } - - // Output: - // Rick - // Fadi - // Peter - -} - -// The following code example demonstrates how to use CountWithT -// to count the elements in an slice that satisfy a condition. -func ExampleQuery_CountWithT() { - type Pet struct { - Name string - Vaccinated bool - } - - pets := []Pet{ - {Name: "Barley", Vaccinated: true}, - {Name: "Boots", Vaccinated: false}, - {Name: "Whiskers", Vaccinated: false}, - } - - numberUnvaccinated := From(pets). - CountWithT( - func(p Pet) bool { return p.Vaccinated == false }, - ) - - fmt.Printf("There are %d unvaccinated animals.", numberUnvaccinated) - - //Output: - //There are 2 unvaccinated animals. -} - -// The following code example demonstrates how to -// use DistinctByT to return distinct elements from a slice of structs. -func ExampleQuery_DistinctByT() { - type Product struct { - Name string - Code int - } - - products := []Product{ - {Name: "apple", Code: 9}, - {Name: "orange", Code: 4}, - {Name: "apple", Code: 9}, - {Name: "lemon", Code: 12}, - } - - //Exclude duplicates. - var noduplicates []Product - From(products). - DistinctByT( - func(item Product) int { return item.Code }, - ). - ToSlice(&noduplicates) - - for _, product := range noduplicates { - fmt.Printf("%s %d\n", product.Name, product.Code) - } - - // Output: - // apple 9 - // orange 4 - // lemon 12 -} - -// The following code example demonstrates how to use ExceptByT -func ExampleQuery_ExceptByT() { - type Product struct { - Name string - Code int - } - - fruits1 := []Product{ - {Name: "orange", Code: 4}, - {Name: "apple", Code: 9}, - {Name: "lemon", Code: 12}, - {Name: "apple", Code: 9}, - } - - fruits2 := []Product{ - {Name: "apple", Code: 9}, - } - - //Order and exclude duplicates. - var except []Product - From(fruits1). - ExceptByT(From(fruits2), - func(item Product) int { return item.Code }, - ). - ToSlice(&except) - - for _, product := range except { - fmt.Printf("%s %d\n", product.Name, product.Code) - } - - // Output: - // orange 4 - // lemon 12 - -} - -// The following code example demonstrates how to use FirstWithT -// to return the first element of an array that satisfies a condition. -func ExampleQuery_FirstWithT() { - numbers := []int{9, 34, 65, 92, 87, 435, 3, 54, 83, 23, 87, 435, 67, 12, 19} - - first := From(numbers). - FirstWithT( - func(item int) bool { return item > 80 }, - ) - - fmt.Println(first) - - // Output: - // 92 - -} - -// The following code example demonstrates how to use GroupByT -// to group the elements of a slice. -func ExampleQuery_GroupByT() { - - type Pet struct { - Name string - Age int - } - // Create a list of pets. - pets := []Pet{ - {Name: "Barley", Age: 8}, - {Name: "Boots", Age: 4}, - {Name: "Whiskers", Age: 1}, - {Name: "Daisy", Age: 4}, - } - - // Group the pets using Age as the key value - // and selecting only the pet's Name for each value. - var query []Group - From(pets).GroupByT( - func(p Pet) int { return p.Age }, - func(p Pet) string { return p.Name }, - ).OrderByT( - func(g Group) int { return g.Key.(int) }, - ).ToSlice(&query) - - for _, petGroup := range query { - fmt.Printf("%d\n", petGroup.Key) - for _, petName := range petGroup.Group { - fmt.Printf(" %s\n", petName) - } - - } - - // Output: - // 1 - // Whiskers - // 4 - // Boots - // Daisy - // 8 - // Barley -} - -// The following code example demonstrates how to use GroupJoinT -// to perform a grouped join on two slices. -func ExampleQuery_GroupJoinT() { - - type Person struct { - Name string - } - - type Pet struct { - Name string - Owner Person - } - - magnus := Person{Name: "Hedlund, Magnus"} - terry := Person{Name: "Adams, Terry"} - charlotte := Person{Name: "Weiss, Charlotte"} - - barley := Pet{Name: "Barley", Owner: terry} - boots := Pet{Name: "Boots", Owner: terry} - whiskers := Pet{Name: "Whiskers", Owner: charlotte} - daisy := Pet{Name: "Daisy", Owner: magnus} - - people := []Person{magnus, terry, charlotte} - pets := []Pet{barley, boots, whiskers, daisy} - - // Create a slice where each element is a KeyValue - // that contains a person's name as the key and a slice of strings - // of names of the pets they own as a value. - - q := []KeyValue{} - From(people). - GroupJoinT(From(pets), - func(p Person) Person { return p }, - func(p Pet) Person { return p.Owner }, - func(person Person, pets []Pet) KeyValue { - var petNames []string - From(pets). - SelectT( - func(pet Pet) string { return pet.Name }, - ). - ToSlice(&petNames) - return KeyValue{person.Name, petNames} - }, - ).ToSlice(&q) - - for _, obj := range q { - // Output the owner's name. - fmt.Printf("%s:\n", obj.Key) - // Output each of the owner's pet's names. - for _, petName := range obj.Value.([]string) { - fmt.Printf(" %s\n", petName) - } - } - - // Output: - // Hedlund, Magnus: - // Daisy - // Adams, Terry: - // Barley - // Boots - // Weiss, Charlotte: - // Whiskers -} - -// The following code example demonstrates how to use IntersectByT -// to return the elements that appear in each of two slices of products with same Code. -func ExampleQuery_IntersectByT() { - type Product struct { - Name string - Code int - } - - store1 := []Product{ - {Name: "orange", Code: 4}, - {Name: "apple", Code: 9}, - } - - store2 := []Product{ - {Name: "lemon", Code: 12}, - {Name: "apple", Code: 9}, - } - - var duplicates []Product - From(store1). - IntersectByT(From(store2), - func(p Product) int { return p.Code }, - ). - ToSlice(&duplicates) - - for _, p := range duplicates { - fmt.Println(p.Name, "", p.Code) - } - - // Output: - // apple 9 - -} - -// The following code example demonstrates how to use JoinT -// to perform an inner join of two slices based on a common key. -func ExampleQuery_JoinT() { - type Person struct { - Name string - } - - type Pet struct { - Name string - Owner Person - } - - magnus := Person{Name: "Hedlund, Magnus"} - terry := Person{Name: "Adams, Terry"} - charlotte := Person{Name: "Weiss, Charlotte"} - - barley := Pet{Name: "Barley", Owner: terry} - boots := Pet{Name: "Boots", Owner: terry} - whiskers := Pet{Name: "Whiskers", Owner: charlotte} - daisy := Pet{Name: "Daisy", Owner: magnus} - - people := []Person{magnus, terry, charlotte} - pets := []Pet{barley, boots, whiskers, daisy} - - // Create a list of Person-Pet pairs where - // each element is an anonymous type that contains a - // Pet's name and the name of the Person that owns the Pet. - - query := []string{} - From(people). - JoinT(From(pets), - func(person Person) Person { return person }, - func(pet Pet) Person { return pet.Owner }, - func(person Person, pet Pet) string { return fmt.Sprintf("%s - %s", person.Name, pet.Name) }, - ).ToSlice(&query) - - for _, line := range query { - fmt.Println(line) - } - //Output: - //Hedlund, Magnus - Daisy - //Adams, Terry - Barley - //Adams, Terry - Boots - //Weiss, Charlotte - Whiskers -} - -// The following code example demonstrates how to use LastWithT -// to return the last element of an array. -func ExampleQuery_LastWithT() { - numbers := []int{9, 34, 65, 92, 87, 435, 3, 54, - 83, 23, 87, 67, 12, 19} - - last := From(numbers). - LastWithT( - func(n int) bool { return n > 80 }, - ) - - fmt.Println(last) - - //Output: - //87 - -} - -// The following code example demonstrates how to use OrderByDescendingT to order an slice. -func ExampleQuery_OrderByDescendingT() { - type Player struct { - Name string - Points int64 - } - - players := []Player{ - {Name: "Hugo", Points: 4757}, - {Name: "Rick", Points: 7365}, - {Name: "Michael", Points: 2857}, - {Name: "Fadi", Points: 85897}, - {Name: "Peter", Points: 48576}, - } - - //Order and get the top 3 players - var top3Players []KeyValue - From(players). - OrderByDescendingT( - func(p Player) int64 { return p.Points }, - ). - Take(3). - SelectIndexedT( - func(i int, p Player) KeyValue { return KeyValue{Key: i + 1, Value: p} }, - ). - ToSlice(&top3Players) - - for _, rank := range top3Players { - fmt.Printf( - "Rank: #%d - Player: %s - Points: %d\n", - rank.Key, - rank.Value.(Player).Name, - rank.Value.(Player).Points, - ) - - } - // Output: - // Rank: #1 - Player: Fadi - Points: 85897 - // Rank: #2 - Player: Peter - Points: 48576 - // Rank: #3 - Player: Rick - Points: 7365 -} - -// The following code example demonstrates how to use OrderByT -// to sort the elements of a slice. -func ExampleQuery_OrderByT() { - type Pet struct { - Name string - Age int - } - // Create a list of pets. - pets := []Pet{ - {Name: "Barley", Age: 8}, - {Name: "Boots", Age: 4}, - {Name: "Whiskers", Age: 1}, - {Name: "Daisy", Age: 4}, - } - - var orderedPets []Pet - From(pets). - OrderByT( - func(pet Pet) int { return pet.Age }, - ). - ToSlice(&orderedPets) - - for _, pet := range orderedPets { - fmt.Println(pet.Name, "-", pet.Age) - } - - // Output: - // Whiskers - 1 - // Boots - 4 - // Daisy - 4 - // Barley - 8 -} - -// The following code example demonstrates how to use SelectT -// to project over a slice. -func ExampleQuery_SelectT() { - squares := []int{} - - Range(1, 10). - SelectT( - func(x int) int { return x * x }, - ). - ToSlice(&squares) - - fmt.Println(squares) - // Output: - // [1 4 9 16 25 36 49 64 81 100] -} - -// The following code example demonstrates how to use SelectIndexedT -// to determine if the value in a slice of int match their position in the slice. -func ExampleQuery_SelectIndexedT() { - numbers := []int{5, 4, 1, 3, 9, 8, 6, 7, 2, 0} - - var numsInPlace []KeyValue - - From(numbers). - SelectIndexedT( - func(index, num int) KeyValue { return KeyValue{Key: num, Value: (num == index)} }, - ). - ToSlice(&numsInPlace) - - fmt.Println("Number: In-place?") - for _, n := range numsInPlace { - fmt.Printf("%d: %t\n", n.Key, n.Value) - } - - // Output: - // Number: In-place? - // 5: false - // 4: false - // 1: false - // 3: true - // 9: false - // 8: false - // 6: true - // 7: true - // 2: false - // 0: false - -} - -// The following code example demonstrates how to use SelectManyT -// to perform a one-to-many projection over a slice -func ExampleQuery_SelectManyByT() { - - 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} - var results []string - From(people). - SelectManyByT( - func(person Person) Query { return From(person.Pets) }, - func(pet Pet, person Person) interface{} { - return fmt.Sprintf("Owner: %s, Pet: %s", person.Name, pet.Name) - }, - ). - ToSlice(&results) - - 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 -func ExampleQuery_SelectManyT() { - sentences := []string{ - "the quick brown fox jumps over the lazy dog", - "pack my box with five dozen liquor jugs", - "several fabulous dixieland jazz groups played with quick tempo", - "back in my quaint garden jaunty zinnias vie with flaunting phlox", - "five or six big jet planes zoomed quickly by the new tower", - "I quickly explained that many big jobs involve few hazards", - "The wizard quickly jinxed the gnomes before they vaporized", - } - - var results []string - From(sentences). - //Split the sentences in words - SelectManyT(func(sentence string) Query { - return From(strings.Split(sentence, " ")) - }). - //Grouping by word - GroupByT( - func(word string) string { return word }, - func(word string) string { return word }, - ). - //Ordering by word counts - OrderByDescendingT(func(wordGroup Group) int { - return len(wordGroup.Group) - }). - //Then order by word - ThenByT(func(wordGroup Group) string { - return wordGroup.Key.(string) - }). - //Take the top 5 - Take(5). - //Project the words using the index as rank - SelectIndexedT(func(index int, wordGroup Group) string { - return fmt.Sprintf("Rank: #%d, Word: %s, Counts: %d", index+1, wordGroup.Key, len(wordGroup.Group)) - }). - ToSlice(&results) - - for _, result := range results { - fmt.Println(result) - } - - // Output: - // Rank: #1, Word: the, Counts: 4 - // Rank: #2, Word: quickly, Counts: 3 - // Rank: #3, Word: with, Counts: 3 - // Rank: #4, Word: big, Counts: 2 - // Rank: #5, Word: five, Counts: 2 -} - -// 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_SelectManyIndexedT() { - 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 - - From(logFiles). - SelectManyIndexedT(func(fileIndex int, file LogFile) Query { - return From(file.Lines). - SelectIndexedT(func(lineIndex int, line string) string { - return fmt.Sprintf("File:[%d] - %s => line: %d - %s", fileIndex+1, file.Name, lineIndex+1, line) - }) - }). - ToSlice(&results) - - for _, result := range results { - 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. -func ExampleQuery_SelectManyByIndexedT() { - 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} - var results []string - - From(people). - SelectManyByIndexedT( - func(index int, person Person) Query { - return From(person.Pets). - SelectT(func(pet Pet) string { - return fmt.Sprintf("%d - %s", index, pet.Name) - }) - }, - func(indexedPet string, person Person) string { - return fmt.Sprintf("Pet: %s, Owner: %s", indexedPet, person.Name) - }, - ). - ToSlice(&results) - - for _, result := range results { - fmt.Println(result) - } - - // Output: - // Pet: 0 - Daisy, Owner: Hedlund, Magnus - // Pet: 1 - Barley, Owner: Adams, Terry - // Pet: 1 - Boots, Owner: Adams, Terry - // Pet: 2 - Whiskers, Owner: Weiss, Charlotte - -} - -//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"} - - fruit := From(fruits). - SingleWithT( - func(f string) bool { return len(f) > 10 }, - ) - - fmt.Println(fruit) - // Output: - // passionfruit -} - -// The following code example demonstrates how to use SkipWhileT -// to skip elements of an array as long as a condition is true. -func ExampleQuery_SkipWhileT() { - grades := []int{59, 82, 70, 56, 92, 98, 85} - var lowerGrades []int - From(grades). - OrderByDescendingT( - func(g int) int { return g }, - ). - SkipWhileT( - func(g int) bool { return g >= 80 }, - ). - ToSlice(&lowerGrades) - - //"All grades below 80: - fmt.Println(lowerGrades) - // Output: - // [70 59 56] -} - -// The following code example demonstrates how to use SkipWhileIndexedT -// to skip elements of an array as long as a condition that depends -// on the element's index is true. -func ExampleQuery_SkipWhileIndexedT() { - amounts := []int{5000, 2500, 9000, 8000, 6500, 4000, 1500, 5500} - - var query []int - From(amounts). - SkipWhileIndexedT( - func(index int, amount int) bool { return amount > index*1000 }, - ). - ToSlice(&query) - - fmt.Println(query) - // Output: - // [4000 1500 5500] - -} - -// The following code example demonstrates how to use SortT -// to order elements of an slice. -func ExampleQuery_SortT() { - type Pet struct { - Name string - Age int - } - // Create a list of pets. - pets := []Pet{ - {Name: "Barley", Age: 8}, - {Name: "Boots", Age: 4}, - {Name: "Whiskers", Age: 1}, - {Name: "Daisy", Age: 4}, - } - - orderedPets := []Pet{} - From(pets). - SortT( - func(pet1 Pet, pet2 Pet) bool { return pet1.Age < pet2.Age }, - ). - ToSlice(&orderedPets) - - for _, pet := range orderedPets { - fmt.Println(pet.Name, "-", pet.Age) - } - - // Output: - // Whiskers - 1 - // Boots - 4 - // Daisy - 4 - // Barley - 8 - -} - -// The following code example demonstrates how to use TakeWhileT -// to return elements from the start of a slice. -func ExampleQuery_TakeWhileT() { - fruits := []string{"apple", "banana", "mango", "orange", "passionfruit", "grape"} - - var query []string - From(fruits). - TakeWhileT( - func(fruit string) bool { return fruit != "orange" }, - ). - ToSlice(&query) - - fmt.Println(query) - - // Output: - // [apple banana mango] -} - -// The following code example demonstrates how to use TakeWhileIndexedT -// to return elements from the start of a slice as long as -// a condition that uses the element's index is true. -func ExampleQuery_TakeWhileIndexedT() { - - fruits := []string{"apple", "passionfruit", "banana", "mango", - "orange", "blueberry", "grape", "strawberry"} - - var query []string - From(fruits). - TakeWhileIndexedT( - func(index int, fruit string) bool { return len(fruit) >= index }, - ). - ToSlice(&query) - - fmt.Println(query) - - // Output: - // [apple passionfruit banana mango orange blueberry] -} - -// The following code example demonstrates how to use ToMapBy -// by using a key and value selectors to populate a map. -func ExampleQuery_ToMapByT() { - type Product struct { - Name string - Code int - } - - products := []Product{ - {Name: "orange", Code: 4}, - {Name: "apple", Code: 9}, - {Name: "lemon", Code: 12}, - {Name: "apple", Code: 9}, - } - - map1 := map[int]string{} - From(products). - ToMapByT(&map1, - func(item Product) int { return item.Code }, - func(item Product) string { return item.Name }, - ) - - fmt.Println(map1[4]) - fmt.Println(map1[9]) - fmt.Println(map1[12]) - - // Output: - // orange - // apple - // lemon -} - -// The following code example demonstrates how to use WhereT -// to filter a slices. -func ExampleQuery_WhereT() { - fruits := []string{"apple", "passionfruit", "banana", "mango", - "orange", "blueberry", "grape", "strawberry"} - var query []string - From(fruits). - WhereT( - func(f string) bool { return len(f) > 6 }, - ). - ToSlice(&query) - - fmt.Println(query) - - // Output: - // [passionfruit blueberry strawberry] -} - -// The following code example demonstrates how to use WhereIndexedT -// to filter a slice based on a predicate that involves the index of each element. -func ExampleQuery_WhereIndexedT() { - numbers := []int{0, 30, 20, 15, 90, 85, 40, 75} - - var query []int - From(numbers). - WhereIndexedT( - func(index int, number int) bool { return number <= index*10 }, - ). - ToSlice(&query) - - fmt.Println(query) - - // Output: - // [0 15 40] -} - -// The following code example demonstrates how to use the Zip -// method to merge two slices. -func ExampleQuery_ZipT() { - number := []int{1, 2, 3, 4, 5} - words := []string{"one", "two", "three"} - - q := From(number). - ZipT(From(words), - func(a int, b string) []interface{} { return []interface{}{a, b} }, - ) - - fmt.Println(q.Results()) - - // Output: - // [[1 one] [2 two] [3 three]] -} diff --git a/except.go b/except.go index c34a6b4..074c945 100644 --- a/except.go +++ b/except.go @@ -57,3 +57,24 @@ func (q Query) ExceptBy( }, } } + +// ExceptByT is the typed version of ExceptBy. +// +// NOTE: ExceptBy method has better performance than ExceptByT +// +// selectorFn is of a type "func(TSource) TSource" +func (q Query) ExceptByT(q2 Query, selectorFn interface{}) Query { + selectorGenericFunc, err := newGenericFunc( + "ExceptByT", "selectorFn", selectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + selectorFunc := func(item interface{}) interface{} { + return selectorGenericFunc.Call(item) + } + + return q.ExceptBy(q2, selectorFunc) +} diff --git a/except_test.go b/except_test.go index e543dce..3513ee5 100644 --- a/except_test.go +++ b/except_test.go @@ -23,3 +23,9 @@ func TestExceptBy(t *testing.T) { t.Errorf("From(%v).ExceptBy(%v)=%v expected %v", input1, input2, toSlice(q), want) } } + +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/excepttyped.go b/excepttyped.go deleted file mode 100644 index 4d8be73..0000000 --- a/excepttyped.go +++ /dev/null @@ -1,24 +0,0 @@ -package linq - -// ExceptByT is the typed version of ExceptByT. -// -// NOTE: ExceptBy method has better performance than ExceptByT -// -// selectorFn is of a type "func(TSource) TSource" -// -func (q Query) ExceptByT(q2 Query, selectorFn interface{}) Query { - - selectorGenericFunc, err := newGenericFunc( - "ExceptByT", "selectorFn", selectorFn, - simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), - ) - if err != nil { - panic(err) - } - - selectorFunc := func(item interface{}) interface{} { - return selectorGenericFunc.Call(item) - } - - return q.ExceptBy(q2, selectorFunc) -} diff --git a/excepttyped_test.go b/excepttyped_test.go deleted file mode 100644 index 9415e82..0000000 --- a/excepttyped_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package linq - -import "testing" - -func TestExceptByT(t *testing.T) { - tests := []struct { - input1 interface{} - input2 interface{} - selector interface{} - want []interface{} - }{ - {[]int{1, 2, 3, 4, 5, 1, 2, 5}, []int{1}, func(i interface{}) interface{} { - return i.(int) % 2 - }, []interface{}{2, 4, 2}}, - {[]int{1, 2, 3, 4, 5, 1, 2, 5}, []int{1}, func(i int) int { - return i % 2 - }, []interface{}{2, 4, 2}}, - } - - for _, test := range tests { - if q := From(test.input1).ExceptByT(From(test.input2), test.selector); !validateQuery(q, test.want) { - t.Errorf("From(%v).ExceptBy(%v)=%v expected %v", test.input1, test.input2, toSlice(q), test.want) - } - } -} - -func TestExceptByT_PanicWhenFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - - 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/genericfunc_test.go b/genericfunc_test.go index 20c36f1..6087374 100644 --- a/genericfunc_test.go +++ b/genericfunc_test.go @@ -7,7 +7,6 @@ import ( ) func TestNewGenericFunc(t *testing.T) { - tests := []struct { methodName string paramName string @@ -128,5 +127,4 @@ func TestCall(t *testing.T) { } }() } - } diff --git a/groupby.go b/groupby.go index 2affbf8..e5d1320 100644 --- a/groupby.go +++ b/groupby.go @@ -46,3 +46,39 @@ func (q Query) GroupBy( }, } } + +// GroupByT is the typed version of GroupBy. +// +// NOTE: GroupBy method has better performance than GroupByT +// +// keySelectorFn is of a type "func(TSource) TKey" +// +// elementSelectorFn is of a type "func(TSource) TElement" +func (q Query) GroupByT(keySelectorFn interface{}, elementSelectorFn interface{}) Query { + keySelectorGenericFunc, err := newGenericFunc( + "GroupByT", "keySelectorFn", keySelectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + keySelectorFunc := func(item interface{}) interface{} { + return keySelectorGenericFunc.Call(item) + } + + elementSelectorGenericFunc, err := newGenericFunc( + "GroupByT", "elementSelectorFn", elementSelectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + elementSelectorFunc := func(item interface{}) interface{} { + return elementSelectorGenericFunc.Call(item) + + } + + return q.GroupBy(keySelectorFunc, elementSelectorFunc) +} diff --git a/groupby_test.go b/groupby_test.go index 0ecbc65..7cf7392 100644 --- a/groupby_test.go +++ b/groupby_test.go @@ -37,3 +37,23 @@ func TestGroupBy(t *testing.T) { t.Errorf("From(%v).GroupBy()=%v", input, toSlice(q)) } } + +func TestGroupByT_PanicWhenKeySelectorFnIsInvalid(t *testing.T) { + mustPanicWithError(t, "GroupByT: parameter [keySelectorFn] has a invalid function signature. Expected: 'func(T)T', actual: 'func(int,int)bool'", func() { + var r []int + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).GroupByT( + func(i, j int) bool { return true }, + func(i int) int { return i }, + ).ToSlice(&r) + }) +} + +func TestGroupByT_PanicWhenElementSelectorFnIsInvalid(t *testing.T) { + mustPanicWithError(t, "GroupByT: parameter [elementSelectorFn] has a invalid function signature. Expected: 'func(T)T', actual: 'func(int,int)int'", func() { + var r []int + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).GroupByT( + func(i int) bool { return true }, + func(i, j int) int { return i }, + ).ToSlice(&r) + }) +} diff --git a/groupbytyped.go b/groupbytyped.go deleted file mode 100644 index b1fc36b..0000000 --- a/groupbytyped.go +++ /dev/null @@ -1,39 +0,0 @@ -package linq - -// GroupByT is the typed version of GroupBy. -// -// NOTE: GroupBy method has better performance than GroupByT -// -// keySelectorFn is of a type "func(TSource) TKey" -// -// elementSelectorFn is of a type "func(TSource) TElement" -// -func (q Query) GroupByT(keySelectorFn interface{}, elementSelectorFn interface{}) Query { - - keySelectorGenericFunc, err := newGenericFunc( - "GroupByT", "keySelectorFn", keySelectorFn, - simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), - ) - if err != nil { - panic(err) - } - - keySelectorFunc := func(item interface{}) interface{} { - return keySelectorGenericFunc.Call(item) - } - - elementSelectorGenericFunc, err := newGenericFunc( - "GroupByT", "elementSelectorFn", elementSelectorFn, - simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), - ) - if err != nil { - panic(err) - } - - elementSelectorFunc := func(item interface{}) interface{} { - return elementSelectorGenericFunc.Call(item) - - } - - return q.GroupBy(keySelectorFunc, elementSelectorFunc) -} diff --git a/groupbytyped_test.go b/groupbytyped_test.go deleted file mode 100644 index 91531ad..0000000 --- a/groupbytyped_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package linq - -import ( - "reflect" - "testing" -) - -func TestGroupByT(t *testing.T) { - - tests := []struct { - input interface{} - keySelector interface{} - elementSelector interface{} - wantEven []interface{} - wantOdd []interface{} - }{ - { - []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, - func(i interface{}) interface{} { return i.(int) % 2 }, - func(i interface{}) interface{} { return i.(int) }, - []interface{}{2, 4, 6, 8}, []interface{}{1, 3, 5, 7, 9}, - }, - { - []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, - func(i int) int { return i % 2 }, - func(i int) int { return i }, - []interface{}{2, 4, 6, 8}, []interface{}{1, 3, 5, 7, 9}, - }, - } - for _, test := range tests { - q := From(test.input).GroupByT( - test.keySelector, - test.elementSelector, - ) - - next := q.Iterate() - eq := true - for item, ok := next(); ok; item, ok = next() { - group := item.(Group) - switch group.Key.(int) { - case 0: - if !reflect.DeepEqual(group.Group, test.wantEven) { - eq = false - } - case 1: - if !reflect.DeepEqual(group.Group, test.wantOdd) { - eq = false - } - default: - eq = false - } - } - - if !eq { - t.Errorf("From(%v).GroupByT()=%v", test.input, toSlice(q)) - } - } -} - -func TestGroupByT_PanicWhenKeyFunctionParameterInTypeDoesntMatch(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - - var r []int - From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).GroupByT( - func(i, j int) bool { return true }, - func(i int) int { return i }, - ).ToSlice(&r) -} - -func TestGroupByT_PanicWhenElementSelectorFunctionParameterInTypeDoesntMatch(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - - var r []int - From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).GroupByT( - func(i int) bool { return true }, - func(i, j int) int { return i }, - ).ToSlice(&r) -} diff --git a/groupjoin.go b/groupjoin.go index 7a3b56b..99a80c8 100644 --- a/groupjoin.go +++ b/groupjoin.go @@ -1,5 +1,7 @@ package linq +import "reflect" + // GroupJoin correlates the elements of two collections based on key equality, // and groups the results. // @@ -48,3 +50,58 @@ func (q Query) GroupJoin( }, } } + +// GroupJoinT is the typed version of GroupJoin. +// +// NOTE: GroupJoin method has better performance than GroupJoinT +// +// inner: The query to join to the outer query. +// +// outerKeySelectorFn is of a type "func(TOuter) TKey" +// +// innerKeySelectorFn is of a type "func(TInner) TKey" +// +// resultSelectorFn: is of a type "func(TOuter, inners []TInner) TResult" +func (q Query) GroupJoinT(inner Query, outerKeySelectorFn interface{}, innerKeySelectorFn interface{}, resultSelectorFn interface{}) Query { + outerKeySelectorGenericFunc, err := newGenericFunc( + "GroupJoinT", "outerKeySelectorFn", outerKeySelectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + outerKeySelectorFunc := func(item interface{}) interface{} { + return outerKeySelectorGenericFunc.Call(item) + } + + innerKeySelectorFuncGenericFunc, err := newGenericFunc( + "GroupJoinT", "innerKeySelectorFn", innerKeySelectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + innerKeySelectorFunc := func(item interface{}) interface{} { + return innerKeySelectorFuncGenericFunc.Call(item) + } + + resultSelectorGenericFunc, err := newGenericFunc( + "GroupJoinT", "resultSelectorFn", resultSelectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + resultSelectorFunc := func(outer interface{}, inners []interface{}) interface{} { + innerSliceType := reflect.MakeSlice(resultSelectorGenericFunc.Cache.TypesIn[1], 0, 0) + innersSlicePointer := reflect.New(innerSliceType.Type()) + From(inners).ToSlice(innersSlicePointer.Interface()) + innersTyped := reflect.Indirect(innersSlicePointer).Interface() + return resultSelectorGenericFunc.Call(outer, innersTyped) + } + + return q.GroupJoin(inner, outerKeySelectorFunc, innerKeySelectorFunc, resultSelectorFunc) +} diff --git a/groupjoin_test.go b/groupjoin_test.go index ddc759a..cc23a79 100644 --- a/groupjoin_test.go +++ b/groupjoin_test.go @@ -23,3 +23,36 @@ func TestGroupJoin(t *testing.T) { t.Errorf("From().GroupJoin()=%v expected %v", toSlice(q), want) } } + +func TestGroupJoinT_PanicWhenOuterKeySelectorFnIsInvalid(t *testing.T) { + mustPanicWithError(t, "GroupJoinT: parameter [outerKeySelectorFn] has a invalid function signature. Expected: 'func(T)T', actual: 'func(int,int)int'", func() { + From([]int{0, 1, 2}).GroupJoinT( + From([]int{1, 2, 3, 4, 5, 6, 7, 8, 9}), + func(i, j int) int { return i }, + func(i int) int { return i % 2 }, + func(outer int, inners []int) KeyValue { return KeyValue{outer, len(inners)} }, + ) + }) +} + +func TestGroupJoinT_PanicWhenInnerKeySelectorFnIsInvalid(t *testing.T) { + mustPanicWithError(t, "GroupJoinT: parameter [innerKeySelectorFn] has a invalid function signature. Expected: 'func(T)T', actual: 'func(int,int)int'", func() { + From([]int{0, 1, 2}).GroupJoinT( + From([]int{1, 2, 3, 4, 5, 6, 7, 8, 9}), + func(i int) int { return i }, + func(i, j int) int { return i % 2 }, + func(outer int, inners []int) KeyValue { return KeyValue{outer, len(inners)} }, + ) + }) +} + +func TestGroupJoinT_PanicWhenResultSelectorFnIsInvalid(t *testing.T) { + mustPanicWithError(t, "GroupJoinT: parameter [resultSelectorFn] has a invalid function signature. Expected: 'func(T,T)T', actual: 'func(int,int,[]int)linq.KeyValue'", func() { + From([]int{0, 1, 2}).GroupJoinT( + From([]int{1, 2, 3, 4, 5, 6, 7, 8, 9}), + func(i int) int { return i }, + func(i int) int { return i % 2 }, + func(outer, j int, inners []int) KeyValue { return KeyValue{outer, len(inners)} }, + ) + }) +} diff --git a/groupjointyped.go b/groupjointyped.go deleted file mode 100644 index edbe36c..0000000 --- a/groupjointyped.go +++ /dev/null @@ -1,60 +0,0 @@ -package linq - -import "reflect" - -// GroupJoinT is the typed version of GroupJoin. -// -// NOTE: GroupJoin method has better performance than GroupJoinT -// -// inner: The query to join to the outer query. -// -// outerKeySelectorFn is of a type "func(TOuter) TKey" -// -// innerKeySelectorFn is of a type "func(TInner) TKey" -// -// resultSelectorFn: is of a type "func(TOuter, inners []TInner) TResult" -// -func (q Query) GroupJoinT(inner Query, outerKeySelectorFn interface{}, innerKeySelectorFn interface{}, resultSelectorFn interface{}) Query { - - outerKeySelectorGenericFunc, err := newGenericFunc( - "GroupJoinT", "outerKeySelectorFn", outerKeySelectorFn, - simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), - ) - if err != nil { - panic(err) - } - - outerKeySelectorFunc := func(item interface{}) interface{} { - return outerKeySelectorGenericFunc.Call(item) - } - - innerKeySelectorFuncGenericFunc, err := newGenericFunc( - "GroupJoinT", "innerKeySelectorFn", innerKeySelectorFn, - simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), - ) - if err != nil { - panic(err) - } - - innerKeySelectorFunc := func(item interface{}) interface{} { - return innerKeySelectorFuncGenericFunc.Call(item) - } - - resultSelectorGenericFunc, err := newGenericFunc( - "GroupJoinT", "resultSelectorFn", resultSelectorFn, - simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))), - ) - if err != nil { - panic(err) - } - - resultSelectorFunc := func(outer interface{}, inners []interface{}) interface{} { - innerSliceType := reflect.MakeSlice(resultSelectorGenericFunc.Cache.TypesIn[1], 0, 0) - innersSlicePointer := reflect.New(innerSliceType.Type()) - From(inners).ToSlice(innersSlicePointer.Interface()) - innersTyped := reflect.Indirect(innersSlicePointer).Interface() - return resultSelectorGenericFunc.Call(outer, innersTyped) - } - - return q.GroupJoin(inner, outerKeySelectorFunc, innerKeySelectorFunc, resultSelectorFunc) -} diff --git a/groupjointyped_test.go b/groupjointyped_test.go deleted file mode 100644 index 58e4004..0000000 --- a/groupjointyped_test.go +++ /dev/null @@ -1,82 +0,0 @@ -package linq - -import "testing" - -func TestGroupJoinT(t *testing.T) { - tests := []struct { - outer interface{} - inner interface{} - outerKeySelector interface{} - innerKeySelector interface{} - resultSelector interface{} - want []interface{} - }{ - { - []int{0, 1, 2}, []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, - func(i int) int { return i }, - func(i int) int { return i % 2 }, - func(outer int, inners []int) KeyValue { return KeyValue{outer, len(inners)} }, - []interface{}{KeyValue{0, 4}, KeyValue{1, 5}, KeyValue{2, 0}}, - }, - } - - for _, test := range tests { - if q := From(test.outer).GroupJoinT(From(test.inner), test.outerKeySelector, test.innerKeySelector, test.resultSelector); !validateQuery(q, test.want) { - t.Errorf("From().GroupJoin()=%v expected %v", toSlice(q), test.want) - } - } -} - -func TestGroupJoinT_PanicWhenOuterKeySelectorFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - - From([]int{0, 1, 2}).GroupJoinT( - From([]int{1, 2, 3, 4, 5, 6, 7, 8, 9}), - func(i, j int) int { return i }, - func(i int) int { return i % 2 }, - func(outer int, inners []int) KeyValue { return KeyValue{outer, len(inners)} }, - ) -} - -func TestGroupJoinT_PanicWhenInnerKeySelectorFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - - From([]int{0, 1, 2}).GroupJoinT( - From([]int{1, 2, 3, 4, 5, 6, 7, 8, 9}), - func(i int) int { return i }, - func(i, j int) int { return i % 2 }, - func(outer int, inners []int) KeyValue { return KeyValue{outer, len(inners)} }, - ) -} - -func TestGroupJoinT_PanicWhenResultSelectorFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - - From([]int{0, 1, 2}).GroupJoinT( - From([]int{1, 2, 3, 4, 5, 6, 7, 8, 9}), - func(i int) int { return i }, - func(i int) int { return i % 2 }, - func(outer, j int, inners []int) KeyValue { return KeyValue{outer, len(inners)} }, - ) -} diff --git a/intersect.go b/intersect.go index 6499bb8..728528e 100644 --- a/intersect.go +++ b/intersect.go @@ -65,3 +65,24 @@ func (q Query) IntersectBy( }, } } + +// IntersectByT is the typed version of IntersectBy. +// +// NOTE: IntersectBy method has better performance than IntersectByT +// +// selectorFn is of a type "func(TSource) TSource" +func (q Query) IntersectByT(q2 Query, selectorFn interface{}) Query { + selectorGenericFunc, err := newGenericFunc( + "IntersectByT", "selectorFn", selectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + selectorFunc := func(item interface{}) interface{} { + return selectorGenericFunc.Call(item) + } + + return q.IntersectBy(q2, selectorFunc) +} diff --git a/intersect_test.go b/intersect_test.go index a22e1b3..f68a32e 100644 --- a/intersect_test.go +++ b/intersect_test.go @@ -23,3 +23,11 @@ func TestIntersectBy(t *testing.T) { t.Errorf("From(%v).IntersectBy(%v)=%v expected %v", input1, input2, toSlice(q), want) } } + +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 { + return i % 2 + }) + }) +} diff --git a/intersecttyped.go b/intersecttyped.go deleted file mode 100644 index 359f1bc..0000000 --- a/intersecttyped.go +++ /dev/null @@ -1,24 +0,0 @@ -package linq - -// IntersectByT is the typed version of IntersectBy. -// -// NOTE: IntersectBy method has better performance than IntersectByT -// -// selectorFn is of a type "func(TSource) TSource" -// -func (q Query) IntersectByT(q2 Query, selectorFn interface{}) Query { - - selectorGenericFunc, err := newGenericFunc( - "IntersectByT", "selectorFn", selectorFn, - simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), - ) - if err != nil { - panic(err) - } - - selectorFunc := func(item interface{}) interface{} { - return selectorGenericFunc.Call(item) - } - - return q.IntersectBy(q2, selectorFunc) -} diff --git a/intersecttyped_test.go b/intersecttyped_test.go deleted file mode 100644 index 6dec0fe..0000000 --- a/intersecttyped_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package linq - -import "testing" - -func TestIntersectByT(t *testing.T) { - tests := []struct { - input1 interface{} - input2 interface{} - selector interface{} - want []interface{} - }{ - { - []int{5, 7, 8}, []int{1, 4, 7, 9, 12, 3}, func(i interface{}) interface{} { - return i.(int) % 2 - }, []interface{}{5, 8}, - }, - - { - []int{5, 7, 8}, []int{1, 4, 7, 9, 12, 3}, func(i int) int { - return i % 2 - }, []interface{}{5, 8}, - }, - } - - for _, test := range tests { - if q := From(test.input1).IntersectByT(From(test.input2), test.selector); !validateQuery(q, test.want) { - t.Errorf("From(%v).IntersectBy(%v)=%v expected %v", test.input1, test.input2, toSlice(q), test.want) - } - } -} - -func TestIntersectByT_PanicWhenFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - - From([]int{5, 7, 8}).IntersectByT(From([]int{1, 4, 7, 9, 12, 3}), func(i, x int) int { - return i % 2 - }) -} diff --git a/join.go b/join.go index 95cb835..0fa1daa 100644 --- a/join.go +++ b/join.go @@ -54,3 +54,57 @@ func (q Query) Join( }, } } + +// JoinT is the typed version of Join. +// +// NOTE: Join method has better performance than JoinT +// +// outerKeySelectorFn is of a type "func(TOuter) TKey" +// +// innerKeySelectorFn is of a type "func(TInner) TKey" +// +// resultSelectorFn is of a type "func(TOuter,TInner) TResult" +func (q Query) JoinT(inner Query, + outerKeySelectorFn interface{}, + innerKeySelectorFn interface{}, + resultSelectorFn interface{}, +) Query { + outerKeySelectorGenericFunc, err := newGenericFunc( + "JoinT", "outerKeySelectorFn", outerKeySelectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + outerKeySelectorFunc := func(item interface{}) interface{} { + return outerKeySelectorGenericFunc.Call(item) + } + + innerKeySelectorFuncGenericFunc, err := newGenericFunc( + "JoinT", "innerKeySelectorFn", + innerKeySelectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + innerKeySelectorFunc := func(item interface{}) interface{} { + return innerKeySelectorFuncGenericFunc.Call(item) + } + + resultSelectorGenericFunc, err := newGenericFunc( + "JoinT", "resultSelectorFn", resultSelectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + resultSelectorFunc := func(outer interface{}, inner interface{}) interface{} { + return resultSelectorGenericFunc.Call(outer, inner) + } + + return q.Join(inner, outerKeySelectorFunc, innerKeySelectorFunc, resultSelectorFunc) +} diff --git a/join_test.go b/join_test.go index 8d5ad35..25dc292 100644 --- a/join_test.go +++ b/join_test.go @@ -25,3 +25,36 @@ func TestJoin(t *testing.T) { t.Errorf("From().Join()=%v expected %v", toSlice(q), want) } } + +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( + From([]int{1, 2, 3, 4, 5, 6, 7, 8, 9}), + func(i, j int) int { return i }, + func(i int) int { return i % 2 }, + func(outer int, inner int) KeyValue { return KeyValue{outer, inner} }, + ) + }) +} + +func TestJoinT_PanicWhenInnerKeySelectorFnIsInvalid(t *testing.T) { + mustPanicWithError(t, "JoinT: parameter [innerKeySelectorFn] has a invalid function signature. Expected: 'func(T)T', actual: 'func(int,int)int'", func() { + From([]int{0, 1, 2}).JoinT( + From([]int{1, 2, 3, 4, 5, 6, 7, 8, 9}), + func(i int) int { return i }, + func(i, j int) int { return i % 2 }, + func(outer int, inners []int) KeyValue { return KeyValue{outer, len(inners)} }, + ) + }) +} + +func TestJoinT_PanicWhenResultSelectorFnIsInvalid(t *testing.T) { + mustPanicWithError(t, "JoinT: parameter [resultSelectorFn] has a invalid function signature. Expected: 'func(T,T)T', actual: 'func(int,int,int)linq.KeyValue'", func() { + From([]int{0, 1, 2}).JoinT( + From([]int{1, 2, 3, 4, 5, 6, 7, 8, 9}), + func(i int) int { return i }, + func(i int) int { return i % 2 }, + func(outer int, inner, j int) KeyValue { return KeyValue{outer, inner} }, + ) + }) +} diff --git a/jointyped.go b/jointyped.go deleted file mode 100644 index ae02c0b..0000000 --- a/jointyped.go +++ /dev/null @@ -1,57 +0,0 @@ -package linq - -// JoinT is the typed version of Join. -// -// NOTE: Join method has better performance than JoinT -// -// outerKeySelectorFn is of a type "func(TOuter) TKey" -// -// innerKeySelectorFn is of a type "func(TInner) TKey" -// -// resultSelectorFn is of a type "func(TOuter,TInner) TResult" -// -func (q Query) JoinT(inner Query, - outerKeySelectorFn interface{}, - innerKeySelectorFn interface{}, - resultSelectorFn interface{}, -) Query { - - outerKeySelectorGenericFunc, err := newGenericFunc( - "JoinT", "outerKeySelectorFn", outerKeySelectorFn, - simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), - ) - if err != nil { - panic(err) - } - - outerKeySelectorFunc := func(item interface{}) interface{} { - return outerKeySelectorGenericFunc.Call(item) - } - - innerKeySelectorFuncGenericFunc, err := newGenericFunc( - "JoinT", "innerKeySelectorFn", - innerKeySelectorFn, - simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), - ) - if err != nil { - panic(err) - } - - innerKeySelectorFunc := func(item interface{}) interface{} { - return innerKeySelectorFuncGenericFunc.Call(item) - } - - resultSelectorGenericFunc, err := newGenericFunc( - "JoinT", "resultSelectorFn", resultSelectorFn, - simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))), - ) - if err != nil { - panic(err) - } - - resultSelectorFunc := func(outer interface{}, inner interface{}) interface{} { - return resultSelectorGenericFunc.Call(outer, inner) - } - - return q.Join(inner, outerKeySelectorFunc, innerKeySelectorFunc, resultSelectorFunc) -} diff --git a/jointyped_test.go b/jointyped_test.go deleted file mode 100644 index 187eaa7..0000000 --- a/jointyped_test.go +++ /dev/null @@ -1,111 +0,0 @@ -package linq - -import "testing" - -func TestJoinT(t *testing.T) { - outer := []int{0, 1, 2, 3, 4, 5, 8} - inner := []int{1, 2, 1, 4, 7, 6, 7, 2} - want := []interface{}{ - KeyValue{1, 1}, - KeyValue{1, 1}, - KeyValue{2, 2}, - KeyValue{2, 2}, - KeyValue{4, 4}, - } - - q := From(outer).JoinT( - From(inner), - func(i interface{}) interface{} { return i }, - func(i interface{}) interface{} { return i }, - func(outer interface{}, inner interface{}) interface{} { - return KeyValue{outer, inner} - }) - - if !validateQuery(q, want) { - t.Errorf("From().Join()=%v expected %v", toSlice(q), want) - } - - tests := []struct { - outer interface{} - inner interface{} - outerKeySelector interface{} - innerKeySelector interface{} - resultSelector interface{} - want []interface{} - }{ - { - []int{0, 1, 2, 3, 4, 5, 8}, []int{1, 2, 1, 4, 7, 6, 7, 2}, - func(i interface{}) interface{} { return i }, - func(i interface{}) interface{} { return i }, - func(outer interface{}, inner interface{}) interface{} { return KeyValue{outer, inner} }, - []interface{}{KeyValue{1, 1}, KeyValue{1, 1}, KeyValue{2, 2}, KeyValue{2, 2}, KeyValue{4, 4}}, - }, - { - []int{0, 1, 2, 3, 4, 5, 8}, []int{1, 2, 1, 4, 7, 6, 7, 2}, - func(i int) int { return i }, - func(i int) int { return i }, - func(outer int, inner int) KeyValue { return KeyValue{outer, inner} }, - []interface{}{KeyValue{1, 1}, KeyValue{1, 1}, KeyValue{2, 2}, KeyValue{2, 2}, KeyValue{4, 4}}, - }, - } - - for _, test := range tests { - if q := From(test.outer).JoinT(From(test.inner), test.outerKeySelector, test.innerKeySelector, test.resultSelector); !validateQuery(q, test.want) { - t.Errorf("From().Join()=%v expected %v", toSlice(q), test.want) - } - } -} - -func TestJoinT_PanicWhenOuterKeySelectorFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - - From([]int{0, 1, 2}).JoinT( - From([]int{1, 2, 3, 4, 5, 6, 7, 8, 9}), - func(i, j int) int { return i }, - func(i int) int { return i % 2 }, - func(outer int, inner int) KeyValue { return KeyValue{outer, inner} }, - ) -} - -func TestJoinT_PanicWhenInnerKeySelectorFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - - From([]int{0, 1, 2}).JoinT( - From([]int{1, 2, 3, 4, 5, 6, 7, 8, 9}), - func(i int) int { return i }, - func(i, j int) int { return i % 2 }, - func(outer int, inners []int) KeyValue { return KeyValue{outer, len(inners)} }, - ) -} - -func TestJoinT_PanicWhenResultSelectorFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - - From([]int{0, 1, 2}).JoinT( - From([]int{1, 2, 3, 4, 5, 6, 7, 8, 9}), - func(i int) int { return i }, - func(i int) int { return i % 2 }, - func(outer int, inner, j int) KeyValue { return KeyValue{outer, inner} }, - ) -} diff --git a/orderby.go b/orderby.go index b22f0e0..527602b 100644 --- a/orderby.go +++ b/orderby.go @@ -43,6 +43,27 @@ func (q Query) OrderBy( } } +// OrderByT is the typed version of OrderBy. +// +// NOTE: OrderBy method has better performance than OrderByT +// +// selectorFn is of a type "func(TSource) TKey" +func (q Query) OrderByT(selectorFn interface{}) OrderedQuery { + selectorGenericFunc, err := newGenericFunc( + "OrderByT", "selectorFn", selectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + selectorFunc := func(item interface{}) interface{} { + return selectorGenericFunc.Call(item) + } + + return q.OrderBy(selectorFunc) +} + // OrderByDescending sorts the elements of a collection in descending order. // Elements are sorted according to a key. func (q Query) OrderByDescending( @@ -70,6 +91,27 @@ func (q Query) OrderByDescending( } } +// OrderByDescendingT is the typed version of OrderByDescending. +// +// NOTE: OrderByDescending method has better performance than OrderByDescendingT +// +// selectorFn is of a type "func(TSource) TKey" +func (q Query) OrderByDescendingT(selectorFn interface{}) OrderedQuery { + selectorGenericFunc, err := newGenericFunc( + "OrderByDescendingT", "selectorFn", selectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + selectorFunc := func(item interface{}) interface{} { + return selectorGenericFunc.Call(item) + } + + return q.OrderByDescending(selectorFunc) +} + // ThenBy performs a subsequent ordering of the elements in a collection // in ascending order. This method enables you to specify multiple sort criteria // by applying any number of ThenBy or ThenByDescending methods. @@ -98,6 +140,27 @@ func (oq OrderedQuery) ThenBy( } } +// ThenByT is the typed version of ThenBy. +// +// NOTE: ThenBy method has better performance than ThenByT +// +// selectorFn is of a type "func(TSource) TKey" +func (oq OrderedQuery) ThenByT(selectorFn interface{}) OrderedQuery { + selectorGenericFunc, err := newGenericFunc( + "ThenByT", "selectorFn", selectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + selectorFunc := func(item interface{}) interface{} { + return selectorGenericFunc.Call(item) + } + + return oq.ThenBy(selectorFunc) +} + // ThenByDescending performs a subsequent ordering of the elements in a collection // in descending order. This method enables you to specify multiple sort criteria // by applying any number of ThenBy or ThenByDescending methods. @@ -126,6 +189,29 @@ func (oq OrderedQuery) ThenByDescending( } } +// ThenByDescendingT is the typed version of ThenByDescending. +// +// NOTE: ThenByDescending method has better performance than ThenByDescendingT +// +// selectorFn is of a type "func(TSource) TKey" +func (oq OrderedQuery) ThenByDescendingT(selectorFn interface{}) OrderedQuery { + selectorFunc, ok := selectorFn.(func(interface{}) interface{}) + if !ok { + selectorGenericFunc, err := newGenericFunc( + "ThenByDescending", "selectorFn", selectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + selectorFunc = func(item interface{}) interface{} { + return selectorGenericFunc.Call(item) + } + } + return oq.ThenByDescending(selectorFunc) +} + // 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, @@ -150,6 +236,27 @@ func (q Query) Sort(less func(i, j interface{}) bool) Query { } } +// SortT is the typed version of Sort. +// +// NOTE: Sort method has better performance than SortT +// +// lessFn is of a type "func(TSource,TSource) bool" +func (q Query) SortT(lessFn interface{}) Query { + lessGenericFunc, err := newGenericFunc( + "SortT", "lessFn", lessFn, + simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(bool))), + ) + if err != nil { + panic(err) + } + + lessFunc := func(i, j interface{}) bool { + return lessGenericFunc.Call(i, j).(bool) + } + + return q.Sort(lessFunc) +} + type sorter struct { items []interface{} less func(i, j interface{}) bool diff --git a/orderby_test.go b/orderby_test.go index 484b219..3d4c538 100644 --- a/orderby_test.go +++ b/orderby_test.go @@ -35,6 +35,12 @@ func TestOrderBy(t *testing.T) { } } +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 }) + }) +} + func TestOrderByDescending(t *testing.T) { slice := make([]foo, 100) @@ -57,6 +63,12 @@ func TestOrderByDescending(t *testing.T) { } } +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 }) + }) +} + func TestThenBy(t *testing.T) { slice := make([]foo, 1000) @@ -79,6 +91,14 @@ func TestThenBy(t *testing.T) { } } +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}). + OrderByT(func(item int) int { return item }). + ThenByT(func(item, j int) bool { return true }) + }) +} + func TestThenByDescending(t *testing.T) { slice := make([]foo, 1000) @@ -101,6 +121,14 @@ func TestThenByDescending(t *testing.T) { } } +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}). + OrderByT(func(item int) int { return item }). + ThenByDescendingT(func(item, j int) bool { return true }) + }) +} + func TestSort(t *testing.T) { slice := make([]foo, 100) @@ -122,3 +150,9 @@ func TestSort(t *testing.T) { 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/orderbytyped.go b/orderbytyped.go deleted file mode 100644 index 29b2db3..0000000 --- a/orderbytyped.go +++ /dev/null @@ -1,119 +0,0 @@ -package linq - -// OrderByT is the typed version of OrderBy. -// -// NOTE: OrderBy method has better performance than OrderByT -// -// selectorFn is of a type "func(TSource) TKey" -// -func (q Query) OrderByT(selectorFn interface{}) OrderedQuery { - - selectorGenericFunc, err := newGenericFunc( - "OrderByT", "selectorFn", selectorFn, - simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), - ) - if err != nil { - panic(err) - } - - selectorFunc := func(item interface{}) interface{} { - return selectorGenericFunc.Call(item) - } - - return q.OrderBy(selectorFunc) - -} - -// OrderByDescendingT is the typed version of OrderByDescending. -// -// NOTE: OrderByDescending method has better performance than OrderByDescendingT -// -// selectorFn is of a type "func(TSource) TKey" -// -func (q Query) OrderByDescendingT(selectorFn interface{}) OrderedQuery { - - selectorGenericFunc, err := newGenericFunc( - "OrderByDescendingT", "selectorFn", selectorFn, - simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), - ) - if err != nil { - panic(err) - } - - selectorFunc := func(item interface{}) interface{} { - return selectorGenericFunc.Call(item) - } - - return q.OrderByDescending(selectorFunc) -} - -// ThenByT is the typed version of ThenBy. -// -// NOTE: ThenBy method has better performance than ThenByT -// -// selectorFn is of a type "func(TSource) TKey" -// -func (oq OrderedQuery) ThenByT(selectorFn interface{}) OrderedQuery { - selectorGenericFunc, err := newGenericFunc( - "ThenByT", "selectorFn", selectorFn, - simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), - ) - if err != nil { - panic(err) - } - - selectorFunc := func(item interface{}) interface{} { - return selectorGenericFunc.Call(item) - } - - return oq.ThenBy(selectorFunc) -} - -// ThenByDescendingT is the typed version of ThenByDescending. -// -// NOTE: ThenByDescending method has better performance than ThenByDescendingT -// -// selectorFn is of a type "func(TSource) TKey" -// -func (oq OrderedQuery) ThenByDescendingT(selectorFn interface{}) OrderedQuery { - - selectorFunc, ok := selectorFn.(func(interface{}) interface{}) - if !ok { - selectorGenericFunc, err := newGenericFunc( - "ThenByDescending", "selectorFn", selectorFn, - simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), - ) - if err != nil { - panic(err) - } - - selectorFunc = func(item interface{}) interface{} { - return selectorGenericFunc.Call(item) - } - } - - return oq.ThenByDescending(selectorFunc) -} - -// SortT is the typed version of Sort. -// -// NOTE: Sort method has better performance than SortT -// -// lessFn is of a type "func(TSource,TSource) bool" -// -func (q Query) SortT(lessFn interface{}) Query { - - lessGenericFunc, err := newGenericFunc( - "SortT", "lessFn", lessFn, - simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(bool))), - ) - if err != nil { - panic(err) - } - - lessFunc := func(i, j interface{}) bool { - return lessGenericFunc.Call(i, j).(bool) - } - - return q.Sort(lessFunc) -} diff --git a/orderbytyped_test.go b/orderbytyped_test.go deleted file mode 100644 index e562e67..0000000 --- a/orderbytyped_test.go +++ /dev/null @@ -1,182 +0,0 @@ -package linq - -import "testing" - -func TestOrderByT(t *testing.T) { - slice := make([]foo, 100) - - for i := len(slice) - 1; i >= 0; i-- { - slice[i].f1 = i - } - - q := From(slice).OrderByT(func(i interface{}) interface{} { - return i.(foo).f1 - }) - - j := 0 - next := q.Iterate() - for item, ok := next(); ok; item, ok = next() { - if item.(foo).f1 != j { - t.Errorf("OrderBy()[%v]=%v expected %v", j, item, foo{f1: j}) - } - - j++ - } -} - -func TestOrderByT_PanicWhenFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - - From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).OrderByT(func(item, j int) int { return item + 2 }) -} - -func TestOrderByDescendingT(t *testing.T) { - slice := make([]foo, 100) - - for i := 0; i < len(slice); i++ { - slice[i].f1 = i - } - - q := From(slice).OrderByDescendingT(func(i foo) int { - return i.f1 - }) - - j := len(slice) - 1 - next := q.Iterate() - for item, ok := next(); ok; item, ok = next() { - if item.(foo).f1 != j { - t.Errorf("OrderByDescendingT()[%v]=%v expected %v", j, item, foo{f1: j}) - } - - j-- - } -} - -func TestOrderByDescendingT_PanicWhenFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - - From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).OrderByDescendingT(func(item, j int) int { return item + 2 }) -} - -func TestThenByT(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 := From(slice).OrderByT(func(i foo) bool { - return i.f2 - }).ThenByT(func(i foo) int { - return i.f1 - }) - - next := q.Iterate() - for item, ok := next(); ok; item, ok = next() { - if item.(foo).f2 != (item.(foo).f1%2 == 0) { - t.Errorf("OrderBy().ThenBy()=%v", item) - } - } -} - -func TestThenByT_PanicWhenFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - - From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}). - OrderByT(func(item int) int { return item }). - ThenByT(func(item, j int) bool { return true }) -} - -func TestThenByTDescending(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 := From(slice).OrderByT(func(i foo) bool { - return i.f2 - }).ThenByDescendingT(func(i foo) int { - return i.f1 - }) - - next := q.Iterate() - for item, ok := next(); ok; item, ok = next() { - if item.(foo).f2 != (item.(foo).f1%2 == 0) { - t.Errorf("OrderByT().ThenByDescendingT()=%v", item) - } - } -} - -func TestThenByDescendingT_PanicWhenFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - - From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}). - OrderByT(func(item int) int { return item }). - ThenByDescendingT(func(item, j int) bool { return true }) -} - -func TestSortT(t *testing.T) { - slice := make([]foo, 100) - - for i := len(slice) - 1; i >= 0; i-- { - slice[i].f1 = i - } - - q := From(slice).SortT(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.(foo).f1 != j { - t.Errorf("Sort()[%v]=%v expected %v", j, item, foo{f1: j}) - } - - j++ - } -} - -func TestSortT_PanicWhenFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - - 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 ef42dc4..823653e 100644 --- a/result.go +++ b/result.go @@ -18,6 +18,27 @@ func (q Query) All(predicate func(interface{}) bool) bool { return true } +// AllT is the typed version of All. +// +// NOTE: All method has better performance than AllT +// +// predicateFn is of a type "func(TSource) bool" +func (q Query) AllT(predicateFn interface{}) bool { + + predicateGenericFunc, err := newGenericFunc( + "AllT", "predicateFn", predicateFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))), + ) + if err != nil { + panic(err) + } + predicateFunc := func(item interface{}) bool { + return predicateGenericFunc.Call(item).(bool) + } + + return q.All(predicateFunc) +} + // Any determines whether any element of a collection exists. func (q Query) Any() bool { _, ok := q.Iterate()() @@ -37,6 +58,28 @@ func (q Query) AnyWith(predicate func(interface{}) bool) bool { return false } +// AnyWithT is the typed version of AnyWith. +// +// NOTE: AnyWith method has better performance than AnyWithT +// +// predicateFn is of a type "func(TSource) bool" +func (q Query) AnyWithT(predicateFn interface{}) bool { + + predicateGenericFunc, err := newGenericFunc( + "AnyWithT", "predicateFn", predicateFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))), + ) + if err != nil { + panic(err) + } + + predicateFunc := func(item interface{}) bool { + return predicateGenericFunc.Call(item).(bool) + } + + return q.AnyWith(predicateFunc) +} + // Average computes the average of a collection of numeric values. func (q Query) Average() (r float64) { next := q.Iterate() @@ -118,6 +161,28 @@ func (q Query) CountWith(predicate func(interface{}) bool) (r int) { return } +// CountWithT is the typed version of CountWith. +// +// NOTE: CountWith method has better performance than CountWithT +// +// predicateFn is of a type "func(TSource) bool" +func (q Query) CountWithT(predicateFn interface{}) int { + + predicateGenericFunc, err := newGenericFunc( + "CountWithT", "predicateFn", predicateFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))), + ) + if err != nil { + panic(err) + } + + predicateFunc := func(item interface{}) bool { + return predicateGenericFunc.Call(item).(bool) + } + + return q.CountWith(predicateFunc) +} + // First returns the first element of a collection. func (q Query) First() interface{} { item, _ := q.Iterate()() @@ -138,6 +203,28 @@ func (q Query) FirstWith(predicate func(interface{}) bool) interface{} { return nil } +// FirstWithT is the typed version of FirstWith. +// +// NOTE: FirstWith method has better performance than FirstWithT +// +// predicateFn is of a type "func(TSource) bool" +func (q Query) FirstWithT(predicateFn interface{}) interface{} { + + predicateGenericFunc, err := newGenericFunc( + "FirstWithT", "predicateFn", predicateFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))), + ) + if err != nil { + panic(err) + } + + predicateFunc := func(item interface{}) bool { + return predicateGenericFunc.Call(item).(bool) + } + + return q.FirstWith(predicateFunc) +} + // Last returns the last element of a collection. func (q Query) Last() (r interface{}) { next := q.Iterate() @@ -163,6 +250,28 @@ func (q Query) LastWith(predicate func(interface{}) bool) (r interface{}) { return } +// LastWithT is the typed version of LastWith. +// +// NOTE: LastWith method has better performance than LastWithT +// +// predicateFn is of a type "func(TSource) bool" +func (q Query) LastWithT(predicateFn interface{}) interface{} { + + predicateGenericFunc, err := newGenericFunc( + "LastWithT", "predicateFn", predicateFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))), + ) + if err != nil { + panic(err) + } + + predicateFunc := func(item interface{}) bool { + return predicateGenericFunc.Call(item).(bool) + } + + return q.LastWith(predicateFunc) +} + // Max returns the maximum value in a collection of values. func (q Query) Max() (r interface{}) { next := q.Iterate() @@ -267,6 +376,27 @@ func (q Query) SingleWith(predicate func(interface{}) bool) (r interface{}) { return } +// SingleWithT is the typed version of SingleWith. +// +// NOTE: SingleWith method has better performance than SingleWithT +// +// predicateFn is of a type "func(TSource) bool" +func (q Query) SingleWithT(predicateFn interface{}) interface{} { + predicateGenericFunc, err := newGenericFunc( + "SingleWithT", "predicateFn", predicateFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))), + ) + if err != nil { + panic(err) + } + + predicateFunc := func(item interface{}) bool { + return predicateGenericFunc.Call(item).(bool) + } + + return q.SingleWith(predicateFunc) +} + // SumInts computes the sum of a collection of numeric values. // // Values can be of any integer type: int, int8, int16, int32, int64. @@ -381,6 +511,41 @@ func (q Query) ToMapBy( res.Elem().Set(m) } +// ToMapByT is the typed version of ToMapBy. +// +// NOTE: ToMapBy method has better performance than ToMapByT +// +// keySelectorFn is of a type "func(TSource)TKey" +// +// valueSelectorFn is of a type "func(TSource)TValue" +func (q Query) ToMapByT(result interface{}, keySelectorFn interface{}, valueSelectorFn interface{}) { + keySelectorGenericFunc, err := newGenericFunc( + "ToMapByT", "keySelectorFn", keySelectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + keySelectorFunc := func(item interface{}) interface{} { + return keySelectorGenericFunc.Call(item) + } + + valueSelectorGenericFunc, err := newGenericFunc( + "ToMapByT", "valueSelectorFn", valueSelectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + valueSelectorFunc := func(item interface{}) interface{} { + return valueSelectorGenericFunc.Call(item) + } + + q.ToMapBy(result, keySelectorFunc, valueSelectorFunc) +} + // ToSlice iterates over a collection and populates the result slice with elements. // Collection elements must be assignable to the slice's element type. // ToSlice doesn't empty the result slice before populating it. diff --git a/result_test.go b/result_test.go index b771e15..ce894aa 100644 --- a/result_test.go +++ b/result_test.go @@ -25,6 +25,12 @@ func TestAll(t *testing.T) { } } +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 }) + }) +} + func TestAny(t *testing.T) { tests := []struct { input interface{} @@ -62,6 +68,12 @@ func TestAnyWith(t *testing.T) { } } +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 }) + }) +} + func TestAverage(t *testing.T) { tests := []struct { input interface{} @@ -138,6 +150,12 @@ func TestCountWith(t *testing.T) { } } +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 }) + }) +} + func TestFirst(t *testing.T) { tests := []struct { input interface{} @@ -172,6 +190,12 @@ func TestFirstWith(t *testing.T) { } } +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 }) + }) +} + func TestLast(t *testing.T) { tests := []struct { input interface{} @@ -206,6 +230,12 @@ func TestLastWith(t *testing.T) { } } +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 }) + }) +} + func TestMax(t *testing.T) { tests := []struct { input interface{} @@ -304,6 +334,12 @@ func TestSingleWith(t *testing.T) { } } +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 TestSumInts(t *testing.T) { tests := []struct { input interface{} @@ -407,6 +443,28 @@ func TestToMapBy(t *testing.T) { } } +func TestToMapByT_PanicWhenKeySelectorFnIsInvalid(t *testing.T) { + mustPanicWithError(t, "ToMapByT: parameter [keySelectorFn] has a invalid function signature. Expected: 'func(T)T', actual: 'func(int,int)int'", func() { + result := make(map[int]bool) + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).ToMapByT( + &result, + func(item, j int) int { return item + 2 }, + func(item int) int { return item + 2 }, + ) + }) +} + +func TestToMapByT_PanicWhenValueSelectorFnIsInvalid(t *testing.T) { + mustPanicWithError(t, "ToMapByT: parameter [valueSelectorFn] has a invalid function signature. Expected: 'func(T)T', actual: 'func(int,int)int'", func() { + result := make(map[int]bool) + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).ToMapByT( + &result, + func(item int) int { return item + 2 }, + func(item, j int) int { return item + 2 }, + ) + }) +} + func TestToSlice(t *testing.T) { input := []int{1, 2, 3, 4} diff --git a/resulttyped.go b/resulttyped.go deleted file mode 100644 index f41238d..0000000 --- a/resulttyped.go +++ /dev/null @@ -1,175 +0,0 @@ -package linq - -// AllT is the typed version of All. -// -// NOTE: All method has better performance than AllT -// -// predicateFn is of a type "func(TSource) bool" -// -func (q Query) AllT(predicateFn interface{}) bool { - - predicateGenericFunc, err := newGenericFunc( - "AllT", "predicateFn", predicateFn, - simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))), - ) - if err != nil { - panic(err) - } - predicateFunc := func(item interface{}) bool { - return predicateGenericFunc.Call(item).(bool) - } - - return q.All(predicateFunc) -} - -// AnyWithT is the typed version of AnyWith. -// -// NOTE: AnyWith method has better performance than AnyWithT -// -// predicateFn is of a type "func(TSource) bool" -// -func (q Query) AnyWithT(predicateFn interface{}) bool { - - predicateGenericFunc, err := newGenericFunc( - "AnyWithT", "predicateFn", predicateFn, - simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))), - ) - if err != nil { - panic(err) - } - - predicateFunc := func(item interface{}) bool { - return predicateGenericFunc.Call(item).(bool) - } - - return q.AnyWith(predicateFunc) -} - -// CountWithT is the typed version of CountWith. -// -// NOTE: CountWith method has better performance than CountWithT -// -// predicateFn is of a type "func(TSource) bool" -// -func (q Query) CountWithT(predicateFn interface{}) int { - - predicateGenericFunc, err := newGenericFunc( - "CountWithT", "predicateFn", predicateFn, - simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))), - ) - if err != nil { - panic(err) - } - - predicateFunc := func(item interface{}) bool { - return predicateGenericFunc.Call(item).(bool) - } - - return q.CountWith(predicateFunc) -} - -// FirstWithT is the typed version of FirstWith. -// -// NOTE: FirstWith method has better performance than FirstWithT -// -// predicateFn is of a type "func(TSource) bool" -// -func (q Query) FirstWithT(predicateFn interface{}) interface{} { - - predicateGenericFunc, err := newGenericFunc( - "FirstWithT", "predicateFn", predicateFn, - simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))), - ) - if err != nil { - panic(err) - } - - predicateFunc := func(item interface{}) bool { - return predicateGenericFunc.Call(item).(bool) - } - - return q.FirstWith(predicateFunc) -} - -// LastWithT is the typed version of LastWith. -// -// NOTE: LastWith method has better performance than LastWithT -// -// predicateFn is of a type "func(TSource) bool" -// -func (q Query) LastWithT(predicateFn interface{}) interface{} { - - predicateGenericFunc, err := newGenericFunc( - "LastWithT", "predicateFn", predicateFn, - simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))), - ) - if err != nil { - panic(err) - } - - predicateFunc := func(item interface{}) bool { - return predicateGenericFunc.Call(item).(bool) - } - - return q.LastWith(predicateFunc) -} - -// SingleWithT is the typed version of SingleWith. -// -// NOTE: SingleWith method has better performance than SingleWithT -// -// predicateFn is of a type "func(TSource) bool" -// -func (q Query) SingleWithT(predicateFn interface{}) interface{} { - - predicateGenericFunc, err := newGenericFunc( - "SingleWithT", "predicateFn", predicateFn, - simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))), - ) - if err != nil { - panic(err) - } - - predicateFunc := func(item interface{}) bool { - return predicateGenericFunc.Call(item).(bool) - } - - return q.SingleWith(predicateFunc) -} - -// ToMapByT is the typed version of ToMapBy. -// -// NOTE: ToMapBy method has better performance than ToMapByT -// -// keySelectorFn is of a type "func(TSource)TKey" -// -// valueSelectorFn is of a type "func(TSource)TValue" -// -func (q Query) ToMapByT(result interface{}, keySelectorFn interface{}, valueSelectorFn interface{}) { - - keySelectorGenericFunc, err := newGenericFunc( - "ToMapByT", "keySelectorFn", keySelectorFn, - simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), - ) - if err != nil { - panic(err) - } - - keySelectorFunc := func(item interface{}) interface{} { - return keySelectorGenericFunc.Call(item) - } - - valueSelectorGenericFunc, err := newGenericFunc( - "ToMapByT", "valueSelectorFn", valueSelectorFn, - simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), - ) - if err != nil { - panic(err) - } - - valueSelectorFunc := func(item interface{}) interface{} { - return valueSelectorGenericFunc.Call(item) - } - - q.ToMapBy(result, keySelectorFunc, valueSelectorFunc) -} diff --git a/resulttyped_test.go b/resulttyped_test.go deleted file mode 100644 index 8922b1b..0000000 --- a/resulttyped_test.go +++ /dev/null @@ -1,249 +0,0 @@ -package linq - -import ( - "reflect" - "testing" -) - -func TestAllT(t *testing.T) { - input := []int{2, 4, 6, 8} - - r1 := From(input).AllT(func(i int) bool { - return i%2 == 0 - }) - r2 := From(input).AllT(func(i int) bool { - return i%2 != 0 - }) - - if !r1 { - t.Errorf("From(%v).AllT()=%v", input, r1) - } - - if r2 { - t.Errorf("From(%v).AllT()=%v", input, r2) - } -} - -func TestAllT_PanicWhenFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - - From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).AllT(func(item int) int { return item + 2 }) -} - -func TestAnyWithT(t *testing.T) { - tests := []struct { - input interface{} - predicate interface{} - want bool - }{ - {[]int{1, 2, 2, 3, 1}, func(i int) bool { return i == 4 }, false}, - {[]int{1, 2, 2, 3, 1}, func(i int) bool { return i == 4 }, false}, - {[9]int{1, 1, 1, 2, 1, 2, 3, 4, 2}, func(i int) bool { return i == 4 }, true}, - {[]int{}, func(i int) bool { return i == 4 }, false}, - } - - for _, test := range tests { - if r := From(test.input).AnyWithT(test.predicate); r != test.want { - t.Errorf("From(%v).AnyWithT()=%v expected %v", test.input, r, test.want) - } - } -} - -func TestAnyWithT_PanicWhenFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - - From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).AnyWithT(func(item int) int { return item + 2 }) -} - -func TestCountWithT(t *testing.T) { - tests := []struct { - input interface{} - predicate interface{} - want int - }{ - {[]int{1, 2, 2, 3, 1}, func(i interface{}) bool { return i.(int) <= 2 }, 4}, - {[]int{1, 2, 2, 3, 1}, func(i int) bool { return i <= 2 }, 4}, - {[]int{}, func(i int) bool { return i <= 2 }, 0}, - } - - for _, test := range tests { - if r := From(test.input).CountWithT(test.predicate); r != test.want { - t.Errorf("From(%v).CountWithT()=%v expected %v", test.input, r, test.want) - } - } -} - -func TestCountWithT_PanicWhenFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - - From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).CountWithT(func(item int) int { return item + 2 }) -} - -func TestFirstWithT(t *testing.T) { - tests := []struct { - input interface{} - predicate interface{} - want interface{} - }{ - {[]int{1, 2, 2, 3, 1}, func(i interface{}) bool { return i.(int) > 2 }, 3}, - {[]int{1, 2, 2, 3, 1}, func(i int) bool { return i > 2 }, 3}, - {[]int{}, func(i interface{}) bool { return i.(int) > 2 }, nil}, - } - - for _, test := range tests { - if r := From(test.input).FirstWithT(test.predicate); r != test.want { - t.Errorf("From(%v).FirstWithT()=%v expected %v", test.input, r, test.want) - } - } -} - -func TestFirstWithT_PanicWhenFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - - From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).FirstWithT(func(item int) int { return item + 2 }) -} - -func TestLastWithT(t *testing.T) { - tests := []struct { - input interface{} - predicate interface{} - want interface{} - }{ - {[]int{1, 2, 2, 3, 1, 4, 2, 5, 1, 1}, func(i interface{}) bool { return i.(int) > 2 }, 5}, - {[]int{1, 2, 2, 3, 1, 4, 2, 5, 1, 1}, func(i int) bool { return i > 2 }, 5}, - {[]int{}, func(i interface{}) bool { return i.(int) > 2 }, nil}, - } - - for _, test := range tests { - if r := From(test.input).LastWithT(test.predicate); r != test.want { - t.Errorf("From(%v).LastWith()=%v expected %v", test.input, r, test.want) - } - } -} - -func TestLastWithT_PanicWhenFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - - From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).LastWithT(func(item int) int { return item + 2 }) -} - -func TestSingleWithT(t *testing.T) { - tests := []struct { - input interface{} - predicate interface{} - want interface{} - }{ - {[]int{1, 2, 2, 3, 1}, func(i interface{}) bool { return i.(int) > 2 }, 3}, - {[]int{1, 1, 1}, func(i int) bool { return i > 2 }, nil}, - {[]int{5, 1, 1, 10, 2, 2}, func(i interface{}) bool { return i.(int) > 2 }, nil}, - {[]int{}, func(i interface{}) bool { return i.(int) > 2 }, nil}, - } - - for _, test := range tests { - if r := From(test.input).SingleWithT(test.predicate); r != test.want { - t.Errorf("From(%v).SingleWithT()=%v expected %v", test.input, r, test.want) - } - } -} - -func TestSingleWithT_PanicWhenFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - - From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).SingleWithT(func(item int) int { return item + 2 }) -} - -func TestToMapByT(t *testing.T) { - input := make(map[int]bool) - input[1] = true - input[2] = false - input[3] = true - - result := make(map[int]bool) - From(input).ToMapByT(&result, - func(i KeyValue) interface{} { - return i.Key - }, - func(i KeyValue) interface{} { - return i.Value - }) - - if !reflect.DeepEqual(result, input) { - t.Errorf("From(%v).ToMapByT()=%v expected %v", input, result, input) - } -} - -func TestToMapByT_PanicWhenKeySelectorFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - result := make(map[int]bool) - From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).ToMapByT( - &result, - func(item, j int) int { return item + 2 }, - func(item int) int { return item + 2 }, - ) -} - -func TestToMapByT_PanicWhenValueSelectorFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - result := make(map[int]bool) - From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).ToMapByT( - &result, - func(item int) int { return item + 2 }, - func(item, j int) int { return item + 2 }, - ) -} diff --git a/select.go b/select.go index e607229..ef074fc 100644 --- a/select.go +++ b/select.go @@ -31,6 +31,28 @@ func (q Query) Select(selector func(interface{}) interface{}) Query { } } +// SelectT is the typed version of Select. +// +// NOTE: Select method has better performance than SelectT +// +// selectorFn is of a type "func(TSource)TResult" +func (q Query) SelectT(selectorFn interface{}) Query { + + selectGenericFunc, err := newGenericFunc( + "SelectT", "selectorFn", selectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + selectorFunc := func(item interface{}) interface{} { + return selectGenericFunc.Call(item) + } + + return q.Select(selectorFunc) +} + // SelectIndexed projects each element of a collection into a new form // by incorporating the element's index. Returns a query with the result // of invoking the transform function on each element of original source. @@ -69,3 +91,24 @@ func (q Query) SelectIndexed(selector func(int, interface{}) interface{}) Query }, } } + +// SelectIndexedT is the typed version of SelectIndexed. +// +// NOTE: SelectIndexed method has better performance than SelectIndexedT +// +// selectorFn is of a type "func(int,TSource)TResult" +func (q Query) SelectIndexedT(selectorFn interface{}) Query { + selectGenericFunc, err := newGenericFunc( + "SelectIndexedT", "selectorFn", selectorFn, + simpleParamValidator(newElemTypeSlice(new(int), new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + selectorFunc := func(index int, item interface{}) interface{} { + return selectGenericFunc.Call(index, item) + } + + return q.SelectIndexed(selectorFunc) +} diff --git a/select_test.go b/select_test.go index 7c42377..a32349b 100644 --- a/select_test.go +++ b/select_test.go @@ -26,6 +26,12 @@ func TestSelect(t *testing.T) { } } +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 }) + }) +} + func TestSelectIndexed(t *testing.T) { tests := []struct { input interface{} @@ -46,3 +52,9 @@ func TestSelectIndexed(t *testing.T) { } } } + +func TestSelectIndexedT_PanicWhenSelectorFnIsInvalid(t *testing.T) { + mustPanicWithError(t, "SelectIndexedT: parameter [selectorFn] has a invalid function signature. Expected: 'func(int,T)T', actual: 'func(string,int)int'", func() { + From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).SelectIndexedT(func(index string, item int) int { return item + 2 }) + }) +} diff --git a/selectmany.go b/selectmany.go index 269cf84..874f4c8 100644 --- a/selectmany.go +++ b/selectmany.go @@ -32,6 +32,28 @@ func (q Query) SelectMany(selector func(interface{}) Query) Query { } } +// SelectManyT is the typed version of SelectMany. +// +// NOTE: SelectMany method has better performance than SelectManyT +// +// selectorFn is of a type "func(TSource)Query" +func (q Query) SelectManyT(selectorFn interface{}) Query { + + selectManyGenericFunc, err := newGenericFunc( + "SelectManyT", "selectorFn", selectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(Query))), + ) + if err != nil { + panic(err) + } + + selectorFunc := func(inner interface{}) Query { + return selectManyGenericFunc.Call(inner).(Query) + } + return q.SelectMany(selectorFunc) + +} + // SelectManyIndexed projects each element of a collection to a Query, iterates and // flattens the resulting collection into one collection. // @@ -72,6 +94,28 @@ func (q Query) SelectManyIndexed(selector func(int, interface{}) Query) Query { } } +// SelectManyIndexedT is the typed version of SelectManyIndexed. +// +// NOTE: SelectManyIndexed method has better performance than SelectManyIndexedT +// +// selectorFn is of a type "func(int,TSource)Query" +func (q Query) SelectManyIndexedT(selectorFn interface{}) Query { + + selectManyIndexedGenericFunc, err := newGenericFunc( + "SelectManyIndexedT", "selectorFn", selectorFn, + simpleParamValidator(newElemTypeSlice(new(int), new(genericType)), newElemTypeSlice(new(Query))), + ) + if err != nil { + panic(err) + } + + selectorFunc := func(index int, inner interface{}) Query { + return selectManyIndexedGenericFunc.Call(index, inner).(Query) + } + + return q.SelectManyIndexed(selectorFunc) +} + // SelectManyBy projects each element of a collection to a Query, iterates and // flattens the resulting collection into one collection, and invokes // a result selector function on each element therein. @@ -110,6 +154,42 @@ func (q Query) SelectManyBy( } } +// SelectManyByT is the typed version of SelectManyBy. +// +// NOTE: SelectManyBy method has better performance than SelectManyByT +// +// selectorFn is of a type "func(TSource)Query" +// +// resultSelectorFn is of a type "func(TSource,TCollection)TResult" +func (q Query) SelectManyByT(selectorFn interface{}, resultSelectorFn interface{}) Query { + + selectorGenericFunc, err := newGenericFunc( + "SelectManyByT", "selectorFn", selectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(Query))), + ) + if err != nil { + panic(err) + } + + selectorFunc := func(outer interface{}) Query { + return selectorGenericFunc.Call(outer).(Query) + } + + resultSelectorGenericFunc, err := newGenericFunc( + "SelectManyByT", "resultSelectorFn", resultSelectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + resultSelectorFunc := func(outer interface{}, item interface{}) interface{} { + return resultSelectorGenericFunc.Call(outer, item) + } + + return q.SelectManyBy(selectorFunc, resultSelectorFunc) +} + // SelectManyByIndexed projects each element of a collection to a Query, iterates and // flattens the resulting collection into one collection, and invokes // a result selector function on each element therein. @@ -149,3 +229,38 @@ func (q Query) SelectManyByIndexed(selector func(int, interface{}) Query, }, } } + +// SelectManyByIndexedT is the typed version of SelectManyByIndexed. +// +// NOTE: SelectManyByIndexed method has better performance than SelectManyByIndexedT +// +// selectorFn is of a type "func(int,TSource)Query" +// +// resultSelectorFn is of a type "func(TSource,TCollection)TResult" +func (q Query) SelectManyByIndexedT(selectorFn interface{}, resultSelectorFn interface{}) Query { + selectorGenericFunc, err := newGenericFunc( + "SelectManyByIndexedT", "selectorFn", selectorFn, + simpleParamValidator(newElemTypeSlice(new(int), new(genericType)), newElemTypeSlice(new(Query))), + ) + if err != nil { + panic(err) + } + + selectorFunc := func(index int, outer interface{}) Query { + return selectorGenericFunc.Call(index, outer).(Query) + } + + resultSelectorGenericFunc, err := newGenericFunc( + "SelectManyByIndexedT", "resultSelectorFn", resultSelectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + resultSelectorFunc := func(outer interface{}, item interface{}) interface{} { + return resultSelectorGenericFunc.Call(outer, item) + } + + return q.SelectManyByIndexed(selectorFunc, resultSelectorFunc) +} diff --git a/selectmany_test.go b/selectmany_test.go index c390ce3..9a2548c 100644 --- a/selectmany_test.go +++ b/selectmany_test.go @@ -26,6 +26,12 @@ func TestSelectMany(t *testing.T) { } } +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 }) + }) +} + func TestSelectManyIndexed(t *testing.T) { tests := []struct { input interface{} @@ -50,6 +56,12 @@ func TestSelectManyIndexed(t *testing.T) { } } +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 }) + }) +} + func TestSelectManyBy(t *testing.T) { tests := []struct { input interface{} @@ -76,6 +88,21 @@ func TestSelectManyBy(t *testing.T) { } } +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) + }) +} + +func TestSelectManyByT_PanicWhenResultSelectorFnIsInvalid(t *testing.T) { + mustPanicWithError(t, "SelectManyByT: parameter [resultSelectorFn] has a invalid function signature. Expected: 'func(T,T)T', actual: 'func()'", func() { + From([][]int{{1, 1, 1, 2}, {1, 2, 3, 4, 2}}).SelectManyByT( + func(item interface{}) Query { return From(item) }, + func() {}, + ) + }) +} + func TestSelectManyIndexedBy(t *testing.T) { tests := []struct { input interface{} @@ -107,3 +134,21 @@ func TestSelectManyIndexedBy(t *testing.T) { } } } + +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( + func(item int) interface{} { return item + 2 }, + 2, + ) + }) +} + +func TestSelectManyIndexedByT_PanicWhenResultSelectorFnIsInvalid(t *testing.T) { + mustPanicWithError(t, "SelectManyByIndexedT: parameter [resultSelectorFn] has a invalid function signature. Expected: 'func(T,T)T', actual: 'func()'", func() { + From([][]int{{1, 1, 1, 2}, {1, 2, 3, 4, 2}}).SelectManyByIndexedT( + func(index int, item interface{}) Query { return From(item) }, + func() {}, + ) + }) +} diff --git a/selectmanytyped.go b/selectmanytyped.go deleted file mode 100644 index 04a89e9..0000000 --- a/selectmanytyped.go +++ /dev/null @@ -1,121 +0,0 @@ -package linq - -// SelectManyT is the typed version of SelectMany. -// -// NOTE: SelectMany method has better performance than SelectManyT -// -// selectorFn is of a type "func(TSource)Query" -// -func (q Query) SelectManyT(selectorFn interface{}) Query { - - selectManyGenericFunc, err := newGenericFunc( - "SelectManyT", "selectorFn", selectorFn, - simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(Query))), - ) - if err != nil { - panic(err) - } - - selectorFunc := func(inner interface{}) Query { - return selectManyGenericFunc.Call(inner).(Query) - } - return q.SelectMany(selectorFunc) - -} - -// SelectManyIndexedT is the typed version of SelectManyIndexed. -// -// NOTE: SelectManyIndexed method has better performance than SelectManyIndexedT -// -// selectorFn is of a type "func(int,TSource)Query" -// -func (q Query) SelectManyIndexedT(selectorFn interface{}) Query { - - selectManyIndexedGenericFunc, err := newGenericFunc( - "SelectManyIndexedT", "selectorFn", selectorFn, - simpleParamValidator(newElemTypeSlice(new(int), new(genericType)), newElemTypeSlice(new(Query))), - ) - if err != nil { - panic(err) - } - - selectorFunc := func(index int, inner interface{}) Query { - return selectManyIndexedGenericFunc.Call(index, inner).(Query) - } - - return q.SelectManyIndexed(selectorFunc) -} - -// SelectManyByT is the typed version of SelectManyBy. -// -// NOTE: SelectManyBy method has better performance than SelectManyByT -// -// selectorFn is of a type "func(TSource)Query" -// -// resultSelectorFn is of a type "func(TSource,TCollection)TResult" -// -func (q Query) SelectManyByT(selectorFn interface{}, resultSelectorFn interface{}) Query { - - selectorGenericFunc, err := newGenericFunc( - "SelectManyByT", "selectorFn", selectorFn, - simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(Query))), - ) - if err != nil { - panic(err) - } - - selectorFunc := func(outer interface{}) Query { - return selectorGenericFunc.Call(outer).(Query) - } - - resultSelectorGenericFunc, err := newGenericFunc( - "SelectManyByT", "resultSelectorFn", resultSelectorFn, - simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))), - ) - if err != nil { - panic(err) - } - - resultSelectorFunc := func(outer interface{}, item interface{}) interface{} { - return resultSelectorGenericFunc.Call(outer, item) - } - - return q.SelectManyBy(selectorFunc, resultSelectorFunc) -} - -// SelectManyByIndexedT is the typed version of SelectManyByIndexed. -// -// NOTE: SelectManyByIndexed method has better performance than SelectManyByIndexedT -// -// selectorFn is of a type "func(int,TSource)Query" -// -// resultSelectorFn is of a type "func(TSource,TCollection)TResult" -// -func (q Query) SelectManyByIndexedT(selectorFn interface{}, resultSelectorFn interface{}) Query { - - selectorGenericFunc, err := newGenericFunc( - "SelectManyByIndexedT", "selectorFn", selectorFn, - simpleParamValidator(newElemTypeSlice(new(int), new(genericType)), newElemTypeSlice(new(Query))), - ) - if err != nil { - panic(err) - } - - selectorFunc := func(index int, outer interface{}) Query { - return selectorGenericFunc.Call(index, outer).(Query) - } - - resultSelectorGenericFunc, err := newGenericFunc( - "SelectManyByIndexedT", "resultSelectorFn", resultSelectorFn, - simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))), - ) - if err != nil { - panic(err) - } - - resultSelectorFunc := func(outer interface{}, item interface{}) interface{} { - return resultSelectorGenericFunc.Call(outer, item) - } - - return q.SelectManyByIndexed(selectorFunc, resultSelectorFunc) -} diff --git a/selectmanytyped_test.go b/selectmanytyped_test.go deleted file mode 100644 index 7638f2f..0000000 --- a/selectmanytyped_test.go +++ /dev/null @@ -1,199 +0,0 @@ -package linq - -import ( - "strconv" - "testing" -) - -func TestSelectManyT(t *testing.T) { - tests := []struct { - input interface{} - selector interface{} - output []interface{} - }{ - {[][]int{{1, 2, 3}, {4, 5, 6, 7}}, func(i interface{}) Query { - return From(i) - }, []interface{}{1, 2, 3, 4, 5, 6, 7}}, - {[][]int{{1, 2, 3}, {4, 5, 6, 7}}, func(i []int) Query { - return From(i) - }, []interface{}{1, 2, 3, 4, 5, 6, 7}}, - {[]string{"str", "ing"}, func(i interface{}) Query { - return FromString(i.(string)) - }, []interface{}{'s', 't', 'r', 'i', 'n', 'g'}}, - } - - for _, test := range tests { - if q := From(test.input).SelectManyT(test.selector); !validateQuery(q, test.output) { - t.Errorf("From(%v).SelectManyT()=%v expected %v", test.input, toSlice(q), test.output) - } - } -} - -func TestSelectManyT_PanicWhenFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - }() - - From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).SelectManyT(func(item int) int { return item + 2 }) -} - -func TestSelectManyIndexedT(t *testing.T) { - tests := []struct { - input interface{} - selector interface{} - output []interface{} - }{ - {[][]int{{1, 2, 3}, {4, 5, 6, 7}}, func(i int, x interface{}) Query { - if i > 0 { - return From(x.([]int)[1:]) - } - return From(x) - }, []interface{}{1, 2, 3, 5, 6, 7}}, - {[][]int{{1, 2, 3}, {4, 5, 6, 7}}, func(i int, x []int) Query { - if i > 0 { - return From(x[1:]) - } - return From(x) - }, []interface{}{1, 2, 3, 5, 6, 7}}, - {[]string{"str", "ing"}, func(i int, x string) Query { - return FromString(x + strconv.Itoa(i)) - }, []interface{}{'s', 't', 'r', '0', 'i', 'n', 'g', '1'}}, - } - - for _, test := range tests { - if q := From(test.input).SelectManyIndexedT(test.selector); !validateQuery(q, test.output) { - t.Errorf("From(%v).SelectManyIndexedT()=%v expected %v", test.input, toSlice(q), test.output) - } - } -} - -func TestSelectManyIndexedT_PanicWhenFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - }() - - From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).SelectManyIndexedT(func(item int) int { return item + 2 }) -} - -func TestSelectManyByT(t *testing.T) { - tests := []struct { - input interface{} - selector interface{} - resultSelector interface{} - output []interface{} - }{ - {[][]int{{1, 2, 3}, {4, 5, 6, 7}}, func(i []int) Query { - return From(i) - }, func(x int, y []int) int { - return x + 1 - }, []interface{}{2, 3, 4, 5, 6, 7, 8}}, - {[]string{"str", "ing"}, func(i string) Query { - return FromString(i) - }, func(x rune, y string) string { - return string(x) + "_" - }, []interface{}{"s_", "t_", "r_", "i_", "n_", "g_"}}, - } - - for _, test := range tests { - if q := From(test.input).SelectManyByT(test.selector, test.resultSelector); !validateQuery(q, test.output) { - t.Errorf("From(%v).SelectManyBy()=%v expected %v", test.input, toSlice(q), test.output) - } - } -} - -func TestSelectManyByT_PanicWhenSelectorFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - }() - - From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).SelectManyByT(func(item int) interface{} { return item + 2 }, 2) -} - -func TestSelectManyByT_PanicWhenResultSelectorFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - }() - - From([][]int{{1, 1, 1, 2}, {1, 2, 3, 4, 2}}).SelectManyByT( - func(item interface{}) Query { return From(item) }, - func() {}, - ) -} - -func TestSelectManyIndexedByT(t *testing.T) { - tests := []struct { - input interface{} - selector interface{} - resultSelector interface{} - output []interface{} - }{ - {[][]int{{1, 2, 3}, {4, 5, 6, 7}}, func(i int, x []int) Query { - if i == 0 { - return From([]int{10, 20, 30}) - } - return From(x) - }, func(x int, y []int) int { - return x + 1 - }, []interface{}{11, 21, 31, 5, 6, 7, 8}}, - {[]string{"st", "ng"}, func(i int, x string) Query { - if i == 0 { - return FromString(x + "r") - } - return FromString("i" + x) - }, func(x rune, y string) string { - return string(x) + "_" - }, []interface{}{"s_", "t_", "r_", "i_", "n_", "g_"}}, - } - - for _, test := range tests { - if q := From(test.input).SelectManyByIndexedT(test.selector, test.resultSelector); !validateQuery(q, test.output) { - t.Errorf("From(%v).SelectManyIndexedByT()=%v expected %v", test.input, toSlice(q), test.output) - } - } -} - -func TestSelectManyIndexedByT_PanicWhenSelectorFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - }() - - From([][]int{{1, 1, 1, 2}, {1, 2, 3, 4, 2}}).SelectManyByIndexedT( - func(item int) interface{} { return item + 2 }, - 2, - ) -} - -func TestSelectManyIndexedByT_PanicWhenResultSelectorFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - }() - - From([][]int{{1, 1, 1, 2}, {1, 2, 3, 4, 2}}).SelectManyByIndexedT( - func(index int, item interface{}) Query { return From(item) }, - func() {}, - ) -} diff --git a/selecttyped.go b/selecttyped.go deleted file mode 100644 index 1ae36a1..0000000 --- a/selecttyped.go +++ /dev/null @@ -1,47 +0,0 @@ -package linq - -// SelectT is the typed version of Select. -// -// NOTE: Select method has better performance than SelectT -// -// selectorFn is of a type "func(TSource)TResult" -// -func (q Query) SelectT(selectorFn interface{}) Query { - - selectGenericFunc, err := newGenericFunc( - "SelectT", "selectorFn", selectorFn, - simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))), - ) - if err != nil { - panic(err) - } - - selectorFunc := func(item interface{}) interface{} { - return selectGenericFunc.Call(item) - } - - return q.Select(selectorFunc) -} - -// SelectIndexedT is the typed version of SelectIndexed. -// -// NOTE: SelectIndexed method has better performance than SelectIndexedT -// -// selectorFn is of a type "func(int,TSource)TResult" -// -func (q Query) SelectIndexedT(selectorFn interface{}) Query { - - selectGenericFunc, err := newGenericFunc( - "SelectIndexedT", "selectorFn", selectorFn, - simpleParamValidator(newElemTypeSlice(new(int), new(genericType)), newElemTypeSlice(new(genericType))), - ) - if err != nil { - panic(err) - } - - selectorFunc := func(index int, item interface{}) interface{} { - return selectGenericFunc.Call(index, item) - } - - return q.SelectIndexed(selectorFunc) -} diff --git a/selecttyped_test.go b/selecttyped_test.go deleted file mode 100644 index bdcac85..0000000 --- a/selecttyped_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package linq - -import ( - "strconv" - "testing" -) - -func TestSelectT(t *testing.T) { - tests := []struct { - input interface{} - selector interface{} - output []interface{} - }{ - {[]int{1, 2, 3}, func(i interface{}) interface{} { - return i.(int) * 2 - }, []interface{}{2, 4, 6}}, - {[]int{1, 2, 3}, func(i int) int { - return i * 2 - }, []interface{}{2, 4, 6}}, - {"str", func(i interface{}) interface{} { - return string(i.(rune)) + "1" - }, []interface{}{"s1", "t1", "r1"}}, - } - - for _, test := range tests { - if q := From(test.input).SelectT(test.selector); !validateQuery(q, test.output) { - t.Errorf("From(%v).Select()=%v expected %v", test.input, toSlice(q), test.output) - } - } -} - -func TestSelectT_PanicWhenFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - }() - - From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).SelectT(func(item, idx int) int { return item + 2 }) -} - -func TestSelectIndexedT(t *testing.T) { - tests := []struct { - input interface{} - selector interface{} - output []interface{} - }{ - {[]int{1, 2, 3}, func(i int, x interface{}) interface{} { - return x.(int) * i - }, []interface{}{0, 2, 6}}, - {[]int{1, 2, 3}, func(i int, x int) int { - return x * i - }, []interface{}{0, 2, 6}}, - {"str", func(i int, x interface{}) interface{} { - return string(x.(rune)) + strconv.Itoa(i) - }, []interface{}{"s0", "t1", "r2"}}, - } - - for _, test := range tests { - if q := From(test.input).SelectIndexedT(test.selector); !validateQuery(q, test.output) { - t.Errorf("From(%v).SelectIndexed()=%v expected %v", test.input, toSlice(q), test.output) - } - } -} - -func TestSelectIndexedT_PanicWhenFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - }() - - From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).SelectIndexedT(func(index string, item int) int { return item + 2 }) -} diff --git a/setup_test.go b/setup_test.go index 268aba8..a4f4de5 100644 --- a/setup_test.go +++ b/setup_test.go @@ -1,5 +1,9 @@ package linq +import "testing" + +import "fmt" + type foo struct { f1 int f2 bool @@ -66,3 +70,14 @@ func validateQuery(q Query, output []interface{}) bool { _, ok2 := next() return !(ok || ok2) } + +func mustPanicWithError(t *testing.T, expectedErr string, f func()) { + defer func() { + r := recover() + err := fmt.Sprintf("%s", r) + if err != expectedErr { + t.Fatalf("got=[%v] expected=[%v]", err, expectedErr) + } + }() + f() +} diff --git a/skip.go b/skip.go index 1110ae5..989559a 100644 --- a/skip.go +++ b/skip.go @@ -54,6 +54,28 @@ func (q Query) SkipWhile(predicate func(interface{}) bool) Query { } } +// SkipWhileT is the typed version of SkipWhile. +// +// NOTE: SkipWhile method has better performance than SkipWhileT +// +// predicateFn is of a type "func(TSource)bool" +func (q Query) SkipWhileT(predicateFn interface{}) Query { + + predicateGenericFunc, err := newGenericFunc( + "SkipWhileT", "predicateFn", predicateFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))), + ) + if err != nil { + panic(err) + } + + predicateFunc := func(item interface{}) bool { + return predicateGenericFunc.Call(item).(bool) + } + + return q.SkipWhile(predicateFunc) +} + // 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. @@ -89,3 +111,24 @@ func (q Query) SkipWhileIndexed(predicate func(int, interface{}) bool) Query { }, } } + +// SkipWhileIndexedT is the typed version of SkipWhileIndexed. +// +// NOTE: SkipWhileIndexed method has better performance than SkipWhileIndexedT +// +// predicateFn is of a type "func(int,TSource)bool" +func (q Query) SkipWhileIndexedT(predicateFn interface{}) Query { + predicateGenericFunc, err := newGenericFunc( + "SkipWhileIndexedT", "predicateFn", predicateFn, + simpleParamValidator(newElemTypeSlice(new(int), new(genericType)), newElemTypeSlice(new(bool))), + ) + if err != nil { + panic(err) + } + + predicateFunc := func(index int, item interface{}) bool { + return predicateGenericFunc.Call(index, item).(bool) + } + + return q.SkipWhileIndexed(predicateFunc) +} diff --git a/skip_test.go b/skip_test.go index 477003a..2dd68c6 100644 --- a/skip_test.go +++ b/skip_test.go @@ -47,6 +47,12 @@ func TestSkipWhile(t *testing.T) { } } +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 }) + }) +} + func TestSkipWhileIndexed(t *testing.T) { tests := []struct { input interface{} @@ -73,3 +79,9 @@ func TestSkipWhileIndexed(t *testing.T) { } } } + +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/skiptyped.go b/skiptyped.go deleted file mode 100644 index 77eb6da..0000000 --- a/skiptyped.go +++ /dev/null @@ -1,47 +0,0 @@ -package linq - -// SkipWhileT is the typed version of SkipWhile. -// -// NOTE: SkipWhile method has better performance than SkipWhileT -// -// predicateFn is of a type "func(TSource)bool" -// -func (q Query) SkipWhileT(predicateFn interface{}) Query { - - predicateGenericFunc, err := newGenericFunc( - "SkipWhileT", "predicateFn", predicateFn, - simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))), - ) - if err != nil { - panic(err) - } - - predicateFunc := func(item interface{}) bool { - return predicateGenericFunc.Call(item).(bool) - } - - return q.SkipWhile(predicateFunc) -} - -// SkipWhileIndexedT is the typed version of SkipWhileIndexed. -// -// NOTE: SkipWhileIndexed method has better performance than SkipWhileIndexedT -// -// predicateFn is of a type "func(int,TSource)bool" -// -func (q Query) SkipWhileIndexedT(predicateFn interface{}) Query { - - predicateGenericFunc, err := newGenericFunc( - "SkipWhileIndexedT", "predicateFn", predicateFn, - simpleParamValidator(newElemTypeSlice(new(int), new(genericType)), newElemTypeSlice(new(bool))), - ) - if err != nil { - panic(err) - } - - predicateFunc := func(index int, item interface{}) bool { - return predicateGenericFunc.Call(index, item).(bool) - } - - return q.SkipWhileIndexed(predicateFunc) -} diff --git a/skiptyped_test.go b/skiptyped_test.go deleted file mode 100644 index 1be6b83..0000000 --- a/skiptyped_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package linq - -import "testing" - -func TestSkipWhileT(t *testing.T) { - tests := []struct { - input interface{} - predicate interface{} - output []interface{} - }{ - {[]int{1, 2}, func(i interface{}) bool { - return i.(int) < 3 - }, []interface{}{}}, - {[]int{4, 1, 2}, func(i interface{}) bool { - return i.(int) < 3 - }, []interface{}{4, 1, 2}}, - {[9]int{1, 1, 1, 2, 1, 2, 3, 4, 2}, func(i int) bool { - return i < 3 - }, []interface{}{3, 4, 2}}, - {"sstr", func(i interface{}) bool { - return i.(rune) == 's' - }, []interface{}{'t', 'r'}}, - } - - for _, test := range tests { - if q := From(test.input).SkipWhileT(test.predicate); !validateQuery(q, test.output) { - t.Errorf("From(%v).SkipWhile()=%v expected %v", test.input, toSlice(q), test.output) - } - } -} - -func TestSkipWhileT_PanicWhenFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - - From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).SkipWhileT(func(item int, x int) bool { return item == 1 }) -} - -func TestSkipWhileIndexedT(t *testing.T) { - tests := []struct { - input interface{} - predicate interface{} - output []interface{} - }{ - {[]int{1, 2}, func(i int, x interface{}) bool { - return x.(int) < 3 - }, []interface{}{}}, - {[]int{4, 1, 2}, func(i int, x interface{}) bool { - return x.(int) < 3 - }, []interface{}{4, 1, 2}}, - {[9]int{1, 1, 1, 2, 1, 2, 3, 4, 2}, func(i int, x int) bool { - return x < 2 || i < 5 - }, []interface{}{2, 3, 4, 2}}, - {"sstr", func(i int, x interface{}) bool { - return x.(rune) == 's' && i < 1 - }, []interface{}{'s', 't', 'r'}}, - } - - for _, test := range tests { - if q := From(test.input).SkipWhileIndexedT(test.predicate); !validateQuery(q, test.output) { - t.Errorf("From(%v).SkipWhileIndexed()=%v expected %v", test.input, toSlice(q), test.output) - } - } -} - -func TestSkipWhileIndexedT_PanicWhenFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - - 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/take.go b/take.go index d21fc96..c785023 100644 --- a/take.go +++ b/take.go @@ -49,6 +49,28 @@ func (q Query) TakeWhile(predicate func(interface{}) bool) Query { } } +// TakeWhileT is the typed version of TakeWhile. +// +// NOTE: TakeWhile method has better performance than TakeWhileT +// +// predicateFn is of a type "func(TSource)bool" +func (q Query) TakeWhileT(predicateFn interface{}) Query { + + predicateGenericFunc, err := newGenericFunc( + "TakeWhileT", "predicateFn", predicateFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))), + ) + if err != nil { + panic(err) + } + + predicateFunc := func(item interface{}) bool { + return predicateGenericFunc.Call(item).(bool) + } + + return q.TakeWhile(predicateFunc) +} + // 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 @@ -82,3 +104,24 @@ func (q Query) TakeWhileIndexed(predicate func(int, interface{}) bool) Query { }, } } + +// TakeWhileIndexedT is the typed version of TakeWhileIndexed. +// +// NOTE: TakeWhileIndexed method has better performance than TakeWhileIndexedT +// +// predicateFn is of a type "func(int,TSource)bool" +func (q Query) TakeWhileIndexedT(predicateFn interface{}) Query { + whereFunc, err := newGenericFunc( + "TakeWhileIndexedT", "predicateFn", predicateFn, + simpleParamValidator(newElemTypeSlice(new(int), new(genericType)), newElemTypeSlice(new(bool))), + ) + if err != nil { + panic(err) + } + + predicateFunc := func(index int, item interface{}) bool { + return whereFunc.Call(index, item).(bool) + } + + return q.TakeWhileIndexed(predicateFunc) +} diff --git a/take_test.go b/take_test.go index 0f6465f..ca4bc63 100644 --- a/take_test.go +++ b/take_test.go @@ -43,6 +43,12 @@ func TestTakeWhile(t *testing.T) { } } +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 }) + }) +} + func TestTakeWhileIndexed(t *testing.T) { tests := []struct { input interface{} @@ -66,3 +72,9 @@ func TestTakeWhileIndexed(t *testing.T) { } } } + +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/taketyped.go b/taketyped.go deleted file mode 100644 index 4c1c14b..0000000 --- a/taketyped.go +++ /dev/null @@ -1,47 +0,0 @@ -package linq - -// TakeWhileT is the typed version of TakeWhile. -// -// NOTE: TakeWhile method has better performance than TakeWhileT -// -// predicateFn is of a type "func(TSource)bool" -// -func (q Query) TakeWhileT(predicateFn interface{}) Query { - - predicateGenericFunc, err := newGenericFunc( - "TakeWhileT", "predicateFn", predicateFn, - simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))), - ) - if err != nil { - panic(err) - } - - predicateFunc := func(item interface{}) bool { - return predicateGenericFunc.Call(item).(bool) - } - - return q.TakeWhile(predicateFunc) -} - -// TakeWhileIndexedT is the typed version of TakeWhileIndexed. -// -// NOTE: TakeWhileIndexed method has better performance than TakeWhileIndexedT -// -// predicateFn is of a type "func(int,TSource)bool" -// -func (q Query) TakeWhileIndexedT(predicateFn interface{}) Query { - - whereFunc, err := newGenericFunc( - "TakeWhileIndexedT", "predicateFn", predicateFn, - simpleParamValidator(newElemTypeSlice(new(int), new(genericType)), newElemTypeSlice(new(bool))), - ) - if err != nil { - panic(err) - } - - predicateFunc := func(index int, item interface{}) bool { - return whereFunc.Call(index, item).(bool) - } - - return q.TakeWhileIndexed(predicateFunc) -} diff --git a/taketyped_test.go b/taketyped_test.go deleted file mode 100644 index 8fc3a05..0000000 --- a/taketyped_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package linq - -import "testing" - -func TestTakeWhileT(t *testing.T) { - tests := []struct { - input interface{} - predicate interface{} - output []interface{} - }{ - {[]int{1, 1, 1, 2, 1, 2}, func(i interface{}) bool { - return i.(int) < 3 - }, []interface{}{1, 1, 1, 2, 1, 2}}, - {[9]int{1, 1, 1, 2, 1, 2, 3, 4, 2}, func(i interface{}) bool { - return i.(int) < 3 - }, []interface{}{1, 1, 1, 2, 1, 2}}, - {"sstr", func(i interface{}) bool { - return i.(rune) == 's' - }, []interface{}{'s', 's'}}, - {"sstr", func(i rune) bool { - return i == 's' - }, []interface{}{'s', 's'}}, - } - - for _, test := range tests { - if q := From(test.input).TakeWhileT(test.predicate); !validateQuery(q, test.output) { - t.Errorf("From(%v).TakeWhile()=%v expected %v", test.input, toSlice(q), test.output) - } - } -} - -func TestTakeWhileT_PanicWhenFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - - From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).TakeWhileT(func(item int) int { return item + 2 }) -} - -func TestTakeWhileIndexedT(t *testing.T) { - tests := []struct { - input interface{} - predicate interface{} - output []interface{} - }{ - {[]int{1, 1, 1, 2}, func(i int, x interface{}) bool { - return x.(int) < 2 || i < 5 - }, []interface{}{1, 1, 1, 2}}, - {[9]int{1, 1, 1, 2, 1, 2, 3, 4, 2}, func(i int, x interface{}) bool { - return x.(int) < 2 || i < 5 - }, []interface{}{1, 1, 1, 2, 1}}, - {"sstr", func(i int, x interface{}) bool { - return x.(rune) == 's' && i < 1 - }, []interface{}{'s'}}, - {"sstr", func(i int, x rune) bool { - return x == 's' && i < 1 - }, []interface{}{'s'}}, - } - - for _, test := range tests { - if q := From(test.input).TakeWhileIndexedT(test.predicate); !validateQuery(q, test.output) { - t.Errorf("From(%v).TakeWhileIndexed()=%v expected %v", test.input, toSlice(q), test.output) - } - } -} - -func TestTakeWhileIndexedT_PanicWhenFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - - From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).TakeWhileIndexedT(func(item int) int { return item + 2 }) -} diff --git a/where.go b/where.go index 26d0bda..fd33367 100644 --- a/where.go +++ b/where.go @@ -19,6 +19,28 @@ func (q Query) Where(predicate func(interface{}) bool) Query { } } +// WhereT is the typed version of Where. +// +// NOTE: Where method has better performance than WhereT +// +// predicateFn is of a type "func(TSource)bool" +func (q Query) WhereT(predicateFn interface{}) Query { + + predicateGenericFunc, err := newGenericFunc( + "WhereT", "predicateFn", predicateFn, + simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))), + ) + if err != nil { + panic(err) + } + + predicateFunc := func(item interface{}) bool { + return predicateGenericFunc.Call(item).(bool) + } + + return q.Where(predicateFunc) +} + // WhereIndexed filters a collection of values based on a predicate. // Each element's index is used in the logic of the predicate function. // @@ -44,3 +66,24 @@ func (q Query) WhereIndexed(predicate func(int, interface{}) bool) Query { }, } } + +// WhereIndexedT is the typed version of WhereIndexed. +// +// NOTE: WhereIndexed method has better performance than WhereIndexedT +// +// predicateFn is of a type "func(int,TSource)bool" +func (q Query) WhereIndexedT(predicateFn interface{}) Query { + predicateGenericFunc, err := newGenericFunc( + "WhereIndexedT", "predicateFn", predicateFn, + simpleParamValidator(newElemTypeSlice(new(int), new(genericType)), newElemTypeSlice(new(bool))), + ) + if err != nil { + panic(err) + } + + predicateFunc := func(index int, item interface{}) bool { + return predicateGenericFunc.Call(index, item).(bool) + } + + return q.WhereIndexed(predicateFunc) +} diff --git a/where_test.go b/where_test.go index 74efc1c..d082854 100644 --- a/where_test.go +++ b/where_test.go @@ -23,6 +23,12 @@ func TestWhere(t *testing.T) { } } +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 }) + }) +} + func TestWhereIndexed(t *testing.T) { tests := []struct { input interface{} @@ -43,3 +49,9 @@ func TestWhereIndexed(t *testing.T) { } } } + +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/wheretyped.go b/wheretyped.go deleted file mode 100644 index b49b894..0000000 --- a/wheretyped.go +++ /dev/null @@ -1,47 +0,0 @@ -package linq - -// WhereT is the typed version of Where. -// -// NOTE: Where method has better performance than WhereT -// -// predicateFn is of a type "func(TSource)bool" -// -func (q Query) WhereT(predicateFn interface{}) Query { - - predicateGenericFunc, err := newGenericFunc( - "WhereT", "predicateFn", predicateFn, - simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))), - ) - if err != nil { - panic(err) - } - - predicateFunc := func(item interface{}) bool { - return predicateGenericFunc.Call(item).(bool) - } - - return q.Where(predicateFunc) -} - -// WhereIndexedT is the typed version of WhereIndexed. -// -// NOTE: WhereIndexed method has better performance than WhereIndexedT -// -// predicateFn is of a type "func(int,TSource)bool" -// -func (q Query) WhereIndexedT(predicateFn interface{}) Query { - - predicateGenericFunc, err := newGenericFunc( - "WhereIndexedT", "predicateFn", predicateFn, - simpleParamValidator(newElemTypeSlice(new(int), new(genericType)), newElemTypeSlice(new(bool))), - ) - if err != nil { - panic(err) - } - - predicateFunc := func(index int, item interface{}) bool { - return predicateGenericFunc.Call(index, item).(bool) - } - - return q.WhereIndexed(predicateFunc) -} diff --git a/wheretyped_test.go b/wheretyped_test.go deleted file mode 100644 index f68ec08..0000000 --- a/wheretyped_test.go +++ /dev/null @@ -1,102 +0,0 @@ -package linq - -import "testing" - -func TestWhereT(t *testing.T) { - tests := []struct { - input interface{} - predicate interface{} - output []interface{} - }{ - {[9]int{1, 1, 1, 2, 1, 2, 3, 4, 2}, func(i interface{}) bool { - return i.(int) >= 3 - }, []interface{}{3, 4}}, - {"sstr", func(i interface{}) bool { - return i.(rune) != 's' - }, []interface{}{'t', 'r'}}, - {"sstr", func(i rune) bool { - return i != 's' - }, []interface{}{'t', 'r'}}, - } - - for _, test := range tests { - if q := From(test.input).WhereT(test.predicate); !validateQuery(q, test.output) { - t.Errorf("From(%v).WhereT()=%v expected %v", test.input, toSlice(q), test.output) - } - } -} - -func TestWhereT_PanicWhenFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - - From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).WhereT(func(item int) int { return item + 2 }) -} - -func TestWhereIndexedT(t *testing.T) { - tests := []struct { - input interface{} - predicate interface{} - output []interface{} - }{ - {[9]int{1, 1, 1, 2, 1, 2, 3, 4, 2}, func(i int, x interface{}) bool { - return x.(int) < 4 && i > 4 - }, []interface{}{2, 3, 2}}, - {"sstr", func(i int, x interface{}) bool { - return x.(rune) != 's' || i == 1 - }, []interface{}{'s', 't', 'r'}}, - {"sstr", func(i int, x rune) bool { - return x != 's' || i == 1 - }, []interface{}{'s', 't', 'r'}}, - } - - for _, test := range tests { - if q := From(test.input).WhereIndexedT(test.predicate); !validateQuery(q, test.output) { - t.Errorf("From(%v).WhereIndexed()=%v expected %v", test.input, toSlice(q), test.output) - } - } -} - -func TestWhereT_PanicWhenParameterInTypeDoesntMatch(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - - var r []int - From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).WhereT(func(item string) bool { return len(item) > 0 }).ToSlice(&r) -} - -func TestWhereIndexedT_PanicWhenFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic") - } - }() - - From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).WhereIndexedT(func(item string) {}) -} - -func TestWhereIndexed_PanicWhenParameterTypesDoesntMatch(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic") - } - }() - var result []int - From([]int{1, 1, 1, 2, 1, 2, 3, 4, 2}).WhereIndexedT(func(index int, item string) bool { return true }).ToSlice(&result) -} diff --git a/zip.go b/zip.go index 1314b9a..c7d6972 100644 --- a/zip.go +++ b/zip.go @@ -33,3 +33,42 @@ func (q Query) Zip( }, } } + +// ZipT is the typed version of Zip. +// +// NOTE: Zip method has better performance than ZipT +// +// resultSelectorFn is of a type "func(TFirst,TSecond)TResult" +func (q Query) ZipT(q2 Query, resultSelectorFn interface{}) Query { + resultSelectorFunc, ok := resultSelectorFn.(func(interface{}, interface{}) interface{}) + if !ok { + resultSelectorGenericFunc, err := newGenericFunc( + "ZipT", "resultSelectorFn", resultSelectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + resultSelectorFunc = func(item1 interface{}, item2 interface{}) interface{} { + return resultSelectorGenericFunc.Call(item1, item2) + } + } + return Query{ + Iterate: func() Iterator { + next1 := q.Iterate() + next2 := q2.Iterate() + + return func() (item interface{}, ok bool) { + item1, ok1 := next1() + item2, ok2 := next2() + + if ok1 && ok2 { + return resultSelectorFunc(item1, item2), true + } + + return nil, false + } + }, + } +} diff --git a/zip_test.go b/zip_test.go index 252553a..335f706 100644 --- a/zip_test.go +++ b/zip_test.go @@ -13,3 +13,14 @@ func TestZip(t *testing.T) { t.Errorf("From(%v).Zip(%v)=%v expected %v", input1, input2, toSlice(q), want) } } + +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} + input2 := []int{2, 4, 5, 1} + + From(input1).ZipT(From(input2), func(i, j, k int) int { + return i + j + }) + }) +} diff --git a/ziptyped.go b/ziptyped.go deleted file mode 100644 index fe99661..0000000 --- a/ziptyped.go +++ /dev/null @@ -1,42 +0,0 @@ -package linq - -// ZipT is the typed version of Zip. -// -// NOTE: Zip method has better performance than ZipT -// -// resultSelectorFn is of a type "func(TFirst,TSecond)TResult" -// -func (q Query) ZipT(q2 Query, resultSelectorFn interface{}) Query { - - resultSelectorFunc, ok := resultSelectorFn.(func(interface{}, interface{}) interface{}) - if !ok { - resultSelectorGenericFunc, err := newGenericFunc( - "ZipT", "resultSelectorFn", resultSelectorFn, - simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))), - ) - if err != nil { - panic(err) - } - - resultSelectorFunc = func(item1 interface{}, item2 interface{}) interface{} { - return resultSelectorGenericFunc.Call(item1, item2) - } - } - return Query{ - Iterate: func() Iterator { - next1 := q.Iterate() - next2 := q2.Iterate() - - return func() (item interface{}, ok bool) { - item1, ok1 := next1() - item2, ok2 := next2() - - if ok1 && ok2 { - return resultSelectorFunc(item1, item2), true - } - - return nil, false - } - }, - } -} diff --git a/ziptyped_test.go b/ziptyped_test.go deleted file mode 100644 index 66f2449..0000000 --- a/ziptyped_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package linq - -import "testing" - -func TestZipT(t *testing.T) { - input1 := []int{1, 2, 3} - input2 := []int{2, 4, 5, 1} - want := []interface{}{3, 6, 8} - - if q := From(input1).ZipT(From(input2), func(i, j interface{}) interface{} { - return i.(int) + j.(int) - }); !validateQuery(q, want) { - t.Errorf("From(%v).Zip(%v)=%v expected %v", input1, input2, toSlice(q), want) - } - - if q := From(input1).ZipT(From(input2), func(i, j int) interface{} { - return i + j - }); !validateQuery(q, want) { - t.Errorf("From(%v).Zip(%v)=%v expected %v", input1, input2, toSlice(q), want) - } -} - -func TestZipT_PanicWhenFunctionIsNotValid(t *testing.T) { - defer func() { - r := recover() - t.Log(r) - if r == nil { - t.Error("This execution should panic", r) - } - - }() - - input1 := []int{1, 2, 3} - input2 := []int{2, 4, 5, 1} - - From(input1).ZipT(From(input2), func(i, j, k int) int { - return i + j - }) - -} From 30ecab56ac93e3fac0a98108489f2c4435e539e3 Mon Sep 17 00:00:00 2001 From: Cleiton Marques Souza Date: Mon, 5 Dec 2016 01:55:52 -0800 Subject: [PATCH 3/9] Change .ToSlice() to overwrite/grow the slice. Prior to this, ToSlice(&v) method used to just appends to the underlying slice. With this patch, we now overwrite the underlying slice, starting from index 0. Fixes #46. --- result.go | 35 ++++++++++++++++++--- result_test.go | 85 +++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 110 insertions(+), 10 deletions(-) diff --git a/result.go b/result.go index 823653e..265c691 100644 --- a/result.go +++ b/result.go @@ -546,17 +546,42 @@ func (q Query) ToMapByT(result interface{}, keySelectorFn interface{}, valueSele q.ToMapBy(result, keySelectorFunc, valueSelectorFunc) } -// ToSlice iterates over a collection and populates the result slice with elements. +// ToSlice iterates over a collection, copy to result slice the collection elements, +// appends items if initial capacity is not enough and returns subslice if initial capacity is excessive. // Collection elements must be assignable to the slice's element type. -// ToSlice doesn't empty the result slice before populating it. func (q Query) ToSlice(result interface{}) { res := reflect.ValueOf(result) slice := reflect.Indirect(res) next := q.Iterate() - + index := 0 for item, ok := next(); ok; item, ok = next() { - slice = reflect.Append(slice, reflect.ValueOf(item)) + if index >= slice.Len() { + slice = grow(slice, 1) + } + slice.Index(index).Set(reflect.ValueOf(item)) + index++ } - res.Elem().Set(slice) + res.Elem().Set(slice.Slice(0, index)) +} + +// grow grows the slice s so that it can hold extra more values, allocating +// more capacity if needed. +func grow(s reflect.Value, extra uint) reflect.Value { + len := s.Len() + newLen := len + int(extra) + cap := s.Cap() + if newLen <= cap { + return s.Slice(0, newLen) + } + if cap == 0 { + cap = int(extra) + } else { + for cap < newLen { + cap *= 2 + } + } + newSlice := reflect.MakeSlice(s.Type(), newLen, cap) + reflect.Copy(newSlice, s) + return newSlice } diff --git a/result_test.go b/result_test.go index ce894aa..1c26504 100644 --- a/result_test.go +++ b/result_test.go @@ -4,6 +4,7 @@ import ( "math" "reflect" "testing" + "unsafe" ) func TestAll(t *testing.T) { @@ -466,12 +467,86 @@ func TestToMapByT_PanicWhenValueSelectorFnIsInvalid(t *testing.T) { } func TestToSlice(t *testing.T) { - input := []int{1, 2, 3, 4} + tests := []struct { + input []int + output []int + want []int + wantedOutputCap int + outputIsANewSlice bool + }{ + // output is nil slice + { + []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + nil, + []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + 16, + true}, + // output is empty slice + { + []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + []int{}, + []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + 16, + true}, + // ToSlice() overwrites existing elements and reslices. + {[]int{1, 2, 3}, + []int{99, 98, 97, 96, 95}, + []int{1, 2, 3}, + 5, + false}, + // cap(out)>len(result): we get the same slice, resliced. cap unchanged. + {[]int{1, 2, 3, 4, 5}, + make([]int, 0, 11), + []int{1, 2, 3, 4, 5}, + 11, + false}, + // cap(out)==len(result): we get the same slice, cap unchanged. + {[]int{1, 2, 3, 4, 5}, + make([]int, 0, 5), + []int{1, 2, 3, 4, 5}, + 5, + false}, + // cap(out) 52 -> 104) + {make([]int, 100), + make([]int, 0, 26), + make([]int, 100), + 104, + true}, + // len(out) > len(result): we get the same slice with len(out)=len(result) and cap unchanged: cap(out')==cap(out) + {[]int{1, 2, 3, 4, 5}, + make([]int, 0, 50), + []int{1, 2, 3, 4, 5}, + 50, + false}, + } + + for c, test := range tests { + initialOutputValue := test.output + From(test.input).ToSlice(&test.output) + modifiedOutputValue := test.output + + // test slice values + if !reflect.DeepEqual(test.output, test.want) { + t.Fatalf("case #%d: From(%#v).ToSlice()=%#v expected=%#v", c, test.input, test.output, test.want) + } - result := []int{} - From(input).ToSlice(&result) + // test capacity of output slice + if cap(test.output) != test.wantedOutputCap { + t.Fatalf("case #%d: cap(output)=%d expected=%d", c, cap(test.output), test.wantedOutputCap) + } - if !reflect.DeepEqual(result, input) { - t.Errorf("From(%v).ToSlice()=%v expected %v", input, result, input) + // test if a new slice is allocated + inPtr := (*reflect.SliceHeader)(unsafe.Pointer(&initialOutputValue)).Data + outPtr := (*reflect.SliceHeader)(unsafe.Pointer(&modifiedOutputValue)).Data + isNewSlice := inPtr != outPtr + if isNewSlice != test.outputIsANewSlice { + t.Fatalf("case #%d: isNewSlice=%v (in=0x%X out=0x%X) expected=%v", c, isNewSlice, inPtr, outPtr, test.outputIsANewSlice) + } } } From 3b37cd94ae012cac3750b48ddcea1ab534141640 Mon Sep 17 00:00:00 2001 From: Ahmet Alp Balkan Date: Wed, 14 Dec 2016 15:19:47 -0800 Subject: [PATCH 4/9] ToSlice: update documentation and example Signed-off-by: Ahmet Alp Balkan --- example_test.go | 2 +- result.go | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/example_test.go b/example_test.go index 5cd6983..1bb837f 100644 --- a/example_test.go +++ b/example_test.go @@ -1339,7 +1339,7 @@ func ExampleQuery_ToMapBy() { // The following code example demonstrates how to use ToSlice to populate a slice. func ExampleQuery_ToSlice() { - result := []int{} + var result []int Range(1, 10).ToSlice(&result) fmt.Println(result) diff --git a/result.go b/result.go index 265c691..9cbfe43 100644 --- a/result.go +++ b/result.go @@ -546,11 +546,15 @@ func (q Query) ToMapByT(result interface{}, keySelectorFn interface{}, valueSele q.ToMapBy(result, keySelectorFunc, valueSelectorFunc) } -// ToSlice iterates over a collection, copy to result slice the collection elements, -// appends items if initial capacity is not enough and returns subslice if initial capacity is excessive. -// Collection elements must be assignable to the slice's element type. -func (q Query) ToSlice(result interface{}) { - res := reflect.ValueOf(result) +// ToSlice iterates over a collection and saves the results in the slice pointed +// by v. It starts saving elements starting from index 0, therefore it will +// overwrite the slice. +// +// If the slice pointed by v has sufficient capacity, v will be pointed to a +// resliced slice. If it does not, a new underlying array will be allocated and +// v will point to it. +func (q Query) ToSlice(v interface{}) { + res := reflect.ValueOf(v) slice := reflect.Indirect(res) next := q.Iterate() index := 0 From d38534852d0dcd87370b9c4688e5a3bd2ef93e95 Mon Sep 17 00:00:00 2001 From: Ahmet Alp Balkan Date: Fri, 16 Dec 2016 11:43:59 -0800 Subject: [PATCH 5/9] ToSlice(): avoid too many calls to grow/Len() Signed-off-by: Ahmet Alp Balkan --- result.go | 33 +++++++++++++++------------------ result_test.go | 2 +- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/result.go b/result.go index 9cbfe43..23cd86c 100644 --- a/result.go +++ b/result.go @@ -547,8 +547,7 @@ func (q Query) ToMapByT(result interface{}, keySelectorFn interface{}, valueSele } // ToSlice iterates over a collection and saves the results in the slice pointed -// by v. It starts saving elements starting from index 0, therefore it will -// overwrite the slice. +// by v. It overwrites the existing slice, starting from index 0. // // If the slice pointed by v has sufficient capacity, v will be pointed to a // resliced slice. If it does not, a new underlying array will be allocated and @@ -556,36 +555,34 @@ func (q Query) ToMapByT(result interface{}, keySelectorFn interface{}, valueSele func (q Query) ToSlice(v interface{}) { res := reflect.ValueOf(v) slice := reflect.Indirect(res) + + cap := slice.Cap() + res.Elem().Set(slice.Slice(0, cap)) // make len(slice)==cap(slice) from now on + next := q.Iterate() index := 0 for item, ok := next(); ok; item, ok = next() { - if index >= slice.Len() { - slice = grow(slice, 1) + if index >= cap { + slice, cap = grow(slice) } slice.Index(index).Set(reflect.ValueOf(item)) index++ } + // reslice the len(res)==cap(res) actual res size res.Elem().Set(slice.Slice(0, index)) } -// grow grows the slice s so that it can hold extra more values, allocating -// more capacity if needed. -func grow(s reflect.Value, extra uint) reflect.Value { - len := s.Len() - newLen := len + int(extra) +// 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) { cap := s.Cap() - if newLen <= cap { - return s.Slice(0, newLen) - } if cap == 0 { - cap = int(extra) + cap = 1 } else { - for cap < newLen { - cap *= 2 - } + cap *= 2 } - newSlice := reflect.MakeSlice(s.Type(), newLen, cap) + newSlice := reflect.MakeSlice(s.Type(), cap, cap) reflect.Copy(newSlice, s) - return newSlice + return newSlice, cap } diff --git a/result_test.go b/result_test.go index 1c26504..7f8d786 100644 --- a/result_test.go +++ b/result_test.go @@ -481,7 +481,7 @@ func TestToSlice(t *testing.T) { []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 16, true}, - // output is empty slice + // output is empty slice (cap=0) { []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, []int{}, From 873aaae58071271eb499b5dec7f067cf4aa6766f Mon Sep 17 00:00:00 2001 From: Ahmet Alp Balkan Date: Mon, 2 Jan 2017 16:24:57 -0800 Subject: [PATCH 6/9] Godoc cosmetics fixes (#50) - Collected ".. is of a type" in bullet lists - Added trailing periods in godocs - Wrapped godoc comments to a consistent length - Moved NOTE blocks to the end. Signed-off-by: Ahmet Alp Balkan --- aggregate.go | 63 +++++++++++++++++++++------------------- compare.go | 22 +++++++------- concat.go | 12 ++++---- distinct.go | 14 ++++----- except.go | 16 +++++------ from.go | 34 +++++++++++----------- genericfunc.go | 15 +++++----- groupby.go | 13 ++++----- groupjoin.go | 31 ++++++++++---------- intersect.go | 12 ++++---- join.go | 22 +++++++------- orderby.go | 57 ++++++++++++++++-------------------- result.go | 78 +++++++++++++++++++++++++------------------------- reverse.go | 6 ++-- select.go | 65 +++++++++++++++++++---------------------- selectmany.go | 51 ++++++++++++++++----------------- skip.go | 38 ++++++++++++------------ take.go | 24 +++++++++------- union.go | 7 ++--- where.go | 16 +++++------ zip.go | 20 ++++++------- 21 files changed, 301 insertions(+), 315 deletions(-) diff --git a/aggregate.go b/aggregate.go index 87b382f..4ed8930 100644 --- a/aggregate.go +++ b/aggregate.go @@ -2,12 +2,12 @@ package linq // Aggregate applies an accumulator function over a sequence. // -// Aggregate method makes it simple to perform a calculation over a sequence of values. -// This method works by calling f() one time for each element in source -// except the first one. Each time f() is called, Aggregate passes both -// the element from the sequence and an aggregated value (as the first argument to f()). -// The first element of source is used as the initial aggregate value. -// The result of f() replaces the previous aggregated value. +// Aggregate method makes it simple to perform a calculation over a sequence of +// values. This method works by calling f() one time for each element in source +// except the first one. Each time f() is called, Aggregate passes both the +// element from the sequence and an aggregated value (as the first argument to +// f()). The first element of source is used as the initial aggregate value. The +// result of f() replaces the previous aggregated value. // // Aggregate returns the final result of f(). func (q Query) Aggregate( @@ -28,9 +28,9 @@ func (q Query) Aggregate( // AggregateT is the typed version of Aggregate. // -// NOTE: Aggregate method has better performance than AggregateT +// - f is of type: func(TSource, TSource) TSource // -// f is of type: func(TSource, TSource) TSource +// NOTE: Aggregate has better performance than AggregateT. func (q Query) AggregateT(f interface{}) interface{} { fGenericFunc, err := newGenericFunc( @@ -48,14 +48,14 @@ func (q Query) AggregateT(f interface{}) interface{} { return q.Aggregate(fFunc) } -// AggregateWithSeed applies an accumulator function over a sequence. -// The specified seed value is used as the initial accumulator value. +// AggregateWithSeed applies an accumulator function over a sequence. The +// specified seed value is used as the initial accumulator value. // -// Aggregate method makes it simple to perform a calculation over a sequence of values. -// This method works by calling f() one time for each element in source -// except the first one. Each time f() is called, Aggregate passes both -// the element from the sequence and an aggregated value (as the first argument to f()). -// The value of the seed parameter is used as the initial aggregate value. +// Aggregate method makes it simple to perform a calculation over a sequence of +// values. This method works by calling f() one time for each element in source +// except the first one. Each time f() is called, Aggregate passes both the +// element from the sequence and an aggregated value (as the first argument to +// f()). The value of the seed parameter is used as the initial aggregate value. // The result of f() replaces the previous aggregated value. // // Aggregate returns the final result of f(). @@ -76,9 +76,10 @@ func (q Query) AggregateWithSeed( // AggregateWithSeedT is the typed version of AggregateWithSeed. // -// NOTE: AggregateWithSeed method has better performance than AggregateWithSeedT +// - f is of type "func(TAccumulate, TSource) TAccumulate" // -// f is of a type "func(TAccumulate, TSource) TAccumulate" +// NOTE: AggregateWithSeed has better performance than +// AggregateWithSeedT. func (q Query) AggregateWithSeedT(seed interface{}, f interface{}) interface{} { fGenericFunc, err := newGenericFunc( "AggregateWithSeed", "f", f, @@ -95,17 +96,19 @@ func (q Query) AggregateWithSeedT(seed interface{}, f interface{}) interface{} { return q.AggregateWithSeed(seed, fFunc) } -// 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. +// 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. // -// Aggregate method makes it simple to perform a calculation over a sequence of values. -// This method works by calling f() one time for each element in source. -// Each time func is called, Aggregate passes both the element from the sequence and an -// aggregated value (as the first argument to func). The value of the seed parameter is used -// as the initial aggregate value. The result of func replaces the previous aggregated value. +// Aggregate method makes it simple to perform a calculation over a sequence of +// values. This method works by calling f() one time for each element in source. +// Each time func is called, Aggregate passes both the element from the sequence +// and an aggregated value (as the first argument to func). The value of the +// seed parameter is used as the initial aggregate value. The result of func +// replaces the previous aggregated value. // -// The final result of func is passed to resultSelector to obtain the final result of Aggregate. +// The final result of func is passed to resultSelector to obtain the final +// result of Aggregate. func (q Query) AggregateWithSeedBy( seed interface{}, f func(interface{}, interface{}) interface{}, @@ -124,11 +127,11 @@ func (q Query) AggregateWithSeedBy( // AggregateWithSeedByT is the typed version of AggregateWithSeedBy. // -// NOTE: AggregateWithSeedBy method has better performance than AggregateWithSeedByT -// -// f is of a type "func(TAccumulate, TSource) TAccumulate" +// - f is of type "func(TAccumulate, TSource) TAccumulate" +// - resultSelectorFn is of type "func(TAccumulate) TResult" // -// resultSelectorFn is of type "func(TAccumulate) TResult" +// NOTE: AggregateWithSeedBy has better performance than +// AggregateWithSeedByT. func (q Query) AggregateWithSeedByT(seed interface{}, f interface{}, resultSelectorFn interface{}) interface{} { fGenericFunc, err := newGenericFunc( "AggregateWithSeedByT", "f", f, diff --git a/compare.go b/compare.go index c78e4aa..3e0ffbf 100644 --- a/compare.go +++ b/compare.go @@ -2,21 +2,21 @@ package linq type comparer func(interface{}, interface{}) int -// Comparable is an interface that has to be implemented by a -// custom collection elememts in order to work with linq. +// Comparable is an interface that has to be implemented by a custom collection +// elememts in order to work with linq. // // Example: -// func (f foo) CompareTo(c Comparable) int { -// a, b := f.f1, c.(foo).f1 +// func (f foo) CompareTo(c Comparable) int { +// a, b := f.f1, c.(foo).f1 // -// if a < b { -// return -1 -// } else if a > b { -// return 1 -// } +// if a < b { +// return -1 +// } else if a > b { +// return 1 +// } // -// return 0 -// } +// return 0 +// } type Comparable interface { CompareTo(Comparable) int } diff --git a/concat.go b/concat.go index 6210126..3101c56 100644 --- a/concat.go +++ b/concat.go @@ -1,7 +1,7 @@ package linq -// Append inserts an item to the end of a collection, -// so it becomes the last item. +// Append inserts an item to the end of a collection, so it becomes the last +// item. func (q Query) Append(item interface{}) Query { return Query{ Iterate: func() Iterator { @@ -28,8 +28,8 @@ func (q Query) Append(item interface{}) Query { // Concat concatenates two collections. // // The Concat method differs from the Union method because the Concat method -// returns all the original elements in the input sequences. -// The Union method returns only unique elements. +// returns all the original elements in the input sequences. The Union method +// returns only unique elements. func (q Query) Concat(q2 Query) Query { return Query{ Iterate: func() Iterator { @@ -53,8 +53,8 @@ func (q Query) Concat(q2 Query) Query { } } -// Prepend inserts an item to the beginning of a collection, -// so it becomes the first item. +// Prepend inserts an item to the beginning of a collection, so it becomes the +// first item. func (q Query) Prepend(item interface{}) Query { return Query{ Iterate: func() Iterator { diff --git a/distinct.go b/distinct.go index 5b64109..aaef37a 100644 --- a/distinct.go +++ b/distinct.go @@ -1,7 +1,7 @@ package linq -// Distinct method returns distinct elements from a collection. -// The result is an unordered collection that contains no duplicate values. +// Distinct method returns distinct elements from a collection. The result is an +// unordered collection that contains no duplicate values. func (q Query) Distinct() Query { return Query{ Iterate: func() Iterator { @@ -22,11 +22,11 @@ func (q Query) Distinct() Query { } } -// Distinct method returns distinct elements from a collection. -// The result is an ordered collection that contains no duplicate values. +// Distinct method returns distinct elements from a collection. The result is an +// ordered collection that contains no duplicate values. // // NOTE: Distinct method on OrderedQuery type has better performance than -// Distinct method on Query type +// Distinct method on Query type. func (oq OrderedQuery) Distinct() OrderedQuery { return OrderedQuery{ orders: oq.orders, @@ -76,9 +76,9 @@ func (q Query) DistinctBy(selector func(interface{}) interface{}) Query { // DistinctByT is the typed version of DistinctBy. // -// NOTE: DistinctBy method has better performance than DistinctByT +// - selectorFn is of type "func(TSource) TSource". // -// selectorFn is of type "func(TSource) TSource". +// NOTE: DistinctBy has better performance than DistinctByT. func (q Query) DistinctByT(selectorFn interface{}) Query { selectorFunc, ok := selectorFn.(func(interface{}) interface{}) if !ok { diff --git a/except.go b/except.go index 074c945..8f881c2 100644 --- a/except.go +++ b/except.go @@ -1,8 +1,7 @@ package linq -// 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. +// 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 Query) Except(q2 Query) Query { return Query{ Iterate: func() Iterator { @@ -27,10 +26,9 @@ func (q Query) Except(q2 Query) 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. +// 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 (q Query) ExceptBy( q2 Query, selector func(interface{}) interface{}) Query { return Query{ @@ -60,9 +58,9 @@ func (q Query) ExceptBy( // ExceptByT is the typed version of ExceptBy. // -// NOTE: ExceptBy method has better performance than ExceptByT +// - selectorFn is of type "func(TSource) TSource" // -// selectorFn is of a type "func(TSource) TSource" +// NOTE: ExceptBy has better performance than ExceptByT. func (q Query) ExceptByT(q2 Query, selectorFn interface{}) Query { selectorGenericFunc, err := newGenericFunc( "ExceptByT", "selectorFn", selectorFn, diff --git a/from.go b/from.go index 56ab451..21ee9f4 100644 --- a/from.go +++ b/from.go @@ -5,30 +5,30 @@ import "reflect" // Iterator is an alias for function to iterate over data. type Iterator func() (item interface{}, ok bool) -// Query is the type returned from query functions. -// It can be iterated manually as shown in the example. +// Query is the type returned from query functions. It can be iterated manually +// as shown in the example. type Query struct { Iterate func() Iterator } -// 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. +// 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. type KeyValue struct { Key interface{} Value interface{} } -// Iterable is an interface that has to be implemented by a -// custom collection in order to work with linq. +// Iterable is an interface that has to be implemented by a custom collection in +// order to work with linq. type Iterable interface { Iterate() Iterator } -// 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 FromIterable internally. +// 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 +// FromIterable internally. func From(source interface{}) Query { src := reflect.ValueOf(source) @@ -84,8 +84,8 @@ func From(source interface{}) Query { } } -// FromChannel initializes a linq query with passed channel, -// linq iterates over channel until it is closed. +// FromChannel initializes a linq query with passed channel, linq iterates over +// channel until it is closed. func FromChannel(source <-chan interface{}) Query { return Query{ Iterate: func() Iterator { @@ -97,8 +97,8 @@ func FromChannel(source <-chan interface{}) Query { } } -// FromString initializes a linq query with passed string, -// linq iterates over runes of string. +// FromString initializes a linq query with passed string, linq iterates over +// runes of string. func FromString(source string) Query { runes := []rune(source) len := len(runes) @@ -120,8 +120,8 @@ func FromString(source string) Query { } } -// FromIterable initializes a linq query with custom collection passed. -// This collection has to implement Iterable interface, linq iterates over items, +// 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. func FromIterable(source Iterable) Query { return Query{ diff --git a/genericfunc.go b/genericfunc.go index ba1255d..77cba2d 100644 --- a/genericfunc.go +++ b/genericfunc.go @@ -6,12 +6,12 @@ import ( "strings" ) -// genericType represents a any reflect.Type +// genericType represents a any reflect.Type. type genericType int var genericTp = reflect.TypeOf(new(genericType)).Elem() -// functionCache keeps genericFunc reflection objects in cache +// functionCache keeps genericFunc reflection objects in cache. type functionCache struct { MethodName string ParamName string @@ -21,12 +21,12 @@ type functionCache struct { TypesOut []reflect.Type } -// genericFunc is a type used to validate and call dynamic functions +// genericFunc is a type used to validate and call dynamic functions. type genericFunc struct { Cache *functionCache } -// Call calls a dynamic function +// Call calls a dynamic function. func (g *genericFunc) Call(params ...interface{}) interface{} { paramsIn := make([]reflect.Value, len(params)) for i, param := range params { @@ -68,7 +68,8 @@ func newGenericFunc(methodName, paramName string, fn interface{}, validateFunc f return &genericFunc{Cache: cache}, nil } -// simpleParamValidator creates a function to validate genericFunc based in the In and Out function parameters +// simpleParamValidator creates a function to validate genericFunc based in the +// In and Out function parameters. func simpleParamValidator(In []reflect.Type, Out []reflect.Type) func(cache *functionCache) error { return func(cache *functionCache) error { var isValid = func() bool { @@ -102,7 +103,7 @@ func simpleParamValidator(In []reflect.Type, Out []reflect.Type) func(cache *fun } } -// newElemTypeSlice creates a slice of items elem types +// newElemTypeSlice creates a slice of items elem types. func newElemTypeSlice(items ...interface{}) []reflect.Type { typeList := make([]reflect.Type, len(items)) for i, item := range items { @@ -114,7 +115,7 @@ func newElemTypeSlice(items ...interface{}) []reflect.Type { return typeList } -// formatFnSignature formats the func signature based in the parameters types +// formatFnSignature formats the func signature based in the parameters types. func formatFnSignature(In []reflect.Type, Out []reflect.Type) string { paramInNames := make([]string, len(In)) for i, typeIn := range In { diff --git a/groupby.go b/groupby.go index e5d1320..376ec10 100644 --- a/groupby.go +++ b/groupby.go @@ -6,9 +6,9 @@ type Group struct { Group []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. +// 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 (q Query) GroupBy( keySelector func(interface{}) interface{}, elementSelector func(interface{}) interface{}, @@ -49,11 +49,10 @@ func (q Query) GroupBy( // GroupByT is the typed version of GroupBy. // -// NOTE: GroupBy method has better performance than GroupByT +// - keySelectorFn is of type "func(TSource) TKey" +// - elementSelectorFn is of type "func(TSource) TElement" // -// keySelectorFn is of a type "func(TSource) TKey" -// -// elementSelectorFn is of a type "func(TSource) TElement" +// NOTE: GroupBy has better performance than GroupByT. func (q Query) GroupByT(keySelectorFn interface{}, elementSelectorFn interface{}) Query { keySelectorGenericFunc, err := newGenericFunc( "GroupByT", "keySelectorFn", keySelectorFn, diff --git a/groupjoin.go b/groupjoin.go index 99a80c8..276923b 100644 --- a/groupjoin.go +++ b/groupjoin.go @@ -5,17 +5,19 @@ import "reflect" // 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. +// 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. +// 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. +// GroupJoin preserves the order of the elements of outer, and for each element +// of outer, the order of the matching elements from inner. func (q Query) GroupJoin( inner Query, outerKeySelector func(interface{}) interface{}, @@ -53,15 +55,12 @@ func (q Query) GroupJoin( // GroupJoinT is the typed version of GroupJoin. // -// NOTE: GroupJoin method has better performance than GroupJoinT +// - inner: The query to join to the outer query. +// - outerKeySelectorFn is of type "func(TOuter) TKey" +// - innerKeySelectorFn is of type "func(TInner) TKey" +// - resultSelectorFn: is of type "func(TOuter, inners []TInner) TResult" // -// inner: The query to join to the outer query. -// -// outerKeySelectorFn is of a type "func(TOuter) TKey" -// -// innerKeySelectorFn is of a type "func(TInner) TKey" -// -// resultSelectorFn: is of a type "func(TOuter, inners []TInner) TResult" +// NOTE: GroupJoin has better performance than GroupJoinT. func (q Query) GroupJoinT(inner Query, outerKeySelectorFn interface{}, innerKeySelectorFn interface{}, resultSelectorFn interface{}) Query { outerKeySelectorGenericFunc, err := newGenericFunc( "GroupJoinT", "outerKeySelectorFn", outerKeySelectorFn, diff --git a/intersect.go b/intersect.go index 728528e..b61eb7d 100644 --- a/intersect.go +++ b/intersect.go @@ -2,8 +2,8 @@ package linq // 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. +// the set that contains all the elements of A that also appear in B, but no +// other elements. func (q Query) Intersect(q2 Query) Query { return Query{ Iterate: func() Iterator { @@ -31,8 +31,8 @@ func (q Query) Intersect(q2 Query) Query { // 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 other elements. +// the set that contains all the elements of A that also appear in B, but no +// other elements. // // IntersectBy invokes a transform function on each element of both collections. func (q Query) IntersectBy( @@ -68,9 +68,9 @@ func (q Query) IntersectBy( // IntersectByT is the typed version of IntersectBy. // -// NOTE: IntersectBy method has better performance than IntersectByT +// - selectorFn is of type "func(TSource) TSource" // -// selectorFn is of a type "func(TSource) TSource" +// NOTE: IntersectBy has better performance than IntersectByT. func (q Query) IntersectByT(q2 Query, selectorFn interface{}) Query { selectorGenericFunc, err := newGenericFunc( "IntersectByT", "selectorFn", selectorFn, diff --git a/join.go b/join.go index 0fa1daa..c1e86bc 100644 --- a/join.go +++ b/join.go @@ -2,14 +2,14 @@ package linq // 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 +// 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. +// 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 (q Query) Join( inner Query, outerKeySelector func(interface{}) interface{}, @@ -57,13 +57,11 @@ func (q Query) Join( // JoinT is the typed version of Join. // -// NOTE: Join method has better performance than JoinT +// - outerKeySelectorFn is of type "func(TOuter) TKey" +// - innerKeySelectorFn is of type "func(TInner) TKey" +// - resultSelectorFn is of type "func(TOuter,TInner) TResult" // -// outerKeySelectorFn is of a type "func(TOuter) TKey" -// -// innerKeySelectorFn is of a type "func(TInner) TKey" -// -// resultSelectorFn is of a type "func(TOuter,TInner) TResult" +// NOTE: Join has better performance than JoinT. func (q Query) JoinT(inner Query, outerKeySelectorFn interface{}, innerKeySelectorFn interface{}, diff --git a/orderby.go b/orderby.go index 527602b..8feb50e 100644 --- a/orderby.go +++ b/orderby.go @@ -8,16 +8,16 @@ type order struct { desc bool } -// OrderedQuery is the type returned from OrderBy, OrderByDescending -// ThenBy and ThenByDescending functions. +// OrderedQuery is the type returned from OrderBy, OrderByDescending ThenBy and +// ThenByDescending functions. type OrderedQuery struct { Query original Query orders []order } -// OrderBy sorts the elements of a collection in ascending order. -// Elements are sorted according to a key. +// 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 { return OrderedQuery{ @@ -45,9 +45,9 @@ func (q Query) OrderBy( // OrderByT is the typed version of OrderBy. // -// NOTE: OrderBy method has better performance than OrderByT +// - selectorFn is of type "func(TSource) TKey" // -// selectorFn is of a type "func(TSource) TKey" +// NOTE: OrderBy has better performance than OrderByT. func (q Query) OrderByT(selectorFn interface{}) OrderedQuery { selectorGenericFunc, err := newGenericFunc( "OrderByT", "selectorFn", selectorFn, @@ -92,10 +92,8 @@ func (q Query) OrderByDescending( } // OrderByDescendingT is the typed version of OrderByDescending. -// -// NOTE: OrderByDescending method has better performance than OrderByDescendingT -// -// selectorFn is of a type "func(TSource) TKey" +// - selectorFn is of type "func(TSource) TKey" +// NOTE: OrderByDescending has better performance than OrderByDescendingT. func (q Query) OrderByDescendingT(selectorFn interface{}) OrderedQuery { selectorGenericFunc, err := newGenericFunc( "OrderByDescendingT", "selectorFn", selectorFn, @@ -112,9 +110,9 @@ func (q Query) OrderByDescendingT(selectorFn interface{}) OrderedQuery { return q.OrderByDescending(selectorFunc) } -// ThenBy performs a subsequent ordering of the elements in a collection -// in ascending order. This method enables you to specify multiple sort criteria -// by applying any number of ThenBy or ThenByDescending methods. +// ThenBy performs a subsequent ordering of the elements in a collection in +// ascending order. This method enables you to specify multiple sort criteria by +// applying any number of ThenBy or ThenByDescending methods. func (oq OrderedQuery) ThenBy( selector func(interface{}) interface{}) OrderedQuery { return OrderedQuery{ @@ -141,10 +139,8 @@ func (oq OrderedQuery) ThenBy( } // ThenByT is the typed version of ThenBy. -// -// NOTE: ThenBy method has better performance than ThenByT -// -// selectorFn is of a type "func(TSource) TKey" +// - selectorFn is of type "func(TSource) TKey" +// NOTE: ThenBy has better performance than ThenByT. func (oq OrderedQuery) ThenByT(selectorFn interface{}) OrderedQuery { selectorGenericFunc, err := newGenericFunc( "ThenByT", "selectorFn", selectorFn, @@ -161,9 +157,9 @@ func (oq OrderedQuery) ThenByT(selectorFn interface{}) OrderedQuery { return oq.ThenBy(selectorFunc) } -// ThenByDescending performs a subsequent ordering of the elements in a collection -// in descending order. This method enables you to specify multiple sort criteria -// by applying any number of ThenBy or ThenByDescending methods. +// ThenByDescending performs a subsequent ordering of the elements in a +// collection in descending order. This method enables you to specify multiple +// sort criteria by applying any number of ThenBy or ThenByDescending methods. func (oq OrderedQuery) ThenByDescending( selector func(interface{}) interface{}) OrderedQuery { return OrderedQuery{ @@ -190,10 +186,8 @@ func (oq OrderedQuery) ThenByDescending( } // ThenByDescendingT is the typed version of ThenByDescending. -// -// NOTE: ThenByDescending method has better performance than ThenByDescendingT -// -// selectorFn is of a type "func(TSource) TKey" +// - selectorFn is of type "func(TSource) TKey" +// NOTE: ThenByDescending has better performance than ThenByDescendingT. func (oq OrderedQuery) ThenByDescendingT(selectorFn interface{}) OrderedQuery { selectorFunc, ok := selectorFn.(func(interface{}) interface{}) if !ok { @@ -212,10 +206,11 @@ func (oq OrderedQuery) ThenByDescendingT(selectorFn interface{}) OrderedQuery { return oq.ThenByDescending(selectorFunc) } -// 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. +// 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 Query) Sort(less func(i, j interface{}) bool) Query { return Query{ Iterate: func() Iterator { @@ -237,10 +232,8 @@ func (q Query) Sort(less func(i, j interface{}) bool) Query { } // SortT is the typed version of Sort. -// -// NOTE: Sort method has better performance than SortT -// -// lessFn is of a type "func(TSource,TSource) bool" +// - lessFn is of type "func(TSource,TSource) bool" +// NOTE: Sort has better performance than SortT. func (q Query) SortT(lessFn interface{}) Query { lessGenericFunc, err := newGenericFunc( "SortT", "lessFn", lessFn, diff --git a/result.go b/result.go index 23cd86c..f75ed8f 100644 --- a/result.go +++ b/result.go @@ -20,9 +20,9 @@ func (q Query) All(predicate func(interface{}) bool) bool { // AllT is the typed version of All. // -// NOTE: All method has better performance than AllT +// - predicateFn is of type "func(TSource) bool" // -// predicateFn is of a type "func(TSource) bool" +// NOTE: All has better performance than AllT. func (q Query) AllT(predicateFn interface{}) bool { predicateGenericFunc, err := newGenericFunc( @@ -60,9 +60,9 @@ func (q Query) AnyWith(predicate func(interface{}) bool) bool { // AnyWithT is the typed version of AnyWith. // -// NOTE: AnyWith method has better performance than AnyWithT +// - predicateFn is of type "func(TSource) bool" // -// predicateFn is of a type "func(TSource) bool" +// NOTE: AnyWith has better performance than AnyWithT. func (q Query) AnyWithT(predicateFn interface{}) bool { predicateGenericFunc, err := newGenericFunc( @@ -147,8 +147,8 @@ func (q Query) Count() (r int) { return } -// CountWith returns a number that represents how many elements -// in the specified collection satisfy a condition. +// 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) { next := q.Iterate() @@ -163,9 +163,9 @@ func (q Query) CountWith(predicate func(interface{}) bool) (r int) { // CountWithT is the typed version of CountWith. // -// NOTE: CountWith method has better performance than CountWithT +// - predicateFn is of type "func(TSource) bool" // -// predicateFn is of a type "func(TSource) bool" +// NOTE: CountWith has better performance than CountWithT. func (q Query) CountWithT(predicateFn interface{}) int { predicateGenericFunc, err := newGenericFunc( @@ -189,8 +189,8 @@ func (q Query) First() interface{} { return item } -// FirstWith returns the first element of a collection that satisfies -// a specified condition. +// FirstWith returns the first element of a collection that satisfies a +// specified condition. func (q Query) FirstWith(predicate func(interface{}) bool) interface{} { next := q.Iterate() @@ -205,9 +205,9 @@ func (q Query) FirstWith(predicate func(interface{}) bool) interface{} { // FirstWithT is the typed version of FirstWith. // -// NOTE: FirstWith method has better performance than FirstWithT +// - predicateFn is of type "func(TSource) bool" // -// predicateFn is of a type "func(TSource) bool" +// NOTE: FirstWith has better performance than FirstWithT. func (q Query) FirstWithT(predicateFn interface{}) interface{} { predicateGenericFunc, err := newGenericFunc( @@ -236,8 +236,8 @@ func (q Query) Last() (r interface{}) { return } -// LastWith returns the last element of a collection that satisfies -// a specified condition. +// LastWith returns the last element of a collection that satisfies a specified +// condition. func (q Query) LastWith(predicate func(interface{}) bool) (r interface{}) { next := q.Iterate() @@ -252,9 +252,9 @@ func (q Query) LastWith(predicate func(interface{}) bool) (r interface{}) { // LastWithT is the typed version of LastWith. // -// NOTE: LastWith method has better performance than LastWithT +// - predicateFn is of type "func(TSource) bool" // -// predicateFn is of a type "func(TSource) bool" +// NOTE: LastWith has better performance than LastWithT. func (q Query) LastWithT(predicateFn interface{}) interface{} { predicateGenericFunc, err := newGenericFunc( @@ -339,8 +339,8 @@ func (q Query) SequenceEqual(q2 Query) bool { return !ok2 } -// Single returns the only element of a collection, and nil -// if there is not exactly one element in the collection. +// 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{} { next := q.Iterate() item, ok := next() @@ -356,8 +356,8 @@ func (q Query) Single() interface{} { return item } -// SingleWith returns the only element of a collection that satisfies -// a specified condition, and nil if more than one such element exists. +// 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{}) { next := q.Iterate() found := false @@ -378,9 +378,9 @@ func (q Query) SingleWith(predicate func(interface{}) bool) (r interface{}) { // SingleWithT is the typed version of SingleWith. // -// NOTE: SingleWith method has better performance than SingleWithT +// - predicateFn is of type "func(TSource) bool" // -// predicateFn is of a type "func(TSource) bool" +// NOTE: SingleWith has better performance than SingleWithT. func (q Query) SingleWithT(predicateFn interface{}) interface{} { predicateGenericFunc, err := newGenericFunc( "SingleWithT", "predicateFn", predicateFn, @@ -399,8 +399,8 @@ func (q Query) SingleWithT(predicateFn interface{}) interface{} { // SumInts computes the sum of a collection of numeric values. // -// Values can be of any integer type: int, int8, int16, int32, int64. -// The result is int64. Method returns zero if collection contains no elements. +// Values can be of any integer type: int, int8, int16, int32, int64. The result +// is int64. Method returns zero if collection contains no elements. func (q Query) SumInts() (r int64) { next := q.Iterate() item, ok := next() @@ -420,8 +420,9 @@ func (q Query) SumInts() (r int64) { // SumUInts computes the sum of a collection of numeric values. // -// Values can be of any unsigned integer type: uint, uint8, uint16, uint32, uint64. -// The result is uint64. Method returns zero if collection contains no elements. +// Values can be of any unsigned integer type: uint, uint8, uint16, uint32, +// uint64. The result is uint64. Method returns zero if collection contains no +// elements. func (q Query) SumUInts() (r uint64) { next := q.Iterate() item, ok := next() @@ -460,8 +461,8 @@ func (q Query) SumFloats() (r float64) { return } -// ToChannel iterates over a collection and outputs each element -// to a channel, then closes it. +// ToChannel iterates over a collection and outputs each element to a channel, +// then closes it. func (q Query) ToChannel(result chan<- interface{}) { next := q.Iterate() @@ -473,9 +474,9 @@ func (q Query) ToChannel(result chan<- interface{}) { } // ToMap iterates over a collection and populates result map with elements. -// Collection elements have to be of KeyValue type to use this method. -// To populate a map with elements of different type use ToMapBy method. -// ToMap doesn't empty the result map before populating it. +// Collection elements have to be of KeyValue type to use this method. To +// populate a map with elements of different type use ToMapBy method. ToMap +// doesn't empty the result map before populating it. func (q Query) ToMap(result interface{}) { q.ToMapBy( result, @@ -487,10 +488,10 @@ func (q Query) ToMap(result interface{}) { }) } -// 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 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 (q Query) ToMapBy( result interface{}, @@ -513,11 +514,10 @@ func (q Query) ToMapBy( // ToMapByT is the typed version of ToMapBy. // -// NOTE: ToMapBy method has better performance than ToMapByT +// - keySelectorFn is of type "func(TSource)TKey" +// - valueSelectorFn is of type "func(TSource)TValue" // -// keySelectorFn is of a type "func(TSource)TKey" -// -// valueSelectorFn is of a type "func(TSource)TValue" +// NOTE: ToMapBy has better performance than ToMapByT. func (q Query) ToMapByT(result interface{}, keySelectorFn interface{}, valueSelectorFn interface{}) { keySelectorGenericFunc, err := newGenericFunc( "ToMapByT", "keySelectorFn", keySelectorFn, diff --git a/reverse.go b/reverse.go index eaacb9f..8716c22 100644 --- a/reverse.go +++ b/reverse.go @@ -2,9 +2,9 @@ package linq // Reverse inverts the order of the elements in a collection. // -// Unlike OrderBy, this sorting method does not consider the actual values themselves -// in determining the order. Rather, it just returns the elements in the reverse order -// from which they are produced by the underlying source. +// Unlike OrderBy, this sorting method does not consider the actual values +// themselves in determining the order. Rather, it just returns the elements in +// the reverse order from which they are produced by the underlying source. func (q Query) Reverse() Query { return Query{ Iterate: func() Iterator { diff --git a/select.go b/select.go index ef074fc..5dc8cc7 100644 --- a/select.go +++ b/select.go @@ -1,17 +1,16 @@ package linq -// Select projects each element of a collection into a new form. -// Returns a query with the result of invoking the transform function -// on each element of original source. +// Select projects each element of a collection into a new form. Returns a query +// with the result of invoking the transform function on each element of +// original source. // -// This projection method requires the transform function, selector, -// to produce one value for each value in the source collection. -// If selector returns a value that is itself a collection, -// it is up to the consumer to traverse the subcollections manually. -// In such a situation, it might be better for your query to return a single -// coalesced collection of values. To achieve this, use the SelectMany method -// instead of Select. Although SelectMany works similarly to Select, -// it differs in that the transform function returns a collection +// This projection method requires the transform function, selector, to produce +// one value for each value in the source collection. If selector returns a +// value that is itself a collection, it is up to the consumer to traverse the +// subcollections manually. In such a situation, it might be better for your +// query to return a single coalesced collection of values. To achieve this, use +// the SelectMany method instead of Select. Although SelectMany works similarly +// to Select, it differs in that the transform function returns a collection // that is then expanded by SelectMany before it is returned. func (q Query) Select(selector func(interface{}) interface{}) Query { return Query{ @@ -32,10 +31,8 @@ func (q Query) Select(selector func(interface{}) interface{}) Query { } // SelectT is the typed version of Select. -// -// NOTE: Select method has better performance than SelectT -// -// selectorFn is of a type "func(TSource)TResult" +// - selectorFn is of type "func(TSource)TResult" +// NOTE: Select has better performance than SelectT. func (q Query) SelectT(selectorFn interface{}) Query { selectGenericFunc, err := newGenericFunc( @@ -53,24 +50,24 @@ func (q Query) SelectT(selectorFn interface{}) Query { return q.Select(selectorFunc) } -// SelectIndexed projects each element of a collection into a new form -// by incorporating the element's index. Returns a query with the result -// of invoking the transform function on each element of original source. +// SelectIndexed projects each element of a collection into a new form by +// incorporating the element's index. Returns a query with the result of +// invoking the transform function on each element of original source. // -// 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. +// 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. // -// This projection method requires the transform function, selector, -// to produce one value for each value in the source collection. -// If selector returns a value that is itself a collection, -// it is up to the consumer to traverse the subcollections manually. -// In such a situation, it might be better for your query to return a single -// coalesced collection of values. To achieve this, use the SelectMany method -// instead of Select. Although SelectMany works similarly to Select, -// it differs in that the transform function returns a collection +// This projection method requires the transform function, selector, to produce +// one value for each value in the source collection. If selector returns a +// value that is itself a collection, it is up to the consumer to traverse the +// subcollections manually. In such a situation, it might be better for your +// query to return a single coalesced collection of values. To achieve this, use +// the SelectMany method instead of Select. Although SelectMany works similarly +// to Select, it differs in that the transform function returns a collection // that is then expanded by SelectMany before it is returned. func (q Query) SelectIndexed(selector func(int, interface{}) interface{}) Query { return Query{ @@ -93,10 +90,8 @@ func (q Query) SelectIndexed(selector func(int, interface{}) interface{}) Query } // SelectIndexedT is the typed version of SelectIndexed. -// -// NOTE: SelectIndexed method has better performance than SelectIndexedT -// -// selectorFn is of a type "func(int,TSource)TResult" +// - selectorFn is of type "func(int,TSource)TResult" +// NOTE: SelectIndexed has better performance than SelectIndexedT. func (q Query) SelectIndexedT(selectorFn interface{}) Query { selectGenericFunc, err := newGenericFunc( "SelectIndexedT", "selectorFn", selectorFn, diff --git a/selectmany.go b/selectmany.go index 874f4c8..0f08623 100644 --- a/selectmany.go +++ b/selectmany.go @@ -34,9 +34,9 @@ func (q Query) SelectMany(selector func(interface{}) Query) Query { // SelectManyT is the typed version of SelectMany. // -// NOTE: SelectMany method has better performance than SelectManyT +// - selectorFn is of type "func(TSource)Query" // -// selectorFn is of a type "func(TSource)Query" +// NOTE: SelectMany has better performance than SelectManyT. func (q Query) SelectManyT(selectorFn interface{}) Query { selectManyGenericFunc, err := newGenericFunc( @@ -54,14 +54,15 @@ func (q Query) SelectManyT(selectorFn interface{}) Query { } -// SelectManyIndexed projects each element of a collection to a Query, iterates and -// flattens the resulting collection into one collection. +// 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. +// 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 (q Query) SelectManyIndexed(selector func(int, interface{}) Query) Query { return Query{ Iterate: func() Iterator { @@ -96,9 +97,9 @@ func (q Query) SelectManyIndexed(selector func(int, interface{}) Query) Query { // SelectManyIndexedT is the typed version of SelectManyIndexed. // -// NOTE: SelectManyIndexed method has better performance than SelectManyIndexedT +// - selectorFn is of type "func(int,TSource)Query" // -// selectorFn is of a type "func(int,TSource)Query" +// NOTE: SelectManyIndexed has better performance than SelectManyIndexedT. func (q Query) SelectManyIndexedT(selectorFn interface{}) Query { selectManyIndexedGenericFunc, err := newGenericFunc( @@ -117,8 +118,8 @@ func (q Query) SelectManyIndexedT(selectorFn interface{}) Query { } // SelectManyBy projects each element of a collection to a Query, iterates and -// flattens the resulting collection into one collection, and invokes -// a result selector function on each element therein. +// flattens the resulting collection into one collection, and invokes a result +// selector function on each element therein. func (q Query) SelectManyBy( selector func(interface{}) Query, resultSelector func(interface{}, interface{}) interface{}, @@ -156,11 +157,10 @@ func (q Query) SelectManyBy( // SelectManyByT is the typed version of SelectManyBy. // -// NOTE: SelectManyBy method has better performance than SelectManyByT -// -// selectorFn is of a type "func(TSource)Query" +// - selectorFn is of type "func(TSource)Query" +// - resultSelectorFn is of type "func(TSource,TCollection)TResult" // -// resultSelectorFn is of a type "func(TSource,TCollection)TResult" +// NOTE: SelectManyBy has better performance than SelectManyByT. func (q Query) SelectManyByT(selectorFn interface{}, resultSelectorFn interface{}) Query { selectorGenericFunc, err := newGenericFunc( @@ -190,11 +190,10 @@ func (q Query) SelectManyByT(selectorFn interface{}, resultSelectorFn interface{ return q.SelectManyBy(selectorFunc, resultSelectorFunc) } -// SelectManyByIndexed projects each element of a collection to a Query, iterates and -// flattens the resulting collection into one collection, and invokes -// a result selector function on each element therein. -// The index of each source element is used in the intermediate projected form -// of that element. +// SelectManyByIndexed projects each element of a collection to a Query, +// iterates and flattens the resulting collection into one collection, and +// invokes a result selector function on each element therein. The index of each +// source element is used in the intermediate projected form of that element. func (q Query) SelectManyByIndexed(selector func(int, interface{}) Query, resultSelector func(interface{}, interface{}) interface{}) Query { @@ -232,11 +231,11 @@ func (q Query) SelectManyByIndexed(selector func(int, interface{}) Query, // SelectManyByIndexedT is the typed version of SelectManyByIndexed. // -// NOTE: SelectManyByIndexed method has better performance than SelectManyByIndexedT -// -// selectorFn is of a type "func(int,TSource)Query" +// - selectorFn is of type "func(int,TSource)Query" +// - resultSelectorFn is of type "func(TSource,TCollection)TResult" // -// resultSelectorFn is of a type "func(TSource,TCollection)TResult" +// NOTE: SelectManyByIndexed has better performance than +// SelectManyByIndexedT. func (q Query) SelectManyByIndexedT(selectorFn interface{}, resultSelectorFn interface{}) Query { selectorGenericFunc, err := newGenericFunc( "SelectManyByIndexedT", "selectorFn", selectorFn, diff --git a/skip.go b/skip.go index 989559a..0fb05d3 100644 --- a/skip.go +++ b/skip.go @@ -1,7 +1,7 @@ package linq -// Skip bypasses a specified number of elements in a collection -// and then returns the remaining elements. +// Skip bypasses a specified number of elements in a collection and then returns +// the remaining elements. func (q Query) Skip(count int) Query { return Query{ Iterate: func() Iterator { @@ -22,13 +22,13 @@ func (q Query) Skip(count int) Query { } } -// SkipWhile bypasses elements in a collection as long as a specified condition is true -// and then returns the remaining elements. +// 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. +// 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 Query) SkipWhile(predicate func(interface{}) bool) Query { return Query{ Iterate: func() Iterator { @@ -56,9 +56,9 @@ func (q Query) SkipWhile(predicate func(interface{}) bool) Query { // SkipWhileT is the typed version of SkipWhile. // -// NOTE: SkipWhile method has better performance than SkipWhileT +// - predicateFn is of type "func(TSource)bool" // -// predicateFn is of a type "func(TSource)bool" +// NOTE: SkipWhile has better performance than SkipWhileT. func (q Query) SkipWhileT(predicateFn interface{}) Query { predicateGenericFunc, err := newGenericFunc( @@ -76,14 +76,14 @@ func (q Query) SkipWhileT(predicateFn interface{}) Query { return q.SkipWhile(predicateFunc) } -// 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. +// 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. +// 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 Query) SkipWhileIndexed(predicate func(int, interface{}) bool) Query { return Query{ Iterate: func() Iterator { @@ -114,9 +114,9 @@ func (q Query) SkipWhileIndexed(predicate func(int, interface{}) bool) Query { // SkipWhileIndexedT is the typed version of SkipWhileIndexed. // -// NOTE: SkipWhileIndexed method has better performance than SkipWhileIndexedT +// - predicateFn is of type "func(int,TSource)bool" // -// predicateFn is of a type "func(int,TSource)bool" +// NOTE: SkipWhileIndexed has better performance than SkipWhileIndexedT. func (q Query) SkipWhileIndexedT(predicateFn interface{}) Query { predicateGenericFunc, err := newGenericFunc( "SkipWhileIndexedT", "predicateFn", predicateFn, diff --git a/take.go b/take.go index c785023..c2707e5 100644 --- a/take.go +++ b/take.go @@ -1,6 +1,7 @@ package linq -// Take returns a specified number of contiguous elements from the start of a collection. +// Take returns a specified number of contiguous elements from the start of a +// collection. func (q Query) Take(count int) Query { return Query{ Iterate: func() Iterator { @@ -19,8 +20,8 @@ func (q Query) Take(count int) Query { } } -// TakeWhile returns elements from a collection as long as a specified condition is true, -// and then skips the remaining elements. +// 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 { return Query{ Iterate: func() Iterator { @@ -51,9 +52,9 @@ func (q Query) TakeWhile(predicate func(interface{}) bool) Query { // TakeWhileT is the typed version of TakeWhile. // -// NOTE: TakeWhile method has better performance than TakeWhileT +// - predicateFn is of type "func(TSource)bool" // -// predicateFn is of a type "func(TSource)bool" +// NOTE: TakeWhile has better performance than TakeWhileT. func (q Query) TakeWhileT(predicateFn interface{}) Query { predicateGenericFunc, err := newGenericFunc( @@ -71,10 +72,11 @@ func (q Query) TakeWhileT(predicateFn interface{}) Query { return q.TakeWhile(predicateFunc) } -// 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. +// 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 Query) TakeWhileIndexed(predicate func(int, interface{}) bool) Query { return Query{ Iterate: func() Iterator { @@ -107,9 +109,9 @@ func (q Query) TakeWhileIndexed(predicate func(int, interface{}) bool) Query { // TakeWhileIndexedT is the typed version of TakeWhileIndexed. // -// NOTE: TakeWhileIndexed method has better performance than TakeWhileIndexedT +// - predicateFn is of type "func(int,TSource)bool" // -// predicateFn is of a type "func(int,TSource)bool" +// NOTE: TakeWhileIndexed has better performance than TakeWhileIndexedT. func (q Query) TakeWhileIndexedT(predicateFn interface{}) Query { whereFunc, err := newGenericFunc( "TakeWhileIndexedT", "predicateFn", predicateFn, diff --git a/union.go b/union.go index 9ca4b65..717b83b 100644 --- a/union.go +++ b/union.go @@ -2,10 +2,9 @@ package linq // 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. +// 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 Query) Union(q2 Query) Query { return Query{ Iterate: func() Iterator { diff --git a/where.go b/where.go index fd33367..6fd4302 100644 --- a/where.go +++ b/where.go @@ -21,9 +21,9 @@ func (q Query) Where(predicate func(interface{}) bool) Query { // WhereT is the typed version of Where. // -// NOTE: Where method has better performance than WhereT +// - predicateFn is of type "func(TSource)bool" // -// predicateFn is of a type "func(TSource)bool" +// NOTE: Where has better performance than WhereT. func (q Query) WhereT(predicateFn interface{}) Query { predicateGenericFunc, err := newGenericFunc( @@ -41,11 +41,11 @@ func (q Query) WhereT(predicateFn interface{}) Query { return q.Where(predicateFunc) } -// WhereIndexed filters a collection of values based on a predicate. -// Each element's index is used in the logic of the predicate function. +// 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. +// 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 Query) WhereIndexed(predicate func(int, interface{}) bool) Query { return Query{ Iterate: func() Iterator { @@ -69,9 +69,9 @@ func (q Query) WhereIndexed(predicate func(int, interface{}) bool) Query { // WhereIndexedT is the typed version of WhereIndexed. // -// NOTE: WhereIndexed method has better performance than WhereIndexedT +// - predicateFn is of type "func(int,TSource)bool" // -// predicateFn is of a type "func(int,TSource)bool" +// NOTE: WhereIndexed has better performance than WhereIndexedT. func (q Query) WhereIndexedT(predicateFn interface{}) Query { predicateGenericFunc, err := newGenericFunc( "WhereIndexedT", "predicateFn", predicateFn, diff --git a/zip.go b/zip.go index c7d6972..8cf18ef 100644 --- a/zip.go +++ b/zip.go @@ -1,15 +1,15 @@ package linq -// Zip applies a specified function to the corresponding elements -// of two collections, producing a collection of the results. +// Zip applies a specified function to the corresponding elements of two +// collections, producing a collection of the results. // // The method steps through the two input collections, applying function -// resultSelector to corresponding elements of the two collections. -// The method returns a collection of the values that are returned by resultSelector. -// If the input collections do not have the same number of elements, -// the method combines elements until it reaches the end of one of the collections. -// For example, if one collection has three elements and the other one has four, -// the result collection has only three elements. +// resultSelector to corresponding elements of the two collections. The method +// returns a collection of the values that are returned by resultSelector. If +// the input collections do not have the same number of elements, the method +// combines elements until it reaches the end of one of the collections. For +// example, if one collection has three elements and the other one has four, the +// result collection has only three elements. func (q Query) Zip( q2 Query, resultSelector func(interface{}, interface{}) interface{}, @@ -36,9 +36,9 @@ func (q Query) Zip( // ZipT is the typed version of Zip. // -// NOTE: Zip method has better performance than ZipT +// - resultSelectorFn is of type "func(TFirst,TSecond)TResult" // -// resultSelectorFn is of a type "func(TFirst,TSecond)TResult" +// NOTE: Zip has better performance than ZipT. func (q Query) ZipT(q2 Query, resultSelectorFn interface{}) Query { resultSelectorFunc, ok := resultSelectorFn.(func(interface{}, interface{}) interface{}) if !ok { From 2edd7ebf49b8f12560c8ba31239aa097ef01ffc7 Mon Sep 17 00:00:00 2001 From: Cleiton Marques Date: Tue, 3 Jan 2017 01:46:02 -0800 Subject: [PATCH 7/9] Fixed ZipT to reuse regular method (#51) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Applied the same generic functions pattern to reuse regular method instead a “reimplementation”. --- zip.go | 38 ++++++++++---------------------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/zip.go b/zip.go index 8cf18ef..252c045 100644 --- a/zip.go +++ b/zip.go @@ -40,35 +40,17 @@ func (q Query) Zip( // // NOTE: Zip has better performance than ZipT. func (q Query) ZipT(q2 Query, resultSelectorFn interface{}) Query { - resultSelectorFunc, ok := resultSelectorFn.(func(interface{}, interface{}) interface{}) - if !ok { - resultSelectorGenericFunc, err := newGenericFunc( - "ZipT", "resultSelectorFn", resultSelectorFn, - simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))), - ) - if err != nil { - panic(err) - } - - resultSelectorFunc = func(item1 interface{}, item2 interface{}) interface{} { - return resultSelectorGenericFunc.Call(item1, item2) - } + resultSelectorGenericFunc, err := newGenericFunc( + "ZipT", "resultSelectorFn", resultSelectorFn, + simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) } - return Query{ - Iterate: func() Iterator { - next1 := q.Iterate() - next2 := q2.Iterate() - - return func() (item interface{}, ok bool) { - item1, ok1 := next1() - item2, ok2 := next2() - - if ok1 && ok2 { - return resultSelectorFunc(item1, item2), true - } - return nil, false - } - }, + resultSelectorFunc := func(item1 interface{}, item2 interface{}) interface{} { + return resultSelectorGenericFunc.Call(item1, item2) } + + return q.Zip(q2, resultSelectorFunc) } From 90aea9a8a940a8492469d7a7460b16ede743e382 Mon Sep 17 00:00:00 2001 From: Ahmet Alp Balkan Date: Sun, 1 Jan 2017 16:12:07 -0800 Subject: [PATCH 8/9] Update readme generics section + cosmetics Just made some changes in the readme to make it easier to follow and rewrote the generics section to make it simpler. Signed-off-by: Ahmet Alp Balkan --- README.md | 95 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 66 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index dc1a392..729cb73 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,21 @@ # go-linq [![GoDoc](https://godoc.org/github.com/ahmetalpbalkan/go-linq?status.svg)](https://godoc.org/github.com/ahmetalpbalkan/go-linq) [![Build Status](https://travis-ci.org/ahmetalpbalkan/go-linq.svg?branch=master)](https://travis-ci.org/ahmetalpbalkan/go-linq) [![Coverage Status](https://coveralls.io/repos/github/ahmetalpbalkan/go-linq/badge.svg?branch=master)](https://coveralls.io/github/ahmetalpbalkan/go-linq?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/ahmetalpbalkan/go-linq)](https://goreportcard.com/report/github.com/ahmetalpbalkan/go-linq) A powerful language integrated query (LINQ) library for Go. -* Written in vanilla Go! -* Safe for concurrent use +* Written in vanilla Go, no dependencies! * Complete lazy evaluation with iterator pattern +* Safe for concurrent use +* Supports generic functions to make your code cleaner and free of type assertions * Supports arrays, slices, maps, strings, channels and custom collections -(collection needs to implement `Iterable` interface and element - `Comparable` -interface) ## Installation $ go get github.com/ahmetalpbalkan/go-linq +We recommend using a dependency manager (e.g. [govendor][govendor] or +[godep][godep]) to maintain a local copy of this package in your projcet. + +[govendor]: https://github.com/kardianos/govendor +[godep]: https://github.com/tools/godep/ + > :warning: :warning: `go-linq` has recently introduced _breaking API changes_ > with v2.0.0. See [release notes](#release-notes) for details. v2.0.0 comes with > a refined interface, dramatically increased performance and memory efficiency, @@ -26,16 +31,20 @@ Usage is as easy as chaining methods like: `From(slice)` `.Where(predicate)` `.Select(selector)` `.Union(data)` -**Example: Find all owners of cars manufactured from 2015** +**Example 1: Find all owners of cars manufactured after 2015** + ```go import . "github.com/ahmetalpbalkan/go-linq" type Car struct { - id, year int + year int owner, model string } -owners := []string{} +... + + +var owners []string From(cars).Where(func(c interface{}) bool { return c.(Car).year >= 2015 @@ -44,7 +53,21 @@ From(cars).Where(func(c interface{}) bool { }).ToSlice(&owners) ``` -**Example: Find the author who has written the most books** +Or, you can use generic functions, like `WhereT` and `SelectT` to simplify your code +(at a performance penalty): + +```go +var owners []string + +From(cars).WhereT(func(c Car) bool { + return c.year >= 2015 +}).SelectT(func(c Car) string { + return c.owner +}).ToSlice(&owners) +``` + +**Example 2: Find the author who has written the most books** + ```go import . "github.com/ahmetalpbalkan/go-linq" @@ -71,7 +94,8 @@ author := From(books).SelectMany( // make a flat array of authors }).First() // take the first author ``` -**Example: Implement a custom method that leaves only values greater than the specified threshold** +**Example 3: Implement a custom method that leaves only values greater than the specified threshold** + ```go type MyQuery Query @@ -95,53 +119,66 @@ func (q MyQuery) GreaterThan(threshold int) Query { result := MyQuery(Range(1,10)).GreaterThan(5).Results() ``` -## Generic functions -All we know that Go doesn't implement generics. On the other hand, Go offer a nice collection of reflection functions -that can be used to emulate some generic behaviors. -Although there are some [performance loss](../master/benchmark_test.go) when using reflection to parse the functions -and parameters types, the use of generic functions will bring you more code readability removing the type casting. -All the generic functions have a T suffix (eg. WhereT, SelectT, etc.). +## Generic Functions + +Although Go doesn't implement generics, with some reflection tricks, you can use go-linq without +typing `interface{}`s and type assertions. This will introduce a performance penalty (5x-10x slower) +but will yield in a cleaner and more readable code. + +Methods with `T` suffix (such as `WhereT`) accept functions with generic types. So instead of + + .Select(func(v interface{}) interface{} {...}) + +you can type: + + .SelectT(func(v YourType) YourOtherType {...}) + +This will make your code free of `interface{}` and type assertions. + +**Example 4: "MapReduce" in a slice of string sentences to list the top 5 most used words using generic functions** -**Example: Implement "MapReduce" in a slice of string sentences to list the top 5 most used words using *generic functions*** ```go var results []string + From(sentences). - //Split the sentences in a slice of words + // split sentences to words SelectManyT(func(sentence string) Query { return From(strings.Split(sentence, " ")) }). - //Grouping the words - GroupByT( + // group the words + GroupByT( func(word string) string { return word }, func(word string) string { return word }, ). - //Ordering by word counts + // order by count OrderByDescendingT(func(wordGroup Group) int { return len(wordGroup.Group) }). - //Then order by word + // order by the word ThenByT(func(wordGroup Group) string { return wordGroup.Key.(string) }). - //Take the top 5 - Take(5). - //Project the words using the index as rank + Take(5). // take the top 5 + // project the words using the index as rank SelectIndexedT(func(index int, wordGroup Group) string { return fmt.Sprintf("Rank: #%d, Word: %s, Counts: %d", index+1, wordGroup.Key, len(wordGroup.Group)) }). ToSlice(&results) ``` -**More examples** can be found in [documentation](https://godoc.org/github.com/ahmetalpbalkan/go-linq). +**More examples** can be found in the [documentation](https://godoc.org/github.com/ahmetalpbalkan/go-linq). ## Release Notes ~~~ v3.0.0 (2016-10-19) -* Generic methods - - Added generic methods support to all functions - - Totally backward compatible with v2.0.0 functions -* AggregateWithSeedBy() +* Breaking: ToSlice() now overwrites existing slice starting + from index 0 and grows/reslices it as needed. +* Generic methods support! + - You can now avoid type assertions and interface{}s + - Keeps it backwards-compatible with v2.0.0. +* Added AggregateWithSeedBy() + v2.0.0 (2016-09-02) * IMPORTANT: This release is a BREAKING CHANGE. The old version is archived at the 'archive/0.9' branch or the 0.9 tags. From fc4279c9eead1a736f56eb361be8d61d34502d8b Mon Sep 17 00:00:00 2001 From: Ahmet Alp Balkan Date: Thu, 5 Jan 2017 12:06:46 -0800 Subject: [PATCH 9/9] Prepare Changelog for v3 release Signed-off-by: Ahmet Alp Balkan --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 729cb73..7551414 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ A powerful language integrated query (LINQ) library for Go. $ go get github.com/ahmetalpbalkan/go-linq We recommend using a dependency manager (e.g. [govendor][govendor] or -[godep][godep]) to maintain a local copy of this package in your projcet. +[godep][godep]) to maintain a local copy of this package in your project. [govendor]: https://github.com/kardianos/govendor [godep]: https://github.com/tools/godep/ @@ -171,12 +171,14 @@ From(sentences). ## Release Notes ~~~ -v3.0.0 (2016-10-19) -* Breaking: ToSlice() now overwrites existing slice starting +v3.0.0 (2017-01-05) +* Breaking change: ToSlice() now overwrites existing slice starting from index 0 and grows/reslices it as needed. -* Generic methods support! +* Generic methods support (thanks @cleitonmarx!) + - Accepting parametrized functions was originally proposed in #26 - You can now avoid type assertions and interface{}s - - Keeps it backwards-compatible with v2.0.0. + - Functions with generic methods are named as "MethodNameT" and + signature for the existing LINQ methods are unchanged. * Added AggregateWithSeedBy() v2.0.0 (2016-09-02)