├── .github ├── dependabot.yml └── workflows │ ├── codeql-analysis.yml │ ├── test.yml │ └── todo-to-issue.yaml ├── .gitignore ├── .gitpod.yml ├── .golangci.yml ├── LICENSE ├── Makefile ├── README-ZH.md ├── README.md ├── _example └── main.go ├── benchmark_test.go ├── codecov.yml ├── go.mod ├── go.sum ├── parallel.go ├── pipeline.go ├── pipeline_test.go ├── slice.go ├── slice_comparable.go ├── slice_comparable_test.go ├── slice_mapping.go ├── slice_mapping_test.go ├── slice_ordered.go ├── slice_ordered_test.go └── slice_test.go /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: / 5 | schedule: 6 | interval: weekly 7 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '36 1 * * 4' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'go' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3.1.0 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v2 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v2 71 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | server-test: 11 | strategy: 12 | matrix: 13 | platform: [ubuntu-latest] 14 | go: ['1.18','1.18.3'] 15 | runs-on: ${{ matrix.platform }} 16 | 17 | steps: 18 | - name: Setup Go 19 | uses: actions/setup-go@v3.3.0 20 | with: 21 | go-version: ${{ matrix.go }} 22 | 23 | - name: Checkout code 24 | uses: actions/checkout@v3.1.0 25 | 26 | - name: go mod package cache 27 | uses: actions/cache@v3 28 | with: 29 | path: ~/go/pkg/mod 30 | key: ${{ runner.os }}-go-${{ matrix.go }} 31 | 32 | - name: Test 33 | run: | 34 | make test 35 | 36 | - name: Codecov 37 | uses: codecov/codecov-action@v3.1.1 38 | with: 39 | token: ${{ secrets.CODECOV_TOKEN }} 40 | file: ./cover.out 41 | flags: unittests 42 | name: codecov-umbrella 43 | 44 | 45 | -------------------------------------------------------------------------------- /.github/workflows/todo-to-issue.yaml: -------------------------------------------------------------------------------- 1 | name: "TODO to Issue" 2 | on: ["push"] 3 | jobs: 4 | build: 5 | runs-on: "ubuntu-latest" 6 | steps: 7 | - uses: "actions/checkout@v3.1.0" 8 | - name: "TODO to Issue" 9 | uses: "alstr/todo-to-issue-action@v4.7" 10 | id: "todo" 11 | -------------------------------------------------------------------------------- /.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 | 17 | *.db 18 | 19 | # ide 20 | .idea 21 | .DS_Store 22 | .vscode 23 | 24 | # temporary file 25 | cover.html 26 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | # This configuration file was automatically generated by Gitpod. 2 | # Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file) 3 | # and commit this file to your remote git repository to share the goodness with others. 4 | 5 | tasks: 6 | - init: go get && go build ./... && make test 7 | command: go run 8 | 9 | 10 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | timeout: 10m 3 | 4 | skip-files: 5 | - ".*_test.go$" 6 | 7 | linters: 8 | enable: 9 | - asciicheck 10 | - depguard 11 | - dogsled 12 | - durationcheck 13 | - errcheck 14 | - errorlint 15 | - exportloopref 16 | - gci 17 | - gofmt 18 | - goimports 19 | - gosec 20 | - misspell 21 | - nakedret 22 | - nilerr 23 | - nolintlint 24 | - revive 25 | - wastedassign 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 xyctruth 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test 2 | test: 3 | go test -race -v -coverprofile=cover.out ./... 4 | 5 | .PHONY: test-ui 6 | test-ui: test 7 | go tool cover -html=cover.out -o cover.html 8 | open cover.html 9 | 10 | .PHONY: fmt 11 | fmt: 12 | gofmt -w $(shell find . -name "*.go") 13 | 14 | .PHONY: lint 15 | lint: 16 | golangci-lint run 17 | -------------------------------------------------------------------------------- /README-ZH.md: -------------------------------------------------------------------------------- 1 | # Stream 2 | 3 | [![Build](https://github.com/xyctruth/stream/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/xyctruth/stream/actions/workflows/test.yml) 4 | [![codecov](https://codecov.io/gh/xyctruth/stream/branch/main/graph/badge.svg?token=ZHMPMQP0CP)](https://codecov.io/gh/xyctruth/stream) 5 | 6 | > [English](./README.md) / [中文](./README-ZH.md) 7 | 8 | `Stream` 是一个基于 Go 1.18+ 泛型的流式处理库, 它支持并行处理流中的数据. 9 | 10 | ## 特性 11 | 12 | - `并行流`: 并行处理流中的数据,保持流中元素原始顺序 13 | - `流水线`: 组合多个操作以减少元素循环,更早地短路 14 | - `惰性调用`: 中间操作是惰性的 15 | 16 | ## 安装 17 | 18 | 需要安装 Go 1.18+ 版本 19 | 20 | ```go 21 | import "github.com/xyctruth/stream" 22 | ``` 23 | 24 | ## 入门 25 | 26 | ```go 27 | // constraints.Ordered 28 | s := stream.NewSliceByOrdered([]string{"d", "a", "b", "c", "a"}). 29 | Filter(func(s string) bool { return s != "b" }). 30 | Map(func(s string) string { return "class_" + s }). 31 | Sort(). 32 | Distinct(). 33 | ToSlice() 34 | ``` 35 | 36 | ## 类型约束 37 | 38 | `any` 接受任何类型的元素, 所以不能使用 `==` `!=` `>` `<` 比较元素, 导致你不能使用 Sort(), Find()...等函数 ,但是你可以使用 SortFunc(fn), FindFunc(fn)... 代替 39 | 40 | ```go 41 | stream.NewSlice([]int{1, 2, 3, 7, 1}) 42 | ``` 43 | 44 | `comparable` 接收的类型可以使用 `==` `!=` 比较元素, 但仍然不能使用 `>` `<` 比较元素, 因此你不能使用 Sort(), Min()...等函数 ,但是你可以使用 SortFunc(fn), MinFunc()... 代替 45 | 46 | ```go 47 | stream.NewSliceByComparable([]int{1, 2, 3, 7, 1}) 48 | ``` 49 | 50 | `constraints.Ordered` 接收的类型可以使用 `==` `!=` `>` `<`, 所以可以使用所有的函数 51 | 52 | ```go 53 | stream.NewSliceByOrdered([]int{1, 2, 3, 7, 1}) 54 | ``` 55 | 56 | ## 类型转换 57 | 58 | 有些时候我们需要使用 `Map` ,`Reduce` 转换切片元素的类型,但是很遗憾目前 Golang 并不支持结构体的方法有额外的类型参数,所有类型参数必须在结构体中声明。在 Golang 支持之前我们暂时使用临时方案解决这个问题。 59 | 60 | ```go 61 | // SliceMappingStream Need to convert the type of slice elements. 62 | // - E elements type 63 | // - MapE map elements type 64 | // - ReduceE reduce elements type 65 | type SliceMappingStream[E any, MapE any, ReduceE any] struct { 66 | SliceStream[E] 67 | } 68 | 69 | s := stream.NewSliceByMapping[int, string, string]([]int{1, 2, 3, 4, 5}). 70 | Filter(func(v int) bool { return v >3 }). 71 | Map(func(v int) string { return "mapping_" + strconv.Itoa(v) }). 72 | Reduce(func(r string, v string) string { return r + v }) 73 | ``` 74 | 75 | ## 并行 76 | 77 | `Parallel` 函数接收一个 `goroutines int` 参数. 如果 goroutines>1 则开启并行, 否则关闭并行, 默认流是关闭并行的。 78 | 79 | 并行会将流中的元素平均划分多个的分区, 并创建相同数量的 goroutine 执行, 并且会保证处理完成后流中元素保持原始顺序. 80 | 81 | ```go 82 | s := stream.NewSliceByOrdered([]string{"d", "a", "b", "c", "a"}). 83 | Parallel(10). 84 | Filter(func(s string) bool { 85 | // 一些耗时操作 86 | return s != "b" 87 | }). 88 | Map(func(s string) string { 89 | // 一些耗时操作 90 | return "class_" + s 91 | }). 92 | ForEach( 93 | func(index int, s string) { 94 | // 一些耗时操作 95 | }, 96 | ).ToSlice() 97 | ``` 98 | 99 | ### 并行类型 100 | 101 | - `First`: 一旦获得第一个返回值,并行处理就结束. `For: AllMatch, AnyMatch, FindFunc` 102 | - `ALL`: 所有元素都需要并行处理,得到所有返回值,然后并行结束. `For: Map, Filter` 103 | - `Action`: 所有元素需要并行处理,不需要返回值. `For: ForEach, Action` 104 | 105 | ### 并行 Goroutines 数量 106 | 107 | 开启并行 goroutine 数量在面对 CPU 操作与 IO 操作有着不同的选择。 一般面对 CPU 操作时 goroutine 数量不需要设置大于 CPU 核心数,而 IO 操作时 goroutine 数量可以设置远远大于 CPU 核心数. 108 | 109 | #### CPU 操作 110 | 111 | [BenchmarkParallelByCPU](./benchmark_test.go) 112 | 113 | ```go 114 | NewSlice(s).Parallel(goroutines).ForEach(func(i int, v int) { 115 | sort.Ints(newArray(1000)) // 模拟 CPU 耗时操作 116 | }) 117 | ``` 118 | 使用6个cpu核心进行基准测试 119 | ```go 120 | go test -run=^$ -benchtime=5s -cpu=6 -bench=^BenchmarkParallelByCPU 121 | 122 | goos: darwin 123 | goarch: amd64 124 | pkg: github.com/xyctruth/stream 125 | cpu: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz 126 | BenchmarkParallelByCPU/no_Parallel(0)-6 710 8265106 ns/op 127 | BenchmarkParallelByCPU/goroutines(2)-6 1387 4333929 ns/op 128 | BenchmarkParallelByCPU/goroutines(4)-6 2540 2361783 ns/op 129 | BenchmarkParallelByCPU/goroutines(6)-6 3024 2100158 ns/op 130 | BenchmarkParallelByCPU/goroutines(8)-6 2347 2531435 ns/op 131 | BenchmarkParallelByCPU/goroutines(10)-6 2622 2306752 ns/op 132 | ``` 133 | 134 | #### IO 操作 135 | 136 | [BenchmarkParallelByIO](./benchmark_test.go) 137 | 138 | ```go 139 | NewSlice(s).Parallel(goroutines).ForEach(func(i int, v int) { 140 | time.Sleep(time.Millisecond) // 模拟 IO 耗时操作 141 | }) 142 | ``` 143 | 使用6个cpu核心进行基准测试 144 | ```go 145 | go test -run=^$ -benchtime=5s -cpu=6 -bench=^BenchmarkParallelByIO 146 | 147 | goos: darwin 148 | goarch: amd64 149 | pkg: github.com/xyctruth/stream 150 | cpu: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz 151 | BenchmarkParallelByIO/no_parallel(0)-6 52 102023558 ns/op 152 | BenchmarkParallelByIO/goroutines(2)-6 100 55807303 ns/op 153 | BenchmarkParallelByIO/goroutines(4)-6 214 27868725 ns/op 154 | BenchmarkParallelByIO/goroutines(6)-6 315 18925789 ns/op 155 | BenchmarkParallelByIO/goroutines(8)-6 411 14439700 ns/op 156 | BenchmarkParallelByIO/goroutines(10)-6 537 11164758 ns/op 157 | BenchmarkParallelByIO/goroutines(50)-6 2629 2310602 ns/op 158 | BenchmarkParallelByIO/goroutines(100)-6 5094 1221887 ns/op 159 | ``` 160 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Stream 2 | 3 | [![Build](https://github.com/xyctruth/stream/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/xyctruth/stream/actions/workflows/test.yml) 4 | [![codecov](https://codecov.io/gh/xyctruth/stream/branch/main/graph/badge.svg?token=ZHMPMQP0CP)](https://codecov.io/gh/xyctruth/stream) 5 | 6 | > [English](./README.md) / [中文](./README-ZH.md) 7 | 8 | `Stream` is a stream processing library based on Go 1.18+ Generics. It supports parallel processing of data in the stream. 9 | 10 | ## Features 11 | 12 | - `Parallel`: Parallel processing of data in the stream, keeping the original order of the elements in the stream 13 | - `Pipeline`: combine multiple operations to reduce element loops, short-circuiting earlier 14 | - `Lazy Invocation`: intermediate operations are lazy 15 | 16 | ## Installation 17 | 18 | Requires Go 1.18+ version installed 19 | 20 | ```go 21 | import "github.com/xyctruth/stream" 22 | ``` 23 | 24 | ## Getting Started 25 | 26 | ```go 27 | s := stream.NewSliceByOrdered([]string{"d", "a", "b", "c", "a"}). 28 | Filter(func(s string) bool { return s != "b" }). 29 | Map(func(s string) string { return "class_" + s }). 30 | Sort(). 31 | Distinct(). 32 | ToSlice() 33 | ``` 34 | 35 | ## Type Constraints 36 | 37 | `any` accepts elements of any type, so you cannot use `==` `!=` `>` `<` to compare elements, which will prevent you from using Sort(), Find()... functions, but you can use SortFunc(fn), FindFunc(fn)... instead 38 | 39 | ```go 40 | stream.NewSlice([]int{1, 2, 3, 7, 1}) 41 | ``` 42 | 43 | `comparable` accepts type can use `==` `!=` to compare elements, but still can't use `>` `<` to compare elements, so you can't use Sort(), Min()... functions, but you can use SortFunc(fn), MinFunc()... instead 44 | 45 | ```go 46 | stream.NewSliceByComparable([]int{1, 2, 3, 7, 1}) 47 | ``` 48 | 49 | `constraints.Ordered` accepts types that can use `==` `!=` `>` `<` to compare elements, so can use all functions 50 | 51 | ```go 52 | stream.NewSliceByOrdered([]int{1, 2, 3, 7, 1}) 53 | ``` 54 | 55 | ## Type Conversion 56 | 57 | Sometimes we need to use `Map` , `Reduce` to convert the type of slice elements, but unfortunately Golang currently does not support structure methods with additional type parameters, all type parameters must be declared in the structure. We work around this with a temporary workaround until Golang supports it. 58 | 59 | ```go 60 | // SliceMappingStream Need to convert the type of slice elements. 61 | // - E elements type 62 | // - MapE map elements type 63 | // - ReduceE reduce elements type 64 | type SliceMappingStream[E any, MapE any, ReduceE any] struct { 65 | SliceStream[E] 66 | } 67 | 68 | s := stream.NewSliceByMapping[int, string, string]([]int{1, 2, 3, 4, 5}). 69 | Filter(func(v int) bool { return v >3 }). 70 | Map(func(v int) string { return "mapping_" + strconv.Itoa(v) }). 71 | Reduce(func(r string, v string) string { return r + v }) 72 | ``` 73 | 74 | ## Parallel 75 | 76 | The `Parallel` function accept a `goroutines int` parameter. If goroutines>1, open Parallel , otherwise close Parallel, the stream Parallel is off by default. 77 | 78 | Parallel will divide the elements in the stream into multiple partitions equally, and create the same number of goroutine to execute, and it will ensure that the elements in the stream remain in the original order after processing is complete. 79 | 80 | ```go 81 | s := stream.NewSliceByOrdered([]string{"d", "a", "b", "c", "a"}). 82 | Parallel(10). 83 | Filter(func(s string) bool { 84 | // some time-consuming operations 85 | return s != "b" 86 | }). 87 | Map(func(s string) string { 88 | // some time-consuming operations 89 | return "class_" + s 90 | }). 91 | ForEach( 92 | func(index int, s string) { 93 | // some time-consuming operations 94 | }, 95 | ).ToSlice() 96 | ``` 97 | 98 | ### Parallel Type 99 | 100 | - `First`: parallel processing ends as soon as the first return value is obtained. `For: AllMatch, AnyMatch, FindFunc` 101 | - `ALL`: All elements need to be processed in parallel, all return values are obtained, and then the parallel is ended. `For: Map, Filter` 102 | - `Action`: All elements need to be processed in parallel, no return value required. `For: ForEach, Action` 103 | 104 | ### Parallel Goroutines Number 105 | 106 | The number of parallel goroutines has different choices for CPU operations and IO operations. Generally, the number of goroutines does not need to be set larger than the number of CPU cores for CPU operations, while the number of goroutines for IO operations can be set to be much larger than the number of CPU cores. 107 | 108 | #### CPU Operations 109 | 110 | [BenchmarkParallelByCPU](./benchmark_test.go) 111 | 112 | ```go 113 | NewSlice(s).Parallel(tt.goroutines).ForEach(func(i int, v int) { 114 | sort.Ints(newArray(1000)) // Simulate time-consuming CPU operations 115 | }) 116 | ``` 117 | Benchmark with 6 cpu cores 118 | ```go 119 | go test -run=^$ -benchtime=5s -cpu=6 -bench=^BenchmarkParallelByCPU 120 | 121 | goos: darwin 122 | goarch: amd64 123 | pkg: github.com/xyctruth/stream 124 | cpu: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz 125 | BenchmarkParallelByCPU/no_Parallel(0)-6 710 8265106 ns/op 126 | BenchmarkParallelByCPU/goroutines(2)-6 1387 4333929 ns/op 127 | BenchmarkParallelByCPU/goroutines(4)-6 2540 2361783 ns/op 128 | BenchmarkParallelByCPU/goroutines(6)-6 3024 2100158 ns/op 129 | BenchmarkParallelByCPU/goroutines(8)-6 2347 2531435 ns/op 130 | BenchmarkParallelByCPU/goroutines(10)-6 2622 2306752 ns/op 131 | ``` 132 | 133 | #### IO Operations 134 | 135 | [BenchmarkParallelByIO](./benchmark_test.go) 136 | 137 | ```go 138 | NewSlice(s).Parallel(tt.goroutines).ForEach(func(i int, v int) { 139 | time.Sleep(time.Millisecond) // Simulate time-consuming IO operations 140 | }) 141 | ``` 142 | Benchmark with 6 cpu cores 143 | ```go 144 | go test -run=^$ -benchtime=5s -cpu=6 -bench=^BenchmarkParallelByIO 145 | 146 | goos: darwin 147 | goarch: amd64 148 | pkg: github.com/xyctruth/stream 149 | cpu: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz 150 | BenchmarkParallelByIO/no_parallel(0)-6 52 102023558 ns/op 151 | BenchmarkParallelByIO/goroutines(2)-6 100 55807303 ns/op 152 | BenchmarkParallelByIO/goroutines(4)-6 214 27868725 ns/op 153 | BenchmarkParallelByIO/goroutines(6)-6 315 18925789 ns/op 154 | BenchmarkParallelByIO/goroutines(8)-6 411 14439700 ns/op 155 | BenchmarkParallelByIO/goroutines(10)-6 537 11164758 ns/op 156 | BenchmarkParallelByIO/goroutines(50)-6 2629 2310602 ns/op 157 | BenchmarkParallelByIO/goroutines(100)-6 5094 1221887 ns/op 158 | ``` 159 | 160 | -------------------------------------------------------------------------------- /_example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/xyctruth/stream" 6 | "strconv" 7 | ) 8 | 9 | func main() { 10 | // Constraints `any` 11 | s1 := stream.NewSlice([]string{"d", "a", "b", "c", "a"}). 12 | Filter(func(s string) bool { return s != "b" }). 13 | Map(func(s string) string { return "class_" + s }). 14 | SortFunc(func(s1, s2 string) bool { return s1 < s2 }). 15 | ToSlice() 16 | fmt.Println(s1) 17 | 18 | // Constraints `comparable` 19 | s2 := stream.NewSliceByComparable([]string{"d", "a", "b", "c", "a"}). 20 | Filter(func(s string) bool { return s != "b" }). 21 | Map(func(s string) string { return "class_" + s }). 22 | SortFunc(func(s1, s2 string) bool { return s1 < s2 }). 23 | Distinct(). 24 | ToSlice() 25 | fmt.Println(s2) 26 | 27 | //Constraints `constraints.Ordered` 28 | s3 := stream.NewSliceByOrdered([]string{"d", "a", "b", "c", "a"}). 29 | Filter(func(s string) bool { return s != "b" }). 30 | Map(func(s string) string { return "class_" + s }). 31 | Sort(). 32 | Distinct(). 33 | ToSlice() 34 | fmt.Println(s3) 35 | 36 | // Parallel 37 | s4 := stream.NewSliceByOrdered([]string{"d", "a", "b", "c", "a"}). 38 | Parallel(10). 39 | Filter(func(s string) bool { 40 | // some time-consuming operations 41 | return s != "b" 42 | }). 43 | Map(func(s string) string { 44 | // some time-consuming operations 45 | return "class_" + s 46 | }). 47 | ForEach( 48 | func(index int, s string) { 49 | // some time-consuming operations 50 | }, 51 | ).ToSlice() 52 | fmt.Println(s4) 53 | 54 | // Need to convert the type of slice elements. 55 | s5 := stream.NewSliceByMapping[int, string, string]([]int{1, 2, 3, 4, 5}). 56 | Filter(func(v int) bool { return v > 3 }). 57 | Map(func(v int) string { return "mapping_" + strconv.Itoa(v) }). 58 | Reduce(func(r string, v string) string { return r + v }) 59 | 60 | fmt.Println(s5) 61 | 62 | } 63 | -------------------------------------------------------------------------------- /benchmark_test.go: -------------------------------------------------------------------------------- 1 | package stream 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func BenchmarkParallelByCPU(b *testing.B) { 11 | tests := []struct { 12 | name string 13 | goroutines int 14 | action func(int, int) 15 | }{ 16 | {name: "no Parallel", goroutines: 0}, 17 | {name: "Goroutines", goroutines: 2}, 18 | {name: "Goroutines", goroutines: 4}, 19 | {name: "Goroutines", goroutines: 6}, 20 | {name: "Goroutines", goroutines: 8}, 21 | {name: "Goroutines", goroutines: 10}, 22 | } 23 | s := newArray(100) 24 | 25 | for _, tt := range tests { 26 | b.Run(fmt.Sprintf("%s(%d)", tt.name, tt.goroutines), func(b *testing.B) { 27 | b.ResetTimer() 28 | for n := 0; n < b.N; n++ { 29 | NewSlice(s).Parallel(tt.goroutines).ForEach(func(i int, v int) { 30 | sort.Ints(newArray(1000)) // Simulate time-consuming CPU operations 31 | }) 32 | } 33 | }) 34 | } 35 | } 36 | 37 | func BenchmarkParallelByIO(b *testing.B) { 38 | tests := []struct { 39 | name string 40 | goroutines int 41 | action func(int, int) 42 | }{ 43 | {name: "no Parallel", goroutines: 0}, 44 | {name: "Goroutines", goroutines: 2}, 45 | {name: "Goroutines", goroutines: 4}, 46 | {name: "Goroutines", goroutines: 6}, 47 | {name: "Goroutines", goroutines: 8}, 48 | {name: "Goroutines", goroutines: 10}, 49 | {name: "Goroutines", goroutines: 50}, 50 | {name: "Goroutines", goroutines: 100}, 51 | } 52 | s := newArray(100) 53 | 54 | for _, tt := range tests { 55 | b.Run(fmt.Sprintf("%s(%d)", tt.name, tt.goroutines), func(b *testing.B) { 56 | b.ResetTimer() 57 | for n := 0; n < b.N; n++ { 58 | NewSlice(s).Parallel(tt.goroutines).ForEach(func(i int, v int) { 59 | time.Sleep(time.Millisecond) // Simulate time-consuming IO operations 60 | }) 61 | } 62 | }) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | threshold: 5% 6 | patch: 7 | default: 8 | target: 70% -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/xyctruth/stream 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/stretchr/testify v1.8.3 7 | golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 8 | ) 9 | 10 | require ( 11 | github.com/davecgh/go-spew v1.1.1 // indirect 12 | github.com/pmezard/go-difflib v1.0.0 // indirect 13 | gopkg.in/yaml.v3 v3.0.1 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= 6 | github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 7 | golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM= 8 | golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 11 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 12 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 13 | -------------------------------------------------------------------------------- /parallel.go: -------------------------------------------------------------------------------- 1 | package stream 2 | 3 | import "context" 4 | 5 | type Parallel[E any, R any] struct { 6 | goroutines int 7 | slice []E 8 | handler func(index int, elem E) (isReturn bool, isComplete bool, result R) 9 | } 10 | 11 | func (p Parallel[E, R]) Run() []R { 12 | partitions := partition(p.slice, p.goroutines) 13 | resultChs := make([]chan []R, len(partitions)) 14 | 15 | ctx, cancel := context.WithCancel(context.Background()) 16 | defer cancel() 17 | 18 | for i, pa := range partitions { 19 | resultChs[i] = make(chan []R) 20 | go p.do(ctx, cancel, resultChs[i], pa) 21 | } 22 | 23 | result := p.resulted(resultChs, len(p.slice)) 24 | return result 25 | } 26 | 27 | func (p Parallel[E, R]) do( 28 | ctx context.Context, 29 | cancel context.CancelFunc, 30 | resultCh chan []R, 31 | pa part) { 32 | 33 | defer close(resultCh) 34 | ret := make([]R, 0, pa.high-pa.low) 35 | 36 | for i := pa.low; i < pa.high; i++ { 37 | select { 38 | case <-ctx.Done(): 39 | break 40 | default: 41 | isReturn, isComplete, r := p.handler(i, p.slice[i]) 42 | if !isReturn { 43 | continue 44 | } 45 | ret = append(ret, r) 46 | if isComplete { 47 | cancel() 48 | break 49 | } 50 | } 51 | } 52 | 53 | if len(ret) > 0 { 54 | resultCh <- ret 55 | } 56 | return 57 | } 58 | 59 | func (p Parallel[E, R]) resulted(resultChs []chan []R, cap int) []R { 60 | results := make([]R, 0, cap) 61 | for _, resultCh := range resultChs { 62 | for result := range resultCh { 63 | results = append(results, result...) 64 | } 65 | } 66 | return results 67 | } 68 | 69 | // part Uniform slices 70 | // This selects a half-open range which includes the first element, but excludes the last. 71 | type part struct { 72 | low int //includes index 73 | high int //excludes index 74 | } 75 | 76 | // partition Given a specified source, evenly part according to the source. 77 | func partition[E any](slice []E, goroutines int) []part { 78 | l := len(slice) 79 | if l == 0 { 80 | return nil 81 | } 82 | if goroutines > l { 83 | goroutines = l 84 | } 85 | partitions := make([]part, 0, goroutines) 86 | size := l / goroutines 87 | rem := l % goroutines 88 | low := 0 89 | high := size 90 | for i := 0; i < goroutines; i++ { 91 | partitions = append(partitions, part{low, high}) 92 | low = high 93 | high = high + size 94 | if i+rem+1 >= goroutines { 95 | high++ 96 | } 97 | } 98 | return partitions 99 | } 100 | -------------------------------------------------------------------------------- /pipeline.go: -------------------------------------------------------------------------------- 1 | package stream 2 | 3 | type Stage[E any, R any] func(index int, e E) (isReturn bool, isComplete bool, ret R) 4 | 5 | type Pipeline[E any] struct { 6 | source []E 7 | goroutines int 8 | stages Stage[E, E] 9 | } 10 | 11 | func (pipe *Pipeline[E]) AddStage(s2 Stage[E, E]) { 12 | if pipe.stages == nil { 13 | pipe.stages = s2 14 | return 15 | } 16 | s1 := pipe.stages 17 | pipe.stages = func(index int, e E) (isReturn bool, isComplete bool, ret E) { 18 | isReturn, _, ret = s1(index, e) 19 | if !isReturn { 20 | return 21 | } 22 | return s2(index, ret) 23 | } 24 | } 25 | 26 | func (pipe *Pipeline[E]) evaluation() { 27 | if pipe.source == nil || pipe.stages == nil { 28 | return 29 | } 30 | pipe.source = pipelineRun(pipe, pipe.stages) 31 | } 32 | 33 | func (pipe *Pipeline[E]) evaluationBool(terminal Stage[E, bool]) *bool { 34 | ret := pipelineRun(pipe, wrapTerminal(pipe.stages, terminal)) 35 | if len(ret) > 0 { 36 | return &ret[0] 37 | } 38 | return nil 39 | } 40 | 41 | func (pipe *Pipeline[E]) evaluationInt(terminal Stage[E, int]) *int { 42 | ret := pipelineRun(pipe, wrapTerminal(pipe.stages, terminal)) 43 | if len(ret) > 0 { 44 | return &ret[0] 45 | } 46 | return nil 47 | } 48 | 49 | func wrapTerminal[E any, R any](stage Stage[E, E], terminalStage Stage[E, R]) Stage[E, R] { 50 | var stages Stage[E, R] 51 | if stage == nil { 52 | stages = terminalStage 53 | } else { 54 | stages = func(i int, v E) (isReturn bool, isComplete bool, ret R) { 55 | isReturn, _, v = stage(i, v) 56 | if !isReturn { 57 | return 58 | } 59 | isReturn, isComplete, ret = terminalStage(i, v) 60 | return 61 | } 62 | } 63 | return stages 64 | } 65 | 66 | func pipelineRun[E any, R any](pipe *Pipeline[E], stages Stage[E, R]) []R { 67 | defer func() { 68 | pipe.stages = nil 69 | }() 70 | 71 | if pipe.goroutines > 1 { 72 | return Parallel[E, R]{pipe.goroutines, pipe.source, stages}.Run() 73 | } 74 | 75 | results := make([]R, 0, len(pipe.source)) 76 | for i, v := range pipe.source { 77 | isReturn, isComplete, ret := stages(i, v) 78 | if !isReturn { 79 | continue 80 | } 81 | results = append(results, ret) 82 | if isComplete { 83 | break 84 | } 85 | } 86 | return results 87 | } 88 | -------------------------------------------------------------------------------- /pipeline_test.go: -------------------------------------------------------------------------------- 1 | package stream 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestPipelineStages(t *testing.T) { 9 | p := &Pipeline[int]{ 10 | source: []int{1, 2, 3}, 11 | } 12 | 13 | p.AddStage(func(index int, e int) (isReturn bool, isComplete bool, ret int) { 14 | return true, false, e * 10 15 | }) 16 | 17 | isReturn, isComplete, ret := p.stages(0, 1) 18 | assert.Equal(t, true, isReturn) 19 | assert.Equal(t, false, isComplete) 20 | assert.Equal(t, ret, 10) 21 | 22 | p.AddStage(func(index int, e int) (isReturn bool, isComplete bool, ret int) { 23 | return true, false, e + 10 24 | }) 25 | isReturn, isComplete, ret = p.stages(0, 1) 26 | assert.Equal(t, true, isReturn) 27 | assert.Equal(t, false, isComplete) 28 | assert.Equal(t, ret, 20) 29 | 30 | p.evaluation() 31 | assert.Equal(t, p.source, []int{20, 30, 40}) 32 | assert.Nil(t, p.stages) 33 | } 34 | 35 | func TestPipeByTermination(t *testing.T) { 36 | tests := []struct { 37 | name string 38 | goroutines int 39 | }{ 40 | { 41 | name: "case", 42 | goroutines: 0, 43 | }, 44 | { 45 | name: "case", 46 | goroutines: 10, 47 | }, 48 | } 49 | for _, tt := range tests { 50 | t.Run(tt.name, func(t *testing.T) { 51 | p := &Pipeline[int]{ 52 | source: []int{1, 2, 3}, 53 | goroutines: tt.goroutines, 54 | } 55 | p.AddStage(func(index int, e int) (isReturn bool, isComplete bool, ret int) { 56 | return true, false, e * 10 57 | }) 58 | 59 | stages := wrapTerminal(p.stages, func(index int, e int) (isReturn bool, isComplete bool, ret int) { 60 | if index == 1 { 61 | return true, true, e * 10 62 | } 63 | return false, false, e * 10 64 | }) 65 | rets := pipelineRun(p, stages) 66 | assert.Equal(t, []int{200}, rets) 67 | assert.Nil(t, p.stages) 68 | }) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /slice.go: -------------------------------------------------------------------------------- 1 | package stream 2 | 3 | import ( 4 | "golang.org/x/exp/slices" 5 | ) 6 | 7 | // SliceStream Generics constraints based on any 8 | type SliceStream[E any] struct { 9 | *Pipeline[E] 10 | } 11 | 12 | // NewSlice new stream instance, generics constraints based on any. 13 | func NewSlice[E any](source []E) SliceStream[E] { 14 | return SliceStream[E]{Pipeline: &Pipeline[E]{source: source}} 15 | } 16 | 17 | // Parallel Goroutines > 1 enable parallel, Goroutines <= 1 disable parallel 18 | func (stream SliceStream[E]) Parallel(goroutines int) SliceStream[E] { 19 | stream.goroutines = goroutines 20 | return stream 21 | } 22 | 23 | // At Returns the element at the given index. Accepts negative integers, which count back from the last item. 24 | // Out of index range ok return false 25 | func (stream SliceStream[E]) At(index int) (elem E, ok bool) { 26 | stream.evaluation() 27 | l := len(stream.source) 28 | if index < 0 { 29 | index = index + l 30 | } 31 | if l == 0 || index < 0 || index >= l { 32 | return 33 | } 34 | return stream.source[index], true 35 | } 36 | 37 | // AllMatch Returns whether all elements in the stream match the provided predicate. 38 | // If the source is empty or nil then true is returned. 39 | // 40 | // Support Parallel. 41 | func (stream SliceStream[E]) AllMatch(predicate func(E) bool) bool { 42 | terminal := func(index int, v E) (isReturn bool, isComplete bool, ret bool) { 43 | return !predicate(v), true, false 44 | } 45 | result := stream.evaluationBool(terminal) 46 | if result != nil { 47 | return *result 48 | } 49 | return true 50 | } 51 | 52 | // AnyMatch Returns whether any elements in the stream match the provided predicate. 53 | // If the source is empty or nil then false is returned. 54 | // 55 | // Support Parallel. 56 | func (stream SliceStream[E]) AnyMatch(predicate func(E) bool) bool { 57 | terminal := func(index int, v E) (isReturn bool, isComplete bool, ret bool) { 58 | return predicate(v), true, true 59 | } 60 | result := stream.evaluationBool(terminal) 61 | if result != nil { 62 | return *result 63 | } 64 | return false 65 | } 66 | 67 | // Append appends elements to the end of this stream 68 | func (stream SliceStream[E]) Append(elements ...E) SliceStream[E] { 69 | stream.evaluation() 70 | newSlice := make([]E, 0, len(stream.source)+len(elements)) 71 | newSlice = append(newSlice, stream.source...) 72 | newSlice = append(newSlice, elements...) 73 | stream.source = newSlice 74 | return stream 75 | } 76 | 77 | // Count Returns the count of elements in this stream. 78 | func (stream SliceStream[E]) Count() int { 79 | stream.evaluation() 80 | return len(stream.source) 81 | } 82 | 83 | // EqualFunc Returns whether the source in the stream is equal to the destination source. 84 | // Equal according to the slices.EqualFunc 85 | func (stream SliceStream[E]) EqualFunc(dest []E, equal func(E, E) bool) bool { 86 | stream.evaluation() 87 | return slices.EqualFunc(stream.source, dest, equal) 88 | } 89 | 90 | // ForEach Performs an action for each element of this stream. 91 | // 92 | // Support Parallel. 93 | // Parallel side effects are not executed in the original order of stream elements. 94 | func (stream SliceStream[E]) ForEach(action func(int, E)) SliceStream[E] { 95 | stage := func(index int, v E) (isReturn bool, isComplete bool, result E) { 96 | action(index, v) 97 | return true, false, v 98 | } 99 | stream.AddStage(stage) 100 | stream.evaluation() 101 | return stream 102 | } 103 | 104 | // First Returns the first element in the stream. 105 | // If the source is empty or nil then E Type default value is returned. ok return false 106 | func (stream SliceStream[E]) First() (elem E, ok bool) { 107 | stream.evaluation() 108 | if len(stream.source) == 0 { 109 | return 110 | } 111 | return stream.source[0], true 112 | } 113 | 114 | // FindFunc Returns the index of the first element in the stream that matches the provided predicate. 115 | // If not found then -1 is returned. 116 | // 117 | // Support Parallel. 118 | // Parallel side effect is that the element found may not be the first to appear 119 | func (stream SliceStream[E]) FindFunc(predicate func(E) bool) int { 120 | terminal := func(index int, v E) (isReturn bool, isComplete bool, ret int) { 121 | return predicate(v), true, index 122 | } 123 | result := stream.evaluationInt(terminal) 124 | if result != nil { 125 | return *result 126 | } 127 | return -1 128 | } 129 | 130 | // Filter Returns a stream consisting of the elements of this stream that match the given predicate. 131 | // 132 | // Support Parallel. 133 | func (stream SliceStream[E]) Filter(predicate func(E) bool) SliceStream[E] { 134 | stage := func(index int, e E) (isReturn bool, isComplete bool, ret E) { 135 | return predicate(e), false, e 136 | } 137 | stream.AddStage(stage) 138 | return stream 139 | } 140 | 141 | // Insert inserts the values source... into s at index 142 | // If index is out of range then use Append to the end 143 | func (stream SliceStream[E]) Insert(index int, elements ...E) SliceStream[E] { 144 | stream.evaluation() 145 | if len(stream.source) <= index { 146 | return stream.Append(elements...) 147 | } 148 | stream.source = slices.Insert(stream.source, index, elements...) 149 | return stream 150 | } 151 | 152 | // Delete Removes the elements s[i:j] from this stream, returning the modified stream. 153 | // If j > len(slice) then j = len(slice) 154 | // If i > j then swap i, j = j, i 155 | // If the source is empty or nil then do nothing 156 | func (stream SliceStream[E]) Delete(i, j int) SliceStream[E] { 157 | stream.evaluation() 158 | if i > j { 159 | i, j = j, i 160 | } 161 | if j > len(stream.source) { 162 | j = len(stream.source) 163 | } 164 | stream.source = append(stream.source[:i], stream.source[j:]...) 165 | return stream 166 | } 167 | 168 | // IsSortedFunc Returns whether stream is sorted in ascending order. 169 | // Compare according to the less function 170 | // - less: return a > b 171 | // If the source is empty or nil then true is returned. 172 | func (stream SliceStream[E]) IsSortedFunc(less func(a, b E) bool) bool { 173 | stream.evaluation() 174 | return slices.IsSortedFunc(stream.source, less) 175 | } 176 | 177 | // Limit Returns a stream consisting of the elements of this stream, truncated to be no longer than maxSize in length. 178 | func (stream SliceStream[E]) Limit(maxSize int) SliceStream[E] { 179 | stream.evaluation() 180 | if stream.source == nil { 181 | return stream 182 | } 183 | newSlice := make([]E, 0, maxSize) 184 | for i := 0; i < len(stream.source) && i < maxSize; i++ { 185 | newSlice = append(newSlice, stream.source[i]) 186 | } 187 | stream.source = newSlice 188 | return stream 189 | } 190 | 191 | type MapperFunc[E any] func(E) E 192 | 193 | // Map Returns a stream consisting of the results of applying the given function to the elements of this stream. 194 | // 195 | // Support Parallel. 196 | func (stream SliceStream[E]) Map(mapper MapperFunc[E]) SliceStream[E] { 197 | stage := func(index int, v E) (isReturn bool, isComplete bool, ret E) { 198 | return true, false, mapper(v) 199 | } 200 | stream.AddStage(stage) 201 | return stream 202 | } 203 | 204 | // MaxFunc Returns the maximum element of this stream. 205 | // - less: return a > b 206 | // If the source is empty or nil then E Type default value is returned. ok return false 207 | func (stream SliceStream[E]) MaxFunc(less func(a, b E) bool) (max E, ok bool) { 208 | stream.evaluation() 209 | if len(stream.source) == 0 { 210 | return 211 | } 212 | for i, v := range stream.source { 213 | if less(v, max) || i == 0 { 214 | max = v 215 | } 216 | } 217 | return max, true 218 | } 219 | 220 | // MinFunc Returns the minimum element of this stream. 221 | // - less: return a < b 222 | // If the source is empty or nil then E Type default value is returned. ok return false 223 | func (stream SliceOrderedStream[E]) MinFunc(less func(a, b E) bool) (min E, ok bool) { 224 | stream.evaluation() 225 | if len(stream.source) == 0 { 226 | return 227 | } 228 | for i, v := range stream.source { 229 | if less(v, min) || i == 0 { 230 | min = v 231 | } 232 | } 233 | return min, true 234 | } 235 | 236 | // Reduce Returns a source consisting of the elements of this stream. 237 | func (stream SliceStream[E]) Reduce(result E, accumulator func(result E, elem E) E) E { 238 | stream.evaluation() 239 | for _, v := range stream.source { 240 | result = accumulator(result, v) 241 | } 242 | return result 243 | } 244 | 245 | // SortFunc Returns a sorted stream consisting of the elements of this stream. 246 | // Sorted according to slices.SortFunc. 247 | func (stream SliceStream[E]) SortFunc(less func(a, b E) bool) SliceStream[E] { 248 | stream.evaluation() 249 | slices.SortFunc(stream.source, less) 250 | return stream 251 | } 252 | 253 | // ToSlice Returns a source in the stream 254 | func (stream SliceStream[E]) ToSlice() []E { 255 | stream.evaluation() 256 | return stream.source 257 | } 258 | -------------------------------------------------------------------------------- /slice_comparable.go: -------------------------------------------------------------------------------- 1 | package stream 2 | 3 | import "golang.org/x/exp/slices" 4 | 5 | // SliceComparableStream Generics constraints based on comparable 6 | type SliceComparableStream[E comparable] struct { 7 | SliceStream[E] 8 | } 9 | 10 | // NewSliceByComparable new stream instance, generics constraints based on comparable 11 | func NewSliceByComparable[E comparable](source []E) SliceComparableStream[E] { 12 | return SliceComparableStream[E]{SliceStream: NewSlice(source)} 13 | } 14 | 15 | // Distinct Returns a stream consisting of the distinct elements of this stream. 16 | // Remove duplicate according to map comparable. 17 | func (stream SliceComparableStream[E]) Distinct() SliceComparableStream[E] { 18 | stream.evaluation() 19 | if stream.source == nil && len(stream.source) < 2 { 20 | return stream 21 | } 22 | 23 | newSlice := make([]E, 0) 24 | distinct := map[E]struct{}{} 25 | for _, v := range stream.source { 26 | if _, ok := distinct[v]; ok { 27 | continue 28 | } 29 | distinct[v] = struct{}{} 30 | newSlice = append(newSlice, v) 31 | } 32 | stream.source = newSlice 33 | return stream 34 | } 35 | 36 | // Equal Returns whether the source in the stream is equal to the destination source. 37 | // Equal according to the slices.Equal. 38 | func (stream SliceComparableStream[E]) Equal(dest []E) bool { 39 | stream.evaluation() 40 | return slices.Equal(stream.source, dest) 41 | } 42 | 43 | // Find Returns the index of the first element in the stream that matches the target element. 44 | // If not found then -1 is returned. 45 | func (stream SliceComparableStream[E]) Find(dest E) int { 46 | stream.evaluation() 47 | for i, v := range stream.source { 48 | if v == dest { 49 | return i 50 | } 51 | } 52 | return -1 53 | } 54 | 55 | // Parallel See: SliceStream.Parallel 56 | func (stream SliceComparableStream[E]) Parallel(goroutines int) SliceComparableStream[E] { 57 | stream.SliceStream = stream.SliceStream.Parallel(goroutines) 58 | return stream 59 | } 60 | 61 | // ForEach See: SliceStream.ForEach 62 | func (stream SliceComparableStream[E]) ForEach(action func(int, E)) SliceComparableStream[E] { 63 | stream.SliceStream.ForEach(action) 64 | return stream 65 | } 66 | 67 | // Filter See: SliceStream.Filter 68 | func (stream SliceComparableStream[E]) Filter(predicate func(E) bool) SliceComparableStream[E] { 69 | stream.SliceStream = stream.SliceStream.Filter(predicate) 70 | return stream 71 | } 72 | 73 | // Limit See: SliceStream.Limit 74 | func (stream SliceComparableStream[E]) Limit(maxSize int) SliceComparableStream[E] { 75 | stream.SliceStream = stream.SliceStream.Limit(maxSize) 76 | return stream 77 | } 78 | 79 | // Map See: SliceStream.Map 80 | func (stream SliceComparableStream[E]) Map(mapper func(E) E) SliceComparableStream[E] { 81 | stream.SliceStream = stream.SliceStream.Map(mapper) 82 | return stream 83 | } 84 | 85 | // SortFunc See: SliceStream.SortFunc 86 | func (stream SliceComparableStream[E]) SortFunc(less func(a, b E) bool) SliceComparableStream[E] { 87 | stream.SliceStream = stream.SliceStream.SortFunc(less) 88 | return stream 89 | } 90 | -------------------------------------------------------------------------------- /slice_comparable_test.go: -------------------------------------------------------------------------------- 1 | package stream 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestSliceComparableDistinct(t *testing.T) { 9 | tests := []struct { 10 | name string 11 | input []int 12 | want []int 13 | }{ 14 | { 15 | name: "case", 16 | input: []int{1, 2, 1}, 17 | want: []int{1, 2}, 18 | }, 19 | { 20 | name: "case", 21 | input: []int{1, 2, 3}, 22 | want: []int{1, 2, 3}, 23 | }, 24 | { 25 | name: "empty", 26 | input: []int{}, 27 | want: []int{}, 28 | }, 29 | { 30 | name: "nil", 31 | input: nil, 32 | want: nil, 33 | }, 34 | } 35 | for _, tt := range tests { 36 | t.Run(tt.name, func(t *testing.T) { 37 | got := NewSliceByComparable(tt.input).Distinct().ToSlice() 38 | assert.Equal(t, tt.want, got) 39 | 40 | got = NewSliceByOrdered(tt.input).Distinct().ToSlice() 41 | assert.Equal(t, tt.want, got) 42 | }) 43 | } 44 | } 45 | 46 | func TestSliceComparableEqual(t *testing.T) { 47 | tests := []struct { 48 | name string 49 | input []int 50 | input2 []int 51 | want bool 52 | }{ 53 | { 54 | name: "case", 55 | input: []int{1, 2}, 56 | input2: []int{1, 2}, 57 | want: true, 58 | }, 59 | { 60 | name: "case", 61 | input: []int{1, 2}, 62 | input2: []int{1, 2}, 63 | want: true, 64 | }, 65 | { 66 | name: "empty", 67 | input: []int{}, 68 | input2: []int{}, 69 | want: true, 70 | }, 71 | { 72 | name: "nil", 73 | input: nil, 74 | input2: nil, 75 | want: true, 76 | }, 77 | { 78 | name: "nil and empty", 79 | input: []int{}, 80 | input2: nil, 81 | want: true, 82 | }, 83 | } 84 | for _, tt := range tests { 85 | t.Run(tt.name, func(t *testing.T) { 86 | got := NewSliceByComparable(tt.input).Equal(tt.input2) 87 | assert.Equal(t, tt.want, got) 88 | 89 | got = NewSliceByOrdered(tt.input).Equal(tt.input2) 90 | assert.Equal(t, tt.want, got) 91 | }) 92 | } 93 | } 94 | 95 | func TestSliceComparableFind(t *testing.T) { 96 | tests := []struct { 97 | name string 98 | input []int 99 | dest int 100 | want int 101 | }{ 102 | { 103 | name: "case", 104 | input: []int{1, 2, 1, 2, 1}, 105 | dest: 1, 106 | want: 0, 107 | }, 108 | { 109 | name: "case", 110 | input: []int{1, 2, 1, 2, 1}, 111 | dest: 2, 112 | want: 1, 113 | }, 114 | { 115 | name: "case", 116 | input: []int{1, 2}, 117 | dest: 3, 118 | want: -1, 119 | }, 120 | { 121 | name: "empty", 122 | input: []int{}, 123 | dest: 1, 124 | want: -1, 125 | }, 126 | { 127 | name: "nil", 128 | input: nil, 129 | dest: 1, 130 | want: -1, 131 | }, 132 | } 133 | for _, tt := range tests { 134 | t.Run(tt.name, func(t *testing.T) { 135 | got := NewSliceByComparable(tt.input).Find(tt.dest) 136 | assert.Equal(t, tt.want, got) 137 | }) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /slice_mapping.go: -------------------------------------------------------------------------------- 1 | package stream 2 | 3 | // SliceMappingStream Need to convert the type of source elements. 4 | // - E elements type 5 | // - MapE map elements type 6 | // - ReduceE reduce elements type 7 | type SliceMappingStream[E any, MapE any, ReduceE any] struct { 8 | SliceStream[E] 9 | } 10 | 11 | // NewSliceByMapping new stream instance, Need to convert the type of source elements. 12 | // 13 | // - E elements type 14 | // - MapE map elements type 15 | // - ReduceE reduce elements type 16 | func NewSliceByMapping[E any, MapE any, ReduceE any](source []E) SliceMappingStream[E, MapE, ReduceE] { 17 | return SliceMappingStream[E, MapE, ReduceE]{SliceStream: NewSlice(source)} 18 | } 19 | 20 | // Map Returns a stream consisting of the results of applying the given function to the elements of this stream. 21 | // 22 | // Support Parallel. 23 | func (stream SliceMappingStream[E, MapE, ReduceE]) Map(mapper func(E) MapE) SliceMappingStream[MapE, MapE, ReduceE] { 24 | if stream.source == nil { 25 | return NewSliceByMapping[MapE, MapE, ReduceE](nil) 26 | } 27 | 28 | terminal := func(index int, v E) (isReturn bool, isComplete bool, ret MapE) { 29 | return true, false, mapper(v) 30 | } 31 | ret := pipelineRun(stream.Pipeline, wrapTerminal(stream.stages, terminal)) 32 | return NewSliceByMapping[MapE, MapE, ReduceE](ret) 33 | } 34 | 35 | // Reduce Returns a source consisting of the elements of this stream. 36 | func (stream SliceMappingStream[E, MapE, ReduceE]) Reduce(result ReduceE, accumulator func(result ReduceE, elem E) ReduceE) ReduceE { 37 | stream.evaluation() 38 | if len(stream.source) == 0 { 39 | return result 40 | } 41 | 42 | for _, v := range stream.source { 43 | result = accumulator(result, v) 44 | } 45 | return result 46 | } 47 | 48 | // Parallel See: SliceStream.Parallel 49 | func (stream SliceMappingStream[E, MapE, ReduceE]) Parallel(goroutines int) SliceMappingStream[E, MapE, ReduceE] { 50 | stream.SliceStream = stream.SliceStream.Parallel(goroutines) 51 | return stream 52 | } 53 | 54 | // ForEach See: SliceStream.ForEach 55 | func (stream SliceMappingStream[E, MapE, ReduceE]) ForEach(action func(int, E)) SliceMappingStream[E, MapE, ReduceE] { 56 | stream.SliceStream = stream.SliceStream.ForEach(action) 57 | return stream 58 | } 59 | 60 | // Filter See: SliceStream.Filter 61 | func (stream SliceMappingStream[E, MapE, ReduceE]) Filter(predicate func(E) bool) SliceMappingStream[E, MapE, ReduceE] { 62 | stream.SliceStream = stream.SliceStream.Filter(predicate) 63 | return stream 64 | } 65 | 66 | // Limit See: SliceStream.Limit 67 | func (stream SliceMappingStream[E, MapE, ReduceE]) Limit(maxSize int) SliceMappingStream[E, MapE, ReduceE] { 68 | stream.SliceStream = stream.SliceStream.Limit(maxSize) 69 | return stream 70 | } 71 | 72 | // SortFunc See: SliceStream.SortFunc 73 | func (stream SliceMappingStream[E, MapE, ReduceE]) SortFunc(less func(a, b E) bool) SliceMappingStream[E, MapE, ReduceE] { 74 | stream.SliceStream = stream.SliceStream.SortFunc(less) 75 | return stream 76 | } 77 | -------------------------------------------------------------------------------- /slice_mapping_test.go: -------------------------------------------------------------------------------- 1 | package stream 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "strconv" 6 | "testing" 7 | ) 8 | 9 | func TestSliceMapping(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | input []int 13 | init string 14 | want []string 15 | want2 string 16 | }{ 17 | { 18 | name: "case", 19 | input: []int{5, 1, 6, 2, 1}, 20 | init: "init-", 21 | want: []string{"mapping_1", "mapping_2", "mapping_1"}, 22 | want2: "init-mapping_1/mapping_2/mapping_1/", 23 | }, 24 | { 25 | name: "empty", 26 | input: []int{}, 27 | init: "init-", 28 | want: []string{}, 29 | want2: "init-", 30 | }, 31 | { 32 | name: "nil", 33 | input: nil, 34 | want: nil, 35 | }, 36 | } 37 | f := func(v int) bool { return v < 5 } 38 | 39 | mapper := func(v int) string { return "mapping_" + strconv.Itoa(v) } 40 | reducer := func(r string, s string) string { return r + s + "/" } 41 | for _, tt := range tests { 42 | t.Run(tt.name, func(t *testing.T) { 43 | 44 | got := NewSliceByMapping[int, string, string](tt.input).Filter(f).Map(mapper).ToSlice() 45 | assert.Equal(t, tt.want, got) 46 | 47 | got = NewSliceByMapping[int, string, string](tt.input).Filter(f).Parallel(2).Map(mapper).ToSlice() 48 | assert.Equal(t, tt.want, got) 49 | 50 | got1 := NewSliceByMapping[int, string, string](tt.input).Filter(f).Map(mapper).Reduce(tt.init, reducer) 51 | assert.Equal(t, tt.want2, got1) 52 | 53 | got1 = NewSliceByMapping[int, string, string](tt.input).Filter(f).Parallel(2).Map(mapper).Reduce(tt.init, reducer) 54 | assert.Equal(t, tt.want2, got1) 55 | 56 | }) 57 | } 58 | } 59 | 60 | func TestParallelSliceMapping(t *testing.T) { 61 | mapper := func(v int) string { return "mapping_" + strconv.Itoa(v) } 62 | reducer := func(r string, s string) string { return r + s + "/" } 63 | tests := []struct { 64 | name string 65 | input []int 66 | init string 67 | }{ 68 | { 69 | name: "case", 70 | input: newArray(100), 71 | init: "haha", 72 | }, 73 | { 74 | name: "case", 75 | input: newArray(200), 76 | init: "", 77 | }, 78 | { 79 | name: "case", 80 | input: newArray(300), 81 | init: "", 82 | }, 83 | } 84 | 85 | for _, tt := range tests { 86 | t.Run(tt.name, func(t *testing.T) { 87 | assert.Equal(t, 88 | NewSliceByMapping[int, string, string](tt.input).Parallel(10).Map(mapper).ToSlice(), 89 | NewSliceByMapping[int, string, string](tt.input).Map(mapper).ToSlice()) 90 | 91 | assert.Equal(t, 92 | NewSliceByMapping[int, string, string](tt.input).Parallel(10).Map(mapper).Reduce(tt.init, reducer), 93 | NewSliceByMapping[int, string, string](tt.input).Map(mapper).Reduce(tt.init, reducer)) 94 | }) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /slice_ordered.go: -------------------------------------------------------------------------------- 1 | package stream 2 | 3 | import ( 4 | "golang.org/x/exp/constraints" 5 | "golang.org/x/exp/slices" 6 | ) 7 | 8 | // SliceOrderedStream Generics constraints based on constraints.Ordered 9 | type SliceOrderedStream[E constraints.Ordered] struct { 10 | SliceComparableStream[E] 11 | } 12 | 13 | // NewSliceByOrdered new stream instance, generics constraints based on constraints.Ordered 14 | func NewSliceByOrdered[E constraints.Ordered](source []E) SliceOrderedStream[E] { 15 | return SliceOrderedStream[E]{SliceComparableStream: NewSliceByComparable(source)} 16 | } 17 | 18 | // IsSorted reports whether x is sorted in ascending order. 19 | // Compare according to the constraints.Ordered. 20 | // If the source is empty or nil then true is returned. 21 | func (stream SliceOrderedStream[E]) IsSorted() bool { 22 | stream.evaluation() 23 | return slices.IsSorted(stream.source) 24 | } 25 | 26 | // Max Returns the maximum element of this stream. 27 | // Compare according to the constraints.Ordered. 28 | // If the source is empty or nil then E Type default value is returned. ok return false 29 | func (stream SliceOrderedStream[E]) Max() (max E, ok bool) { 30 | stream.evaluation() 31 | if len(stream.source) == 0 { 32 | return 33 | } 34 | for i, v := range stream.source { 35 | if v > max || i == 0 { 36 | max = v 37 | } 38 | } 39 | return max, true 40 | } 41 | 42 | // Min Returns the minimum element of this stream. 43 | // Compare according to the constraints.Ordered. 44 | // If the source is empty or nil then E Type default value is returned. ok return false 45 | func (stream SliceOrderedStream[E]) Min() (min E, ok bool) { 46 | stream.evaluation() 47 | if len(stream.source) == 0 { 48 | return 49 | } 50 | for i, v := range stream.source { 51 | if v < min || i == 0 { 52 | min = v 53 | } 54 | } 55 | return min, true 56 | } 57 | 58 | // Sort Returns a sorted stream consisting of the elements of this stream. 59 | // Sorted according to slices.Sort. 60 | func (stream SliceOrderedStream[E]) Sort() SliceOrderedStream[E] { 61 | stream.evaluation() 62 | slices.Sort(stream.source) 63 | return stream 64 | } 65 | 66 | // Distinct See SliceComparableStream.Distinct 67 | func (stream SliceOrderedStream[E]) Distinct() SliceOrderedStream[E] { 68 | stream.SliceComparableStream = stream.SliceComparableStream.Distinct() 69 | return stream 70 | } 71 | 72 | // Parallel See: SliceStream.Parallel 73 | func (stream SliceOrderedStream[E]) Parallel(goroutines int) SliceOrderedStream[E] { 74 | stream.SliceStream = stream.SliceStream.Parallel(goroutines) 75 | return stream 76 | } 77 | 78 | // ForEach See: SliceStream.ForEach 79 | func (stream SliceOrderedStream[E]) ForEach(action func(int, E)) SliceOrderedStream[E] { 80 | stream.SliceStream = stream.SliceStream.ForEach(action) 81 | return stream 82 | } 83 | 84 | // Filter See: SliceStream.Filter 85 | func (stream SliceOrderedStream[E]) Filter(predicate func(E) bool) SliceOrderedStream[E] { 86 | stream.SliceStream = stream.SliceStream.Filter(predicate) 87 | return stream 88 | } 89 | 90 | // Limit See: SliceStream.Limit 91 | func (stream SliceOrderedStream[E]) Limit(maxSize int) SliceOrderedStream[E] { 92 | stream.SliceStream = stream.SliceStream.Limit(maxSize) 93 | return stream 94 | } 95 | 96 | // Map See: SliceStream.Map 97 | func (stream SliceOrderedStream[E]) Map(mapper func(E) E) SliceOrderedStream[E] { 98 | stream.SliceStream = stream.SliceStream.Map(mapper) 99 | return stream 100 | } 101 | 102 | // SortFunc See: SliceStream.SortFunc 103 | func (stream SliceOrderedStream[E]) SortFunc(less func(a, b E) bool) SliceOrderedStream[E] { 104 | stream.SliceStream = stream.SliceStream.SortFunc(less) 105 | return stream 106 | } 107 | -------------------------------------------------------------------------------- /slice_ordered_test.go: -------------------------------------------------------------------------------- 1 | package stream 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestSliceOrderedIsSorted(t *testing.T) { 9 | tests := []struct { 10 | name string 11 | input []int 12 | want bool 13 | }{ 14 | { 15 | name: "case", 16 | input: []int{1, 2, 1, 5}, 17 | want: false, 18 | }, 19 | { 20 | name: "case", 21 | input: []int{-1, -2, -1, -5}, 22 | want: false, 23 | }, 24 | { 25 | name: "case", 26 | input: []int{10, 11, 12, 13}, 27 | want: true, 28 | }, 29 | { 30 | name: "case", 31 | input: []int{-1, -2, -3, -4}, 32 | want: false, 33 | }, 34 | { 35 | name: "case", 36 | input: []int{-4, -3, -2, -1}, 37 | want: true, 38 | }, 39 | { 40 | name: "empty", 41 | input: []int{}, 42 | want: true, 43 | }, 44 | { 45 | name: "nil", 46 | input: nil, 47 | want: true, 48 | }, 49 | } 50 | for _, tt := range tests { 51 | t.Run(tt.name, func(t *testing.T) { 52 | got := NewSliceByOrdered(tt.input).IsSorted() 53 | assert.Equal(t, tt.want, got) 54 | }) 55 | } 56 | } 57 | 58 | func TestSliceOrderedMax(t *testing.T) { 59 | tests := []struct { 60 | name string 61 | input []int 62 | want int 63 | ok bool 64 | }{ 65 | { 66 | name: "case", 67 | input: []int{1, 2, 1, 5}, 68 | want: 5, 69 | ok: true, 70 | }, 71 | { 72 | name: "case", 73 | input: []int{-1, -2, -1, -5}, 74 | want: -1, 75 | ok: true, 76 | }, 77 | { 78 | name: "case", 79 | input: []int{10, 2, 1, 5}, 80 | want: 10, 81 | ok: true, 82 | }, 83 | { 84 | name: "empty", 85 | input: []int{}, 86 | want: 0, 87 | ok: false, 88 | }, 89 | { 90 | name: "nil", 91 | input: nil, 92 | want: 0, 93 | ok: false, 94 | }, 95 | } 96 | for _, tt := range tests { 97 | t.Run(tt.name, func(t *testing.T) { 98 | got, ok := NewSliceByOrdered(tt.input).Max() 99 | assert.Equal(t, tt.want, got) 100 | assert.Equal(t, tt.ok, ok) 101 | }) 102 | } 103 | } 104 | 105 | func TestSliceOrderedMin(t *testing.T) { 106 | tests := []struct { 107 | name string 108 | input []int 109 | want int 110 | ok bool 111 | }{ 112 | { 113 | name: "case", 114 | input: []int{1, 2, 1, 5}, 115 | want: 1, 116 | ok: true, 117 | }, 118 | { 119 | name: "case", 120 | input: []int{10, 2, 3, 1}, 121 | want: 1, 122 | ok: true, 123 | }, 124 | { 125 | name: "case", 126 | input: []int{-1, -2, -3, -1}, 127 | want: -3, 128 | ok: true, 129 | }, 130 | { 131 | name: "empty", 132 | input: []int{}, 133 | want: 0, 134 | ok: false, 135 | }, 136 | { 137 | name: "nil", 138 | input: nil, 139 | want: 0, 140 | ok: false, 141 | }, 142 | } 143 | for _, tt := range tests { 144 | t.Run(tt.name, func(t *testing.T) { 145 | got, ok := NewSliceByOrdered(tt.input).Min() 146 | assert.Equal(t, tt.want, got) 147 | assert.Equal(t, tt.ok, ok) 148 | 149 | }) 150 | } 151 | } 152 | 153 | func TestSliceOrderedSort(t *testing.T) { 154 | tests := []struct { 155 | name string 156 | input []int 157 | want []int 158 | }{ 159 | { 160 | name: "case", 161 | input: []int{1, 2, 1, 5}, 162 | want: []int{1, 1, 2, 5}, 163 | }, 164 | { 165 | name: "empty", 166 | input: []int{}, 167 | want: []int{}, 168 | }, 169 | { 170 | name: "nil", 171 | input: nil, 172 | want: nil, 173 | }, 174 | } 175 | for _, tt := range tests { 176 | t.Run(tt.name, func(t *testing.T) { 177 | got := NewSliceByOrdered(tt.input).Sort() 178 | assert.Equal(t, tt.want, got.source) 179 | }) 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /slice_test.go: -------------------------------------------------------------------------------- 1 | package stream 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "math/rand" 6 | "sync/atomic" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func newArray(count int) []int { 12 | r := rand.New(rand.NewSource(time.Now().Unix())) 13 | s := make([]int, count) 14 | for i := 0; i < count; i++ { 15 | s[i] = r.Intn(count * 2) 16 | } 17 | return s 18 | } 19 | 20 | func newArrayN(count int, n int) []int { 21 | r := rand.New(rand.NewSource(time.Now().Unix())) 22 | s := make([]int, count) 23 | for i := 0; i < count; i++ { 24 | s[i] = r.Intn(n) 25 | } 26 | return s 27 | } 28 | 29 | func TestNewSliceStream(t *testing.T) { 30 | tests := []struct { 31 | name string 32 | input []int 33 | want []int 34 | }{ 35 | { 36 | name: "case", 37 | input: []int{1, 2}, 38 | want: []int{1, 2}, 39 | }, 40 | { 41 | name: "empty", 42 | input: []int{}, 43 | want: []int{}, 44 | }, 45 | { 46 | name: "nil", 47 | input: nil, 48 | want: nil, 49 | }, 50 | } 51 | 52 | for _, tt := range tests { 53 | t.Run(tt.name, func(t *testing.T) { 54 | got := NewSlice(tt.input).ToSlice() 55 | assert.Equal(t, tt.want, got) 56 | }) 57 | } 58 | } 59 | 60 | func TestSliceAt(t *testing.T) { 61 | tests := []struct { 62 | name string 63 | input []int 64 | index int 65 | want int 66 | ok bool 67 | }{ 68 | { 69 | name: "case", 70 | input: []int{1, 2, 3}, 71 | index: 1, 72 | want: 2, 73 | ok: true, 74 | }, 75 | { 76 | name: "case", 77 | input: []int{1, 2, 3}, 78 | index: -1, 79 | want: 3, 80 | ok: true, 81 | }, 82 | { 83 | name: "case", 84 | input: []int{1, 2, 3}, 85 | index: 5, 86 | want: 0, 87 | ok: false, 88 | }, 89 | { 90 | name: "case", 91 | input: []int{1, 2, 3}, 92 | index: -4, 93 | want: 0, 94 | ok: false, 95 | }, 96 | { 97 | name: "empty", 98 | input: []int{}, 99 | index: 0, 100 | want: 0, 101 | ok: false, 102 | }, 103 | { 104 | name: "nil", 105 | input: nil, 106 | want: 0, 107 | ok: false, 108 | }, 109 | } 110 | for _, tt := range tests { 111 | t.Run(tt.name, func(t *testing.T) { 112 | got, ok := NewSlice(tt.input).At(tt.index) 113 | assert.Equal(t, tt.want, got) 114 | assert.Equal(t, tt.ok, ok) 115 | }) 116 | } 117 | } 118 | 119 | func TestSliceAllMatch(t *testing.T) { 120 | tests := []struct { 121 | name string 122 | input []int 123 | predicate func(v int) bool 124 | want bool 125 | }{ 126 | { 127 | name: "case", 128 | input: []int{1, 2}, 129 | predicate: func(v int) bool { return v < 3 }, 130 | want: true, 131 | }, 132 | { 133 | name: "case", 134 | input: []int{1, 2}, 135 | predicate: func(v int) bool { return v > 3 }, 136 | want: false, 137 | }, 138 | { 139 | name: "case", 140 | input: newArrayN(100, 200), 141 | predicate: func(v int) bool { return v > 100 }, 142 | want: false, 143 | }, 144 | { 145 | name: "case", 146 | input: newArrayN(100, 200), 147 | predicate: func(v int) bool { return v < 200 }, 148 | want: true, 149 | }, 150 | { 151 | name: "empty", 152 | input: []int{}, 153 | predicate: func(v int) bool { return v > 3 }, 154 | want: true, 155 | }, 156 | { 157 | name: "nil", 158 | input: nil, 159 | predicate: func(v int) bool { return v > 3 }, 160 | want: true, 161 | }, 162 | } 163 | for _, tt := range tests { 164 | t.Run(tt.name, func(t *testing.T) { 165 | got := NewSlice(tt.input).AllMatch(tt.predicate) 166 | assert.Equal(t, tt.want, got) 167 | 168 | got = NewSlice(tt.input).Parallel(2).AllMatch(tt.predicate) 169 | assert.Equal(t, tt.want, got) 170 | }) 171 | } 172 | } 173 | 174 | func TestSliceAnyMatch(t *testing.T) { 175 | tests := []struct { 176 | name string 177 | input []int 178 | predicate func(v int) bool 179 | want bool 180 | }{ 181 | { 182 | name: "case", 183 | input: []int{1, 2, 1, 1, 1, 1, 1, 1}, 184 | predicate: func(v int) bool { return v == 1 }, 185 | want: true, 186 | }, 187 | { 188 | name: "case", 189 | input: []int{1, 2, 1, 2, 2, 1}, 190 | predicate: func(v int) bool { return v == 3 }, 191 | want: false, 192 | }, 193 | { 194 | name: "case", 195 | input: append([]int{300}, newArrayN(100, 200)...), 196 | predicate: func(v int) bool { time.Sleep(time.Millisecond); return v == 300 }, 197 | want: true, 198 | }, 199 | { 200 | name: "empty", 201 | input: []int{}, 202 | predicate: func(v int) bool { return v > 3 }, 203 | want: false, 204 | }, 205 | { 206 | name: "nil", 207 | input: nil, 208 | predicate: func(v int) bool { return v > 3 }, 209 | want: false, 210 | }, 211 | } 212 | for _, tt := range tests { 213 | t.Run(tt.name, func(t *testing.T) { 214 | got := NewSlice(tt.input).AnyMatch(tt.predicate) 215 | assert.Equal(t, tt.want, got) 216 | 217 | got = NewSlice(tt.input).Parallel(2).AnyMatch(tt.predicate) 218 | assert.Equal(t, tt.want, got) 219 | }) 220 | } 221 | } 222 | 223 | func TestSliceAppend(t *testing.T) { 224 | tests := []struct { 225 | name string 226 | input []int 227 | input2 []int 228 | want []int 229 | }{ 230 | { 231 | name: "case", 232 | input: []int{1, 2}, 233 | input2: []int{3, 4}, 234 | want: []int{1, 2, 3, 4}, 235 | }, 236 | { 237 | name: "empty", 238 | input: []int{1, 2}, 239 | input2: []int{}, 240 | want: []int{1, 2}, 241 | }, 242 | { 243 | name: "nil", 244 | input: []int{1, 2}, 245 | input2: nil, 246 | want: []int{1, 2}, 247 | }, 248 | { 249 | name: "empty", 250 | input: []int{}, 251 | input2: []int{3, 4}, 252 | want: []int{3, 4}, 253 | }, 254 | { 255 | name: "nil", 256 | input: nil, 257 | input2: []int{3, 4}, 258 | want: []int{3, 4}, 259 | }, 260 | } 261 | for _, tt := range tests { 262 | t.Run(tt.name, func(t *testing.T) { 263 | got := NewSlice(tt.input).Append(tt.input2...).ToSlice() 264 | assert.Equal(t, tt.want, got) 265 | 266 | got[0] = 100000 267 | if len(tt.input) > 0 && len(got) > 0 { 268 | assert.NotEqual(t, tt.want[0], got[0]) 269 | } 270 | 271 | }) 272 | } 273 | } 274 | 275 | func TestSliceCount(t *testing.T) { 276 | tests := []struct { 277 | name string 278 | input []int 279 | want int 280 | }{ 281 | { 282 | name: "case", 283 | input: []int{1, 2}, 284 | want: 2, 285 | }, 286 | { 287 | name: "empty", 288 | input: []int{}, 289 | want: 0, 290 | }, 291 | { 292 | name: "nil", 293 | input: nil, 294 | want: 0, 295 | }, 296 | } 297 | for _, tt := range tests { 298 | t.Run(tt.name, func(t *testing.T) { 299 | got := NewSlice(tt.input).Count() 300 | assert.Equal(t, tt.want, got) 301 | }) 302 | } 303 | } 304 | 305 | func TestSliceEqualFunc(t *testing.T) { 306 | tests := []struct { 307 | name string 308 | input []int 309 | input2 []int 310 | want bool 311 | }{ 312 | { 313 | name: "case", 314 | input: []int{1, 2}, 315 | input2: []int{1, 2}, 316 | want: true, 317 | }, 318 | { 319 | name: "case", 320 | input: []int{1, 2}, 321 | input2: []int{1, 2}, 322 | want: true, 323 | }, 324 | { 325 | name: "empty", 326 | input: []int{}, 327 | input2: []int{}, 328 | want: true, 329 | }, 330 | { 331 | name: "nil", 332 | input: nil, 333 | input2: nil, 334 | want: true, 335 | }, 336 | { 337 | name: "nil and empty", 338 | input: []int{}, 339 | input2: nil, 340 | want: true, 341 | }, 342 | } 343 | for _, tt := range tests { 344 | t.Run(tt.name, func(t *testing.T) { 345 | got := NewSlice(tt.input).EqualFunc(tt.input2, func(a int, b int) bool { return a == b }) 346 | assert.Equal(t, tt.want, got) 347 | }) 348 | } 349 | } 350 | 351 | func TestSliceForEach(t *testing.T) { 352 | tests := []struct { 353 | name string 354 | input []int 355 | }{ 356 | { 357 | name: "case", 358 | input: newArray(100), 359 | }, 360 | { 361 | name: "case", 362 | input: newArray(123), 363 | }, 364 | { 365 | name: "case", 366 | input: newArray(1000), 367 | }, 368 | { 369 | name: "case", 370 | input: newArray(1234), 371 | }, 372 | { 373 | name: "case", 374 | input: newArray(10000), 375 | }, 376 | { 377 | name: "case", 378 | input: newArray(12345), 379 | }, 380 | { 381 | name: "nil", 382 | input: nil, 383 | }, 384 | { 385 | name: "empty", 386 | input: []int{}, 387 | }, 388 | } 389 | for _, tt := range tests { 390 | t.Run(tt.name, func(t *testing.T) { 391 | got := NewSlice(tt.input).ForEach(func(i int, v int) { assert.Equal(t, tt.input[i], v) }).ToSlice() 392 | assert.Equal(t, tt.input, got) 393 | 394 | got = NewSliceByComparable(tt.input).ForEach(func(i int, v int) { assert.Equal(t, tt.input[i], v) }).ToSlice() 395 | assert.Equal(t, tt.input, got) 396 | 397 | got = NewSliceByOrdered(tt.input).ForEach(func(i int, v int) { assert.Equal(t, tt.input[i], v) }).ToSlice() 398 | assert.Equal(t, tt.input, got) 399 | 400 | got = NewSliceByMapping[int, int, int](tt.input).ForEach(func(i int, v int) { assert.Equal(t, tt.input[i], v) }).ToSlice() 401 | assert.Equal(t, tt.input, got) 402 | 403 | got = NewSlice(tt.input).Parallel(10).ForEach(func(i int, v int) { assert.Equal(t, tt.input[i], v) }).ToSlice() 404 | assert.Equal(t, tt.input, got) 405 | 406 | var count int64 = 0 407 | NewSlice(tt.input).Parallel(10).ForEach(func(i int, v int) { atomic.AddInt64(&count, 1) }) 408 | assert.Equal(t, int64(len(tt.input)), count) 409 | }) 410 | } 411 | } 412 | 413 | func TestSliceFindFunc(t *testing.T) { 414 | tests := []struct { 415 | name string 416 | input []int 417 | predicate func(v int) bool 418 | want int 419 | }{ 420 | { 421 | name: "case", 422 | input: []int{1, 2, 1, 2, 1}, 423 | predicate: func(v int) bool { return v == 1 }, 424 | want: 0, 425 | }, 426 | { 427 | name: "case", 428 | input: []int{1, 2, 1, 2, 1}, 429 | predicate: func(v int) bool { return v == 2 }, 430 | want: 1, 431 | }, 432 | { 433 | name: "case", 434 | input: []int{1, 2}, 435 | predicate: func(v int) bool { return v == 3 }, 436 | want: -1, 437 | }, 438 | { 439 | name: "empty", 440 | input: []int{}, 441 | predicate: func(v int) bool { return v == 1 }, 442 | want: -1, 443 | }, 444 | { 445 | name: "nil", 446 | input: nil, 447 | predicate: func(v int) bool { return v == 1 }, 448 | want: -1, 449 | }, 450 | } 451 | for _, tt := range tests { 452 | t.Run(tt.name, func(t *testing.T) { 453 | got := NewSlice(tt.input).FindFunc(tt.predicate) 454 | assert.Equal(t, tt.want, got) 455 | 456 | got = NewSlice(tt.input).Parallel(4).FindFunc(tt.predicate) 457 | if got == -1 && tt.want != got { 458 | assert.Equal(t, tt.input[tt.want], tt.input[got]) 459 | } 460 | 461 | }) 462 | } 463 | } 464 | 465 | func TestSliceFilter(t *testing.T) { 466 | tests := []struct { 467 | name string 468 | input []string 469 | predicate func(v string) bool 470 | want []string 471 | }{ 472 | { 473 | name: "match", 474 | input: []string{"a", "b", "c"}, 475 | predicate: func(v string) bool { return v != "b" }, 476 | want: []string{"a", "c"}, 477 | }, 478 | { 479 | name: "no match", 480 | input: []string{"a", "b"}, 481 | predicate: func(v string) bool { return v == "c" }, 482 | want: []string{}, 483 | }, 484 | { 485 | name: "nil", 486 | input: nil, 487 | predicate: nil, 488 | want: nil, 489 | }, 490 | } 491 | for _, tt := range tests { 492 | t.Run(tt.name, func(t *testing.T) { 493 | got := NewSlice(tt.input).Filter(tt.predicate).ToSlice() 494 | assert.Equal(t, tt.want, got) 495 | 496 | got = NewSlice(tt.input).Parallel(2).Filter(tt.predicate).ToSlice() 497 | assert.Equal(t, tt.want, got) 498 | 499 | got = NewSliceByComparable(tt.input).Filter(tt.predicate).ToSlice() 500 | assert.Equal(t, tt.want, got) 501 | 502 | got = NewSliceByComparable(tt.input).Parallel(2).Filter(tt.predicate).ToSlice() 503 | assert.Equal(t, tt.want, got) 504 | 505 | got = NewSliceByOrdered(tt.input).Filter(tt.predicate).ToSlice() 506 | assert.Equal(t, tt.want, got) 507 | 508 | got = NewSliceByOrdered(tt.input).Parallel(2).Filter(tt.predicate).ToSlice() 509 | assert.Equal(t, tt.want, got) 510 | }) 511 | } 512 | 513 | tests1 := []struct { 514 | name string 515 | input []int 516 | predicate func(v int) bool 517 | want int 518 | }{ 519 | { 520 | name: "match", 521 | input: newArray(100), 522 | predicate: func(v int) bool { return v < 100 }, 523 | }, 524 | { 525 | name: "match", 526 | input: newArray(200), 527 | predicate: func(v int) bool { return v < 200 }, 528 | }, 529 | { 530 | name: "match", 531 | input: newArray(300), 532 | predicate: func(v int) bool { return v > 300 }, 533 | }, 534 | } 535 | for _, tt := range tests1 { 536 | t.Run(tt.name, func(t *testing.T) { 537 | assert.Equal(t, 538 | NewSliceByOrdered(tt.input).Parallel(10).Filter(tt.predicate).ToSlice(), 539 | NewSliceByOrdered(tt.input).Filter(tt.predicate).ToSlice()) 540 | }) 541 | } 542 | } 543 | 544 | func TestSliceFirst(t *testing.T) { 545 | tests := []struct { 546 | name string 547 | input []int 548 | want int 549 | ok bool 550 | }{ 551 | { 552 | name: "case", 553 | input: []int{1, 2, 1}, 554 | want: 1, 555 | ok: true, 556 | }, 557 | { 558 | name: "empty", 559 | input: []int{}, 560 | want: 0, 561 | ok: false, 562 | }, 563 | { 564 | name: "nil", 565 | input: nil, 566 | want: 0, 567 | ok: false, 568 | }, 569 | } 570 | for _, tt := range tests { 571 | t.Run(tt.name, func(t *testing.T) { 572 | got, ok := NewSlice(tt.input).First() 573 | assert.Equal(t, tt.want, got) 574 | assert.Equal(t, tt.ok, ok) 575 | }) 576 | } 577 | } 578 | 579 | func TestSliceInsert(t *testing.T) { 580 | tests := []struct { 581 | name string 582 | input1 []int 583 | input2 []int 584 | input3 int 585 | want []int 586 | }{ 587 | { 588 | name: "case", 589 | input1: []int{1, 2, 3}, 590 | input2: []int{4, 5}, 591 | input3: 1, 592 | want: []int{1, 4, 5, 2, 3}, 593 | }, 594 | { 595 | name: "case", 596 | input1: []int{1, 2, 3}, 597 | input2: []int{4, 5}, 598 | input3: 3, 599 | want: []int{1, 2, 3, 4, 5}, 600 | }, 601 | { 602 | name: "case", 603 | input1: []int{1, 2, 3}, 604 | input2: []int{4, 5}, 605 | input3: 5, 606 | want: []int{1, 2, 3, 4, 5}, 607 | }, 608 | { 609 | name: "empty", 610 | input1: []int{}, 611 | input2: []int{4, 5}, 612 | input3: 0, 613 | want: []int{4, 5}, 614 | }, 615 | { 616 | name: "nil", 617 | input1: []int{}, 618 | input2: []int{4, 5}, 619 | input3: 0, 620 | want: []int{4, 5}, 621 | }, 622 | } 623 | for _, tt := range tests { 624 | t.Run(tt.name, func(t *testing.T) { 625 | got := NewSlice(tt.input1).Insert(tt.input3, tt.input2...).ToSlice() 626 | assert.Equal(t, tt.want, got) 627 | }) 628 | } 629 | } 630 | 631 | func TestSliceDelete(t *testing.T) { 632 | tests := []struct { 633 | name string 634 | input1 []int 635 | input2 int 636 | input3 int 637 | want []int 638 | }{ 639 | { 640 | name: "case", 641 | input1: []int{1, 2, 3}, 642 | input2: 1, 643 | input3: 2, 644 | want: []int{1, 3}, 645 | }, 646 | { 647 | name: "case", 648 | input1: []int{1, 2, 3}, 649 | input2: 0, 650 | input3: 1, 651 | want: []int{2, 3}, 652 | }, 653 | { 654 | name: "case", 655 | input1: []int{1, 2, 3}, 656 | input2: 1, 657 | input3: 0, 658 | want: []int{2, 3}, 659 | }, 660 | { 661 | name: "case", 662 | input1: []int{1, 2, 3}, 663 | input2: 1, 664 | input3: 3, 665 | want: []int{1}, 666 | }, 667 | { 668 | name: "case", 669 | input1: []int{1, 2, 3}, 670 | input2: 1, 671 | input3: 4, 672 | want: []int{1}, 673 | }, 674 | { 675 | name: "empty", 676 | input1: []int{}, 677 | input2: 0, 678 | input3: 1, 679 | want: []int{}, 680 | }, 681 | { 682 | name: "nil", 683 | input1: []int{}, 684 | input2: 0, 685 | input3: 1, 686 | want: []int{}, 687 | }, 688 | } 689 | for _, tt := range tests { 690 | t.Run(tt.name, func(t *testing.T) { 691 | got := NewSlice(tt.input1).Delete(tt.input2, tt.input3).ToSlice() 692 | assert.Equal(t, tt.want, got) 693 | }) 694 | } 695 | } 696 | 697 | func TestSliceIsSorted(t *testing.T) { 698 | tests := []struct { 699 | name string 700 | input []int 701 | want bool 702 | }{ 703 | { 704 | name: "case", 705 | input: []int{1, 2, 1, 5}, 706 | want: false, 707 | }, 708 | { 709 | name: "case", 710 | input: []int{-1, -2, -1, -5}, 711 | want: false, 712 | }, 713 | { 714 | name: "case", 715 | input: []int{10, 11, 12, 13}, 716 | want: true, 717 | }, 718 | { 719 | name: "case", 720 | input: []int{-1, -2, -3, -4}, 721 | want: false, 722 | }, 723 | { 724 | name: "case", 725 | input: []int{-4, -3, -2, -1}, 726 | want: true, 727 | }, 728 | { 729 | name: "empty", 730 | input: []int{}, 731 | want: true, 732 | }, 733 | { 734 | name: "nil", 735 | input: nil, 736 | want: true, 737 | }, 738 | } 739 | for _, tt := range tests { 740 | t.Run(tt.name, func(t *testing.T) { 741 | got := NewSlice(tt.input).IsSortedFunc(func(a, b int) bool { return a < b }) 742 | assert.Equal(t, tt.want, got) 743 | }) 744 | } 745 | } 746 | 747 | func TestSliceLimit(t *testing.T) { 748 | tests := []struct { 749 | name string 750 | input []int 751 | limit int 752 | want []int 753 | }{ 754 | { 755 | name: "case", 756 | input: []int{1, 2, 1}, 757 | limit: 5, 758 | want: []int{1, 2, 1}, 759 | }, 760 | { 761 | name: "case", 762 | input: []int{1, 2, 1}, 763 | limit: 2, 764 | want: []int{1, 2}, 765 | }, 766 | { 767 | name: "case", 768 | input: []int{1, 2, 1}, 769 | limit: 3, 770 | want: []int{1, 2, 1}, 771 | }, 772 | { 773 | name: "case", 774 | input: []int{1, 2, 1}, 775 | limit: 0, 776 | want: []int{}, 777 | }, 778 | { 779 | name: "empty", 780 | input: []int{}, 781 | limit: 5, 782 | want: []int{}, 783 | }, 784 | { 785 | name: "nil", 786 | input: nil, 787 | limit: 5, 788 | want: nil, 789 | }, 790 | } 791 | for _, tt := range tests { 792 | t.Run(tt.name, func(t *testing.T) { 793 | got := NewSlice(tt.input).Limit(tt.limit).ToSlice() 794 | assert.Equal(t, tt.want, got) 795 | 796 | got = NewSliceByComparable(tt.input).Limit(tt.limit).ToSlice() 797 | assert.Equal(t, tt.want, got) 798 | 799 | got = NewSliceByOrdered(tt.input).Limit(tt.limit).ToSlice() 800 | assert.Equal(t, tt.want, got) 801 | 802 | got = NewSliceByMapping[int, int, int](tt.input).Limit(tt.limit).ToSlice() 803 | assert.Equal(t, tt.want, got) 804 | }) 805 | } 806 | } 807 | 808 | func TestSliceMap(t *testing.T) { 809 | tests := []struct { 810 | name string 811 | input []int 812 | mapper func(int) int 813 | want []int 814 | }{ 815 | { 816 | name: "case", 817 | input: []int{1, 2, 1}, 818 | mapper: func(i int) int { return i * 2 }, 819 | want: []int{2, 4, 2}, 820 | }, 821 | { 822 | name: "case", 823 | input: []int{1, 2, 1}, 824 | mapper: func(i int) int { return i * 2 }, 825 | want: []int{2, 4, 2}, 826 | }, 827 | { 828 | name: "empty", 829 | input: []int{}, 830 | mapper: func(i int) int { return i * 2 }, 831 | want: []int{}, 832 | }, 833 | { 834 | name: "nil", 835 | input: nil, 836 | mapper: func(i int) int { return i * 2 }, 837 | want: nil, 838 | }, 839 | } 840 | for _, tt := range tests { 841 | t.Run(tt.name, func(t *testing.T) { 842 | got := NewSlice(tt.input).Map(tt.mapper).ToSlice() 843 | assert.Equal(t, tt.want, got) 844 | 845 | got = NewSlice(tt.input).Parallel(2).Map(tt.mapper).ToSlice() 846 | assert.Equal(t, tt.want, got) 847 | 848 | got = NewSliceByComparable(tt.input).Map(tt.mapper).ToSlice() 849 | assert.Equal(t, tt.want, got) 850 | 851 | got = NewSliceByComparable(tt.input).Parallel(2).Map(tt.mapper).ToSlice() 852 | assert.Equal(t, tt.want, got) 853 | 854 | got = NewSliceByOrdered(tt.input).Map(tt.mapper).ToSlice() 855 | assert.Equal(t, tt.want, got) 856 | 857 | got = NewSliceByOrdered(tt.input).Parallel(2).Map(tt.mapper).ToSlice() 858 | assert.Equal(t, tt.want, got) 859 | }) 860 | } 861 | 862 | tests = []struct { 863 | name string 864 | input []int 865 | mapper func(int) int 866 | want []int 867 | }{ 868 | { 869 | name: "case", 870 | input: newArray(100), 871 | mapper: func(i int) int { return i * 2 }, 872 | }, 873 | { 874 | name: "case", 875 | input: newArray(200), 876 | mapper: func(i int) int { return i * 3 }, 877 | }, 878 | { 879 | name: "case", 880 | input: newArray(300), 881 | mapper: func(i int) int { return i * 4 }, 882 | }, 883 | } 884 | for _, tt := range tests { 885 | t.Run(tt.name, func(t *testing.T) { 886 | assert.Equal(t, 887 | NewSliceByOrdered(tt.input).Parallel(10).Map(tt.mapper).ToSlice(), 888 | NewSliceByOrdered(tt.input).Map(tt.mapper).ToSlice()) 889 | }) 890 | } 891 | } 892 | 893 | func TestSliceMaxFunc(t *testing.T) { 894 | tests := []struct { 895 | name string 896 | input []int 897 | want int 898 | ok bool 899 | }{ 900 | { 901 | name: "case", 902 | input: []int{1, 2, 1, 5}, 903 | want: 5, 904 | ok: true, 905 | }, 906 | { 907 | name: "case", 908 | input: []int{-1, -2, -1, -5}, 909 | want: -1, 910 | ok: true, 911 | }, 912 | { 913 | name: "case", 914 | input: []int{10, 2, 1, 5}, 915 | want: 10, 916 | ok: true, 917 | }, 918 | { 919 | name: "empty", 920 | input: []int{}, 921 | want: 0, 922 | ok: false, 923 | }, 924 | { 925 | name: "nil", 926 | input: nil, 927 | want: 0, 928 | ok: false, 929 | }, 930 | } 931 | for _, tt := range tests { 932 | t.Run(tt.name, func(t *testing.T) { 933 | got, ok := NewSlice(tt.input).MaxFunc(func(a, b int) bool { return a > b }) 934 | assert.Equal(t, tt.want, got) 935 | assert.Equal(t, tt.ok, ok) 936 | }) 937 | } 938 | } 939 | 940 | func TestSliceMinFunc(t *testing.T) { 941 | tests := []struct { 942 | name string 943 | input []int 944 | want int 945 | ok bool 946 | }{ 947 | { 948 | name: "case", 949 | input: []int{1, 2, 1, 5}, 950 | want: 1, 951 | ok: true, 952 | }, 953 | { 954 | name: "case", 955 | input: []int{10, 2, 3, 1}, 956 | want: 1, 957 | ok: true, 958 | }, 959 | { 960 | name: "case", 961 | input: []int{-1, -2, -3, -1}, 962 | want: -3, 963 | ok: true, 964 | }, 965 | { 966 | name: "empty", 967 | input: []int{}, 968 | want: 0, 969 | ok: false, 970 | }, 971 | { 972 | name: "nil", 973 | input: nil, 974 | want: 0, 975 | ok: false, 976 | }, 977 | } 978 | for _, tt := range tests { 979 | t.Run(tt.name, func(t *testing.T) { 980 | got, ok := NewSliceByOrdered(tt.input).MinFunc(func(a, b int) bool { return a < b }) 981 | assert.Equal(t, tt.want, got) 982 | assert.Equal(t, tt.ok, ok) 983 | }) 984 | } 985 | } 986 | 987 | func TestSliceReduce(t *testing.T) { 988 | tests := []struct { 989 | name string 990 | input []int 991 | init int 992 | accumulator func(int, int) int 993 | want int 994 | }{ 995 | { 996 | name: "case", 997 | input: []int{1, 2, 1, 10}, 998 | init: -1, 999 | accumulator: func(i int, j int) int { return i + j }, 1000 | want: 13, 1001 | }, 1002 | { 1003 | name: "empty", 1004 | input: []int{}, 1005 | init: 0, 1006 | accumulator: func(i int, j int) int { return i + j }, 1007 | want: 0, 1008 | }, 1009 | { 1010 | name: "nil", 1011 | input: nil, 1012 | init: 10, 1013 | accumulator: func(i int, j int) int { return i + j }, 1014 | want: 10, 1015 | }, 1016 | } 1017 | for _, tt := range tests { 1018 | t.Run(tt.name, func(t *testing.T) { 1019 | got := NewSlice(tt.input).Reduce(tt.init, tt.accumulator) 1020 | assert.Equal(t, tt.want, got) 1021 | }) 1022 | } 1023 | } 1024 | 1025 | func TestSliceSortFunc(t *testing.T) { 1026 | tests := []struct { 1027 | name string 1028 | input []int 1029 | less func(a, b int) bool 1030 | want []int 1031 | }{ 1032 | { 1033 | name: "case", 1034 | input: []int{1, 2, 1, 5}, 1035 | less: func(a, b int) bool { return a > b }, 1036 | want: []int{5, 2, 1, 1}, 1037 | }, 1038 | { 1039 | name: "case", 1040 | input: []int{1, 2, 1, 5}, 1041 | less: func(a, b int) bool { return a < b }, 1042 | want: []int{1, 1, 2, 5}, 1043 | }, 1044 | { 1045 | name: "empty", 1046 | input: []int{}, 1047 | less: func(a, b int) bool { return a > b }, 1048 | want: []int{}, 1049 | }, 1050 | { 1051 | name: "nil", 1052 | input: nil, 1053 | less: func(a, b int) bool { return a > b }, 1054 | want: nil, 1055 | }, 1056 | } 1057 | for _, tt := range tests { 1058 | t.Run(tt.name, func(t *testing.T) { 1059 | got := NewSlice(tt.input).SortFunc(tt.less).ToSlice() 1060 | assert.Equal(t, tt.want, got) 1061 | 1062 | got = NewSliceByComparable(tt.input).SortFunc(tt.less).ToSlice() 1063 | assert.Equal(t, tt.want, got) 1064 | 1065 | got = NewSliceByOrdered(tt.input).SortFunc(tt.less).ToSlice() 1066 | assert.Equal(t, tt.want, got) 1067 | 1068 | got = NewSliceByMapping[int, int, int](tt.input).SortFunc(tt.less).ToSlice() 1069 | assert.Equal(t, tt.want, got) 1070 | }) 1071 | } 1072 | } 1073 | 1074 | func TestSlicePipelines(t *testing.T) { 1075 | tests := []struct { 1076 | name string 1077 | input []string 1078 | predicate func(v string) bool 1079 | mapper func(v string) string 1080 | want []string 1081 | }{ 1082 | { 1083 | name: "case", 1084 | input: []string{"a", "b", "c"}, 1085 | predicate: func(v string) bool { return v != "b" }, 1086 | mapper: func(v string) string { 1087 | return v + "1" 1088 | }, 1089 | want: []string{"a1", "c1"}, 1090 | }, 1091 | { 1092 | name: "case", 1093 | input: []string{"a", "b"}, 1094 | predicate: func(v string) bool { return v == "c" }, 1095 | want: []string{}, 1096 | }, 1097 | { 1098 | name: "nil", 1099 | input: nil, 1100 | predicate: nil, 1101 | want: nil, 1102 | }, 1103 | } 1104 | for _, tt := range tests { 1105 | t.Run(tt.name, func(t *testing.T) { 1106 | got := NewSlice(tt.input).Filter(tt.predicate).Map(tt.mapper).ToSlice() 1107 | assert.Equal(t, tt.want, got) 1108 | 1109 | got = NewSlice(tt.input).Parallel(2).Filter(tt.predicate).Map(tt.mapper).ToSlice() 1110 | assert.Equal(t, tt.want, got) 1111 | 1112 | got = NewSliceByComparable(tt.input).Filter(tt.predicate).Map(tt.mapper).ToSlice() 1113 | assert.Equal(t, tt.want, got) 1114 | 1115 | got = NewSliceByComparable(tt.input).Parallel(2).Filter(tt.predicate).Map(tt.mapper).ToSlice() 1116 | assert.Equal(t, tt.want, got) 1117 | 1118 | got = NewSliceByOrdered(tt.input).Filter(tt.predicate).Map(tt.mapper).ToSlice() 1119 | assert.Equal(t, tt.want, got) 1120 | 1121 | got = NewSliceByOrdered(tt.input).Parallel(2).Filter(tt.predicate).Map(tt.mapper).ToSlice() 1122 | assert.Equal(t, tt.want, got) 1123 | }) 1124 | } 1125 | } 1126 | 1127 | func TestSlicePipelineForEach(t *testing.T) { 1128 | tests := []struct { 1129 | name string 1130 | input []int 1131 | mapper func(v int) int 1132 | want []int 1133 | }{ 1134 | { 1135 | name: "case", 1136 | input: []int{1, 2, 3}, 1137 | mapper: func(v int) int { 1138 | return v + 1 1139 | }, 1140 | want: []int{2, 3, 4}, 1141 | }, 1142 | } 1143 | 1144 | for _, tt := range tests { 1145 | t.Run(tt.name, func(t *testing.T) { 1146 | s1 := NewSliceByOrdered(tt.input).Map(tt.mapper).ForEach(func(i int, v int) { assert.Equal(t, tt.want[i], v) }) 1147 | assert.Nil(t, s1.stages) 1148 | assert.Equal(t, tt.want, s1.ToSlice()) 1149 | 1150 | s2 := NewSliceByOrdered(tt.input).Parallel(2).Map(tt.mapper).ForEach(func(i int, v int) { assert.Equal(t, tt.want[i], v) }) 1151 | assert.Nil(t, s2.stages) 1152 | assert.Equal(t, tt.want, s2.ToSlice()) 1153 | }) 1154 | } 1155 | } 1156 | 1157 | func TestSlicePipelineSort(t *testing.T) { 1158 | tests := []struct { 1159 | name string 1160 | input []int 1161 | mapper func(v int) int 1162 | want []int 1163 | }{ 1164 | { 1165 | name: "case", 1166 | input: []int{30, 10, 20}, 1167 | mapper: func(v int) int { 1168 | return v + 1 1169 | }, 1170 | want: []int{11, 21, 31}, 1171 | }, 1172 | } 1173 | 1174 | for _, tt := range tests { 1175 | t.Run(tt.name, func(t *testing.T) { 1176 | s1 := NewSliceByOrdered(tt.input).Map(tt.mapper).Sort() 1177 | assert.Nil(t, s1.stages) 1178 | assert.Equal(t, tt.want, s1.ToSlice()) 1179 | 1180 | s2 := NewSliceByOrdered(tt.input).Parallel(2).Map(tt.mapper).Sort() 1181 | assert.Nil(t, s2.stages) 1182 | assert.Equal(t, tt.want, s2.ToSlice()) 1183 | }) 1184 | } 1185 | } 1186 | --------------------------------------------------------------------------------