diff --git a/README.md b/README.md index 475464c..7551414 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 project. + +[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 @@ -96,10 +120,66 @@ func (q MyQuery) GreaterThan(threshold int) Query { result := MyQuery(Range(1,10)).GreaterThan(5).Results() ``` -**More examples** can be found in [documentation](https://godoc.org/github.com/ahmetalpbalkan/go-linq). +## 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** + +```go +var results []string + +From(sentences). + // split sentences to words + SelectManyT(func(sentence string) Query { + return From(strings.Split(sentence, " ")) + }). + // group the words + GroupByT( + func(word string) string { return word }, + func(word string) string { return word }, + ). + // order by count + OrderByDescendingT(func(wordGroup Group) int { + return len(wordGroup.Group) + }). + // order by the word + ThenByT(func(wordGroup Group) string { + return wordGroup.Key.(string) + }). + 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 the [documentation](https://godoc.org/github.com/ahmetalpbalkan/go-linq). ## Release Notes ~~~ +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 (thanks @cleitonmarx!) + - Accepting parametrized functions was originally proposed in #26 + - You can now avoid type assertions and interface{}s + - 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) * IMPORTANT: This release is a BREAKING CHANGE. The old version diff --git a/aggregate.go b/aggregate.go index a8658c5..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( @@ -26,14 +26,36 @@ func (q Query) Aggregate( return result } -// AggregateWithSeed applies an accumulator function over a sequence. -// The specified seed value is used as the initial accumulator value. +// AggregateT is the typed version of Aggregate. +// +// - 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( + "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. // -// 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(). @@ -51,3 +73,89 @@ func (q Query) AggregateWithSeed( return result } + +// AggregateWithSeedT is the typed version of AggregateWithSeed. +// +// - f is of 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, + 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. +// +// 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) +} + +// AggregateWithSeedByT is the typed version of AggregateWithSeedBy. +// +// - f is of type "func(TAccumulate, TSource) TAccumulate" +// - 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, + 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 816b6ab..0fe1a51 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 { @@ -25,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" @@ -41,3 +53,67 @@ func TestAggregateWithSeed(t *testing.T) { t.Errorf("From(%v).AggregateWithSeed()=%v expected %v", input, r, want) } } + +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" + + 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) + } +} + +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/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/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 7a32c31..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, @@ -73,3 +73,26 @@ func (q Query) DistinctBy(selector func(interface{}) interface{}) Query { }, } } + +// DistinctByT is the typed version of DistinctBy. +// +// - 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 { + 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/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..1bb837f 100644 --- a/example_test.go +++ b/example_test.go @@ -1,6 +1,10 @@ package linq -import "fmt" +import ( + "fmt" + "strings" + "time" +) func ExampleKeyValue() { m := make(map[int]bool) @@ -18,13 +22,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 +94,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 +724,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 +735,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 +750,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 +761,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 +776,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,83 +793,1704 @@ func ExampleQuery_OrderBy() { // [10 8 6 4 2 9 7 5 3 1] } -func ExampleQuery_SelectMany() { - input := [][]int{{1, 2, 3}, {4, 5, 6, 7}} +// 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).SelectMany(func(i interface{}) Query { - return From(i) - }) + q := From(input).Prepend(1) + first := q.First() - fmt.Println(q.Results()) + fmt.Println(first) // Output: - // [1 2 3 4 5 6 7] + // 1 } -func ExampleQuery_Union() { - q := Range(1, 10).Union(Range(6, 10)) +// The following code example demonstrates how to use Reverse +// to reverse the order of elements in a string. +func ExampleQuery_Reverse() { + input := "apple" - fmt.Println(q.Results()) + var output []rune + From(input). + Reverse(). + ToSlice(&output) + + fmt.Println(string(output)) // Output: - // [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15] + // elppa } -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} - }) +// The following code example demonstrates how to use Select +// to project over a slice of values. +func ExampleQuery_Select() { + squares := []int{} - fmt.Println(q.Results()) + Range(1, 10). + Select( + func(x interface{}) interface{} { return x.(int) * x.(int) }, + ). + ToSlice(&squares) + fmt.Println(squares) // Output: - // [[1 one] [2 two] [3 three]] + // [1 4 9 16 25 36 49 64 81 100] } -func ExampleQuery_ToChannel() { - c := make(chan interface{}) +func ExampleQuery_SelectMany() { + input := [][]int{{1, 2, 3}, {4, 5, 6, 7}} - go func() { - Repeat(10, 3).ToChannel(c) - }() + q := From(input). + SelectMany( + func(i interface{}) Query { return From(i) }, + ) - for i := range c { - fmt.Println(i) - } + fmt.Println(q.Results()) // Output: - // 10 - // 10 - // 10 + // [1 2 3 4 5 6 7] } -func ExampleQuery_ToMapBy() { - input := [][]interface{}{{1, true}} +// 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"} - 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] - }) + result := []string{} + From(fruits). + SelectIndexed( + func(index int, fruit interface{}) interface{} { return fruit.(string)[:index] }, + ). + ToSlice(&result) fmt.Println(result) - // Output: - // map[1:true] + // [ b ma ora pass grape] + } -func ExampleQuery_ToSlice() { - result := []int{} - Range(1, 10).ToSlice(&result) +// 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 + } + + 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). + 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: + // 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{}) + + go func() { + Repeat(10, 3).ToChannel(c) + }() + + for i := range c { + fmt.Println(i) + } + + // Output: + // 10 + // 10 + // 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] + }, + ) + + fmt.Println(result) + + // Output: + // map[1:true] +} + +// The following code example demonstrates how to use ToSlice to populate a slice. +func ExampleQuery_ToSlice() { + var result []int + Range(1, 10).ToSlice(&result) fmt.Println(result) // 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]] +} + +// 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/except.go b/except.go index c34a6b4..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{ @@ -57,3 +55,24 @@ func (q Query) ExceptBy( }, } } + +// ExceptByT is the typed version of ExceptBy. +// +// - selectorFn is of 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, + 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/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 new file mode 100644 index 0000000..77cba2d --- /dev/null +++ b/genericfunc.go @@ -0,0 +1,138 @@ +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..6087374 --- /dev/null +++ b/genericfunc_test.go @@ -0,0 +1,130 @@ +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/groupby.go b/groupby.go index 2affbf8..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{}, @@ -46,3 +46,38 @@ func (q Query) GroupBy( }, } } + +// GroupByT is the typed version of GroupBy. +// +// - keySelectorFn is of type "func(TSource) TKey" +// - elementSelectorFn is of 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, + 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/groupjoin.go b/groupjoin.go index 7a3b56b..276923b 100644 --- a/groupjoin.go +++ b/groupjoin.go @@ -1,19 +1,23 @@ package linq +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{}, @@ -48,3 +52,55 @@ func (q Query) GroupJoin( }, } } + +// GroupJoinT is the typed version of GroupJoin. +// +// - 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" +// +// 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, + 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/intersect.go b/intersect.go index 6499bb8..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( @@ -65,3 +65,24 @@ func (q Query) IntersectBy( }, } } + +// IntersectByT is the typed version of IntersectBy. +// +// - selectorFn is of 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, + 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/join.go b/join.go index 95cb835..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{}, @@ -54,3 +54,55 @@ func (q Query) Join( }, } } + +// JoinT is the typed version of Join. +// +// - outerKeySelectorFn is of type "func(TOuter) TKey" +// - innerKeySelectorFn is of type "func(TInner) TKey" +// - resultSelectorFn is of type "func(TOuter,TInner) TResult" +// +// NOTE: Join has better performance than JoinT. +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/orderby.go b/orderby.go index b22f0e0..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{ @@ -43,6 +43,27 @@ func (q Query) OrderBy( } } +// OrderByT is the typed version of OrderBy. +// +// - selectorFn is of type "func(TSource) TKey" +// +// NOTE: OrderBy has better performance than OrderByT. +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,9 +91,28 @@ func (q Query) OrderByDescending( } } -// 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. +// OrderByDescendingT is the typed version of OrderByDescending. +// - 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, + 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. func (oq OrderedQuery) ThenBy( selector func(interface{}) interface{}) OrderedQuery { return OrderedQuery{ @@ -98,9 +138,28 @@ func (oq OrderedQuery) ThenBy( } } -// 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. +// ThenByT is the typed version of ThenBy. +// - 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, + 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. func (oq OrderedQuery) ThenByDescending( selector func(interface{}) interface{}) OrderedQuery { return OrderedQuery{ @@ -126,10 +185,32 @@ func (oq OrderedQuery) ThenByDescending( } } -// 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. +// ThenByDescendingT is the typed version of ThenByDescending. +// - 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 { + 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, 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 { @@ -150,6 +231,25 @@ func (q Query) Sort(less func(i, j interface{}) bool) Query { } } +// SortT is the typed version of Sort. +// - 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, + 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/result.go b/result.go index ef42dc4..f75ed8f 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. +// +// - predicateFn is of type "func(TSource) bool" +// +// NOTE: All has better performance than AllT. +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. +// +// - predicateFn is of type "func(TSource) bool" +// +// NOTE: AnyWith has better performance than AnyWithT. +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() @@ -104,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() @@ -118,14 +161,36 @@ func (q Query) CountWith(predicate func(interface{}) bool) (r int) { return } +// CountWithT is the typed version of CountWith. +// +// - predicateFn is of type "func(TSource) bool" +// +// NOTE: CountWith has better performance than CountWithT. +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()() 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() @@ -138,6 +203,28 @@ func (q Query) FirstWith(predicate func(interface{}) bool) interface{} { return nil } +// FirstWithT is the typed version of FirstWith. +// +// - predicateFn is of type "func(TSource) bool" +// +// NOTE: FirstWith has better performance than FirstWithT. +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() @@ -149,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() @@ -163,6 +250,28 @@ func (q Query) LastWith(predicate func(interface{}) bool) (r interface{}) { return } +// LastWithT is the typed version of LastWith. +// +// - predicateFn is of type "func(TSource) bool" +// +// NOTE: LastWith has better performance than LastWithT. +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() @@ -230,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() @@ -247,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 @@ -267,10 +376,31 @@ func (q Query) SingleWith(predicate func(interface{}) bool) (r interface{}) { return } +// SingleWithT is the typed version of SingleWith. +// +// - predicateFn is of type "func(TSource) bool" +// +// NOTE: SingleWith has better performance than SingleWithT. +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. -// 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() @@ -290,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() @@ -330,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() @@ -343,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, @@ -357,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{}, @@ -381,17 +512,77 @@ func (q Query) ToMapBy( res.Elem().Set(m) } -// 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. -func (q Query) ToSlice(result interface{}) { - res := reflect.ValueOf(result) +// ToMapByT is the typed version of ToMapBy. +// +// - keySelectorFn is of type "func(TSource)TKey" +// - valueSelectorFn is of 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, + 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 saves the results in the slice pointed +// 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 +// v will point to it. +func (q Query) ToSlice(v interface{}) { + res := reflect.ValueOf(v) slice := reflect.Indirect(res) - next := q.Iterate() + 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() { - slice = reflect.Append(slice, reflect.ValueOf(item)) + if index >= cap { + slice, cap = grow(slice) + } + slice.Index(index).Set(reflect.ValueOf(item)) + index++ } - res.Elem().Set(slice) + // reslice the len(res)==cap(res) actual res size + res.Elem().Set(slice.Slice(0, index)) +} + +// 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 cap == 0 { + cap = 1 + } else { + cap *= 2 + } + newSlice := reflect.MakeSlice(s.Type(), cap, cap) + reflect.Copy(newSlice, s) + return newSlice, cap } diff --git a/result_test.go b/result_test.go index b771e15..7f8d786 100644 --- a/result_test.go +++ b/result_test.go @@ -4,6 +4,7 @@ import ( "math" "reflect" "testing" + "unsafe" ) func TestAll(t *testing.T) { @@ -25,6 +26,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 +69,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 +151,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 +191,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 +231,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 +335,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,13 +444,109 @@ 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} + 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 (cap=0) + { + []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) + } } } 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 e607229..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{ @@ -31,24 +30,44 @@ func (q Query) Select(selector func(interface{}) interface{}) Query { } } -// 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. +// SelectT is the typed version of Select. +// - selectorFn is of type "func(TSource)TResult" +// NOTE: Select has better performance than SelectT. +func (q Query) SelectT(selectorFn interface{}) Query { + + 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. // -// 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{ @@ -69,3 +88,22 @@ func (q Query) SelectIndexed(selector func(int, interface{}) interface{}) Query }, } } + +// SelectIndexedT is the typed version of SelectIndexed. +// - selectorFn is of type "func(int,TSource)TResult" +// NOTE: SelectIndexed has better performance than SelectIndexedT. +func (q Query) SelectIndexedT(selectorFn interface{}) Query { + selectGenericFunc, err := newGenericFunc( + "SelectIndexedT", "selectorFn", selectorFn, + simpleParamValidator(newElemTypeSlice(new(int), new(genericType)), newElemTypeSlice(new(genericType))), + ) + if err != nil { + panic(err) + } + + selectorFunc := func(index int, item interface{}) interface{} { + return selectGenericFunc.Call(index, item) + } + + return q.SelectIndexed(selectorFunc) +} 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 25ea8c3..0f08623 100644 --- a/selectmany.go +++ b/selectmany.go @@ -32,14 +32,37 @@ func (q Query) SelectMany(selector func(interface{}) Query) Query { } } -// SelectManyIndexed projects each element of a collection to a Query, iterates and -// flattens the resulting collection into one collection. +// SelectManyT is the typed version of SelectMany. +// +// - selectorFn is of type "func(TSource)Query" // -// 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. +// NOTE: SelectMany has better performance than SelectManyT. +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. +// +// 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 { @@ -72,9 +95,31 @@ func (q Query) SelectManyIndexed(selector func(int, interface{}) Query) Query { } } +// SelectManyIndexedT is the typed version of SelectManyIndexed. +// +// - selectorFn is of type "func(int,TSource)Query" +// +// NOTE: SelectManyIndexed has better performance than SelectManyIndexedT. +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. +// 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{}, @@ -103,18 +148,52 @@ func (q Query) SelectManyBy( } } - item = resultSelector(outer, item) + item = resultSelector(item, outer) return } }, } } -// 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. +// SelectManyByT is the typed version of SelectManyBy. +// +// - selectorFn is of type "func(TSource)Query" +// - resultSelectorFn is of type "func(TSource,TCollection)TResult" +// +// NOTE: SelectManyBy has better performance than SelectManyByT. +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. 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 { @@ -143,9 +222,44 @@ func (q Query) SelectManyByIndexed(selector func(int, interface{}) Query, } } - item = resultSelector(outer, item) + item = resultSelector(item, outer) return } }, } } + +// SelectManyByIndexedT is the typed version of SelectManyByIndexed. +// +// - selectorFn is of type "func(int,TSource)Query" +// - resultSelectorFn is of 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, + 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 c1218a9..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{} @@ -60,12 +72,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_"}}, } @@ -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{} @@ -89,7 +116,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 +124,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_"}}, } @@ -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/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..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 { @@ -54,14 +54,36 @@ func (q Query) SkipWhile(predicate func(interface{}) bool) Query { } } -// SkipWhileIndexed bypasses elements in a collection as long as a specified condition -// is true and then returns the remaining elements. The element's index is used -// in the logic of the predicate function. +// SkipWhileT is the typed version of SkipWhile. // -// 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. +// - predicateFn is of type "func(TSource)bool" +// +// NOTE: SkipWhile has better performance than SkipWhileT. +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. +// +// 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 { @@ -89,3 +111,24 @@ func (q Query) SkipWhileIndexed(predicate func(int, interface{}) bool) Query { }, } } + +// SkipWhileIndexedT is the typed version of SkipWhileIndexed. +// +// - predicateFn is of 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, + 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/take.go b/take.go index d21fc96..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 { @@ -49,10 +50,33 @@ func (q Query) TakeWhile(predicate func(interface{}) bool) Query { } } -// TakeWhileIndexed returns elements from a collection as long as a specified condition -// is true. The element's index is used in the logic of the predicate function. -// The first argument of predicate represents the zero-based index of the element -// within collection. The second argument represents the element to test. +// TakeWhileT is the typed version of TakeWhile. +// +// - predicateFn is of type "func(TSource)bool" +// +// NOTE: TakeWhile has better performance than TakeWhileT. +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 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 { @@ -82,3 +106,24 @@ func (q Query) TakeWhileIndexed(predicate func(int, interface{}) bool) Query { }, } } + +// TakeWhileIndexedT is the typed version of TakeWhileIndexed. +// +// - predicateFn is of 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, + 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/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 26d0bda..6fd4302 100644 --- a/where.go +++ b/where.go @@ -19,11 +19,33 @@ func (q Query) Where(predicate func(interface{}) bool) Query { } } -// WhereIndexed filters a collection of values based on a predicate. -// Each element's index is used in the logic of the predicate function. +// WhereT is the typed version of Where. // -// The first argument represents the zero-based index of the element within collection. -// The second argument of predicate represents the element to test. +// - predicateFn is of type "func(TSource)bool" +// +// NOTE: Where has better performance than WhereT. +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. +// +// 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 { @@ -44,3 +66,24 @@ func (q Query) WhereIndexed(predicate func(int, interface{}) bool) Query { }, } } + +// WhereIndexedT is the typed version of WhereIndexed. +// +// - predicateFn is of 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, + 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/zip.go b/zip.go index 1314b9a..252c045 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{}, @@ -33,3 +33,24 @@ func (q Query) Zip( }, } } + +// ZipT is the typed version of Zip. +// +// - resultSelectorFn is of type "func(TFirst,TSecond)TResult" +// +// NOTE: Zip has better performance than ZipT. +func (q Query) ZipT(q2 Query, resultSelectorFn interface{}) Query { + 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 q.Zip(q2, resultSelectorFunc) +} 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 + }) + }) +}