Skip to content

Commit

Permalink
Merge pull request #53 from ahmetalpbalkan/v3-changelogfix-
Browse files Browse the repository at this point in the history
Release v3.0.0
  • Loading branch information
ahmetb authored Jan 5, 2017
2 parents ba42dda + fc4279c commit 363486a
Show file tree
Hide file tree
Showing 42 changed files with 4,369 additions and 303 deletions.
100 changes: 90 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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
Expand All @@ -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"

Expand All @@ -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

Expand All @@ -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
Expand Down
134 changes: 121 additions & 13 deletions aggregate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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().
Expand All @@ -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)
}
76 changes: 76 additions & 0 deletions aggregate_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package linq

import "testing"
import "strings"

func TestAggregate(t *testing.T) {
tests := []struct {
Expand All @@ -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"
Expand All @@ -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
},
)
})
}
Loading

0 comments on commit 363486a

Please sign in to comment.