├── go.mod ├── go.sum ├── .gitignore ├── count.go ├── aggregate.go ├── empty.go ├── count_test.go ├── range_test.go ├── average_test.go ├── repeat_test.go ├── foreach.go ├── empty_test.go ├── reverse_test.go ├── repeat.go ├── foreach_test.go ├── chunk_test.go ├── concat_test.go ├── aggregate_test.go ├── all.go ├── any.go ├── range.go ├── .github └── workflows │ └── test.yml ├── where_test.go ├── select_test.go ├── average.go ├── select.go ├── all_test.go ├── any_test.go ├── concat.go ├── sum_test.go ├── zip_test.go ├── reverse.go ├── where.go ├── first.go ├── sum.go ├── iter_bench_test.go ├── elementat_test.go ├── elementat.go ├── chunk.go ├── defaultifempty_test.go ├── last.go ├── selectmany_test.go ├── defaultifempty.go ├── orderbyfunc_test.go ├── zip.go ├── join_test.go ├── slice.go ├── orderbyfunc.go ├── contains.go ├── max_test.go ├── min_test.go ├── contains_test.go ├── last_test.go ├── LICENSE ├── single.go ├── first_test.go ├── linq.go ├── groupby_test.go ├── distinct.go ├── selectmany.go ├── groupjoin.go ├── skip_test.go ├── groupjoin_test.go ├── groupby.go ├── single_test.go ├── take_test.go ├── iter_test.go ├── union_test.go ├── union.go ├── except.go ├── except_test.go ├── intersect.go ├── distinct_test.go ├── intersect_test.go ├── generator.go ├── iter.go ├── map_test.go ├── map.go ├── join.go ├── min.go ├── max.go ├── util.go ├── take.go ├── skip.go ├── orderby_test.go ├── generator_test.go ├── orderby.go └── README.md /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/makiuchi-d/linq/v2 2 | 3 | go 1.18 4 | 5 | require golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA= 2 | golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | -------------------------------------------------------------------------------- /count.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | // Count returns a number that represents how many elements in the specified sequence 4 | func Count[T any, E IEnumerable[T]](src E) (int, error) { 5 | c := 0 6 | err := ForEach(src, func(v T) error { 7 | c++ 8 | return nil 9 | }) 10 | if err != nil { 11 | return 0, err 12 | } 13 | return c, nil 14 | } 15 | -------------------------------------------------------------------------------- /aggregate.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | // Aggregate applies an accumulator function over a sequence. 4 | func Aggregate[S, T any, E IEnumerable[S]](src E, seed T, fn func(acc T, v S) (T, error)) (T, error) { 5 | acc := seed 6 | err := ForEach(src, func(v S) (err error) { 7 | acc, err = fn(acc, v) 8 | return err 9 | }) 10 | return acc, err 11 | } 12 | -------------------------------------------------------------------------------- /empty.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | type emptyEnumerator[T any] struct{} 4 | 5 | // Empty returns an empty IEnumerable[T] that has the specified type argument. 6 | func Empty[T any]() Enumerable[T] { 7 | return func() Enumerator[T] { 8 | return emptyEnumerator[T]{} 9 | } 10 | } 11 | 12 | func (emptyEnumerator[T]) Next() (def T, _ error) { 13 | return def, EOC 14 | } 15 | -------------------------------------------------------------------------------- /count_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/makiuchi-d/linq/v2" 7 | ) 8 | 9 | func TestCount(t *testing.T) { 10 | src := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} 11 | 12 | r, err := linq.Count(linq.FromSlice(src)) 13 | if err != nil { 14 | t.Fatalf("%v", err) 15 | } 16 | exp := 9 17 | if r != exp { 18 | t.Fatalf("%v, wants %v", r, exp) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /range_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/makiuchi-d/linq/v2" 8 | ) 9 | 10 | func TestRange(t *testing.T) { 11 | e := linq.Range(3, 5) 12 | r, err := linq.ToSlice(e) 13 | if err != nil { 14 | t.Fatalf("%v", err) 15 | } 16 | 17 | exp := []int{3, 4, 5, 6, 7} 18 | if !reflect.DeepEqual(r, exp) { 19 | t.Fatalf("%v, wants %v", r, exp) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /average_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/makiuchi-d/linq/v2" 7 | ) 8 | 9 | func TestAverage(t *testing.T) { 10 | src := linq.FromSlice([]byte{100, 110, 120, 130}) 11 | r, err := linq.Average(src) 12 | if err != nil { 13 | t.Fatalf("%v", err) 14 | } 15 | exp := float64(100+110+120+130) / 4 16 | if r != exp { 17 | t.Fatalf("%v, wants %v", r, exp) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /repeat_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/makiuchi-d/linq/v2" 8 | ) 9 | 10 | func TestRepeat(t *testing.T) { 11 | r, err := linq.ToSlice(linq.Repeat(rune('A'), 5)) 12 | if err != nil { 13 | t.Fatalf("%v", err) 14 | } 15 | exp := []rune{'A', 'A', 'A', 'A', 'A'} 16 | if !reflect.DeepEqual(r, exp) { 17 | t.Fatalf("%v, wants %v", r, exp) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /foreach.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | // ForEach performs the specified function on each element of the specified enumerator. 4 | func ForEach[T any, E IEnumerable[T]](src E, f func(T) error) error { 5 | e := src() 6 | for { 7 | v, err := e.Next() 8 | if err != nil { 9 | if isEOC(err) { 10 | return nil 11 | } 12 | return err 13 | } 14 | err = f(v) 15 | if err != nil { 16 | return err 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /empty_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/makiuchi-d/linq/v2" 8 | ) 9 | 10 | func TestEmpty(t *testing.T) { 11 | r, err := linq.ToSlice(linq.Empty[uint]()) 12 | if err != nil { 13 | t.Fatalf("%v", err) 14 | } 15 | if len(r) != 0 { 16 | t.Fatalf("not empty: %#v", r) 17 | } 18 | if reflect.TypeOf(r) != reflect.TypeOf([]uint{}) { 19 | t.Fatalf("%T != []uint", r) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /reverse_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/makiuchi-d/linq/v2" 8 | ) 9 | 10 | func TestReverse(t *testing.T) { 11 | src := linq.FromSlice([]int{1, 2, 3, 4, 5}) 12 | r, err := linq.ToSlice( 13 | linq.Reverse(src)) 14 | if err != nil { 15 | t.Fatalf("%v", err) 16 | } 17 | exp := []int{5, 4, 3, 2, 1} 18 | if !reflect.DeepEqual(r, exp) { 19 | t.Fatalf("%v, wants %v", r, exp) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /repeat.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | type repeatEnumerator[T any] struct { 4 | v T 5 | n int 6 | } 7 | 8 | // Repeat generates a sequence that contains one repeated value. 9 | func Repeat[T any](v T, n int) Enumerable[T] { 10 | return func() Enumerator[T] { 11 | return &repeatEnumerator[T]{v: v, n: n} 12 | } 13 | } 14 | 15 | func (e *repeatEnumerator[T]) Next() (def T, _ error) { 16 | if e.n <= 0 { 17 | return def, EOC 18 | } 19 | e.n-- 20 | return e.v, nil 21 | } 22 | -------------------------------------------------------------------------------- /foreach_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/makiuchi-d/linq/v2" 7 | ) 8 | 9 | func TestForEach(t *testing.T) { 10 | c := 0 11 | s := 0 12 | err := linq.ForEach( 13 | linq.FromSlice([]int{1, 2, 3, 4, 5}), 14 | func(n int) error { 15 | c++ 16 | s += n 17 | return nil 18 | }) 19 | if err != nil { 20 | t.Fatalf("%v", err) 21 | } 22 | if c != 5 || s != 15 { 23 | t.Fatalf("(c, s) = (%v, %v) wants (%v, %v)", c, s, 5, 15) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /chunk_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/makiuchi-d/linq/v2" 8 | ) 9 | 10 | func TestChunk(t *testing.T) { 11 | 12 | src := []int{1, 2, 3, 4, 5, 6, 7, 8} 13 | e := linq.Chunk(linq.FromSlice(src), 3) 14 | r, err := linq.ToSlice(e) 15 | if err != nil { 16 | t.Fatalf("%v", err) 17 | } 18 | 19 | exp := [][]int{{1, 2, 3}, {4, 5, 6}, {7, 8}} 20 | 21 | if !reflect.DeepEqual(r, exp) { 22 | t.Fatalf("%v, wants %v", r, exp) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /concat_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/makiuchi-d/linq/v2" 8 | ) 9 | 10 | func TestConcat(t *testing.T) { 11 | e1 := linq.FromSlice([]int{4, 5, 6}) 12 | e2 := linq.FromSlice([]int{1, 2, 3}) 13 | 14 | r, err := linq.ToSlice( 15 | linq.Concat(e1, e2)) 16 | if err != nil { 17 | t.Fatalf("%v", err) 18 | } 19 | exp := []int{4, 5, 6, 1, 2, 3} 20 | if !reflect.DeepEqual(r, exp) { 21 | t.Fatalf("%v, wants %v", r, exp) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /aggregate_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/makiuchi-d/linq/v2" 7 | ) 8 | 9 | func TestAggregate(t *testing.T) { 10 | src := []byte{100, 100, 200, 200} 11 | 12 | r, err := linq.Aggregate( 13 | linq.FromSlice(src), 14 | 0, 15 | func(acc int, v byte) (int, error) { 16 | return acc + int(v), nil 17 | }) 18 | if err != nil { 19 | t.Fatalf("%v", err) 20 | } 21 | exp := 600 22 | if r != exp { 23 | t.Fatalf("%v, wants %v", r, exp) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /all.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | // All determines whether all elements of a sequence satisfy a condition. 4 | func All[T any, E IEnumerable[T]](src E, pred func(v T) (bool, error)) (bool, error) { 5 | e := src() 6 | for { 7 | v, err := e.Next() 8 | if err != nil { 9 | if isEOC(err) { 10 | return true, nil 11 | } 12 | return false, err 13 | } 14 | ok, err := pred(v) 15 | if err != nil { 16 | return false, err 17 | } 18 | if !ok { 19 | return false, nil 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /any.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | // Any determines whether any element of a sequence satisfies a condition. 4 | func Any[T any, E IEnumerable[T]](src E, pred func(v T) (bool, error)) (bool, error) { 5 | e := src() 6 | for { 7 | v, err := e.Next() 8 | if err != nil { 9 | if isEOC(err) { 10 | return false, nil 11 | } 12 | return false, err 13 | } 14 | ok, err := pred(v) 15 | if err != nil { 16 | return false, err 17 | } 18 | if ok { 19 | return true, nil 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /range.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | type rangeEnumerator struct { 4 | st int 5 | cnt int 6 | i int 7 | } 8 | 9 | // Range generates a sequence of integral numbers within a specified range. 10 | func Range(start, count int) Enumerable[int] { 11 | return func() Enumerator[int] { 12 | return &rangeEnumerator{st: start, cnt: count} 13 | } 14 | } 15 | 16 | func (e *rangeEnumerator) Next() (int, error) { 17 | if e.i >= e.cnt { 18 | return 0, EOC 19 | } 20 | 21 | r := e.st + e.i 22 | e.i++ 23 | return r, nil 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | go: [ '1.18', '1.23' ] 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v5 21 | with: 22 | go-version: ${{ matrix.go }} 23 | 24 | - name: Test 25 | run: go test -v -race -shuffle=on ./... 26 | -------------------------------------------------------------------------------- /where_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/makiuchi-d/linq/v2" 8 | ) 9 | 10 | func TestWhere(t *testing.T) { 11 | e1 := linq.FromSlice([]int16{10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}) 12 | e2 := linq.Where(e1, func(v int16) (bool, error) { return v%2 != 0, nil }) 13 | r, err := linq.ToSlice(e2) 14 | if err != nil { 15 | t.Fatalf("%v", err) 16 | } 17 | 18 | exp := []int16{9, 7, 5, 3, 1} 19 | if !reflect.DeepEqual(r, exp) { 20 | t.Fatalf("wants: %#v, got %#v", exp, r) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /select_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/makiuchi-d/linq/v2" 8 | ) 9 | 10 | func TestSelect(t *testing.T) { 11 | e1 := linq.FromSlice([]string{ 12 | "a", "bb", "ccc", "dddd", "eeeee", 13 | }) 14 | e2 := linq.Select(e1, func(s string) (int, error) { return len(s), nil }) 15 | 16 | r, err := linq.ToSlice(e2) 17 | if err != nil { 18 | t.Fatalf(err.Error()) 19 | } 20 | 21 | exp := []int{1, 2, 3, 4, 5} 22 | if !reflect.DeepEqual(r, exp) { 23 | t.Fatalf("got %#v, wants %#v", r, exp) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /average.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | import "golang.org/x/exp/constraints" 4 | 5 | type Real interface { 6 | constraints.Integer | constraints.Float 7 | } 8 | 9 | // Average computes the average of a sequence of numeric (real number) values. 10 | func Average[T Real, E IEnumerable[T]](src E) (float64, error) { 11 | n := 0 12 | t := float64(0) 13 | err := ForEach(src, func(v T) error { 14 | t += float64(v) 15 | n++ 16 | return nil 17 | }) 18 | if err != nil { 19 | return 0, err 20 | } 21 | if n == 0 { 22 | return 0, InvalidOperation 23 | } 24 | return t / float64(n), nil 25 | } 26 | -------------------------------------------------------------------------------- /select.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | type selectEnumerator[S, T any] struct { 4 | src Enumerator[S] 5 | sel func(v S) (T, error) 6 | } 7 | 8 | // Select projects each element of a sequence into a new form. 9 | func Select[S, T any, E IEnumerable[S]](src E, selector func(v S) (T, error)) Enumerable[T] { 10 | return func() Enumerator[T] { 11 | return &selectEnumerator[S, T]{src: src(), sel: selector} 12 | } 13 | } 14 | 15 | func (e *selectEnumerator[S, T]) Next() (def T, _ error) { 16 | v, err := e.src.Next() 17 | if err != nil { 18 | return def, err 19 | } 20 | 21 | return e.sel(v) 22 | } 23 | -------------------------------------------------------------------------------- /all_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/makiuchi-d/linq/v2" 7 | ) 8 | 9 | func TestAll(t *testing.T) { 10 | isEven := func(v int) (bool, error) { 11 | return v%2 == 0, nil 12 | } 13 | 14 | tests := []struct { 15 | src []int 16 | exp bool 17 | }{ 18 | {[]int{2, 4, 6, 8, 10}, true}, 19 | {[]int{2, 4, 6, 7, 10}, false}, 20 | } 21 | 22 | for _, test := range tests { 23 | e := linq.FromSlice(test.src) 24 | r, err := linq.All(e, isEven) 25 | if err != nil { 26 | t.Fatalf("%v", err) 27 | } 28 | if r != test.exp { 29 | t.Fatalf("wants: %v, got %v", test.exp, r) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /any_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/makiuchi-d/linq/v2" 7 | ) 8 | 9 | func TestAny(t *testing.T) { 10 | isOdd := func(v int) (bool, error) { 11 | return v%2 != 0, nil 12 | } 13 | 14 | tests := []struct { 15 | src []int 16 | exp bool 17 | }{ 18 | {[]int{2, 4, 6, 8, 10}, false}, 19 | {[]int{2, 4, 6, 7, 10}, true}, 20 | } 21 | 22 | for _, test := range tests { 23 | e := linq.FromSlice(test.src) 24 | r, err := linq.Any(e, isOdd) 25 | if err != nil { 26 | t.Fatalf("%v", err) 27 | } 28 | if r != test.exp { 29 | t.Fatalf("wants: %v, got %v", test.exp, r) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /concat.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | type concatEnumerator[T any] struct { 4 | fst Enumerator[T] 5 | snd Enumerable[T] 6 | } 7 | 8 | // Concat concatenates two sequences. 9 | func Concat[T any, E IEnumerable[T]](first, second E) Enumerable[T] { 10 | return func() Enumerator[T] { 11 | return &concatEnumerator[T]{fst: first(), snd: Enumerable[T](second)} 12 | } 13 | } 14 | 15 | func (e *concatEnumerator[T]) Next() (def T, _ error) { 16 | v, err := e.fst.Next() 17 | if err == nil { 18 | return v, nil 19 | } 20 | if !isEOC(err) { 21 | return def, err 22 | } 23 | if e.snd == nil { 24 | return def, EOC 25 | } 26 | e.fst, e.snd = e.snd(), nil 27 | return e.Next() 28 | } 29 | -------------------------------------------------------------------------------- /sum_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/makiuchi-d/linq/v2" 7 | ) 8 | 9 | func TestSum(t *testing.T) { 10 | src := []int16{100, 200, 300, 400, 500} 11 | r, err := linq.Sum(linq.FromSlice(src)) 12 | if err != nil { 13 | t.Fatalf("%v", err) 14 | } 15 | exp := 1500 16 | if r != exp { 17 | t.Fatalf("%v, wants %v", r, exp) 18 | } 19 | 20 | rf, err := linq.Sumf( 21 | linq.Select(linq.FromSlice(src), func(v int16) (float32, error) { return float32(v) / 16, nil })) 22 | if err != nil { 23 | t.Fatalf("%v", err) 24 | } 25 | expf := float64(1500) / 16 26 | if rf != expf { 27 | t.Fatalf("%v, wants %v", r, exp) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /zip_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/makiuchi-d/linq/v2" 9 | ) 10 | 11 | func TestZip(t *testing.T) { 12 | nums := []int{1, 2, 3, 4} 13 | words := []string{"one", "two", "three"} 14 | 15 | e := linq.Zip( 16 | linq.FromSlice(nums), 17 | linq.FromSlice(words), 18 | func(n int, w string) (string, error) { 19 | return fmt.Sprintf("%v %v", n, w), nil 20 | }) 21 | 22 | r, err := linq.ToSlice(e) 23 | if err != nil { 24 | t.Fatalf("%v", err) 25 | } 26 | 27 | exp := []string{"1 one", "2 two", "3 three"} 28 | if !reflect.DeepEqual(exp, r) { 29 | t.Fatalf("%q, wants %q", r, exp) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /reverse.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | type reverseEnumerator[T any] struct { 4 | src Enumerator[T] 5 | s []T 6 | i int 7 | } 8 | 9 | // Reverse inverts the order of the elements in a sequence. 10 | func Reverse[T any, E IEnumerable[T]](src E) Enumerable[T] { 11 | return func() Enumerator[T] { 12 | return &reverseEnumerator[T]{ 13 | src: src(), 14 | } 15 | } 16 | } 17 | 18 | func (e *reverseEnumerator[T]) Next() (def T, _ error) { 19 | if e.s == nil { 20 | s, err := toSlice(e.src) 21 | if err != nil { 22 | return def, err 23 | } 24 | e.s = s 25 | } 26 | 27 | l := len(e.s) 28 | i := e.i 29 | if i >= l { 30 | return def, EOC 31 | } 32 | e.i++ 33 | return e.s[l-i-1], nil 34 | } 35 | -------------------------------------------------------------------------------- /where.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | type whereEnumerator[T any] struct { 4 | src Enumerator[T] 5 | pred func(v T) (bool, error) 6 | } 7 | 8 | // Where filters a sequence of values based on a predicate. 9 | func Where[T any, E IEnumerable[T]](src E, pred func(v T) (bool, error)) Enumerable[T] { 10 | return func() Enumerator[T] { 11 | return &whereEnumerator[T]{src: src(), pred: pred} 12 | } 13 | } 14 | 15 | func (e *whereEnumerator[T]) Next() (def T, _ error) { 16 | for { 17 | v, err := e.src.Next() 18 | if err != nil { 19 | return def, err 20 | } 21 | 22 | ok, err := e.pred(v) 23 | if err != nil { 24 | return def, err 25 | } 26 | if ok { 27 | return v, nil 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /first.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | // First returns the first element in a sequence 4 | func First[T any, E IEnumerable[T]](src E) (def T, _ error) { 5 | e := src() 6 | v, err := e.Next() 7 | if err != nil { 8 | if isEOC(err) { 9 | err = InvalidOperation 10 | } 11 | return def, err 12 | } 13 | return v, nil 14 | } 15 | 16 | // FirstOrDefault returns the first element of the sequence, or a specified default value if no such element is found. 17 | func FirstOrDefault[T any, E IEnumerable[T]](src E, defaultValue T) (T, error) { 18 | v, err := First(src) 19 | if err != nil { 20 | if isInvalidOperation(err) { 21 | err = nil 22 | } 23 | return defaultValue, err 24 | } 25 | return v, nil 26 | } 27 | -------------------------------------------------------------------------------- /sum.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | import "golang.org/x/exp/constraints" 4 | 5 | // Sum calculates the sum of a integer sequence. 6 | func Sum[T constraints.Integer, E IEnumerable[T]](src E) (int, error) { 7 | sum := 0 8 | err := ForEach(src, func(v T) error { 9 | sum += int(v) 10 | return nil 11 | }) 12 | if err != nil { 13 | return 0, err 14 | } 15 | return sum, nil 16 | } 17 | 18 | // Sumf calculates the sum of a floating number sequence. 19 | func Sumf[T constraints.Float, E IEnumerable[T]](src E) (float64, error) { 20 | var sum float64 21 | err := ForEach(src, func(v T) error { 22 | sum += float64(v) 23 | return nil 24 | }) 25 | if err != nil { 26 | return 0, err 27 | } 28 | return sum, nil 29 | } 30 | -------------------------------------------------------------------------------- /iter_bench_test.go: -------------------------------------------------------------------------------- 1 | //go:build go1.23 2 | 3 | package linq_test 4 | 5 | import ( 6 | "errors" 7 | "testing" 8 | 9 | "github.com/makiuchi-d/linq/v2" 10 | ) 11 | 12 | var src = linq.Range(0, 100000) 13 | 14 | func BenchmarkFor(b *testing.B) { 15 | for range b.N { 16 | e := src() 17 | for { 18 | _, err := e.Next() 19 | if err != nil { 20 | if errors.Is(err, linq.EOC) { 21 | break 22 | } 23 | } 24 | } 25 | } 26 | } 27 | 28 | func BenchmarkForEach(b *testing.B) { 29 | for range b.N { 30 | linq.ForEach(src, func(n int) error { 31 | return nil 32 | }) 33 | } 34 | } 35 | 36 | func BenchmarkRangeFunc(b *testing.B) { 37 | for range b.N { 38 | for _, _ = range src.All() { 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /elementat_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/makiuchi-d/linq/v2" 8 | ) 9 | 10 | func TestElementAt(t *testing.T) { 11 | src := []int{1, 2, 3, 4, 5} 12 | 13 | r, err := linq.ElementAt(linq.FromSlice(src), 3) 14 | if err != nil { 15 | t.Fatalf("%v", err) 16 | } 17 | exp := 4 18 | if r != exp { 19 | t.Fatalf("%v, wants %v", r, exp) 20 | } 21 | _, err = linq.ElementAt(linq.FromSlice(src), 10) 22 | if !errors.Is(err, linq.OutOfRange) { 23 | t.Fatalf("%#v, wants %#v", err, linq.OutOfRange) 24 | } 25 | 26 | r, err = linq.ElementAtOrDefault(linq.FromSlice(src), 10) 27 | if err != nil { 28 | t.Fatalf("%v", err) 29 | } 30 | if r != 0 { 31 | t.Fatalf("%v, wants 0", r) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /elementat.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | // ElementAt returns the element at a specified index in a sequence. 4 | func ElementAt[T any, E IEnumerable[T]](src E, n int) (def T, err error) { 5 | e := src() 6 | var v T 7 | for i := 0; i <= n; i++ { 8 | v, err = e.Next() 9 | if err != nil { 10 | if isEOC(err) { 11 | err = OutOfRange 12 | } 13 | return def, err 14 | } 15 | } 16 | return v, nil 17 | } 18 | 19 | // ElementAtOrDefault returns the element at a specified index in a sequence or a default value if the index is out of range. 20 | func ElementAtOrDefault[T any, E IEnumerable[T]](src E, n int) (T, error) { 21 | v, err := ElementAt(src, n) 22 | if err != nil { 23 | if isOutOfRange(err) { 24 | err = nil 25 | } 26 | } 27 | return v, err 28 | } 29 | -------------------------------------------------------------------------------- /chunk.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | type chunkEnumerator[T any] struct { 4 | src Enumerator[T] 5 | size int 6 | } 7 | 8 | // Chunk splits the elements of a sequence into chunks of size at most `size`. 9 | func Chunk[T any, E IEnumerable[T]](src E, size int) Enumerable[[]T] { 10 | return func() Enumerator[[]T] { 11 | return &chunkEnumerator[T]{ 12 | src: src(), 13 | size: size, 14 | } 15 | } 16 | } 17 | 18 | func (e *chunkEnumerator[T]) Next() ([]T, error) { 19 | s := make([]T, 0, e.size) 20 | 21 | for i := 0; i < e.size; i++ { 22 | v, err := e.src.Next() 23 | if err != nil { 24 | if isEOC(err) { 25 | break 26 | } 27 | return nil, err 28 | } 29 | s = append(s, v) 30 | } 31 | 32 | if len(s) == 0 { 33 | return nil, EOC 34 | } 35 | 36 | return s, nil 37 | } 38 | -------------------------------------------------------------------------------- /defaultifempty_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/makiuchi-d/linq/v2" 8 | ) 9 | 10 | func TestDefaultIfEmpty(t *testing.T) { 11 | src := linq.FromSlice([]string{"aaa", "bbb", "ccc"}) 12 | r, err := linq.ToSlice( 13 | linq.DefaultIfEmpty(src, "default")) 14 | if err != nil { 15 | t.Fatalf("%v", err) 16 | } 17 | exp := []string{"aaa", "bbb", "ccc"} 18 | if !reflect.DeepEqual(r, exp) { 19 | t.Fatalf("%v, wants %v", r, exp) 20 | } 21 | 22 | src = linq.FromSlice([]string{}) 23 | r, err = linq.ToSlice( 24 | linq.DefaultIfEmpty(src, "default")) 25 | if err != nil { 26 | t.Fatalf("%v", err) 27 | } 28 | exp = []string{"default"} 29 | if !reflect.DeepEqual(r, exp) { 30 | t.Fatalf("%v, wants %v", r, exp) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /last.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | // Last returns the last element of a sequence that satisfies a specified condition. 4 | func Last[T any, E IEnumerable[T]](src E) (def T, _ error) { 5 | var last T 6 | var eocerr error = InvalidOperation 7 | e := src() 8 | for { 9 | v, err := e.Next() 10 | if err != nil { 11 | if isEOC(err) { 12 | return last, eocerr 13 | } 14 | return def, err 15 | } 16 | last = v 17 | eocerr = nil 18 | } 19 | } 20 | 21 | // LastOrDefault returns the last element of a sequence that satisfies a condition, or a specified default value if no such element is found. 22 | func LastOrDefault[T any, E IEnumerable[T]](src E, defaultValue T) (T, error) { 23 | v, err := Last(src) 24 | if err != nil { 25 | if isInvalidOperation(err) { 26 | return defaultValue, nil 27 | } 28 | } 29 | return v, err 30 | } 31 | -------------------------------------------------------------------------------- /selectmany_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/makiuchi-d/linq/v2" 8 | ) 9 | 10 | func TestSelectMany(t *testing.T) { 11 | s := [][]string{ 12 | {"a", "bb", "ccc"}, 13 | {"dddd", "eeeee", "ffffff"}, 14 | {"ggggggg", "hhhhhhhh", "iiiiiiiii"}, 15 | } 16 | 17 | e1 := linq.FromSlice(s) 18 | e2 := linq.SelectMany(e1, 19 | func(e []string) (linq.OrderedEnumerable[string], error) { 20 | return linq.OrderBy(linq.FromSlice(e), func(s string) (string, error) { return s, nil }), nil 21 | }, 22 | func(v string) (int, error) { return len(v), nil }) 23 | 24 | r, err := linq.ToSlice(e2) 25 | if err != nil { 26 | t.Fatalf("%v", err) 27 | } 28 | 29 | exp := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} 30 | if !reflect.DeepEqual(exp, r) { 31 | t.Fatalf("%v, wants %v", r, exp) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /defaultifempty.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | type defaultIfEmptyEnumerator[T any] struct { 4 | src Enumerator[T] 5 | def T 6 | rest Enumerator[T] 7 | } 8 | 9 | // DefaultIfEmpty returns the elements of the specified sequence or the specified value in a singleton collection if the sequence is empty. 10 | func DefaultIfEmpty[T any, E IEnumerable[T]](src E, defaultValue T) Enumerable[T] { 11 | return func() Enumerator[T] { 12 | return &defaultIfEmptyEnumerator[T]{src: src(), def: defaultValue} 13 | } 14 | } 15 | 16 | func (e *defaultIfEmptyEnumerator[T]) Next() (def T, _ error) { 17 | if e.rest != nil { 18 | return e.rest.Next() 19 | } 20 | v, err := e.src.Next() 21 | if err != nil { 22 | if isEOC(err) { 23 | e.rest = Empty[T]()() 24 | return e.def, nil 25 | } 26 | return def, err 27 | } 28 | e.rest = e.src 29 | return v, nil 30 | } 31 | -------------------------------------------------------------------------------- /orderbyfunc_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/makiuchi-d/linq/v2" 8 | ) 9 | 10 | func TestOrderByFunc(t *testing.T) { 11 | src := linq.FromSlice([]string{ 12 | "grape", "passionfruit", "banana", "mango", 13 | "orange", "raspberry", "apple", "blueberry"}) 14 | e := linq.OrderByFunc(src, func(a, b string) bool { 15 | switch l := len(a) - len(b); { 16 | case l < 0: 17 | return true 18 | case l > 0: 19 | return false 20 | default: 21 | return a < b 22 | } 23 | }) 24 | r, err := linq.ToSlice(e) 25 | if err != nil { 26 | t.Fatalf("%v", err) 27 | } 28 | exp := []string{ 29 | "apple", 30 | "grape", 31 | "mango", 32 | "banana", 33 | "orange", 34 | "blueberry", 35 | "raspberry", 36 | "passionfruit", 37 | } 38 | if !reflect.DeepEqual(r, exp) { 39 | t.Fatalf("%v, wants %v", r, exp) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /zip.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | type zipEnumerator[S1, S2, T any] struct { 4 | first Enumerator[S1] 5 | second Enumerator[S2] 6 | sel func(S1, S2) (T, error) 7 | } 8 | 9 | // Zip applies a specified function to the corresponding elements of two sequences, producing a sequence of the results. 10 | func Zip[S1, S2, T any, E1 IEnumerable[S1], E2 IEnumerable[S2]](first E1, second E2, resultSelector func(S1, S2) (T, error)) Enumerable[T] { 11 | return func() Enumerator[T] { 12 | return &zipEnumerator[S1, S2, T]{ 13 | first: first(), 14 | second: second(), 15 | sel: resultSelector, 16 | } 17 | } 18 | } 19 | 20 | func (e *zipEnumerator[S1, S2, T]) Next() (def T, _ error) { 21 | t, err := e.first.Next() 22 | if err != nil { 23 | return def, err 24 | } 25 | 26 | u, err := e.second.Next() 27 | if err != nil { 28 | return def, err 29 | } 30 | 31 | return e.sel(t, u) 32 | } 33 | -------------------------------------------------------------------------------- /join_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/makiuchi-d/linq/v2" 9 | ) 10 | 11 | func TestJoin(t *testing.T) { 12 | nums := linq.Range(3, 6) 13 | strs := linq.FromSlice( 14 | []string{"one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"}) 15 | 16 | e := linq.Join(nums, strs, 17 | func(n int) (int, error) { return n, nil }, 18 | func(s string) (int, error) { return len(s), nil }, 19 | func(n int, s string) (string, error) { return fmt.Sprintf("%d:%s", n, s), nil }) 20 | 21 | r, err := linq.ToSlice(e) 22 | if err != nil { 23 | t.Fatalf("%v", err) 24 | } 25 | 26 | exp := []string{ 27 | "3:one", "3:two", "3:six", "3:ten", 28 | "4:four", "4:five", "4:nine", 29 | "5:three", "5:seven", "5:eight", 30 | } 31 | if !reflect.DeepEqual(r, exp) { 32 | t.Fatalf("%v, wants %v", r, exp) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /slice.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | type sliceEnumerator[T any] struct { 4 | s []T 5 | i int 6 | } 7 | 8 | // FromSlice generates an IEnumerable[T] from a slice. 9 | func FromSlice[S ~[]T, T any](s S) Enumerable[T] { 10 | return func() Enumerator[T] { 11 | return &sliceEnumerator[T]{s: []T(s)} 12 | } 13 | } 14 | 15 | // ToSlice creates a slice from an IEnumerable[T] 16 | func ToSlice[T any, E IEnumerable[T]](src E) ([]T, error) { 17 | return toSlice(src()) 18 | } 19 | 20 | func toSlice[T any](e Enumerator[T]) ([]T, error) { 21 | s := make([]T, 0) 22 | for { 23 | v, err := e.Next() 24 | if err != nil { 25 | if isEOC(err) { 26 | break 27 | } 28 | return s, err 29 | } 30 | s = append(s, v) 31 | } 32 | return s, nil 33 | } 34 | 35 | func (e *sliceEnumerator[T]) Next() (def T, _ error) { 36 | if e.i >= len(e.s) { 37 | return def, EOC 38 | } 39 | i := e.i 40 | e.i++ 41 | return e.s[i], nil 42 | } 43 | -------------------------------------------------------------------------------- /orderbyfunc.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | import "sort" 4 | 5 | type orderByFuncEnumerator[T any] struct { 6 | src Enumerator[T] 7 | less func(a, b T) bool 8 | sorted []T 9 | i int 10 | } 11 | 12 | // OrderByFunc sorts the elements of a sequence by the provided less function. 13 | func OrderByFunc[T any, E IEnumerable[T]](src E, less func(a, b T) bool) Enumerable[T] { 14 | return func() Enumerator[T] { 15 | return &orderByFuncEnumerator[T]{src: src(), less: less} 16 | } 17 | } 18 | 19 | func (o *orderByFuncEnumerator[T]) Next() (def T, _ error) { 20 | if o.sorted == nil { 21 | s, err := toSlice(o.src) 22 | if err != nil { 23 | return def, err 24 | } 25 | 26 | sort.Slice(s, func(i, j int) bool { 27 | return o.less(s[i], s[j]) 28 | }) 29 | 30 | o.sorted = s 31 | } 32 | 33 | if o.i >= len(o.sorted) { 34 | return def, EOC 35 | } 36 | 37 | i := o.i 38 | o.i++ 39 | return o.sorted[i], nil 40 | } 41 | -------------------------------------------------------------------------------- /contains.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | // Contains cetermines whether a sequence contains a specified element. 4 | func Contains[T comparable, E IEnumerable[T]](src E, val T) (bool, error) { 5 | e := src() 6 | for { 7 | t, err := e.Next() 8 | if err != nil { 9 | if isEOC(err) { 10 | return false, nil 11 | } 12 | return false, err 13 | } 14 | 15 | if t == val { 16 | return true, nil 17 | } 18 | } 19 | } 20 | 21 | // ContainsFunc determines whether a sequence contains a specified element by using a specified comparer function. 22 | func ContainsFunc[T any, E IEnumerable[T]](src E, val T, equals func(T, T) (bool, error)) (bool, error) { 23 | e := src() 24 | for { 25 | t, err := e.Next() 26 | if err != nil { 27 | if isEOC(err) { 28 | return false, nil 29 | } 30 | return false, err 31 | } 32 | 33 | eq, err := equals(t, val) 34 | if err != nil { 35 | return false, err 36 | } 37 | if eq { 38 | return true, nil 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /max_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | 7 | "github.com/makiuchi-d/linq/v2" 8 | ) 9 | 10 | func TestMax(t *testing.T) { 11 | src := []byte{ 12 | 189, 47, 155, 170, 155, 99, 136, 3, 161, 231, 13 | } 14 | 15 | r, err := linq.Max(linq.FromSlice(src)) 16 | if err != nil { 17 | t.Fatalf("%v", err) 18 | } 19 | exp := byte(231) 20 | if r != exp { 21 | t.Fatalf("%v, wants %v", r, exp) 22 | } 23 | 24 | r, err = linq.MaxBy(linq.FromSlice(src), func(v byte) (float64, error) { 25 | return math.Abs(float64(v) - 150), nil 26 | }) 27 | if err != nil { 28 | t.Fatalf("%v", err) 29 | } 30 | exp = byte(3) 31 | if r != exp { 32 | t.Fatalf("%v, wants %v", r, exp) 33 | } 34 | 35 | r, err = linq.MaxByFunc(linq.FromSlice(src), func(a, b byte) (bool, error) { 36 | if a%2 != b%2 { 37 | return a%2 == 0, nil 38 | } 39 | return a > b, nil 40 | }) 41 | if err != nil { 42 | t.Fatalf("%v", err) 43 | } 44 | exp = byte(170) 45 | if r != exp { 46 | t.Fatalf("%v, wants %v", r, exp) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /min_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | 7 | "github.com/makiuchi-d/linq/v2" 8 | ) 9 | 10 | func TestMin(t *testing.T) { 11 | src := []byte{ 12 | 189, 47, 155, 170, 155, 99, 136, 3, 161, 231, 13 | } 14 | 15 | r, err := linq.Min(linq.FromSlice(src)) 16 | if err != nil { 17 | t.Fatalf("%v", err) 18 | } 19 | exp := byte(3) 20 | if r != exp { 21 | t.Fatalf("%v, wants %v", r, exp) 22 | } 23 | 24 | r, err = linq.MinBy(linq.FromSlice(src), func(v byte) (float64, error) { 25 | return math.Abs(float64(v) - 100), nil 26 | }) 27 | if err != nil { 28 | t.Fatalf("%v", err) 29 | } 30 | exp = byte(99) 31 | if r != exp { 32 | t.Fatalf("%v, wants %v", r, exp) 33 | } 34 | 35 | r, err = linq.MinByFunc(linq.FromSlice(src), func(a, b byte) (bool, error) { 36 | if a%2 != b%2 { 37 | return a%2 == 0, nil 38 | } 39 | return a < b, nil 40 | }) 41 | if err != nil { 42 | t.Fatalf("%v", err) 43 | } 44 | exp = byte(136) 45 | if r != exp { 46 | t.Fatalf("%v, wants %v", r, exp) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /contains_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/makiuchi-d/linq/v2" 7 | ) 8 | 9 | func TestContains(t *testing.T) { 10 | r, err := linq.Contains(linq.FromSlice([]int{1, 2, 3, 4, 5}), 3) 11 | if err != nil { 12 | t.Fatalf("%v", err) 13 | } 14 | if r != true { 15 | t.Fatalf("3 must be contained") 16 | } 17 | r, err = linq.Contains(linq.FromSlice([]int{1, 2, 3, 4, 5}), 6) 18 | if err != nil { 19 | t.Fatalf("%v", err) 20 | } 21 | if r != false { 22 | t.Fatalf("6 must not be contained") 23 | } 24 | } 25 | 26 | func TestContainsFunc(t *testing.T) { 27 | type Product struct { 28 | Name string 29 | Code int 30 | } 31 | src := []Product{ 32 | {Name: "apple", Code: 9}, 33 | {Name: "orange", Code: 4}, 34 | {Name: "lemon", Code: 12}, 35 | } 36 | e := linq.FromSlice(src) 37 | v := Product{Name: "apple", Code: 1} 38 | r, err := linq.ContainsFunc(e, v, func(a, b Product) (bool, error) { 39 | return a.Name == b.Name, nil 40 | }) 41 | if err != nil { 42 | t.Fatalf("%v", err) 43 | } 44 | if r != true { 45 | t.Fatalf("must be true") 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /last_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/makiuchi-d/linq/v2" 8 | ) 9 | 10 | func TestLast(t *testing.T) { 11 | src := []int{1, 2, 3, 4, 5, 6, 7} 12 | r, err := linq.Last( 13 | linq.Where(linq.FromSlice(src), 14 | func(v int) (bool, error) { 15 | return v%2 == 0, nil 16 | })) 17 | if err != nil { 18 | t.Fatalf("%v", err) 19 | } 20 | exp := 6 21 | if r != exp { 22 | t.Fatalf("%v, wants %v", r, exp) 23 | } 24 | 25 | _, err = linq.Last(linq.Empty[int]()) 26 | if !errors.Is(err, linq.InvalidOperation) { 27 | t.Fatalf("%#v, wants %#v", err, linq.InvalidOperation) 28 | } 29 | } 30 | 31 | func TestLastOrDefault(t *testing.T) { 32 | src := []int{1, 2, 3} 33 | def := 42 34 | r, err := linq.LastOrDefault(linq.Empty[int](), def) 35 | if err != nil { 36 | t.Fatalf("%v", err) 37 | } 38 | exp := def 39 | if r != exp { 40 | t.Fatalf("%v, wants %v", r, exp) 41 | } 42 | r, err = linq.LastOrDefault(linq.FromSlice(src), def) 43 | if err != nil { 44 | t.Fatalf("%v", err) 45 | } 46 | exp = 3 47 | if r != exp { 48 | t.Fatalf("%v, wants %v", r, exp) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 MakKi (makki_d) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /single.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | // Single returns the only element of a sequence, and return an error InvalidOperation if more than one such element exists. 4 | func Single[T any, E IEnumerable[T]](src E) (def T, _ error) { 5 | e := src() 6 | v, err := e.Next() 7 | if err != nil { 8 | if isEOC(err) { 9 | err = InvalidOperation 10 | } 11 | return def, err 12 | } 13 | _, err = e.Next() 14 | if err == nil { 15 | return def, InvalidOperation 16 | } 17 | if !isEOC(err) { 18 | return def, err 19 | } 20 | return v, nil 21 | } 22 | 23 | // SingleOrDefault returns the only element of a sequence, or a specified default value if no such element exists; this function returns an error InvalidOperation if more than one element satisfies the condition. 24 | func SingleOrDefault[T any, E IEnumerable[T]](src E, defaultValue T) (def T, _ error) { 25 | e := src() 26 | v, err := e.Next() 27 | if err != nil { 28 | if isEOC(err) { 29 | return defaultValue, nil 30 | } 31 | return def, err 32 | } 33 | _, err = e.Next() 34 | if err == nil { 35 | return def, InvalidOperation 36 | } 37 | if !isEOC(err) { 38 | return def, err 39 | } 40 | return v, nil 41 | } 42 | -------------------------------------------------------------------------------- /first_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/makiuchi-d/linq/v2" 8 | ) 9 | 10 | func TestFirst(t *testing.T) { 11 | src := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} 12 | 13 | r, err := linq.First( 14 | linq.Where(linq.FromSlice(src), 15 | func(v int) (bool, error) { 16 | return (v > 5 && v%3 == 0), nil 17 | })) 18 | if err != nil { 19 | t.Fatalf("%v", err) 20 | } 21 | exp := 6 22 | if r != exp { 23 | t.Fatalf("%v, wants %v", r, exp) 24 | } 25 | 26 | _, err = linq.First(linq.Empty[int]()) 27 | if !errors.Is(err, linq.InvalidOperation) { 28 | t.Fatalf("%#v, wants %#v", err, linq.InvalidOperation) 29 | } 30 | 31 | } 32 | 33 | func TestFirstOrDefault(t *testing.T) { 34 | src := []int{1, 2, 3} 35 | def := 42 36 | r, err := linq.FirstOrDefault(linq.Empty[int](), def) 37 | if err != nil { 38 | t.Fatalf("%v", err) 39 | } 40 | exp := def 41 | if r != exp { 42 | t.Fatalf("%v, wants %v", r, exp) 43 | } 44 | 45 | r, err = linq.FirstOrDefault(linq.FromSlice(src), def) 46 | if err != nil { 47 | t.Fatalf("%v", err) 48 | } 49 | exp = 1 50 | if r != exp { 51 | t.Fatalf("%v, wants %v", r, exp) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /linq.go: -------------------------------------------------------------------------------- 1 | // LINQ for Go with type parameters 2 | package linq 3 | 4 | // IEnumerable[T] is a queryable collection. 5 | type IEnumerable[T any] interface { 6 | Enumerable[T] | OrderedEnumerable[T] 7 | } 8 | 9 | // Enumerable[T] is an implementation of IEnumerable[T]. 10 | type Enumerable[T any] func() Enumerator[T] 11 | 12 | // OrderedEnumerable[T] is an implementation of IEnumerable[T], which is generated by OrderBy or ThenBy. 13 | type OrderedEnumerable[T any] func() Enumerator[T] 14 | 15 | // Enumerator[T] is an enumerator of the collection. 16 | type Enumerator[T any] interface { 17 | // Next returns a next element of this collection. 18 | // It returns EOC as an `error` when it reaches the end of the collection. 19 | Next() (T, error) 20 | } 21 | 22 | // Error : LINQ error type. 23 | type Error string 24 | 25 | const ( 26 | // EOC : End of the collection. 27 | EOC Error = "End of the collection" 28 | 29 | // OutOfRange : Index out of range. 30 | OutOfRange Error = "Out of range" 31 | 32 | // InvalidOperation : Invalid operation such as no element satisfying the condition. 33 | InvalidOperation Error = "Invalid operation" 34 | ) 35 | 36 | func (e Error) Error() string { 37 | return string(e) 38 | } 39 | -------------------------------------------------------------------------------- /groupby_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/makiuchi-d/linq/v2" 8 | ) 9 | 10 | func TestGroupBy(t *testing.T) { 11 | src := linq.FromSlice([]string{ 12 | "blueberry", "chimpanzee", "abacus", "banana", "apple", "cheese", "elephant", "umbrella", "anteater", 13 | }) 14 | 15 | e := linq.GroupBy(src, func(s string) (byte, error) { return s[0], nil }) 16 | 17 | mexp := map[byte][]string{ 18 | 'a': {"abacus", "anteater", "apple"}, 19 | 'b': {"banana", "blueberry"}, 20 | 'c': {"cheese", "chimpanzee"}, 21 | 'e': {"elephant"}, 22 | 'u': {"umbrella"}, 23 | } 24 | 25 | linq.ForEach(e, func(grp linq.Grouping[string, byte]) error { 26 | s, err := linq.ToSlice( 27 | linq.OrderBy(grp.Enumerable, func(s string) (string, error) { return s, nil })) 28 | if err != nil { 29 | t.Fatalf("%c: %v", grp.Key, err) 30 | } 31 | k := grp.Key 32 | exp, ok := mexp[k] 33 | if !ok { 34 | t.Fatalf("invalid key: %c", k) 35 | } 36 | if !reflect.DeepEqual(s, exp) { 37 | t.Fatalf("%c: %v, wants %v", k, s, exp) 38 | } 39 | delete(mexp, k) 40 | return nil 41 | }) 42 | if len(mexp) > 0 { 43 | t.Fatalf("unreturned items: %v", mexp) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /distinct.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | type distinctEnumerator[T any, K comparable] struct { 4 | src Enumerator[T] 5 | hmap *hashMap[K, T] 6 | } 7 | 8 | // Distinct returns distinct elements from a sequence by using the specified comparer functions. 9 | func Distinct[T any, E IEnumerable[T]](src E, equals func(T, T) (bool, error), getHashCode func(T) (int, error)) Enumerable[T] { 10 | return func() Enumerator[T] { 11 | return &distinctEnumerator[T, int]{ 12 | src: src(), 13 | hmap: newHashMap(getHashCode, equals), 14 | } 15 | } 16 | } 17 | 18 | // DistinctBy returns distinct elements from a sequence according to a specified key selector function. 19 | func DistinctBy[T any, K comparable, E IEnumerable[T]](src E, keySelector func(v T) (K, error)) Enumerable[T] { 20 | return func() Enumerator[T] { 21 | return &distinctEnumerator[T, K]{ 22 | src: src(), 23 | hmap: newKeyMap(keySelector), 24 | } 25 | } 26 | } 27 | 28 | func (e *distinctEnumerator[T, int]) Next() (def T, _ error) { 29 | for { 30 | v, err := e.src.Next() 31 | if err != nil { 32 | return def, err 33 | } 34 | 35 | added, err := e.hmap.add(v) 36 | if err != nil { 37 | return def, err 38 | } 39 | if added { 40 | return v, nil 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /selectmany.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | type selectManyEnumerator[S, C, T any] struct { 4 | src Enumerator[S] 5 | csel func(S) (Enumerable[C], error) 6 | rsel func(C) (T, error) 7 | 8 | cur Enumerator[C] 9 | } 10 | 11 | // SelectMany projects each element of a sequence to an Enumerable[T] and flattens the resulting sequences into one sequence. 12 | func SelectMany[S, C, T any, E IEnumerable[S], EC IEnumerable[C]](src E, collectionSelector func(S) (EC, error), resultSelector func(C) (T, error)) Enumerable[T] { 13 | return func() Enumerator[T] { 14 | return &selectManyEnumerator[S, C, T]{ 15 | src: src(), 16 | csel: func(s S) (Enumerable[C], error) { 17 | c, err := collectionSelector(s) 18 | return Enumerable[C](c), err 19 | }, 20 | rsel: resultSelector, 21 | } 22 | } 23 | } 24 | 25 | func (e *selectManyEnumerator[S, C, T]) Next() (def T, _ error) { 26 | if e.cur == nil { 27 | t, err := e.src.Next() 28 | if err != nil { 29 | return def, err // includes case of EndOfCollection 30 | } 31 | 32 | c, err := e.csel(t) 33 | if err != nil { 34 | return def, err 35 | } 36 | 37 | e.cur = c() 38 | } 39 | 40 | u, err := e.cur.Next() 41 | if err != nil { 42 | if isEOC(err) { 43 | e.cur = nil 44 | return e.Next() 45 | } 46 | return def, err 47 | } 48 | 49 | return e.rsel(u) 50 | } 51 | -------------------------------------------------------------------------------- /groupjoin.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | type groupJoinEnumerator[S1, S2, T any, K comparable] struct { 4 | eOut Enumerator[S1] 5 | eIn Enumerator[S2] 6 | ksOut func(S1) (K, error) 7 | ksIn func(S2) (K, error) 8 | rSel func(S1, Enumerable[S2]) (T, error) 9 | 10 | ms2 *hashMap[K, S2] 11 | } 12 | 13 | // GroupJoin correlates the elements of two sequences based on equality of keys and groups the results. 14 | func GroupJoin[S1, S2, T any, K comparable, E1 IEnumerable[S1], E2 IEnumerable[S2]]( 15 | outer E1, 16 | inner E2, 17 | outerKeySelector func(S1) (K, error), 18 | innerKeySelector func(S2) (K, error), 19 | resultSelector func(S1, Enumerable[S2]) (T, error), 20 | ) Enumerable[T] { 21 | return func() Enumerator[T] { 22 | return &groupJoinEnumerator[S1, S2, T, K]{ 23 | eOut: outer(), 24 | eIn: inner(), 25 | ksOut: outerKeySelector, 26 | ksIn: innerKeySelector, 27 | rSel: resultSelector, 28 | } 29 | } 30 | } 31 | 32 | func (e *groupJoinEnumerator[S1, S2, T, K]) Next() (def T, _ error) { 33 | out, err := e.eOut.Next() 34 | if err != nil { 35 | return def, err 36 | } 37 | if e.ms2 == nil { 38 | e.ms2 = newHashMap(e.ksIn, alwaysNotEqual[S2]) 39 | if err := e.ms2.addAll(e.eIn); err != nil { 40 | return def, err 41 | } 42 | } 43 | k, err := e.ksOut(out) 44 | if err != nil { 45 | return def, err 46 | } 47 | 48 | return e.rSel(out, FromSlice(e.ms2.gets(k))) 49 | } 50 | -------------------------------------------------------------------------------- /skip_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/makiuchi-d/linq/v2" 8 | ) 9 | 10 | func TestSkip(t *testing.T) { 11 | tests := []struct { 12 | src linq.Enumerable[int] 13 | n int 14 | exp []int 15 | }{ 16 | {linq.Range(0, 5), 0, []int{0, 1, 2, 3, 4}}, 17 | {linq.Range(0, 5), 2, []int{2, 3, 4}}, 18 | {linq.Range(0, 5), 5, []int{}}, 19 | {linq.Range(0, 5), 6, []int{}}, 20 | } 21 | for _, test := range tests { 22 | e := linq.Skip(test.src, test.n) 23 | r, err := linq.ToSlice(e) 24 | if err != nil { 25 | t.Fatalf("Skip(%v): %v", test.n, err) 26 | } 27 | if !reflect.DeepEqual(r, test.exp) { 28 | t.Fatalf("Skip(%v): %v, wants %v", test.n, r, test.exp) 29 | } 30 | } 31 | } 32 | 33 | func TestSkipWhile(t *testing.T) { 34 | src := linq.Range(0, 10) 35 | r, err := linq.ToSlice( 36 | linq.SkipWhile(src, func(n int) (bool, error) { return n < 5, nil })) 37 | if err != nil { 38 | t.Fatalf("%v", err) 39 | } 40 | exp := []int{5, 6, 7, 8, 9} 41 | if !reflect.DeepEqual(r, exp) { 42 | t.Fatalf("%v, wants %v", r, exp) 43 | } 44 | } 45 | 46 | func TestSkipLast(t *testing.T) { 47 | src := linq.Range(0, 10) 48 | r, err := linq.ToSlice( 49 | linq.SkipLast(src, 3)) 50 | if err != nil { 51 | t.Fatalf("%v", err) 52 | } 53 | exp := []int{0, 1, 2, 3, 4, 5, 6} 54 | if !reflect.DeepEqual(r, exp) { 55 | t.Fatalf("%v, wants %v", r, exp) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /groupjoin_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/makiuchi-d/linq/v2" 8 | ) 9 | 10 | func TestGroupJoin(t *testing.T) { 11 | type Person struct { 12 | Name string 13 | } 14 | 15 | type Pet struct { 16 | Name string 17 | Owner *Person 18 | } 19 | 20 | alice := &Person{"Alice"} 21 | bob := &Person{"Bob"} 22 | charlie := &Person{"Charlie"} 23 | 24 | abby := &Pet{"Abby", alice} 25 | bailey := &Pet{"Bailey", bob} 26 | bella := &Pet{"Bella", bob} 27 | cody := &Pet{"Cody", charlie} 28 | 29 | people := linq.FromSlice([]*Person{alice, bob, charlie}) 30 | pets := linq.FromSlice([]*Pet{cody, bella, abby, bailey}) 31 | 32 | r, err := linq.ToSlice( 33 | linq.GroupJoin( 34 | people, pets, 35 | func(p *Person) (*Person, error) { return p, nil }, 36 | func(p *Pet) (*Person, error) { return p.Owner, nil }, 37 | func(person *Person, pets linq.Enumerable[*Pet]) ([]string, error) { 38 | names, err := linq.ToSlice( 39 | linq.Select(pets, func(p *Pet) (string, error) { return p.Name, nil })) 40 | if err != nil { 41 | return nil, err 42 | } 43 | return append([]string{person.Name}, names...), nil 44 | })) 45 | if err != nil { 46 | t.Fatalf("%v", err) 47 | } 48 | 49 | exp := [][]string{ 50 | {"Alice", "Abby"}, 51 | {"Bob", "Bella", "Bailey"}, 52 | {"Charlie", "Cody"}, 53 | } 54 | if !reflect.DeepEqual(r, exp) { 55 | t.Fatalf("%v, wants %v", r, exp) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /groupby.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | // Grouping represents a collection of objects that have a common key. 4 | type Grouping[T any, K comparable] struct { 5 | Enumerable Enumerable[T] 6 | Key K 7 | } 8 | 9 | type groupByEnumerator[T any, K comparable] struct { 10 | src Enumerable[T] 11 | ksel func(T) (K, error) 12 | 13 | ks []K 14 | m map[K][]T 15 | i int 16 | } 17 | 18 | // GroupBy groups the elements of a sequence according to a specified key selector function. 19 | func GroupBy[T any, K comparable, E IEnumerable[T]](src E, keySelector func(T) (K, error)) Enumerable[Grouping[T, K]] { 20 | return func() Enumerator[Grouping[T, K]] { 21 | return &groupByEnumerator[T, K]{ 22 | src: Enumerable[T](src), 23 | ksel: keySelector, 24 | } 25 | } 26 | } 27 | 28 | func (e *groupByEnumerator[T, K]) Next() (def Grouping[T, K], _ error) { 29 | if e.ks == nil { 30 | ks := make([]K, 0) 31 | m := make(map[K][]T) 32 | err := ForEach(e.src, func(v T) error { 33 | k, err := e.ksel(v) 34 | if err != nil { 35 | return err 36 | } 37 | if _, ok := m[k]; !ok { 38 | ks = append(ks, k) 39 | } 40 | m[k] = append(m[k], v) 41 | return nil 42 | }) 43 | if err != nil { 44 | return def, err 45 | } 46 | e.ks = ks 47 | e.m = m 48 | } 49 | 50 | if e.i >= len(e.ks) { 51 | return def, EOC 52 | } 53 | 54 | k := e.ks[e.i] 55 | e.i++ 56 | return Grouping[T, K]{ 57 | Enumerable: FromSlice(e.m[k]), 58 | Key: k, 59 | }, nil 60 | } 61 | -------------------------------------------------------------------------------- /single_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/makiuchi-d/linq/v2" 8 | ) 9 | 10 | func TestSingle(t *testing.T) { 11 | src := linq.FromSlice([]int{1, 2, 3}) 12 | 13 | r, err := linq.Single(linq.Where(src, 14 | func(v int) (bool, error) { 15 | return v%2 == 0, nil 16 | })) 17 | if err != nil { 18 | t.Fatalf("%v", err) 19 | } 20 | exp := 2 21 | if r != exp { 22 | t.Fatalf("%v, wants %v", r, exp) 23 | } 24 | 25 | _, err = linq.Single(linq.Where(src, 26 | func(v int) (bool, error) { 27 | return v%2 != 0, nil 28 | })) 29 | if !errors.Is(err, linq.InvalidOperation) { 30 | t.Fatalf("%#v, wants %#v", err, linq.InvalidOperation) 31 | } 32 | } 33 | 34 | func TestSingleOrDefault(t *testing.T) { 35 | src := linq.FromSlice([]int{1, 2, 3}) 36 | def := 42 37 | r, err := linq.SingleOrDefault(linq.Where(src, 38 | func(v int) (bool, error) { return v%2 == 0, nil }), 39 | def) 40 | if err != nil { 41 | t.Fatalf("%v", err) 42 | } 43 | exp := 2 44 | if r != exp { 45 | t.Fatalf("%v, wants %v", r, exp) 46 | } 47 | r, err = linq.SingleOrDefault(linq.Empty[int](), def) 48 | if err != nil { 49 | t.Fatalf("%v", err) 50 | } 51 | exp = def 52 | if r != exp { 53 | t.Fatalf("%v, wants %v", r, exp) 54 | } 55 | _, err = linq.SingleOrDefault(linq.Where(src, 56 | func(v int) (bool, error) { return v%2 != 0, nil }), 57 | def) 58 | if !errors.Is(err, linq.InvalidOperation) { 59 | t.Fatalf("%#v, wants %#v", err, linq.InvalidOperation) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /take_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/makiuchi-d/linq/v2" 8 | ) 9 | 10 | func TestTake(t *testing.T) { 11 | tests := []struct { 12 | src linq.Enumerable[int] 13 | cnt int 14 | exp []int 15 | }{ 16 | {linq.Range(0, 5), 3, []int{0, 1, 2}}, 17 | {linq.Range(0, 5), 8, []int{0, 1, 2, 3, 4}}, 18 | } 19 | for _, test := range tests { 20 | r, err := linq.ToSlice( 21 | linq.Take(test.src, test.cnt)) 22 | if err != nil { 23 | t.Fatalf("%v: %v", test.cnt, err) 24 | } 25 | if !reflect.DeepEqual(r, test.exp) { 26 | t.Fatalf("%v, wants %v", r, test.exp) 27 | } 28 | } 29 | 30 | } 31 | 32 | func TestTakeWhile(t *testing.T) { 33 | src := linq.Range(0, 10) 34 | r, err := linq.ToSlice( 35 | linq.TakeWhile(src, func(n int) (bool, error) { return n < 5, nil })) 36 | if err != nil { 37 | t.Fatalf("%v", err) 38 | } 39 | exp := []int{0, 1, 2, 3, 4} 40 | if !reflect.DeepEqual(r, exp) { 41 | t.Fatalf("%v, wants %v", r, exp) 42 | } 43 | } 44 | 45 | func TestTakeLast(t *testing.T) { 46 | tests := []struct { 47 | src linq.Enumerable[int] 48 | n int 49 | exp []int 50 | }{ 51 | {linq.Range(0, 10), 5, []int{5, 6, 7, 8, 9}}, 52 | {linq.Range(0, 3), 5, []int{0, 1, 2}}, 53 | } 54 | for i, test := range tests { 55 | r, err := linq.ToSlice( 56 | linq.TakeLast(test.src, test.n)) 57 | if err != nil { 58 | t.Fatalf("%v: %v", i, err) 59 | } 60 | if !reflect.DeepEqual(r, test.exp) { 61 | t.Fatalf("%v, wants %v", r, test.exp) 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /iter_test.go: -------------------------------------------------------------------------------- 1 | //go:build go1.23 2 | 3 | package linq_test 4 | 5 | import ( 6 | "context" 7 | "reflect" 8 | "testing" 9 | 10 | "github.com/makiuchi-d/linq/v2" 11 | ) 12 | 13 | func TestIterAll(t *testing.T) { 14 | src := linq.FromSlice([]int{1, 2, 3, 4, 5}) 15 | c := 0 16 | s := 0 17 | for n, err := range src.All() { 18 | if err != nil { 19 | t.Fatalf("%v", err) 20 | } 21 | c++ 22 | s += n 23 | } 24 | if c != 5 || s != 15 { 25 | t.Fatalf("(c, s) = (%v, %v) wants (%v, %v)", c, s, 5, 15) 26 | } 27 | } 28 | 29 | func TestFromIterator(t *testing.T) { 30 | ctx, cancel := context.WithCancel(context.Background()) 31 | defer cancel() 32 | e := linq.FromIterator(ctx, func(yield func(int) bool) { 33 | _ = yield(1) && yield(2) && yield(3) && yield(4) && yield(5) 34 | }) 35 | e = linq.Take(e, 3) 36 | 37 | a, err := linq.ToSlice(e) 38 | if err != nil { 39 | t.Fatalf("%+v", err) 40 | } 41 | exp := []int{1, 2, 3} 42 | if !reflect.DeepEqual(a, exp) { 43 | t.Fatalf("ToSlice: %v, wants %v", a, exp) 44 | } 45 | } 46 | 47 | func TestFromIterator2(t *testing.T) { 48 | ctx, cancel := context.WithCancel(context.Background()) 49 | defer cancel() 50 | e := linq.FromIterator2(ctx, func(yield func(int, string) bool) { 51 | _ = yield(1, "one") && yield(2, "two") && yield(3, "three") 52 | }) 53 | 54 | a, err := linq.ToMap(e) 55 | if err != nil { 56 | t.Fatalf("%+v", err) 57 | } 58 | exp := map[int]string{1: "one", 2: "two", 3: "three"} 59 | if !reflect.DeepEqual(a, exp) { 60 | t.Fatalf("ToMap: %v, wants %v", a, exp) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /union_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/makiuchi-d/linq/v2" 8 | ) 9 | 10 | func TestUnion(t *testing.T) { 11 | fst := linq.FromSlice([]int{2, 3, 4, 5}) 12 | snd := linq.FromSlice([]int{0, 2, 4, 6, 8}) 13 | 14 | e := linq.Union( 15 | fst, snd, 16 | func(a, b int) (bool, error) { return a == b, nil }, 17 | func(a int) (int, error) { return a / 3, nil }) 18 | 19 | r, err := linq.ToSlice(e) 20 | if err != nil { 21 | t.Fatalf("%v", err) 22 | } 23 | exp := []int{2, 3, 4, 5, 0, 6, 8} 24 | if !reflect.DeepEqual(r, exp) { 25 | t.Fatalf("%v, wants %v", r, exp) 26 | } 27 | } 28 | 29 | func TestUnionBy(t *testing.T) { 30 | type Planet struct { 31 | Name string 32 | OrderFromSun int 33 | } 34 | fst := []Planet{ 35 | {"Marcury", 1}, 36 | {"Venus", 2}, 37 | {"Earth", 3}, 38 | {"Mars", 4}, 39 | {"Jupiter", 5}, 40 | } 41 | snd := []Planet{ 42 | {"Mars", 4}, 43 | {"Jupiter", 5}, 44 | {"Saturn", 6}, 45 | {"Uranus", 7}, 46 | {"Neptune", 8}, 47 | } 48 | 49 | e := linq.UnionBy( 50 | linq.FromSlice(fst), linq.FromSlice(snd), 51 | func(v Planet) (int, error) { return v.OrderFromSun, nil }) 52 | 53 | r, err := linq.ToSlice(e) 54 | if err != nil { 55 | t.Fatalf("%v", err) 56 | } 57 | exp := []Planet{ 58 | {"Marcury", 1}, 59 | {"Venus", 2}, 60 | {"Earth", 3}, 61 | {"Mars", 4}, 62 | {"Jupiter", 5}, 63 | {"Saturn", 6}, 64 | {"Uranus", 7}, 65 | {"Neptune", 8}, 66 | } 67 | if !reflect.DeepEqual(r, exp) { 68 | t.Fatalf("%v, wants %v", r, exp) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /union.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | type unionEnumerator[T any, H comparable] struct { 4 | fst Enumerator[T] 5 | snd Enumerator[T] 6 | hmap *hashMap[H, T] 7 | } 8 | 9 | // Union produces the set union of two sequences by using the specified comparer functions. 10 | func Union[T any, E IEnumerable[T]](first, second E, equals func(T, T) (bool, error), getHashCode func(T) (int, error)) Enumerable[T] { 11 | return func() Enumerator[T] { 12 | return &unionEnumerator[T, int]{ 13 | fst: first(), 14 | snd: second(), 15 | hmap: newHashMap(getHashCode, equals), 16 | } 17 | } 18 | } 19 | 20 | // UnionBy produces the set union of two sequences according to a specified key selector function. 21 | func UnionBy[T any, K comparable, E IEnumerable[T]](first, second E, keySelector func(v T) (K, error)) Enumerable[T] { 22 | return func() Enumerator[T] { 23 | return &unionEnumerator[T, K]{ 24 | fst: first(), 25 | snd: second(), 26 | hmap: newKeyMap(keySelector), 27 | } 28 | } 29 | } 30 | 31 | func (e *unionEnumerator[T, H]) Next() (def T, _ error) { 32 | if e.fst != nil { 33 | v, err := e.fst.Next() 34 | if err == nil { 35 | if _, err = e.hmap.add(v); err != nil { 36 | return def, err 37 | } 38 | return v, nil 39 | } 40 | if !isEOC(err) { 41 | return def, err 42 | } 43 | e.fst = nil 44 | } 45 | 46 | for { 47 | v, err := e.snd.Next() 48 | if err != nil { 49 | return def, err 50 | } 51 | has, err := e.hmap.has(v) 52 | if err != nil { 53 | return def, err 54 | } 55 | if !has { 56 | return v, nil 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /except.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | type exceptEnumerator[T any, H comparable] struct { 4 | fst Enumerator[T] 5 | snd Enumerator[T] 6 | eq func(T, T) (bool, error) 7 | hash func(T) (H, error) 8 | hmap *hashMap[H, T] 9 | } 10 | 11 | // Except produces the set difference of two sequences by using the specified comparer functions. 12 | func Except[T any, E IEnumerable[T]](first, second E, equals func(T, T) (bool, error), getHashCode func(T) (int, error)) Enumerable[T] { 13 | return func() Enumerator[T] { 14 | return &exceptEnumerator[T, int]{ 15 | fst: first(), 16 | snd: second(), 17 | eq: equals, 18 | hash: getHashCode, 19 | } 20 | } 21 | } 22 | 23 | // ExceptBy produces the set difference of two sequences according to a specified key selector function. 24 | func ExceptBy[T any, K comparable, E IEnumerable[T]](first, second E, keySelector func(v T) (K, error)) Enumerable[T] { 25 | return func() Enumerator[T] { 26 | return &exceptEnumerator[T, K]{ 27 | fst: first(), 28 | snd: second(), 29 | eq: alwaysEqual[T], 30 | hash: keySelector, 31 | } 32 | } 33 | } 34 | 35 | func (e *exceptEnumerator[T, H]) Next() (def T, _ error) { 36 | if e.hmap == nil { 37 | hm := newHashMap(e.hash, e.eq) 38 | if err := hm.addAll(e.snd); err != nil { 39 | return def, err 40 | } 41 | e.hmap = hm 42 | } 43 | 44 | for { 45 | v, err := e.fst.Next() 46 | if err != nil { 47 | return def, err 48 | } 49 | has, err := e.hmap.has(v) 50 | if err != nil { 51 | return def, err 52 | } 53 | if !has { 54 | return v, nil 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /except_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/makiuchi-d/linq/v2" 8 | ) 9 | 10 | func TestExcept(t *testing.T) { 11 | e1 := linq.FromSlice([]string{"Mercury", "Venus", "Earth", "Jupiter"}) 12 | e2 := linq.FromSlice([]string{"Mercury", "Earth", "Mars", "Jupiter"}) 13 | 14 | e := linq.Except(e1, e2, 15 | func(a, b string) (bool, error) { return a == b, nil }, 16 | func(a string) (int, error) { return len(a), nil }) 17 | 18 | r, err := linq.ToSlice(e) 19 | if err != nil { 20 | t.Fatalf("%v", err) 21 | } 22 | exp := []string{"Venus"} 23 | if !reflect.DeepEqual(r, exp) { 24 | t.Fatalf("%q, wants %q", r, exp) 25 | } 26 | } 27 | 28 | func TestExceptBy(t *testing.T) { 29 | type PlanetType int 30 | const ( 31 | Rock PlanetType = iota 32 | Ice 33 | Gas 34 | Liquid 35 | ) 36 | type Planet struct { 37 | Name string 38 | Type PlanetType 39 | OrderFromSun int 40 | } 41 | p1 := []Planet{ 42 | {"Marcury", Rock, 1}, 43 | {"Venus", Rock, 2}, 44 | {"Earth", Rock, 3}, 45 | {"Jupiter", Gas, 5}, 46 | } 47 | p2 := []Planet{ 48 | {"Marcury", Rock, 1}, 49 | {"Earth", Rock, 3}, 50 | {"Mars", Rock, 4}, 51 | {"Jupiter", Gas, 5}, 52 | } 53 | 54 | e := linq.ExceptBy( 55 | linq.FromSlice(p1), 56 | linq.FromSlice(p2), 57 | func(p Planet) (string, error) { return p.Name, nil }) 58 | 59 | r, err := linq.ToSlice(e) 60 | if err != nil { 61 | t.Fatalf("%v", err) 62 | } 63 | exp := []Planet{{"Venus", Rock, 2}} 64 | if !reflect.DeepEqual(r, exp) { 65 | t.Fatalf("%v, wants %v", r, exp) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /intersect.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | type intersectEnumerator[T any, H comparable] struct { 4 | fst Enumerator[T] 5 | snd Enumerator[T] 6 | eq func(T, T) (bool, error) 7 | hash func(T) (H, error) 8 | hmap *hashMap[H, T] 9 | } 10 | 11 | // Intersect produces the set intersection of two sequences by using the specified comparer functions. 12 | func Intersect[T any, E IEnumerable[T]](first, second E, equals func(T, T) (bool, error), getHashCode func(T) (int, error)) Enumerable[T] { 13 | return func() Enumerator[T] { 14 | return &intersectEnumerator[T, int]{ 15 | fst: first(), 16 | snd: second(), 17 | eq: equals, 18 | hash: getHashCode, 19 | } 20 | } 21 | } 22 | 23 | // IntersectBy produces the set intersection of two sequences according to a specified key selector function. 24 | func IntersectBy[T any, K comparable, E IEnumerable[T]](first, second E, keySelector func(v T) (K, error)) Enumerable[T] { 25 | return func() Enumerator[T] { 26 | return &intersectEnumerator[T, K]{ 27 | fst: first(), 28 | snd: second(), 29 | eq: alwaysEqual[T], 30 | hash: keySelector, 31 | } 32 | } 33 | } 34 | 35 | func (e *intersectEnumerator[T, H]) Next() (def T, _ error) { 36 | if e.hmap == nil { 37 | hm := newHashMap(e.hash, e.eq) 38 | if err := hm.addAll(e.snd); err != nil { 39 | return def, err 40 | } 41 | e.hmap = hm 42 | } 43 | 44 | for { 45 | v, err := e.fst.Next() 46 | if err != nil { 47 | return def, err 48 | } 49 | has, err := e.hmap.has(v) 50 | if err != nil { 51 | return def, err 52 | } 53 | if has { 54 | return v, nil 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /distinct_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/makiuchi-d/linq/v2" 8 | ) 9 | 10 | func TestDistinct(t *testing.T) { 11 | ages := []int{21, 46, 46, 55, 17, 21, 55, 55, 25, 25} 12 | 13 | e := linq.Distinct( 14 | linq.FromSlice(ages), 15 | func(a, b int) (bool, error) { return a == b, nil }, 16 | func(a int) (int, error) { return a / 10, nil }) 17 | 18 | r, err := linq.ToSlice(e) 19 | if err != nil { 20 | t.Fatalf("%v", err) 21 | } 22 | 23 | exp := []int{21, 46, 55, 17, 25} 24 | if !reflect.DeepEqual(r, exp) { 25 | t.Fatalf("%v, wants %v", r, exp) 26 | } 27 | } 28 | 29 | func TestDistinctBy(t *testing.T) { 30 | type PlanetType int 31 | const ( 32 | Rock PlanetType = iota 33 | Ice 34 | Gas 35 | Liquid 36 | ) 37 | type Planet struct { 38 | Name string 39 | Type PlanetType 40 | OrderFromSun int 41 | } 42 | planets := []Planet{ 43 | {"Marcury", Rock, 1}, 44 | {"Venus", Rock, 2}, 45 | {"Earth", Rock, 3}, 46 | {"Mars", Rock, 4}, 47 | {"Jupiter", Gas, 5}, 48 | {"Saturn", Gas, 6}, 49 | {"Uranus", Liquid, 7}, 50 | {"Neptune", Liquid, 8}, 51 | {"Pluto", Ice, 9}, // dwarf planet 52 | } 53 | 54 | e := linq.DistinctBy(linq.FromSlice(planets), func(p Planet) (PlanetType, error) { 55 | return p.Type, nil 56 | }) 57 | r, err := linq.ToSlice(linq.Select(e, func(p Planet) (string, error) { return p.Name, nil })) 58 | if err != nil { 59 | t.Fatalf("%v", err) 60 | } 61 | 62 | exp := []string{"Marcury", "Jupiter", "Uranus", "Pluto"} 63 | if !reflect.DeepEqual(r, exp) { 64 | t.Fatalf("%v, wants %v", r, exp) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /intersect_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/makiuchi-d/linq/v2" 8 | ) 9 | 10 | func TestIntersect(t *testing.T) { 11 | fst := linq.FromSlice([]int{1, 2, 3, 4, 5, 6, 7}) 12 | snd := linq.FromSlice([]int{0, 2, 4, 6, 8, 10}) 13 | 14 | e := linq.Intersect( 15 | fst, snd, 16 | func(a, b int) (bool, error) { return a == b, nil }, 17 | func(a int) (int, error) { return a / 3, nil }) 18 | 19 | r, err := linq.ToSlice(e) 20 | if err != nil { 21 | t.Fatalf("%v", err) 22 | } 23 | 24 | exp := []int{2, 4, 6} 25 | 26 | if !reflect.DeepEqual(r, exp) { 27 | t.Fatalf("%v, wants %v", r, exp) 28 | } 29 | } 30 | 31 | func TestIntersectBy(t *testing.T) { 32 | type PlanetType int 33 | const ( 34 | Rock PlanetType = iota 35 | Ice 36 | Gas 37 | Liquid 38 | ) 39 | type Planet struct { 40 | Name string 41 | Type PlanetType 42 | OrderFromSun int 43 | } 44 | p1 := []Planet{ 45 | {"Marcury", Rock, 1}, 46 | {"Venus", Rock, 2}, 47 | {"Earth", Rock, 3}, 48 | {"Jupiter", Gas, 5}, 49 | } 50 | p2 := []Planet{ 51 | {"Marcury", Rock, 1}, 52 | {"Earth", Rock, 3}, 53 | {"Mars", Rock, 4}, 54 | {"Jupiter", Gas, 5}, 55 | } 56 | 57 | e := linq.IntersectBy( 58 | linq.FromSlice(p1), 59 | linq.FromSlice(p2), 60 | func(p Planet) (string, error) { return p.Name, nil }) 61 | 62 | r, err := linq.ToSlice(e) 63 | if err != nil { 64 | t.Fatalf("%v", err) 65 | } 66 | exp := []Planet{ 67 | {"Marcury", Rock, 1}, 68 | {"Earth", Rock, 3}, 69 | {"Jupiter", Gas, 5}, 70 | } 71 | if !reflect.DeepEqual(r, exp) { 72 | t.Fatalf("%v, wants %v", r, exp) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /generator.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type generator[T any] struct { 8 | ch <-chan T 9 | resume func() error 10 | err error 11 | panic any 12 | } 13 | 14 | var _ Enumerator[int] = &generator[int]{} 15 | 16 | // Generator makes the given function an enumerator generator. 17 | func Generator[T any](ctx context.Context, g func(yield func(T) error) error) Enumerable[T] { 18 | return func() Enumerator[T] { 19 | do := make(chan struct{}) 20 | done := make(chan struct{}) 21 | ch := make(chan T) 22 | 23 | ge := &generator[T]{ch: ch} 24 | 25 | ge.resume = func() error { 26 | select { 27 | case <-done: 28 | return ge.err 29 | case do <- struct{}{}: 30 | return nil 31 | } 32 | } 33 | 34 | go func() { 35 | defer close(done) 36 | defer close(ch) 37 | defer func() { 38 | ge.panic = recover() 39 | }() 40 | 41 | select { 42 | case <-ctx.Done(): 43 | ge.err = ctx.Err() 44 | return 45 | case <-do: 46 | } 47 | 48 | yield := func(t T) error { 49 | select { 50 | case <-ctx.Done(): 51 | return ctx.Err() 52 | case ch <- t: 53 | } 54 | 55 | select { 56 | case <-ctx.Done(): 57 | return ctx.Err() 58 | case <-do: 59 | return nil 60 | } 61 | } 62 | 63 | ge.err = g(yield) 64 | if ge.err == nil { 65 | ge.err = EOC 66 | } 67 | }() 68 | 69 | return ge 70 | } 71 | } 72 | 73 | func (g *generator[T]) Next() (zero T, _ error) { 74 | if err := g.resume(); err != nil { 75 | return zero, err 76 | } 77 | t, ok := <-g.ch 78 | if !ok { 79 | if g.panic != nil { 80 | panic(g.panic) 81 | } 82 | return zero, g.err 83 | } 84 | return t, nil 85 | } 86 | -------------------------------------------------------------------------------- /iter.go: -------------------------------------------------------------------------------- 1 | //go:build go1.23 2 | 3 | package linq 4 | 5 | import ( 6 | "context" 7 | "iter" 8 | ) 9 | 10 | // All returns an iterator function 11 | func (e Enumerable[T]) All() iter.Seq2[T, error] { 12 | return func(yield func(T, error) bool) { 13 | er := e() 14 | for { 15 | v, err := er.Next() 16 | if err != nil { 17 | if isEOC(err) { 18 | return 19 | } 20 | yield(v, err) 21 | return 22 | } 23 | 24 | if !yield(v, nil) { 25 | return 26 | } 27 | } 28 | } 29 | } 30 | 31 | // All returns an iterator function 32 | func (e OrderedEnumerable[T]) All() iter.Seq2[T, error] { 33 | return func(yield func(T, error) bool) { 34 | er := e() 35 | for { 36 | v, err := er.Next() 37 | if err != nil { 38 | if isEOC(err) { 39 | return 40 | } 41 | yield(v, err) 42 | return 43 | } 44 | 45 | if !yield(v, nil) { 46 | return 47 | } 48 | } 49 | } 50 | } 51 | 52 | // FromIterator generates an IEnumerable[T] from an iterator function. 53 | func FromIterator[T any](ctx context.Context, it iter.Seq[T]) Enumerable[T] { 54 | return Generator(ctx, func(yield func(T) error) error { 55 | for v := range it { 56 | if err := yield(v); err != nil { 57 | return err 58 | } 59 | } 60 | return nil 61 | }) 62 | } 63 | 64 | // FromIterator2 generates an IEnumerable[KeyValue[K,V]] from an iterator function. 65 | func FromIterator2[K comparable, V any](ctx context.Context, it iter.Seq2[K, V]) Enumerable[KeyValue[K, V]] { 66 | return Generator(ctx, func(yield func(KeyValue[K, V]) error) error { 67 | for k, v := range it { 68 | if err := yield(KeyValue[K, V]{k, v}); err != nil { 69 | return err 70 | } 71 | } 72 | return nil 73 | }) 74 | } 75 | -------------------------------------------------------------------------------- /map_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "sort" 7 | "testing" 8 | 9 | "github.com/makiuchi-d/linq/v2" 10 | ) 11 | 12 | func TestMaps(t *testing.T) { 13 | m := map[int]string{ 14 | 1: "One", 15 | 3: "Three", 16 | 10: "Ten", 17 | 11: "Eleven", 18 | 12: "Twelve", 19 | 20: "Twenty", 20 | } 21 | 22 | e1 := linq.FromMap(m) 23 | 24 | e2 := linq.Where(e1, func(kv linq.KeyValue[int, string]) (bool, error) { return kv.Key%2 != 0, nil }) 25 | e3 := linq.Select(e2, func(kv linq.KeyValue[int, string]) (string, error) { return kv.Value, nil }) 26 | s, err := linq.ToSlice(e3) 27 | 28 | if err != nil { 29 | t.Fatalf("%v", err) 30 | } 31 | 32 | exp := []string{"One", "Three", "Eleven"} 33 | sort.Strings(s) 34 | sort.Strings(exp) 35 | if !reflect.DeepEqual(s, exp) { 36 | t.Fatalf("wants: %#v, got %#v", exp, s) 37 | } 38 | } 39 | 40 | func TestSliceToMap(t *testing.T) { 41 | s := []byte{5, 10, 15, 20} 42 | 43 | e1 := linq.FromSlice(s) 44 | e2 := linq.Select(e1, func(v byte) (linq.KeyValue[byte, string], error) { 45 | return linq.KV(v, fmt.Sprintf("%02x", v)), nil 46 | }) 47 | m, err := linq.ToMap(e2) 48 | if err != nil { 49 | t.Fatalf("%v", err) 50 | } 51 | 52 | exp := map[byte]string{ 53 | 5: "05", 54 | 10: "0a", 55 | 15: "0f", 56 | 20: "14", 57 | } 58 | if !reflect.DeepEqual(m, exp) { 59 | t.Fatalf("wants: %#v, got %#v", exp, m) 60 | } 61 | } 62 | 63 | func TestToMapFunc(t *testing.T) { 64 | s := []byte{5, 10, 15, 20} 65 | e1 := linq.FromSlice(s) 66 | m, err := linq.ToMapFunc(e1, func(v byte) (int, string, error) { 67 | return int(v), fmt.Sprintf("%02x", v), nil 68 | }) 69 | if err != nil { 70 | t.Fatalf("%v", err) 71 | } 72 | 73 | exp := map[int]string{ 74 | 5: "05", 75 | 10: "0a", 76 | 15: "0f", 77 | 20: "14", 78 | } 79 | if !reflect.DeepEqual(m, exp) { 80 | t.Fatalf("wants: %#v, got %#v", exp, m) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /map.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | // KeyValue pair as an element of map[K]V 4 | type KeyValue[K comparable, V any] struct { 5 | Key K 6 | Value V 7 | } 8 | 9 | // KV creates a KeyValue[K,V] 10 | func KV[K comparable, V any](k K, v V) KeyValue[K, V] { 11 | return KeyValue[K, V]{ 12 | Key: k, 13 | Value: v, 14 | } 15 | } 16 | 17 | type mapEnumerator[K comparable, V any] struct { 18 | m map[K]V 19 | k []K 20 | i int 21 | } 22 | 23 | // FromMap generates an IEnumerable[T] from a map. 24 | func FromMap[T ~map[K]V, K comparable, V any](m T) Enumerable[KeyValue[K, V]] { 25 | return func() Enumerator[KeyValue[K, V]] { 26 | ks := make([]K, 0, len(m)) 27 | for k := range m { 28 | ks = append(ks, k) 29 | } 30 | return &mapEnumerator[K, V]{m: m, k: ks} 31 | } 32 | } 33 | 34 | func (e *mapEnumerator[K, V]) Next() (def KeyValue[K, V], _ error) { 35 | if e.i >= len(e.k) { 36 | return def, EOC 37 | } 38 | k := e.k[e.i] 39 | e.i++ 40 | return KV(k, e.m[k]), nil 41 | } 42 | 43 | // ToMap creates a map[K]V from an IEnumerable[T]. 44 | // T must be a type KeyValue[K, V]. 45 | func ToMap[K comparable, V any, E IEnumerable[KeyValue[K, V]]](src E) (map[K]V, error) { 46 | e := src() 47 | m := make(map[K]V) 48 | for { 49 | kv, err := e.Next() 50 | if err != nil { 51 | if isEOC(err) { 52 | return m, nil 53 | } 54 | return m, err 55 | } 56 | 57 | m[kv.Key] = kv.Value 58 | } 59 | } 60 | 61 | // ToMapFunc creates a map[K]V from an IEnumerable[T] according to specified key-value selector function. 62 | func ToMapFunc[T any, K comparable, V any, E IEnumerable[T]](src E, selector func(T) (K, V, error)) (map[K]V, error) { 63 | e := src() 64 | m := make(map[K]V) 65 | for { 66 | t, err := e.Next() 67 | if err != nil { 68 | if isEOC(err) { 69 | return m, nil 70 | } 71 | return m, err 72 | } 73 | 74 | k, v, err := selector(t) 75 | if err != nil { 76 | return m, err 77 | } 78 | 79 | m[k] = v 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /join.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | type joinEnumerator[S1, S2, T any, K comparable] struct { 4 | eOut Enumerator[S1] 5 | eIn Enumerator[S2] 6 | ksOut func(S1) (K, error) 7 | ksIn func(S2) (K, error) 8 | rSel func(S1, S2) (T, error) 9 | 10 | s1 *S1 11 | ks1 K 12 | ms2 map[K][]S2 13 | i int 14 | } 15 | 16 | // Join correlates the elements of two sequences based on matching keys. 17 | func Join[S1, S2, T any, K comparable, E1 IEnumerable[S1], E2 IEnumerable[S2]]( 18 | outer E1, 19 | inner E2, 20 | outerKeySelector func(S1) (K, error), 21 | innerKeySelector func(S2) (K, error), 22 | resultSelector func(S1, S2) (T, error), 23 | ) Enumerable[T] { 24 | return func() Enumerator[T] { 25 | return &joinEnumerator[S1, S2, T, K]{ 26 | eOut: outer(), 27 | eIn: inner(), 28 | ksOut: outerKeySelector, 29 | ksIn: innerKeySelector, 30 | rSel: resultSelector, 31 | } 32 | } 33 | } 34 | 35 | func (e *joinEnumerator[S1, S2, T, K]) Next() (def T, _ error) { 36 | if e.s1 == nil { 37 | s1, err := e.eOut.Next() 38 | if err != nil { 39 | return def, err 40 | } 41 | ks1, err := e.ksOut(s1) 42 | if err != nil { 43 | return def, err 44 | } 45 | e.s1 = &s1 46 | e.ks1 = ks1 47 | } 48 | 49 | if e.ms2 == nil { 50 | m, err := innerMap(e.eIn, e.ksIn) 51 | if err != nil { 52 | return def, err 53 | } 54 | e.ms2 = m 55 | } 56 | 57 | s := e.ms2[e.ks1] 58 | 59 | if e.i >= len(s) { 60 | e.i = 0 61 | e.s1 = nil 62 | return e.Next() 63 | } 64 | 65 | i := e.i 66 | e.i++ 67 | 68 | return e.rSel(*e.s1, s[i]) 69 | } 70 | 71 | func innerMap[S2 any, K comparable](e Enumerator[S2], ks func(S2) (K, error)) (map[K][]S2, error) { 72 | m := make(map[K][]S2) 73 | for { 74 | s2, err := e.Next() 75 | if err != nil { 76 | if isEOC(err) { 77 | return m, nil 78 | } 79 | return nil, err 80 | } 81 | 82 | k, err := ks(s2) 83 | if err != nil { 84 | return nil, err 85 | } 86 | m[k] = append(m[k], s2) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /min.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | import "golang.org/x/exp/constraints" 4 | 5 | // Min returns the minimum value in a sequence of values. 6 | func Min[T constraints.Ordered, E IEnumerable[T]](src E) (def T, _ error) { 7 | e := src() 8 | min, err := e.Next() 9 | if err != nil { 10 | if isEOC(err) { 11 | err = InvalidOperation 12 | } 13 | return def, err 14 | } 15 | for { 16 | v, err := e.Next() 17 | if err != nil { 18 | if isEOC(err) { 19 | return min, nil 20 | } 21 | return def, err 22 | } 23 | if v < min { 24 | min = v 25 | } 26 | } 27 | } 28 | 29 | // MinBy returns the minimum value in a generic sequence according to a specified key selector function. 30 | func MinBy[T any, K constraints.Ordered, E IEnumerable[T]](src E, keySelector func(T) (K, error)) (def T, _ error) { 31 | e := src() 32 | min, err := e.Next() 33 | if err != nil { 34 | if isEOC(err) { 35 | err = InvalidOperation 36 | } 37 | return def, err 38 | } 39 | mink, err := keySelector(min) 40 | if err != nil { 41 | return def, err 42 | } 43 | for { 44 | v, err := e.Next() 45 | if err != nil { 46 | if isEOC(err) { 47 | return min, nil 48 | } 49 | return def, err 50 | } 51 | k, err := keySelector(v) 52 | if err != nil { 53 | return def, err 54 | } 55 | if k < mink { 56 | min, mink = v, k 57 | } 58 | } 59 | } 60 | 61 | // MinByFunc returns the minimum value in a generic sequence according to a comparer function. 62 | func MinByFunc[T any, E IEnumerable[T]](src E, less func(a, b T) (bool, error)) (def T, _ error) { 63 | e := src() 64 | min, err := e.Next() 65 | if err != nil { 66 | if isEOC(err) { 67 | err = InvalidOperation 68 | } 69 | return def, err 70 | } 71 | for { 72 | v, err := e.Next() 73 | if err != nil { 74 | if isEOC(err) { 75 | return min, nil 76 | } 77 | return def, err 78 | } 79 | b, err := less(v, min) 80 | if err != nil { 81 | return def, err 82 | } 83 | if b { 84 | min = v 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /max.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | import "golang.org/x/exp/constraints" 4 | 5 | // Maxby returns the maximum value in a sequence of values. 6 | func Max[T constraints.Ordered, E IEnumerable[T]](src E) (def T, _ error) { 7 | e := src() 8 | max, err := e.Next() 9 | if err != nil { 10 | if isEOC(err) { 11 | err = InvalidOperation 12 | } 13 | return def, err 14 | } 15 | for { 16 | v, err := e.Next() 17 | if err != nil { 18 | if isEOC(err) { 19 | return max, nil 20 | } 21 | return def, err 22 | } 23 | if v > max { 24 | max = v 25 | } 26 | } 27 | } 28 | 29 | // MaxBy returns the maximum value in a generic sequence according to a specified key selector function. 30 | func MaxBy[T any, K constraints.Ordered, E IEnumerable[T]](src E, keySelector func(T) (K, error)) (def T, _ error) { 31 | e := src() 32 | max, err := e.Next() 33 | if err != nil { 34 | if isEOC(err) { 35 | err = InvalidOperation 36 | } 37 | return def, err 38 | } 39 | maxk, err := keySelector(max) 40 | if err != nil { 41 | return def, err 42 | } 43 | for { 44 | v, err := e.Next() 45 | if err != nil { 46 | if isEOC(err) { 47 | return max, nil 48 | } 49 | return def, err 50 | } 51 | k, err := keySelector(v) 52 | if err != nil { 53 | return def, err 54 | } 55 | if k > maxk { 56 | max, maxk = v, k 57 | } 58 | } 59 | } 60 | 61 | // MaxByFunc returns the maximum value in a generic sequence according to a comparer function. 62 | func MaxByFunc[T any, E IEnumerable[T]](src E, greater func(a, b T) (bool, error)) (def T, _ error) { 63 | e := src() 64 | max, err := e.Next() 65 | if err != nil { 66 | if isEOC(err) { 67 | err = InvalidOperation 68 | } 69 | return def, err 70 | } 71 | for { 72 | v, err := e.Next() 73 | if err != nil { 74 | if isEOC(err) { 75 | return max, nil 76 | } 77 | return def, err 78 | } 79 | b, err := greater(v, max) 80 | if err != nil { 81 | return def, err 82 | } 83 | if b { 84 | max = v 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | import "errors" 4 | 5 | func isEOC(err error) bool { 6 | return errors.Is(err, EOC) 7 | } 8 | 9 | func isOutOfRange(err error) bool { 10 | return errors.Is(err, OutOfRange) 11 | } 12 | 13 | func isInvalidOperation(err error) bool { 14 | return errors.Is(err, InvalidOperation) 15 | } 16 | 17 | type hashMap[H comparable, V any] struct { 18 | m map[H][]V 19 | hash func(V) (H, error) 20 | eq func(V, V) (bool, error) 21 | } 22 | 23 | func newHashMap[H comparable, V any](hash func(V) (H, error), eq func(V, V) (bool, error)) *hashMap[H, V] { 24 | return &hashMap[H, V]{ 25 | m: make(map[H][]V), 26 | hash: hash, 27 | eq: eq, 28 | } 29 | } 30 | 31 | func newKeyMap[K comparable, V any](ks func(V) (K, error)) *hashMap[K, V] { 32 | return newHashMap(ks, alwaysEqual[V]) 33 | } 34 | 35 | func alwaysEqual[T any](_, _ T) (bool, error) { 36 | return true, nil 37 | } 38 | 39 | func alwaysNotEqual[T any](_, _ T) (bool, error) { 40 | return false, nil 41 | } 42 | 43 | func (hm *hashMap[H, V]) gets(h H) []V { 44 | return hm.m[h] 45 | } 46 | 47 | func (hm *hashMap[H, V]) has(v V) (bool, error) { 48 | h, err := hm.hash(v) 49 | if err != nil { 50 | return false, err 51 | } 52 | for _, u := range hm.m[h] { 53 | eq, err := hm.eq(u, v) 54 | if err != nil { 55 | return false, err 56 | } 57 | if eq { 58 | return true, nil 59 | } 60 | } 61 | return false, nil 62 | } 63 | 64 | func (hm *hashMap[H, V]) add(v V) (bool, error) { 65 | h, err := hm.hash(v) 66 | if err != nil { 67 | return false, err 68 | } 69 | for _, u := range hm.m[h] { 70 | eq, err := hm.eq(u, v) 71 | if err != nil { 72 | return false, err 73 | } 74 | if eq { 75 | return false, nil 76 | } 77 | } 78 | hm.m[h] = append(hm.m[h], v) 79 | return true, nil 80 | } 81 | 82 | func (hm *hashMap[H, V]) addAll(e Enumerator[V]) error { 83 | for { 84 | v, err := e.Next() 85 | if err != nil { 86 | if isEOC(err) { 87 | return nil 88 | } 89 | return err 90 | } 91 | 92 | if _, err = hm.add(v); err != nil { 93 | return err 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /take.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | type takeEnumerator[T any] struct { 4 | src Enumerator[T] 5 | cnt int 6 | } 7 | 8 | // Take returns a specified number of contiguous elements from the start of a sequence. 9 | func Take[T any, E IEnumerable[T]](src E, count int) Enumerable[T] { 10 | return func() Enumerator[T] { 11 | return &takeEnumerator[T]{src: src(), cnt: count} 12 | } 13 | } 14 | 15 | func (e *takeEnumerator[T]) Next() (def T, _ error) { 16 | if e.cnt <= 0 { 17 | return def, EOC 18 | } 19 | e.cnt-- 20 | return e.src.Next() 21 | } 22 | 23 | type takeWhileEnumerator[T any] struct { 24 | src Enumerator[T] 25 | pred func(T) (bool, error) 26 | } 27 | 28 | // TakeWhile returns elements from a sequence as long as a specified condition is true, and then skips the remaining elements. 29 | func TakeWhile[T any, E IEnumerable[T]](src E, pred func(T) (bool, error)) Enumerable[T] { 30 | return func() Enumerator[T] { 31 | return &takeWhileEnumerator[T]{src: src(), pred: pred} 32 | } 33 | } 34 | 35 | func (e *takeWhileEnumerator[T]) Next() (def T, _ error) { 36 | v, err := e.src.Next() 37 | if err != nil { 38 | return def, err 39 | } 40 | ok, err := e.pred(v) 41 | if err != nil { 42 | return def, err 43 | } 44 | if !ok { 45 | return def, EOC 46 | } 47 | return v, nil 48 | } 49 | 50 | type takeLastEnumerator[T any] struct { 51 | src Enumerator[T] 52 | cnt int 53 | buf []T 54 | ofs int 55 | i int 56 | } 57 | 58 | // TakeLast returns a new enumerable collection that contains the last count elements from source. 59 | func TakeLast[T any, E IEnumerable[T]](src E, count int) Enumerable[T] { 60 | return func() Enumerator[T] { 61 | return &takeLastEnumerator[T]{src: src(), cnt: count} 62 | } 63 | } 64 | 65 | func (e *takeLastEnumerator[T]) Next() (def T, _ error) { 66 | if e.buf == nil { 67 | e.buf = make([]T, e.cnt) 68 | i := 0 69 | for ; ; i++ { 70 | v, err := e.src.Next() 71 | if err != nil { 72 | if isEOC(err) { 73 | break 74 | } 75 | return def, err 76 | } 77 | e.buf[i%e.cnt] = v 78 | } 79 | if i < e.cnt { 80 | e.buf = e.buf[:i] 81 | } else { 82 | e.ofs = i 83 | } 84 | } 85 | i := e.i 86 | if i >= len(e.buf) { 87 | return def, EOC 88 | } 89 | e.i++ 90 | return e.buf[(e.ofs+i)%len(e.buf)], nil 91 | } 92 | -------------------------------------------------------------------------------- /skip.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | type skipEnumerator[T any] struct { 4 | src Enumerator[T] 5 | cnt int 6 | } 7 | 8 | // Skip bypasses a specified number of elements in a sequence and then returns the remaining elements. 9 | func Skip[T any, E IEnumerable[T]](src E, count int) Enumerable[T] { 10 | return func() Enumerator[T] { 11 | return &skipEnumerator[T]{src: src(), cnt: count} 12 | } 13 | } 14 | 15 | func (e *skipEnumerator[T]) Next() (def T, _ error) { 16 | for ; e.cnt > 0; e.cnt-- { 17 | _, err := e.src.Next() 18 | if err != nil { 19 | return def, err 20 | } 21 | } 22 | return e.src.Next() 23 | } 24 | 25 | type skipWhileEnumerator[T any] struct { 26 | src Enumerator[T] 27 | pred func(T) (bool, error) 28 | skipped bool 29 | } 30 | 31 | // SkipWhile bypasses elements in a sequence as long as a specified condition is true and then returns the remaining elements. 32 | func SkipWhile[T any, E IEnumerable[T]](src E, pred func(T) (bool, error)) Enumerable[T] { 33 | return func() Enumerator[T] { 34 | return &skipWhileEnumerator[T]{src: src(), pred: pred} 35 | } 36 | } 37 | 38 | func (e *skipWhileEnumerator[T]) Next() (def T, _ error) { 39 | if e.skipped { 40 | return e.src.Next() 41 | } 42 | for { 43 | v, err := e.src.Next() 44 | if err != nil { 45 | return def, err 46 | } 47 | ok, err := e.pred(v) 48 | if err != nil { 49 | return def, err 50 | } 51 | if !ok { 52 | e.skipped = true 53 | return v, nil 54 | } 55 | } 56 | } 57 | 58 | type skipLastEnumerator[T any] struct { 59 | src Enumerator[T] 60 | cnt int 61 | buf []T 62 | i int 63 | } 64 | 65 | // SkipLast returns a new enumerable collection that contains the elements from source with the last count elements of the source collection omitted. 66 | func SkipLast[T any, E IEnumerable[T]](src E, count int) Enumerable[T] { 67 | return func() Enumerator[T] { 68 | return &skipLastEnumerator[T]{src: src(), cnt: count} 69 | } 70 | } 71 | 72 | func (e *skipLastEnumerator[T]) Next() (def T, _ error) { 73 | if e.buf == nil { 74 | e.buf = make([]T, e.cnt) 75 | for i := 0; i < e.cnt; i++ { 76 | v, err := e.src.Next() 77 | if err != nil { 78 | return def, err 79 | } 80 | e.buf[i] = v 81 | } 82 | } 83 | 84 | i := e.i % e.cnt 85 | r := e.buf[i] 86 | v, err := e.src.Next() 87 | if err != nil { 88 | return def, err 89 | } 90 | e.buf[i] = v 91 | e.i++ 92 | return r, nil 93 | } 94 | -------------------------------------------------------------------------------- /orderby_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/makiuchi-d/linq/v2" 7 | ) 8 | 9 | func TestOrderBy(t *testing.T) { 10 | fruits := []string{ 11 | "grape", "passionfruit", "banana", "mango", 12 | "orange", "raspberry", "apple", "blueberry"} 13 | 14 | e1 := linq.FromSlice(fruits) 15 | e2 := linq.OrderBy(e1, func(v string) (int, error) { return len(v), nil }) 16 | r, err := linq.ToSlice(e2) 17 | if err != nil { 18 | t.Fatalf("%v", err) 19 | } 20 | 21 | for i := 1; i < len(r); i++ { 22 | if len(r[i-1]) > len(r[i]) { 23 | t.Fatalf("len(%q) > len(%q)\n\t%v", r[i-1], r[i], r) 24 | } 25 | } 26 | } 27 | 28 | func TestOrderByDescending(t *testing.T) { 29 | fruits := []string{ 30 | "grape", "passionfruit", "banana", "mango", 31 | "orange", "raspberry", "apple", "blueberry"} 32 | 33 | e1 := linq.FromSlice(fruits) 34 | e2 := linq.OrderByDescending(e1, func(v string) (int, error) { return len(v), nil }) 35 | r, err := linq.ToSlice(e2) 36 | if err != nil { 37 | t.Fatalf("%v", err) 38 | } 39 | 40 | for i := 1; i < len(r); i++ { 41 | if len(r[i-1]) < len(r[i]) { 42 | t.Fatalf("len(%q) < len(%q)\n\t%v", r[i-1], r[i], r) 43 | } 44 | } 45 | } 46 | 47 | func TestThenBy(t *testing.T) { 48 | fruits := []string{ 49 | "grape", "passionfruit", "banana", "mango", 50 | "orange", "raspberry", "apple", "blueberry"} 51 | 52 | e1 := linq.FromSlice(fruits) 53 | e2 := linq.OrderByDescending(e1, func(v string) (int, error) { return len(v), nil }) 54 | e3 := linq.ThenBy(e2, func(v string) (string, error) { return v, nil }) 55 | r, err := linq.ToSlice(e3) 56 | if err != nil { 57 | t.Fatalf("%v", err) 58 | } 59 | 60 | for i := 1; i < len(r); i++ { 61 | if len(r[i-1]) < len(r[i]) { 62 | t.Fatalf("len(%q) < len(%q)\n\t%v", r[i-1], r[i], r) 63 | } 64 | if len(r[i-1]) == len(r[i]) && r[i-1] > r[i] { 65 | t.Fatalf("%q > %q\n\t%v", r[i-1], r[i], r) 66 | } 67 | } 68 | } 69 | 70 | func TestThenByDescending(t *testing.T) { 71 | fruits := []string{ 72 | "grape", "passionfruit", "banana", "mango", 73 | "orange", "raspberry", "apple", "blueberry"} 74 | 75 | e1 := linq.FromSlice(fruits) 76 | e2 := linq.OrderBy(e1, func(v string) (int, error) { return len(v), nil }) 77 | e3 := linq.ThenByDescending(e2, func(v string) (string, error) { return v, nil }) 78 | r, err := linq.ToSlice(e3) 79 | if err != nil { 80 | t.Fatalf("%v", err) 81 | } 82 | 83 | for i := 1; i < len(r); i++ { 84 | if len(r[i-1]) > len(r[i]) { 85 | t.Fatalf("len(%q) > len(%q)\n\t%v", r[i-1], r[i], r) 86 | } 87 | if len(r[i-1]) == len(r[i]) && r[i-1] < r[i] { 88 | t.Fatalf("%q < %q\n\t%v", r[i-1], r[i], r) 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /generator_test.go: -------------------------------------------------------------------------------- 1 | package linq_test 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "reflect" 8 | "testing" 9 | 10 | "github.com/makiuchi-d/linq/v2" 11 | ) 12 | 13 | func ExampleGenerator() { 14 | ctx, cancel := context.WithCancel(context.Background()) 15 | defer cancel() 16 | 17 | gen := linq.Generator(ctx, func(yield func(int) error) error { 18 | for i := 0; i < 10; i++ { 19 | err := yield(i) 20 | if err != nil { 21 | return err 22 | } 23 | } 24 | return nil 25 | }) 26 | 27 | r, err := linq.ToSlice(gen) 28 | if err != nil { 29 | panic(err) 30 | } 31 | fmt.Println(r) 32 | // Output: 33 | // [0 1 2 3 4 5 6 7 8 9] 34 | } 35 | 36 | func TestGenerator(t *testing.T) { 37 | ctx, cancel := context.WithCancel(context.Background()) 38 | defer cancel() 39 | 40 | gen := linq.Generator(ctx, func(yield func(int) error) error { 41 | for i := 0; i < 10; i++ { 42 | err := yield(i) 43 | if err != nil { 44 | return err 45 | } 46 | } 47 | return nil 48 | }) 49 | 50 | r, err := linq.ToSlice(gen) 51 | if err != nil { 52 | t.Fatalf("%v", err) 53 | } 54 | 55 | exp := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} 56 | if !reflect.DeepEqual(r, exp) { 57 | t.Fatalf("wants: %#v, got %#v", r, exp) 58 | } 59 | } 60 | 61 | func TestGeneratorError(t *testing.T) { 62 | ctx, cancel := context.WithCancel(context.Background()) 63 | defer cancel() 64 | 65 | experr := errors.New("expect error") 66 | 67 | gen := linq.Generator(ctx, func(yield func(int) error) error { 68 | err := yield(1) 69 | if err != nil { 70 | return err 71 | } 72 | return experr 73 | }) 74 | 75 | g := gen() 76 | n, err := g.Next() 77 | if n != 1 || err != nil { 78 | t.Fatalf("Next: %v, %v, wants 1, nil", n, err) 79 | } 80 | _, err = g.Next() 81 | if !errors.Is(err, experr) { 82 | t.Fatalf("error: %q, wants %q", err, experr) 83 | } 84 | 85 | _, err = g.Next() 86 | if !errors.Is(err, experr) { 87 | t.Fatalf("error: %q, wants %q", err, experr) 88 | } 89 | } 90 | 91 | func TestGeneratorCancel(t *testing.T) { 92 | ctx, cancel := context.WithCancel(context.Background()) 93 | 94 | gen := linq.Generator(ctx, func(yield func(int) error) error { 95 | for i := 1; i <= 3; i++ { 96 | err := yield(i) 97 | if err != nil { 98 | return err 99 | } 100 | } 101 | return nil 102 | }) 103 | 104 | g := gen() 105 | n, err := g.Next() 106 | if n != 1 || err != nil { 107 | t.Fatalf("Next: %v, %v, wants 1, nil", n, err) 108 | } 109 | cancel() 110 | _, err = g.Next() 111 | if err == nil || !errors.Is(err, ctx.Err()) { 112 | t.Fatalf("error: %q, wants \"context canceled\"", err) 113 | } 114 | 115 | _, err = g.Next() 116 | if err == nil || !errors.Is(err, ctx.Err()) { 117 | t.Fatalf("error: %q, wants \"context canceled\"", err) 118 | } 119 | } 120 | 121 | func TestGeneratorPanic(t *testing.T) { 122 | ctx, cancel := context.WithCancel(context.Background()) 123 | defer cancel() 124 | 125 | ep := errors.New("expect panic") 126 | 127 | gen := linq.Generator(ctx, func(yield func(int) error) error { 128 | err := yield(1) 129 | if err != nil { 130 | return err 131 | } 132 | panic(ep) 133 | }) 134 | 135 | g := gen() 136 | n, err := g.Next() 137 | if n != 1 || err != nil { 138 | t.Fatalf("Next: %v, %v, wants 1, nil", n, err) 139 | } 140 | 141 | defer func() { 142 | p := recover() 143 | if p != ep { 144 | t.Fatalf("panic: %v, wants %v", p, ep) 145 | } 146 | }() 147 | 148 | _, _ = g.Next() 149 | 150 | t.Fatalf("no panic") 151 | } 152 | -------------------------------------------------------------------------------- /orderby.go: -------------------------------------------------------------------------------- 1 | package linq 2 | 3 | import ( 4 | "sort" 5 | 6 | "golang.org/x/exp/constraints" 7 | ) 8 | 9 | type orderedEnumerator[T any] struct { 10 | src Enumerator[T] 11 | newcmps []func([]T) (comparer, error) 12 | sorted []T 13 | i int 14 | } 15 | 16 | // OrderBy sorts the elements of a sequence in ascending order according to a key. 17 | func OrderBy[T any, K constraints.Ordered, E IEnumerable[T]](src E, keySelector func(T) (K, error)) OrderedEnumerable[T] { 18 | return func() Enumerator[T] { 19 | return &orderedEnumerator[T]{ 20 | src: src(), 21 | newcmps: []func([]T) (comparer, error){ 22 | newKeyComparer[kCmpAsc[K]](keySelector), 23 | }, 24 | } 25 | } 26 | } 27 | 28 | // OrderByDescending sorts the elements of a sequence in descending order according to a key. 29 | func OrderByDescending[T any, K constraints.Ordered, E IEnumerable[T]](src E, keySelector func(T) (K, error)) OrderedEnumerable[T] { 30 | return func() Enumerator[T] { 31 | return &orderedEnumerator[T]{ 32 | src: src(), 33 | newcmps: []func([]T) (comparer, error){ 34 | newKeyComparer[kCmpDesc[K]](keySelector), 35 | }, 36 | } 37 | } 38 | } 39 | 40 | // ThenBy performs a subsequent ordering of the elements in a sequence in ascending order according to a key. 41 | func ThenBy[T any, K constraints.Ordered](src OrderedEnumerable[T], keySelector func(T) (K, error)) OrderedEnumerable[T] { 42 | return func() Enumerator[T] { 43 | oe := src().(*orderedEnumerator[T]) 44 | return &orderedEnumerator[T]{ 45 | src: oe.src, 46 | newcmps: append(oe.newcmps, newKeyComparer[kCmpAsc[K]](keySelector)), 47 | } 48 | } 49 | } 50 | 51 | // ThenByDescending performs a subsequent ordering of the elements in a sequence in descending order, according to a key. 52 | func ThenByDescending[T any, K constraints.Ordered](src OrderedEnumerable[T], keySelector func(T) (K, error)) OrderedEnumerable[T] { 53 | return func() Enumerator[T] { 54 | oe := src().(*orderedEnumerator[T]) 55 | return &orderedEnumerator[T]{ 56 | src: oe.src, 57 | newcmps: append(oe.newcmps, newKeyComparer[kCmpDesc[K]](keySelector)), 58 | } 59 | } 60 | } 61 | 62 | func (e *orderedEnumerator[T]) Next() (def T, _ error) { 63 | if e.sorted == nil { 64 | s, err := doSort(e.src, e.newcmps) 65 | if err != nil { 66 | return def, err 67 | } 68 | e.sorted = s 69 | } 70 | if e.i >= len(e.sorted) { 71 | return def, EOC 72 | } 73 | i := e.i 74 | e.i++ 75 | return e.sorted[i], nil 76 | } 77 | 78 | type comparer interface { 79 | compare(i, j int) int 80 | swap(i, j int) 81 | } 82 | 83 | type kCmpAsc[K constraints.Ordered] []K 84 | type kCmpDesc[K constraints.Ordered] []K 85 | 86 | var _ comparer = kCmpAsc[int]{} 87 | var _ comparer = kCmpDesc[int]{} 88 | 89 | type keyComparer[K constraints.Ordered] interface { 90 | kCmpAsc[K] | kCmpDesc[K] 91 | comparer 92 | } 93 | 94 | func newKeyComparer[C keyComparer[K], T any, K constraints.Ordered](keysel func(T) (K, error)) func(s []T) (comparer, error) { 95 | return func(s []T) (comparer, error) { 96 | ks := make([]K, len(s)) 97 | for i, t := range s { 98 | k, err := keysel(t) 99 | if err != nil { 100 | return nil, err 101 | } 102 | ks[i] = k 103 | } 104 | return C(ks), nil 105 | } 106 | } 107 | 108 | func (c kCmpAsc[K]) compare(i, j int) int { 109 | switch { 110 | case c[i] < c[j]: 111 | return -1 112 | case c[i] > c[j]: 113 | return 1 114 | default: 115 | return 0 116 | } 117 | } 118 | 119 | func (c kCmpAsc[K]) swap(i, j int) { 120 | c[i], c[j] = c[j], c[i] 121 | } 122 | 123 | func (c kCmpDesc[K]) compare(i, j int) int { 124 | switch { 125 | case c[i] < c[j]: 126 | return 1 127 | case c[i] > c[j]: 128 | return -1 129 | default: 130 | return 0 131 | } 132 | } 133 | 134 | func (c kCmpDesc[K]) swap(i, j int) { 135 | c[i], c[j] = c[j], c[i] 136 | } 137 | 138 | func doSort[T any](src Enumerator[T], newcmps []func([]T) (comparer, error)) ([]T, error) { 139 | s, err := toSlice(src) 140 | if err != nil { 141 | return nil, err 142 | } 143 | cmps := make([]comparer, len(newcmps)) 144 | for i, newcmp := range newcmps { 145 | cmps[i], err = newcmp(s) 146 | if err != nil { 147 | return nil, err 148 | } 149 | } 150 | sort.Sort(&sorter[T]{src: s, cmps: cmps}) 151 | return s, nil 152 | } 153 | 154 | type sorter[T any] struct { 155 | src []T 156 | cmps []comparer 157 | } 158 | 159 | var _ sort.Interface = &sorter[int]{} 160 | 161 | func (s *sorter[T]) Len() int { 162 | return len(s.src) 163 | } 164 | 165 | func (s *sorter[T]) Less(i, j int) bool { 166 | for _, cmp := range s.cmps { 167 | switch c := cmp.compare(i, j); true { 168 | case c < 0: 169 | return true 170 | case c > 0: 171 | return false 172 | } 173 | } 174 | return true 175 | } 176 | 177 | func (s *sorter[T]) Swap(i, j int) { 178 | s.src[i], s.src[j] = s.src[j], s.src[i] 179 | for _, cmp := range s.cmps { 180 | cmp.swap(i, j) 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LINQ for Go with type parameters 2 | 3 | LINQ (Language-Integrated Query) is a component that adds data querying capabilities of Microsoft .Net languages. 4 | This package provides the implementation of the LINQ functions for Go with type parameters. 5 | 6 | ## Quick Start 7 | 8 | ### Install 9 | 10 | ```Shell 11 | go get github.com/makiuchi-d/linq/v2 12 | ``` 13 | 14 | ### Example 15 | 16 | ```Go 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | 22 | "github.com/makiuchi-d/linq/v2" 23 | ) 24 | 25 | type Student struct { 26 | Name string 27 | Class string 28 | Score int 29 | } 30 | 31 | func main() { 32 | students := []Student{ 33 | // generated by https://testdata.userlocal.jp/ 34 | {"熊木 緑", "1-A", 953}, 35 | {"山本 千佳子", "1-C", 559}, 36 | {"星 雅彦", "1-B", 136}, 37 | {"齊藤 綾子", "1-A", 149}, 38 | {"杉原 和己", "1-C", 737}, 39 | {"山路 信之", "1-B", 425}, 40 | {"佐々木 淑子", "1-C", 759}, 41 | {"三宅 直人", "1-B", 594}, 42 | {"緒方 俊", "1-B", 405}, 43 | {"稲井 隆生", "1-A", 495}, 44 | } 45 | 46 | e1 := linq.FromSlice(students) 47 | e2 := linq.Where(e1, func(s Student) (bool, error) { return s.Class == "1-B", nil }) 48 | e3 := linq.OrderByDescending(e2, func(s Student) (int, error) { return s.Score, nil }) 49 | 50 | // go1.23.0 or later 51 | for s, err := range e3.All() { 52 | if err != nil { 53 | panic(err) 54 | } 55 | fmt.Printf("%d %s\n", s.Score, s.Name) 56 | } 57 | 58 | fmt.Println("----") 59 | 60 | // it also works in older go 61 | err := linq.ForEach(e3, func(s Student) error { 62 | fmt.Printf("%d %s\n", s.Score, s.Name) 63 | return nil 64 | }) 65 | if err != nil { 66 | panic(err) 67 | } 68 | } 69 | ``` 70 | 71 | Output: 72 | ``` 73 | 594 三宅 直人 74 | 425 山路 信之 75 | 405 緒方 俊 76 | 136 星 雅彦 77 | ---- 78 | 594 三宅 直人 79 | 425 山路 信之 80 | 405 緒方 俊 81 | 136 星 雅彦 82 | ``` 83 | 84 | ## Functions 85 | 86 | _italics are unique to this package._ 87 | 88 | #### Sorting Data 89 | 90 | - OrderBy 91 | - OrderByDescending 92 | - _OrderByFunc_ 93 | - ThenBy 94 | - ThenByDescending 95 | - Reverse 96 | 97 | #### Set Operations 98 | 99 | - Distinct 100 | - DistinctBy 101 | - Except 102 | - ExceptBy 103 | - Intersect 104 | - IntersectBy 105 | - Union 106 | - UnionBy 107 | 108 | #### Filtering Data 109 | 110 | - Where 111 | 112 | #### Quantifier Operations 113 | 114 | - All 115 | - Any 116 | - Contains 117 | - _ContainsFunc_ 118 | 119 | #### Projection Operations 120 | 121 | - Select 122 | - SelectMany 123 | - Zip 124 | 125 | #### Partitioning Data 126 | 127 | - Skip 128 | - SkipLast 129 | - SkipWhile 130 | - Take 131 | - TakeLast 132 | - TakeWhile 133 | - Chunk 134 | 135 | #### Join Operations 136 | 137 | - GroupJoin 138 | - Join 139 | 140 | #### Grouping Data 141 | 142 | - GroupBy 143 | 144 | #### Generation Operations 145 | 146 | - DefaultIfEmpty 147 | - Empty 148 | - Range 149 | - Repeat 150 | 151 | #### Element Operations 152 | 153 | - ElementAt 154 | - ElementAtOrDefault 155 | - First 156 | - FirstOrDefault 157 | - Last 158 | - LastOrDefault 159 | - Single 160 | - SingleOrDefault 161 | 162 | #### Converting Data Types 163 | 164 | - _FromMap_ 165 | - _FromSlice_ 166 | - _ToMap_ 167 | - _ToMapFunc_ 168 | - _ToSlice_ 169 | 170 | #### Concatenation Operations 171 | 172 | - Concat 173 | 174 | #### Aggregation Operations 175 | 176 | - Aggregate 177 | - Average 178 | - Count 179 | - Max 180 | - MaxBy 181 | - _MaxByFunc_ 182 | - Min 183 | - MinBy 184 | - _MinByFunc_ 185 | - Sum 186 | - _Sumf_ 187 | 188 | #### Other 189 | 190 | - _ForEach_ 191 | - _Generator_ 192 | 193 | ## C# LINQ Documents 194 | 195 | ### [Standard Query Operators](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/standard-query-operators-overview) 196 | 197 | #### [Sorting Data](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/sorting-data) 198 | 199 | - [x] [OrderBy](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.orderby) 200 | - [x] [OrderByDescending](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.orderbydescending) 201 | - [x] [ThenBy](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.thenby) 202 | - [x] [ThenByDescending](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.thenbydescending) 203 | - [x] [Reverse](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.reverse) 204 | 205 | #### [Set Operations](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/set-operations) 206 | 207 | - [x] [Distinct](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.distinct) 208 | - [x] [DistinctBy](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.distinctby) 209 | - [x] [Except](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.except) 210 | - [x] [ExceptBy](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.exceptby) 211 | - [x] [Intersect](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.intersect) 212 | - [x] [IntersectBy](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.intersectby) 213 | - [x] [Union](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.union) 214 | - [x] [UnionBy](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.unionby) 215 | 216 | #### [Filtering Data](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/filtering-data) 217 | 218 | - [ ] [OfType](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.oftype) 219 | - [x] [Where](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.where) 220 | 221 | #### [Quantifier Operations](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/quantifier-operations) 222 | 223 | - [x] [All](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.all) 224 | - [x] [Any](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.any) 225 | - [x] [Contains](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.contains) 226 | 227 | #### [Projection Operations](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/projection-operations) 228 | 229 | - [x] [Select](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.select) 230 | - [x] [SelectMany](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.selectmany) 231 | - [x] [Zip](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.zip) 232 | 233 | #### [Partitioning Data](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/partitioning-data) 234 | 235 | - [x] [Skip](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.skip) 236 | - [x] [SkipLast](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.skiplast) 237 | - [x] [SkipWhile](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.skipwhile) 238 | - [x] [Take](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.take) 239 | - [x] [TakeLast](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.take) 240 | - [x] [TakeWhile](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.takewhile) 241 | - [x] [Chunk](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.chunk) 242 | 243 | #### [Join Operations](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/join-operations) 244 | 245 | - [x] [Join](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.join) 246 | - [x] [GroupJoin](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.groupjoin) 247 | 248 | #### [Grouping Data](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/grouping-data) 249 | 250 | - [x] [GroupBy](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.groupby) 251 | - [ ] [ToLookup](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.tolookup) 252 | 253 | #### [Generation Operations](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/generation-operations) 254 | 255 | - [x] [DefaultIfEmpty](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.defaultifempty) 256 | - [x] [Empty](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.empty) 257 | - [x] [Range](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.range) 258 | - [x] [Repeat](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.repeat) 259 | 260 | #### [Equality Operations](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/equality-operations) 261 | 262 | - [ ] [SequenceEqual](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.sequenceequal) 263 | 264 | #### [Element Operations](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/element-operations) 265 | 266 | - [x] [ElementAt](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.elementat) 267 | - [x] [ElementAtOrDefault](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.elementatordefault) 268 | - [x] [First](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.first) 269 | - [x] [FirstOrDefault](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.firstordefault) 270 | - [x] [Last](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.last) 271 | - [x] [LastOrDefault](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.lastordefault) 272 | - [x] [Single](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.single) 273 | - [x] [SingleOrDefault](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.singleordefault) 274 | 275 | #### [Converting Data Types](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/converting-data-types) 276 | 277 | - [ ] [AsEnumerable](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.asenumerable) 278 | - [ ] [AsQueryable](https://docs.microsoft.com/en-us/dotnet/api/system.linq.queryable.asqueryable) 279 | - [ ] [Cast](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.cast) 280 | - [ ] [OfType](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.oftype) 281 | - [ ] [ToArray](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.toarray) 282 | - [ ] [ToDictionary](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.todictionary) 283 | - [ ] [ToList](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.tolist) 284 | - [ ] [ToLookup](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.tolookup) 285 | 286 | #### [Concatenation Operations](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/concatenation-operations) 287 | 288 | - [x] [Concat](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.concat) 289 | 290 | #### [Aggregation Operations](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/aggregation-operations) 291 | 292 | - [x] [Aggregate](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.aggregate) 293 | - [x] [Average](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.average) 294 | - [x] [Count](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.count) 295 | - [ ] [LongCount](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.longcount) 296 | - [x] [Max](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.max) 297 | - [x] [MaxBy](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.maxby) 298 | - [x] [Min](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.min) 299 | - [x] [MinBy](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.minby) 300 | - [x] [Sum](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.sum) 301 | --------------------------------------------------------------------------------