├── .codecov.yml ├── .github └── workflows │ └── go.yml ├── .gitignore ├── .golangci.yml ├── LICENSE ├── Makefile ├── README.md ├── bind.go ├── bind_test.go ├── concurrency.go ├── concurrency_test.go ├── errors.go ├── errors_test.go ├── go.mod ├── go.sum ├── internal ├── benchmark │ ├── foreach_test.go │ ├── num_fmt_test.go │ ├── slice_equal_test.go │ └── sort_test.go └── testdata │ └── testdata.go ├── map.go ├── map_test.go ├── math.go ├── math_test.go ├── number.go ├── number_cast.go ├── number_cast_test.go ├── number_test.go ├── rand.go ├── rand_test.go ├── slice.go ├── slice_algo.go ├── slice_algo_test.go ├── slice_conv.go ├── slice_conv_test.go ├── slice_filter.go ├── slice_filter_test.go ├── slice_iter.go ├── slice_iter_test.go ├── slice_sort.go ├── slice_sort_test.go ├── slice_subset.go ├── slice_subset_test.go ├── slice_test.go ├── slice_transform.go ├── slice_transform_test.go ├── slice_uniq.go ├── slice_uniq_test.go ├── string.go ├── string_test.go ├── struct.go ├── time.go ├── time_test.go ├── tuples.go ├── tuples_test.go ├── types.go ├── util.go └── util_test.go /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | range: 80..100 3 | round: down 4 | precision: 2 5 | 6 | status: 7 | project: # measuring the overall project coverage 8 | default: # context, you can create multiple ones with custom titles 9 | enabled: yes # must be yes|true to enable this status 10 | target: 95% # specify the target coverage for each commit status 11 | # option: "auto" (must increase from parent commit or pull request base) 12 | # option: "X%" a static target percentage to hit 13 | if_not_found: success # if parent is not found report status as success, error, or failure 14 | if_ci_failed: error # if ci fails report status as success, error, or failure 15 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: ['*'] 6 | tags: ['v*'] 7 | pull_request: 8 | branches: ['*'] 9 | 10 | jobs: 11 | 12 | build: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | go: ["1.18.x", "1.22.x", "1.23.x"] 17 | include: 18 | - go: 1.23.x 19 | latest: true 20 | 21 | steps: 22 | - name: Setup Go 23 | uses: actions/setup-go@v5 24 | with: 25 | go-version: ${{ matrix.go }} 26 | 27 | - name: Checkout code 28 | uses: actions/checkout@v4 29 | 30 | - name: Load cached dependencies 31 | uses: actions/cache@v4 32 | with: 33 | path: ~/go/pkg/mod 34 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 35 | restore-keys: | 36 | ${{ runner.os }}-go- 37 | 38 | - name: Download Dependencies 39 | run: make prepare 40 | 41 | - name: Lint 42 | run: make lint 43 | 44 | - name: Test 45 | run: make cover 46 | 47 | - name: Upload coverage to codecov.io 48 | uses: codecov/codecov-action@v4 49 | with: 50 | token: ${{ secrets.CODECOV_TOKEN }} 51 | -------------------------------------------------------------------------------- /.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 | # Goland / OS 18 | .idea 19 | .DS_Store 20 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters-settings: 2 | funlen: 3 | lines: 160 4 | statements: 72 5 | gci: 6 | sections: 7 | - standard 8 | - default 9 | - prefix(github.com/tiendc/gofn) 10 | gocyclo: 11 | min-complexity: 30 12 | goimports: 13 | local-prefixes: github.com/golangci/golangci-lint 14 | lll: 15 | line-length: 120 16 | misspell: 17 | locale: US 18 | 19 | linters: 20 | enable: 21 | - bodyclose 22 | - contextcheck 23 | - dogsled 24 | - errcheck 25 | - errname 26 | - errorlint 27 | - exhaustive 28 | - copyloopvar 29 | - forbidigo 30 | - forcetypeassert 31 | - funlen 32 | - gci 33 | - gocognit 34 | - goconst 35 | - gocritic 36 | - gocyclo 37 | - err113 38 | - gofmt 39 | - goimports 40 | - mnd 41 | - gosec 42 | - gosimple 43 | - govet 44 | - ineffassign 45 | - lll 46 | - misspell 47 | - nakedret 48 | - nestif 49 | - nilerr 50 | - rowserrcheck 51 | - staticcheck 52 | - stylecheck 53 | - typecheck 54 | - unconvert 55 | - unparam 56 | - unused 57 | - whitespace 58 | - wrapcheck 59 | 60 | issues: 61 | exclude-rules: 62 | - path: _test\.go 63 | linters: 64 | - funlen 65 | - contextcheck 66 | - staticcheck 67 | - gocyclo 68 | - gocognit 69 | - err113 70 | - forcetypeassert 71 | - wrapcheck 72 | - mnd 73 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 tiendc 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 | all: lint test 2 | 3 | prepare: 4 | @curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell go env GOPATH)/bin v1.60.3 5 | 6 | build: 7 | @go build -v ./... 8 | 9 | test: 10 | @go test -cover -v ./... 11 | 12 | cover: 13 | @go test -race -coverprofile=coverage.txt -coverpkg=./... ./... 14 | @go tool cover -html=coverage.txt -o coverage.html 15 | 16 | lint: 17 | golangci-lint --timeout=5m0s run -v ./... 18 | 19 | bench: 20 | go test -benchmem -count 100 -bench . 21 | 22 | mod: 23 | go mod tidy && go mod vendor 24 | -------------------------------------------------------------------------------- /bind.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | func Bind1Arg0Ret[A1 any](fn func(A1), a1 A1) func() { 4 | return func() { 5 | fn(a1) 6 | } 7 | } 8 | 9 | func Bind1Arg1Ret[A1 any, R1 any](fn func(A1) R1, a1 A1) func() R1 { 10 | return func() R1 { 11 | return fn(a1) 12 | } 13 | } 14 | 15 | func Bind1Arg2Ret[A1 any, R1 any, R2 any](fn func(A1) (R1, R2), a1 A1) func() (R1, R2) { 16 | return func() (R1, R2) { 17 | return fn(a1) 18 | } 19 | } 20 | 21 | func Bind1Arg3Ret[A1 any, R1 any, R2 any, R3 any](fn func(A1) (R1, R2, R3), a1 A1) func() (R1, R2, R3) { 22 | return func() (R1, R2, R3) { 23 | return fn(a1) 24 | } 25 | } 26 | 27 | func Bind2Arg0Ret[A1 any, A2 any](fn func(A1, A2), a1 A1, a2 A2) func() { 28 | return func() { 29 | fn(a1, a2) 30 | } 31 | } 32 | 33 | func Bind2Arg1Ret[A1 any, A2 any, R1 any](fn func(A1, A2) R1, a1 A1, a2 A2) func() R1 { 34 | return func() R1 { 35 | return fn(a1, a2) 36 | } 37 | } 38 | 39 | func Bind2Arg2Ret[A1 any, A2 any, R1 any, R2 any](fn func(A1, A2) (R1, R2), a1 A1, a2 A2) func() (R1, R2) { 40 | return func() (R1, R2) { 41 | return fn(a1, a2) 42 | } 43 | } 44 | 45 | // nolint: lll 46 | func Bind2Arg3Ret[A1 any, A2 any, R1 any, R2 any, R3 any](fn func(A1, A2) (R1, R2, R3), a1 A1, a2 A2) func() (R1, R2, R3) { 47 | return func() (R1, R2, R3) { 48 | return fn(a1, a2) 49 | } 50 | } 51 | 52 | func Bind3Arg0Ret[A1 any, A2 any, A3 any](fn func(A1, A2, A3), a1 A1, a2 A2, a3 A3) func() { 53 | return func() { 54 | fn(a1, a2, a3) 55 | } 56 | } 57 | 58 | func Bind3Arg1Ret[A1 any, A2 any, A3 any, R1 any](fn func(A1, A2, A3) R1, a1 A1, a2 A2, a3 A3) func() R1 { 59 | return func() R1 { 60 | return fn(a1, a2, a3) 61 | } 62 | } 63 | 64 | // nolint: lll 65 | func Bind3Arg2Ret[A1 any, A2 any, A3 any, R1 any, R2 any](fn func(A1, A2, A3) (R1, R2), a1 A1, a2 A2, a3 A3) func() (R1, R2) { 66 | return func() (R1, R2) { 67 | return fn(a1, a2, a3) 68 | } 69 | } 70 | 71 | // nolint: lll 72 | func Bind3Arg3Ret[A1 any, A2 any, A3 any, R1 any, R2 any, R3 any](fn func(A1, A2, A3) (R1, R2, R3), a1 A1, a2 A2, a3 A3) func() (R1, R2, R3) { 73 | return func() (R1, R2, R3) { 74 | return fn(a1, a2, a3) 75 | } 76 | } 77 | 78 | func Bind4Arg0Ret[A1 any, A2 any, A3 any, A4 any](fn func(A1, A2, A3, A4), a1 A1, a2 A2, a3 A3, a4 A4) func() { 79 | return func() { 80 | fn(a1, a2, a3, a4) 81 | } 82 | } 83 | 84 | // nolint: lll 85 | func Bind4Arg1Ret[A1 any, A2 any, A3 any, A4 any, R1 any](fn func(A1, A2, A3, A4) R1, a1 A1, a2 A2, a3 A3, a4 A4) func() R1 { 86 | return func() R1 { 87 | return fn(a1, a2, a3, a4) 88 | } 89 | } 90 | 91 | // nolint: lll 92 | func Bind4Arg2Ret[A1 any, A2 any, A3 any, A4 any, R1 any, R2 any](fn func(A1, A2, A3, A4) (R1, R2), a1 A1, a2 A2, a3 A3, a4 A4) func() (R1, R2) { 93 | return func() (R1, R2) { 94 | return fn(a1, a2, a3, a4) 95 | } 96 | } 97 | 98 | // nolint: lll 99 | func Bind4Arg3Ret[A1 any, A2 any, A3 any, A4 any, R1 any, R2 any, R3 any](fn func(A1, A2, A3, A4) (R1, R2, R3), a1 A1, a2 A2, a3 A3, a4 A4) func() (R1, R2, R3) { 100 | return func() (R1, R2, R3) { 101 | return fn(a1, a2, a3, a4) 102 | } 103 | } 104 | 105 | // nolint: lll 106 | func Bind5Arg0Ret[A1 any, A2 any, A3 any, A4 any, A5 any](fn func(A1, A2, A3, A4, A5), a1 A1, a2 A2, a3 A3, a4 A4, a5 A5) func() { 107 | return func() { 108 | fn(a1, a2, a3, a4, a5) 109 | } 110 | } 111 | 112 | // nolint: lll 113 | func Bind5Arg1Ret[A1 any, A2 any, A3 any, A4 any, A5 any, R1 any](fn func(A1, A2, A3, A4, A5) R1, a1 A1, a2 A2, a3 A3, a4 A4, a5 A5) func() R1 { 114 | return func() R1 { 115 | return fn(a1, a2, a3, a4, a5) 116 | } 117 | } 118 | 119 | // nolint: lll 120 | func Bind5Arg2Ret[A1 any, A2 any, A3 any, A4 any, A5 any, R1 any, R2 any](fn func(A1, A2, A3, A4, A5) (R1, R2), a1 A1, a2 A2, a3 A3, a4 A4, a5 A5) func() (R1, R2) { 121 | return func() (R1, R2) { 122 | return fn(a1, a2, a3, a4, a5) 123 | } 124 | } 125 | 126 | // nolint: lll 127 | func Bind5Arg3Ret[A1 any, A2 any, A3 any, A4 any, A5 any, R1 any, R2 any, R3 any](fn func(A1, A2, A3, A4, A5) (R1, R2, R3), a1 A1, a2 A2, a3 A3, a4 A4, a5 A5) func() (R1, R2, R3) { 128 | return func() (R1, R2, R3) { 129 | return fn(a1, a2, a3, a4, a5) 130 | } 131 | } 132 | 133 | func Partial2Arg0Ret[A1 any, A2 any](fn func(A1, A2), a1 A1) func(A2) { 134 | return func(a2 A2) { 135 | fn(a1, a2) 136 | } 137 | } 138 | 139 | func Partial2Arg1Ret[A1 any, A2 any, R1 any](fn func(A1, A2) R1, a1 A1) func(A2) R1 { 140 | return func(a2 A2) R1 { 141 | return fn(a1, a2) 142 | } 143 | } 144 | 145 | func Partial2Arg2Ret[A1 any, A2 any, R1 any, R2 any](fn func(A1, A2) (R1, R2), a1 A1) func(A2) (R1, R2) { 146 | return func(a2 A2) (R1, R2) { 147 | return fn(a1, a2) 148 | } 149 | } 150 | 151 | // nolint: lll 152 | func Partial2Arg3Ret[A1 any, A2 any, R1 any, R2 any, R3 any](fn func(A1, A2) (R1, R2, R3), a1 A1) func(A2) (R1, R2, R3) { 153 | return func(a2 A2) (R1, R2, R3) { 154 | return fn(a1, a2) 155 | } 156 | } 157 | 158 | func Partial3Arg0Ret[A1 any, A2 any, A3 any](fn func(A1, A2, A3), a1 A1) func(A2, A3) { 159 | return func(a2 A2, a3 A3) { 160 | fn(a1, a2, a3) 161 | } 162 | } 163 | 164 | func Partial3Arg1Ret[A1 any, A2 any, A3 any, R1 any](fn func(A1, A2, A3) R1, a1 A1) func(A2, A3) R1 { 165 | return func(a2 A2, a3 A3) R1 { 166 | return fn(a1, a2, a3) 167 | } 168 | } 169 | 170 | // nolint: lll 171 | func Partial3Arg2Ret[A1 any, A2 any, A3 any, R1 any, R2 any](fn func(A1, A2, A3) (R1, R2), a1 A1) func(A2, A3) (R1, R2) { 172 | return func(a2 A2, a3 A3) (R1, R2) { 173 | return fn(a1, a2, a3) 174 | } 175 | } 176 | 177 | // nolint: lll 178 | func Partial3Arg3Ret[A1 any, A2 any, A3 any, R1 any, R2 any, R3 any](fn func(A1, A2, A3) (R1, R2, R3), a1 A1) func(A2, A3) (R1, R2, R3) { 179 | return func(a2 A2, a3 A3) (R1, R2, R3) { 180 | return fn(a1, a2, a3) 181 | } 182 | } 183 | 184 | func Partial4Arg0Ret[A1 any, A2 any, A3 any, A4 any](fn func(A1, A2, A3, A4), a1 A1) func(A2, A3, A4) { 185 | return func(a2 A2, a3 A3, a4 A4) { 186 | fn(a1, a2, a3, a4) 187 | } 188 | } 189 | 190 | // nolint: lll 191 | func Partial4Arg1Ret[A1 any, A2 any, A3 any, A4 any, R1 any](fn func(A1, A2, A3, A4) R1, a1 A1) func(A2, A3, A4) R1 { 192 | return func(a2 A2, a3 A3, a4 A4) R1 { 193 | return fn(a1, a2, a3, a4) 194 | } 195 | } 196 | 197 | // nolint: lll 198 | func Partial4Arg2Ret[A1 any, A2 any, A3 any, A4 any, R1 any, R2 any](fn func(A1, A2, A3, A4) (R1, R2), a1 A1) func(A2, A3, A4) (R1, R2) { 199 | return func(a2 A2, a3 A3, a4 A4) (R1, R2) { 200 | return fn(a1, a2, a3, a4) 201 | } 202 | } 203 | 204 | // nolint: lll 205 | func Partial4Arg3Ret[A1 any, A2 any, A3 any, A4 any, R1 any, R2 any, R3 any](fn func(A1, A2, A3, A4) (R1, R2, R3), a1 A1) func(A2, A3, A4) (R1, R2, R3) { 206 | return func(a2 A2, a3 A3, a4 A4) (R1, R2, R3) { 207 | return fn(a1, a2, a3, a4) 208 | } 209 | } 210 | 211 | // nolint: lll 212 | func Partial5Arg0Ret[A1 any, A2 any, A3 any, A4 any, A5 any](fn func(A1, A2, A3, A4, A5), a1 A1) func(A2, A3, A4, A5) { 213 | return func(a2 A2, a3 A3, a4 A4, a5 A5) { 214 | fn(a1, a2, a3, a4, a5) 215 | } 216 | } 217 | 218 | // nolint: lll 219 | func Partial5Arg1Ret[A1 any, A2 any, A3 any, A4 any, A5 any, R1 any](fn func(A1, A2, A3, A4, A5) R1, a1 A1) func(A2, A3, A4, A5) R1 { 220 | return func(a2 A2, a3 A3, a4 A4, a5 A5) R1 { 221 | return fn(a1, a2, a3, a4, a5) 222 | } 223 | } 224 | 225 | // nolint: lll 226 | func Partial5Arg2Ret[A1 any, A2 any, A3 any, A4 any, A5 any, R1 any, R2 any](fn func(A1, A2, A3, A4, A5) (R1, R2), a1 A1) func(A2, A3, A4, A5) (R1, R2) { 227 | return func(a2 A2, a3 A3, a4 A4, a5 A5) (R1, R2) { 228 | return fn(a1, a2, a3, a4, a5) 229 | } 230 | } 231 | 232 | // nolint: lll 233 | func Partial5Arg3Ret[A1 any, A2 any, A3 any, A4 any, A5 any, R1 any, R2 any, R3 any](fn func(A1, A2, A3, A4, A5) (R1, R2, R3), a1 A1) func(A2, A3, A4, A5) (R1, R2, R3) { 234 | return func(a2 A2, a3 A3, a4 A4, a5 A5) (R1, R2, R3) { 235 | return fn(a1, a2, a3, a4, a5) 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /bind_test.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func Test_Bind1Arg(t *testing.T) { 10 | fn1Arg0Ret := func(a1 int) { 11 | assert.True(t, a1 == 1) 12 | } 13 | fn1Arg1Ret := func(a1 int) int { 14 | assert.True(t, a1 == 1) 15 | return 1 16 | } 17 | fn1Arg2Ret := func(a1 int) (int, int) { 18 | assert.True(t, a1 == 1) 19 | return 1, 2 20 | } 21 | fn1Arg3Ret := func(a1 int) (int, int, int) { 22 | assert.True(t, a1 == 1) 23 | return 1, 2, 3 24 | } 25 | 26 | Bind1Arg0Ret(fn1Arg0Ret, 1)() 27 | r1 := Bind1Arg1Ret(fn1Arg1Ret, 1)() 28 | assert.True(t, r1 == 1) 29 | r1, r2 := Bind1Arg2Ret(fn1Arg2Ret, 1)() 30 | assert.True(t, r1 == 1 && r2 == 2) 31 | r1, r2, r3 := Bind1Arg3Ret(fn1Arg3Ret, 1)() 32 | assert.True(t, r1 == 1 && r2 == 2 && r3 == 3) 33 | } 34 | 35 | func Test_Bind2Arg(t *testing.T) { 36 | fn2Arg0Ret := func(a1 int, a2 int) { 37 | assert.True(t, a1 == 1 && a2 == 2) 38 | } 39 | fn2Arg1Ret := func(a1 int, a2 int) int { 40 | assert.True(t, a1 == 1 && a2 == 2) 41 | return 1 42 | } 43 | fn2Arg2Ret := func(a1 int, a2 int) (int, int) { 44 | assert.True(t, a1 == 1 && a2 == 2) 45 | return 1, 2 46 | } 47 | fn2Arg3Ret := func(a1 int, a2 int) (int, int, int) { 48 | assert.True(t, a1 == 1 && a2 == 2) 49 | return 1, 2, 3 50 | } 51 | 52 | Bind2Arg0Ret(fn2Arg0Ret, 1, 2)() 53 | r1 := Bind2Arg1Ret(fn2Arg1Ret, 1, 2)() 54 | assert.True(t, r1 == 1) 55 | r1, r2 := Bind2Arg2Ret(fn2Arg2Ret, 1, 2)() 56 | assert.True(t, r1 == 1 && r2 == 2) 57 | r1, r2, r3 := Bind2Arg3Ret(fn2Arg3Ret, 1, 2)() 58 | assert.True(t, r1 == 1 && r2 == 2 && r3 == 3) 59 | } 60 | 61 | func Test_Bind3Arg(t *testing.T) { 62 | fn3Arg0Ret := func(a1 int, a2 int, a3 int) { 63 | assert.True(t, a1 == 1 && a2 == 2 && a3 == 3) 64 | } 65 | fn3Arg1Ret := func(a1 int, a2 int, a3 int) int { 66 | assert.True(t, a1 == 1 && a2 == 2 && a3 == 3) 67 | return 1 68 | } 69 | fn3Arg2Ret := func(a1 int, a2 int, a3 int) (int, int) { 70 | assert.True(t, a1 == 1 && a2 == 2 && a3 == 3) 71 | return 1, 2 72 | } 73 | fn3Arg3Ret := func(a1 int, a2 int, a3 int) (int, int, int) { 74 | assert.True(t, a1 == 1 && a2 == 2 && a3 == 3) 75 | return 1, 2, 3 76 | } 77 | 78 | Bind3Arg0Ret(fn3Arg0Ret, 1, 2, 3)() 79 | r1 := Bind3Arg1Ret(fn3Arg1Ret, 1, 2, 3)() 80 | assert.True(t, r1 == 1) 81 | r1, r2 := Bind3Arg2Ret(fn3Arg2Ret, 1, 2, 3)() 82 | assert.True(t, r1 == 1 && r2 == 2) 83 | r1, r2, r3 := Bind3Arg3Ret(fn3Arg3Ret, 1, 2, 3)() 84 | assert.True(t, r1 == 1 && r2 == 2 && r3 == 3) 85 | } 86 | 87 | func Test_Bind4Arg(t *testing.T) { 88 | fn4Arg0Ret := func(a1 int, a2 int, a3 int, a4 int) { 89 | assert.True(t, a1 == 1 && a2 == 2 && a3 == 3 && a4 == 4) 90 | } 91 | fn4Arg1Ret := func(a1 int, a2 int, a3 int, a4 int) int { 92 | assert.True(t, a1 == 1 && a2 == 2 && a3 == 3 && a4 == 4) 93 | return 1 94 | } 95 | fn4Arg2Ret := func(a1 int, a2 int, a3 int, a4 int) (int, int) { 96 | assert.True(t, a1 == 1 && a2 == 2 && a3 == 3 && a4 == 4) 97 | return 1, 2 98 | } 99 | fn4Arg3Ret := func(a1 int, a2 int, a3 int, a4 int) (int, int, int) { 100 | assert.True(t, a1 == 1 && a2 == 2 && a3 == 3 && a4 == 4) 101 | return 1, 2, 3 102 | } 103 | 104 | Bind4Arg0Ret(fn4Arg0Ret, 1, 2, 3, 4)() 105 | r1 := Bind4Arg1Ret(fn4Arg1Ret, 1, 2, 3, 4)() 106 | assert.True(t, r1 == 1) 107 | r1, r2 := Bind4Arg2Ret(fn4Arg2Ret, 1, 2, 3, 4)() 108 | assert.True(t, r1 == 1 && r2 == 2) 109 | r1, r2, r3 := Bind4Arg3Ret(fn4Arg3Ret, 1, 2, 3, 4)() 110 | assert.True(t, r1 == 1 && r2 == 2 && r3 == 3) 111 | } 112 | 113 | func Test_Bind5Arg(t *testing.T) { 114 | fn5Arg0Ret := func(a1 int, a2 int, a3 int, a4 int, a5 int) { 115 | assert.True(t, a1 == 1 && a2 == 2 && a3 == 3 && a4 == 4 && a5 == 5) 116 | } 117 | fn5Arg1Ret := func(a1 int, a2 int, a3 int, a4 int, a5 int) int { 118 | assert.True(t, a1 == 1 && a2 == 2 && a3 == 3 && a4 == 4 && a5 == 5) 119 | return 1 120 | } 121 | fn5Arg2Ret := func(a1 int, a2 int, a3 int, a4 int, a5 int) (int, int) { 122 | assert.True(t, a1 == 1 && a2 == 2 && a3 == 3 && a4 == 4 && a5 == 5) 123 | return 1, 2 124 | } 125 | fn5Arg3Ret := func(a1 int, a2 int, a3 int, a4 int, a5 int) (int, int, int) { 126 | assert.True(t, a1 == 1 && a2 == 2 && a3 == 3 && a4 == 4 && a5 == 5) 127 | return 1, 2, 3 128 | } 129 | 130 | Bind5Arg0Ret(fn5Arg0Ret, 1, 2, 3, 4, 5)() 131 | r1 := Bind5Arg1Ret(fn5Arg1Ret, 1, 2, 3, 4, 5)() 132 | assert.True(t, r1 == 1) 133 | r1, r2 := Bind5Arg2Ret(fn5Arg2Ret, 1, 2, 3, 4, 5)() 134 | assert.True(t, r1 == 1 && r2 == 2) 135 | r1, r2, r3 := Bind5Arg3Ret(fn5Arg3Ret, 1, 2, 3, 4, 5)() 136 | assert.True(t, r1 == 1 && r2 == 2 && r3 == 3) 137 | } 138 | 139 | func Test_Partial2Arg(t *testing.T) { 140 | fn2Arg0Ret := func(a1 int, a2 int) { 141 | assert.True(t, a1 == 1 && a2 == 2) 142 | } 143 | fn2Arg1Ret := func(a1 int, a2 int) int { 144 | assert.True(t, a1 == 1 && a2 == 2) 145 | return 1 146 | } 147 | fn2Arg2Ret := func(a1 int, a2 int) (int, int) { 148 | assert.True(t, a1 == 1 && a2 == 2) 149 | return 1, 2 150 | } 151 | fn2Arg3Ret := func(a1 int, a2 int) (int, int, int) { 152 | assert.True(t, a1 == 1 && a2 == 2) 153 | return 1, 2, 3 154 | } 155 | 156 | Partial2Arg0Ret(fn2Arg0Ret, 1)(2) 157 | r1 := Partial2Arg1Ret(fn2Arg1Ret, 1)(2) 158 | assert.True(t, r1 == 1) 159 | r1, r2 := Partial2Arg2Ret(fn2Arg2Ret, 1)(2) 160 | assert.True(t, r1 == 1 && r2 == 2) 161 | r1, r2, r3 := Partial2Arg3Ret(fn2Arg3Ret, 1)(2) 162 | assert.True(t, r1 == 1 && r2 == 2 && r3 == 3) 163 | } 164 | 165 | func Test_Partial3Arg(t *testing.T) { 166 | fn3Arg0Ret := func(a1 int, a2 int, a3 int) { 167 | assert.True(t, a1 == 1 && a2 == 2 && a3 == 3) 168 | } 169 | fn3Arg1Ret := func(a1 int, a2 int, a3 int) int { 170 | assert.True(t, a1 == 1 && a2 == 2 && a3 == 3) 171 | return 1 172 | } 173 | fn3Arg2Ret := func(a1 int, a2 int, a3 int) (int, int) { 174 | assert.True(t, a1 == 1 && a2 == 2 && a3 == 3) 175 | return 1, 2 176 | } 177 | fn3Arg3Ret := func(a1 int, a2 int, a3 int) (int, int, int) { 178 | assert.True(t, a1 == 1 && a2 == 2 && a3 == 3) 179 | return 1, 2, 3 180 | } 181 | 182 | Partial3Arg0Ret(fn3Arg0Ret, 1)(2, 3) 183 | r1 := Partial3Arg1Ret(fn3Arg1Ret, 1)(2, 3) 184 | assert.True(t, r1 == 1) 185 | r1, r2 := Partial3Arg2Ret(fn3Arg2Ret, 1)(2, 3) 186 | assert.True(t, r1 == 1 && r2 == 2) 187 | r1, r2, r3 := Partial3Arg3Ret(fn3Arg3Ret, 1)(2, 3) 188 | assert.True(t, r1 == 1 && r2 == 2 && r3 == 3) 189 | } 190 | 191 | func Test_Partial4Arg(t *testing.T) { 192 | fn4Arg0Ret := func(a1 int, a2 int, a3 int, a4 int) { 193 | assert.True(t, a1 == 1 && a2 == 2 && a3 == 3 && a4 == 4) 194 | } 195 | fn4Arg1Ret := func(a1 int, a2 int, a3 int, a4 int) int { 196 | assert.True(t, a1 == 1 && a2 == 2 && a3 == 3 && a4 == 4) 197 | return 1 198 | } 199 | fn4Arg2Ret := func(a1 int, a2 int, a3 int, a4 int) (int, int) { 200 | assert.True(t, a1 == 1 && a2 == 2 && a3 == 3 && a4 == 4) 201 | return 1, 2 202 | } 203 | fn4Arg3Ret := func(a1 int, a2 int, a3 int, a4 int) (int, int, int) { 204 | assert.True(t, a1 == 1 && a2 == 2 && a3 == 3 && a4 == 4) 205 | return 1, 2, 3 206 | } 207 | 208 | Partial4Arg0Ret(fn4Arg0Ret, 1)(2, 3, 4) 209 | r1 := Partial4Arg1Ret(fn4Arg1Ret, 1)(2, 3, 4) 210 | assert.True(t, r1 == 1) 211 | r1, r2 := Partial4Arg2Ret(fn4Arg2Ret, 1)(2, 3, 4) 212 | assert.True(t, r1 == 1 && r2 == 2) 213 | r1, r2, r3 := Partial4Arg3Ret(fn4Arg3Ret, 1)(2, 3, 4) 214 | assert.True(t, r1 == 1 && r2 == 2 && r3 == 3) 215 | } 216 | 217 | func Test_Partial5Arg(t *testing.T) { 218 | fn5Arg0Ret := func(a1 int, a2 int, a3 int, a4 int, a5 int) { 219 | assert.True(t, a1 == 1 && a2 == 2 && a3 == 3 && a4 == 4 && a5 == 5) 220 | } 221 | fn5Arg1Ret := func(a1 int, a2 int, a3 int, a4 int, a5 int) int { 222 | assert.True(t, a1 == 1 && a2 == 2 && a3 == 3 && a4 == 4 && a5 == 5) 223 | return 1 224 | } 225 | fn5Arg2Ret := func(a1 int, a2 int, a3 int, a4 int, a5 int) (int, int) { 226 | assert.True(t, a1 == 1 && a2 == 2 && a3 == 3 && a4 == 4 && a5 == 5) 227 | return 1, 2 228 | } 229 | fn5Arg3Ret := func(a1 int, a2 int, a3 int, a4 int, a5 int) (int, int, int) { 230 | assert.True(t, a1 == 1 && a2 == 2 && a3 == 3 && a4 == 4 && a5 == 5) 231 | return 1, 2, 3 232 | } 233 | 234 | Partial5Arg0Ret(fn5Arg0Ret, 1)(2, 3, 4, 5) 235 | r1 := Partial5Arg1Ret(fn5Arg1Ret, 1)(2, 3, 4, 5) 236 | assert.True(t, r1 == 1) 237 | r1, r2 := Partial5Arg2Ret(fn5Arg2Ret, 1)(2, 3, 4, 5) 238 | assert.True(t, r1 == 1 && r2 == 2) 239 | r1, r2, r3 := Partial5Arg3Ret(fn5Arg3Ret, 1)(2, 3, 4, 5) 240 | assert.True(t, r1 == 1 && r2 == 2 && r3 == 3) 241 | } 242 | -------------------------------------------------------------------------------- /concurrency.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync/atomic" 7 | ) 8 | 9 | // ExecTasks calls ExecTasksEx with stopOnError is true 10 | func ExecTasks( 11 | ctx context.Context, 12 | maxConcurrentTasks uint, 13 | tasks ...func(ctx context.Context) error, 14 | ) error { 15 | errMap := ExecTasksEx(ctx, maxConcurrentTasks, true, tasks...) 16 | for _, v := range errMap { 17 | return v 18 | } 19 | return nil 20 | } 21 | 22 | // ExecTasksEx execute multiple tasks concurrently using Go routines 23 | // maxConcurrentTasks behaves similarly as `pool size`, pass 0 to set no limit. 24 | // In case you want to cancel the execution, use context.WithTimeout() or context.WithCancel(). 25 | // nolint: gocognit 26 | func ExecTasksEx( 27 | ctx context.Context, 28 | maxConcurrentTasks uint, 29 | stopOnError bool, 30 | tasks ...func(ctx context.Context) error, 31 | ) map[int]error { 32 | taskCount := len(tasks) 33 | if taskCount == 0 { 34 | return nil 35 | } 36 | 37 | type execTaskResult struct { 38 | Index int 39 | Error error 40 | } 41 | 42 | stopped := &atomic.Value{} // NOTE: Go 1.18 has no atomic.Bool type 43 | resultChan := make(chan *execTaskResult, taskCount) 44 | var limiterChan chan struct{} 45 | if maxConcurrentTasks != 0 && maxConcurrentTasks < uint(taskCount) { 46 | limiterChan = make(chan struct{}, maxConcurrentTasks) 47 | } else { 48 | maxConcurrentTasks = 0 49 | } 50 | 51 | for i := 0; i < taskCount; i++ { 52 | // In case we set pool size, when out of slot, this will wait until one to be available again 53 | if maxConcurrentTasks != 0 { 54 | limiterChan <- struct{}{} 55 | } 56 | 57 | go func(i int, task func(ctx context.Context) error) { 58 | defer func() { 59 | // In case we set pool size, release the slot when the task ends 60 | if maxConcurrentTasks != 0 { 61 | <-limiterChan 62 | } 63 | 64 | r := recover() 65 | if r != nil && stopped.Load() == nil { 66 | resultChan <- &execTaskResult{Index: i, Error: fmt.Errorf("%w: %v", ErrPanic, r)} 67 | } 68 | }() 69 | 70 | if stopOnError && stopped.Load() != nil { 71 | return 72 | } 73 | 74 | if err := ctx.Err(); err != nil { 75 | resultChan <- &execTaskResult{Index: i, Error: err} 76 | return 77 | } 78 | 79 | err := task(ctx) 80 | if err != nil { 81 | resultChan <- &execTaskResult{Index: i, Error: err} 82 | } else { 83 | resultChan <- nil 84 | } 85 | }(i, tasks[i]) 86 | } 87 | 88 | errResult := map[int]error{} 89 | for i := 0; i < taskCount; i++ { 90 | res := <-resultChan 91 | if res == nil { 92 | continue 93 | } 94 | errResult[res.Index] = res.Error 95 | if stopOnError { 96 | stopped.Store(true) 97 | break 98 | } 99 | } 100 | return errResult 101 | } 102 | 103 | // ExecTaskFunc executes a function on every target objects 104 | func ExecTaskFunc[T any]( 105 | ctx context.Context, 106 | maxConcurrentTasks uint, 107 | taskFunc func(ctx context.Context, obj T) error, 108 | targetObjects ...T, 109 | ) error { 110 | errMap := ExecTaskFuncEx(ctx, maxConcurrentTasks, true, taskFunc, targetObjects...) 111 | for _, v := range errMap { 112 | return v 113 | } 114 | return nil 115 | } 116 | 117 | // ExecTaskFuncEx executes a function on every target objects 118 | func ExecTaskFuncEx[T any]( 119 | ctx context.Context, 120 | maxConcurrentTasks uint, 121 | stopOnError bool, 122 | taskFunc func(ctx context.Context, obj T) error, 123 | targetObjects ...T, 124 | ) map[int]error { 125 | tasks := make([]func(ctx context.Context) error, len(targetObjects)) 126 | for i := range targetObjects { 127 | obj := targetObjects[i] 128 | tasks[i] = func(ctx context.Context) error { 129 | return taskFunc(ctx, obj) 130 | } 131 | } 132 | return ExecTasksEx(ctx, maxConcurrentTasks, stopOnError, tasks...) 133 | } 134 | -------------------------------------------------------------------------------- /concurrency_test.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "math/rand" 7 | "sync" 8 | "testing" 9 | "time" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | // nolint 15 | func Test_ExecTasks(t *testing.T) { 16 | type ctxData struct { 17 | mu sync.Mutex 18 | result []int 19 | } 20 | 21 | errTest := errors.New("test error") 22 | 23 | task1 := func(ctx context.Context) error { 24 | data := ctx.Value("data").(*ctxData) 25 | for i := 0; i < 10; i++ { 26 | if err := ctx.Err(); err != nil { 27 | return err 28 | } 29 | data.mu.Lock() 30 | data.result = append(data.result, i) 31 | data.mu.Unlock() 32 | time.Sleep(time.Duration(20+rand.Intn(100)) * time.Millisecond) 33 | } 34 | return nil 35 | } 36 | task2 := func(ctx context.Context) error { 37 | data := ctx.Value("data").(*ctxData) 38 | for i := 10; i < 20; i++ { 39 | if err := ctx.Err(); err != nil { 40 | return err 41 | } 42 | data.mu.Lock() 43 | data.result = append(data.result, i) 44 | data.mu.Unlock() 45 | time.Sleep(time.Duration(20+rand.Intn(100)) * time.Millisecond) 46 | } 47 | return nil 48 | } 49 | task3 := func(ctx context.Context) error { 50 | data := ctx.Value("data").(*ctxData) 51 | for i := 20; i < 30; i++ { 52 | if err := ctx.Err(); err != nil { 53 | return err 54 | } 55 | if i == 25 { 56 | return errTest 57 | } 58 | data.mu.Lock() 59 | data.result = append(data.result, i) 60 | data.mu.Unlock() 61 | time.Sleep(time.Duration(20+rand.Intn(100)) * time.Millisecond) 62 | } 63 | return nil 64 | } 65 | task4 := func(ctx context.Context) error { 66 | data := ctx.Value("data").(*ctxData) 67 | for i := 30; i < 40; i++ { 68 | if err := ctx.Err(); err != nil { 69 | return err 70 | } 71 | if i == 35 { 72 | return errTest 73 | } 74 | data.mu.Lock() 75 | data.result = append(data.result, i) 76 | data.mu.Unlock() 77 | time.Sleep(time.Duration(20+rand.Intn(100)) * time.Millisecond) 78 | } 79 | return nil 80 | } 81 | task5 := func(ctx context.Context) error { 82 | data := ctx.Value("data").(*ctxData) 83 | for i := 30; i < 40; i++ { 84 | if err := ctx.Err(); err != nil { 85 | return err 86 | } 87 | if i == 35 { 88 | panic(errTest) 89 | } 90 | data.mu.Lock() 91 | data.result = append(data.result, i) 92 | data.mu.Unlock() 93 | time.Sleep(time.Duration(20+rand.Intn(100)) * time.Millisecond) 94 | } 95 | return nil 96 | } 97 | 98 | checkRes := func(res []int, start, end int) bool { 99 | values := []int{} 100 | for i := start; i < end; i++ { 101 | values = append(values, i) 102 | } 103 | return ContainAll(res, values...) 104 | } 105 | 106 | t.Run("no tasks passed", func(t *testing.T) { 107 | ctx := context.Background() 108 | errMap := ExecTasksEx(ctx, 0, false) 109 | assert.Equal(t, 0, len(errMap)) 110 | }) 111 | 112 | t.Run("no pool size, no stop on error", func(t *testing.T) { 113 | data := &ctxData{} 114 | ctx := context.WithValue(context.Background(), "data", data) 115 | 116 | errMap := ExecTasksEx(ctx, 0, false, task1, task2, task3, task4) 117 | assert.Equal(t, 2, len(errMap)) 118 | 119 | result := data.result 120 | assert.Equal(t, 30, len(result)) 121 | assert.True(t, checkRes(result, 0, 10) && checkRes(result, 10, 20) && 122 | checkRes(result, 20, 25) && checkRes(result, 30, 35)) 123 | }) 124 | 125 | t.Run("big pool size, no stop on error", func(t *testing.T) { 126 | data := &ctxData{} 127 | ctx := context.WithValue(context.Background(), "data", data) 128 | 129 | errMap := ExecTasksEx(ctx, 10, false, task1, task2, task3, task4) 130 | assert.Equal(t, 2, len(errMap)) 131 | 132 | result := data.result 133 | assert.Equal(t, 30, len(result)) 134 | assert.True(t, checkRes(result, 0, 10) && checkRes(result, 10, 20) && 135 | checkRes(result, 20, 25) && checkRes(result, 30, 35)) 136 | }) 137 | 138 | t.Run("no pool size, stop on error", func(t *testing.T) { 139 | data := &ctxData{} 140 | ctx := context.WithValue(context.Background(), "data", data) 141 | 142 | // NOTE: call ExecTasks() as ExecTasks() default to stopOnError is true 143 | err := ExecTasks(ctx, 0, task1, task2, task3, task4) 144 | assert.NotNil(t, err) 145 | 146 | data.mu.Lock() 147 | result := data.result 148 | assert.True(t, 30 > len(result)) 149 | data.mu.Unlock() 150 | }) 151 | 152 | t.Run("no pool size, stop on error. but no error occurred", func(t *testing.T) { 153 | data := &ctxData{} 154 | ctx := context.WithValue(context.Background(), "data", data) 155 | 156 | // NOTE: call ExecTasks() as ExecTasks() default to stopOnError is true 157 | err := ExecTasks(ctx, 0, task1, task2) 158 | assert.Nil(t, err) 159 | 160 | result := data.result 161 | assert.Equal(t, 20, len(result)) 162 | assert.True(t, checkRes(result, 0, 20)) 163 | }) 164 | 165 | t.Run("pool size = 1, no stop on error", func(t *testing.T) { 166 | data := &ctxData{} 167 | ctx := context.WithValue(context.Background(), "data", data) 168 | 169 | errMap := ExecTasksEx(ctx, 1, false, task1, task2, task3, task4) 170 | assert.True(t, len(errMap) == 2) 171 | 172 | result := data.result 173 | // As pool size = 1, result from the tasks are in order of the execution 174 | assert.True(t, checkRes(result[:10], 0, 10) && checkRes(result[10:20], 10, 20) && 175 | checkRes(result[20:25], 20, 25) && checkRes(result[25:], 30, 35)) 176 | }) 177 | 178 | t.Run("context timed out", func(t *testing.T) { 179 | data := &ctxData{} 180 | ctx := context.WithValue(context.Background(), "data", data) 181 | ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) 182 | defer cancel() 183 | 184 | errMap := ExecTasksEx(ctx, 0, false, task1, task2, task3, task4) 185 | assert.Equal(t, 4, len(errMap)) 186 | 187 | result := data.result 188 | assert.True(t, 30 > len(result) && len(result) > 0) 189 | }) 190 | 191 | t.Run("context canceled too early", func(t *testing.T) { 192 | data := &ctxData{} 193 | ctx := context.WithValue(context.Background(), "data", data) 194 | ctx, cancel := context.WithCancel(ctx) 195 | cancel() 196 | 197 | errMap := ExecTasksEx(ctx, 0, false, task1, task2, task3, task4) 198 | assert.Equal(t, 4, len(errMap)) 199 | 200 | result := data.result 201 | assert.Equal(t, 0, len(result)) 202 | }) 203 | 204 | t.Run("panic occurred in the task function", func(t *testing.T) { 205 | data := &ctxData{} 206 | ctx := context.WithValue(context.Background(), "data", data) 207 | 208 | errMap := ExecTasksEx(ctx, 0, false, task1, task2, task3, task4, task5) 209 | assert.Equal(t, 3, len(errMap)) 210 | 211 | result := data.result 212 | assert.True(t, len(result) > 0) 213 | }) 214 | } 215 | 216 | // nolint 217 | func Test_ExecTaskFunc(t *testing.T) { 218 | type ctxData struct { 219 | mu sync.Mutex 220 | evens []int 221 | odds []int 222 | } 223 | 224 | errTest := errors.New("test error") 225 | 226 | taskFunc := func(ctx context.Context, v int) error { 227 | data := ctx.Value("data").(*ctxData) 228 | if v > 10 { 229 | return errTest 230 | } 231 | data.mu.Lock() 232 | if v%2 == 0 { 233 | data.evens = append(data.evens, v) 234 | } else { 235 | data.odds = append(data.odds, v) 236 | } 237 | data.mu.Unlock() 238 | time.Sleep(time.Duration(20+rand.Intn(100)) * time.Millisecond) 239 | return nil 240 | } 241 | 242 | t.Run("no tasks passed", func(t *testing.T) { 243 | ctx := context.Background() 244 | err := ExecTaskFunc(ctx, 0, taskFunc) 245 | assert.Nil(t, err) 246 | }) 247 | 248 | t.Run("no pool size, success", func(t *testing.T) { 249 | data := &ctxData{} 250 | ctx := context.WithValue(context.Background(), "data", data) 251 | 252 | errMap := ExecTaskFuncEx(ctx, 0, false, taskFunc, 1, 2, 3, 4, 5) 253 | assert.Equal(t, 0, len(errMap)) 254 | assert.True(t, ContentEqual([]int{2, 4}, data.evens)) 255 | assert.True(t, ContentEqual([]int{1, 3, 5}, data.odds)) 256 | }) 257 | 258 | t.Run("no pool size, no stop on error, failure", func(t *testing.T) { 259 | data := &ctxData{} 260 | ctx := context.WithValue(context.Background(), "data", data) 261 | 262 | errMap := ExecTaskFuncEx(ctx, 0, false, taskFunc, 1, 2, 3, 11, 4, 5) 263 | assert.Equal(t, 1, len(errMap)) 264 | assert.True(t, ContentEqual([]int{2, 4}, data.evens)) 265 | assert.True(t, ContentEqual([]int{1, 3, 5}, data.odds)) 266 | }) 267 | } 268 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | var ( 9 | ErrEmpty = errors.New("container is empty") 10 | ErrIndexOutOfRange = errors.New("index out of range") 11 | ErrOverflow = errors.New("overflow") 12 | ErrPanic = errors.New("panic occurred") 13 | ) 14 | 15 | var ( 16 | // Deprecated: use ErrEmpty instead 17 | ErrSliceEmpty = ErrEmpty 18 | ) 19 | 20 | // ErrWrap wraps an error with a message placed in the right 21 | func ErrWrap(err error, msg string) error { 22 | return fmt.Errorf("%w: %s", err, msg) 23 | } 24 | 25 | // ErrWrapL wraps an error with a message placed in the left 26 | func ErrWrapL(msg string, err error) error { 27 | return fmt.Errorf("%s: %w", msg, err) 28 | } 29 | 30 | // ErrUnwrap unwraps an error to get a slice. 31 | // This function can unwrap error created by errors.Join() and fmt.Errorf(). 32 | // In case there's only single item wrapped in the input, the slice has only 1 item. 33 | func ErrUnwrap(err error) []error { 34 | if err == nil { 35 | return nil 36 | } 37 | u1, ok := err.(interface{ Unwrap() []error }) 38 | if ok { 39 | return u1.Unwrap() 40 | } 41 | if we := errors.Unwrap(err); we != nil { 42 | return []error{we} 43 | } 44 | return nil 45 | } 46 | 47 | // ErrUnwrapToRoot unwraps an error until the deepest one 48 | func ErrUnwrapToRoot(err error) error { 49 | rootErr := err 50 | for { 51 | e := errors.Unwrap(rootErr) 52 | if e == nil { 53 | return rootErr 54 | } 55 | rootErr = e 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /errors_test.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func Test_ErrWrap(t *testing.T) { 12 | e := errors.New("err") 13 | assert.Equal(t, "err: msg", ErrWrap(e, "msg").Error()) 14 | assert.Equal(t, "msg: err", ErrWrapL("msg", e).Error()) 15 | } 16 | 17 | type wrappedErrs struct { //nolint:errname 18 | errs []error 19 | } 20 | 21 | func (we *wrappedErrs) Error() string { 22 | return "" 23 | } 24 | 25 | func (we *wrappedErrs) Unwrap() []error { 26 | return we.errs 27 | } 28 | 29 | func Test_ErrUnwrap(t *testing.T) { 30 | e1 := errors.New("e1") 31 | e2 := fmt.Errorf("e2: %w", e1) 32 | e3 := &wrappedErrs{errs: []error{e1, e2}} 33 | 34 | assert.Equal(t, []error(nil), ErrUnwrap(nil)) 35 | assert.Equal(t, []error(nil), ErrUnwrap(e1)) 36 | assert.Equal(t, []error{e1}, ErrUnwrap(e2)) 37 | assert.Equal(t, []error{e1, e2}, ErrUnwrap(e3)) 38 | } 39 | 40 | func Test_ErrUnwrapToRoot(t *testing.T) { 41 | e1 := errors.New("e1") 42 | e2 := fmt.Errorf("e2: %w", e1) 43 | e3 := fmt.Errorf("e3: %w", e2) 44 | 45 | assert.Equal(t, nil, ErrUnwrapToRoot(nil)) 46 | assert.Equal(t, e1, ErrUnwrapToRoot(e2)) 47 | assert.Equal(t, e1, ErrUnwrapToRoot(e3)) 48 | } 49 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tiendc/gofn 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/stretchr/testify v1.9.0 7 | github.com/tiendc/go-rflutil v0.0.0-20240919184150-3c910c4770e2 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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 6 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 7 | github.com/tiendc/go-rflutil v0.0.0-20240919184150-3c910c4770e2 h1:rn+lV7zG16tc0GCw9hJkdvCOecbGe8Kj33lI8AaJ74g= 8 | github.com/tiendc/go-rflutil v0.0.0-20240919184150-3c910c4770e2/go.mod h1:2nPnVtlbM4w4GOWSmFjKFKl+mhDT7hWgwky4qpRFugo= 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 | -------------------------------------------------------------------------------- /internal/benchmark/foreach_test.go: -------------------------------------------------------------------------------- 1 | package benchmark 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/tiendc/gofn" 7 | "github.com/tiendc/gofn/internal/testdata" 8 | ) 9 | 10 | // Benchmark_ForEach ForEach vs ForEachPtr on big struct slices 11 | // Result: 12 | // 13 | // Benchmark_ForEach/builtin_For-Index 14 | // Benchmark_ForEach/builtin_For-Index-8 34310325 33.90 ns/op 15 | // Benchmark_ForEach/builtin_ForEach 16 | // Benchmark_ForEach/builtin_ForEach-8 36727526 33.68 ns/op 17 | // Benchmark_ForEach/gofn.ForEach 18 | // Benchmark_ForEach/gofn.ForEach-8 34559764 34.38 ns/op 19 | // Benchmark_ForEach/gofn.ForEachPtr 20 | // Benchmark_ForEach/gofn.ForEachPtr-8 97860658 12.11 ns/op 21 | func Benchmark_ForEach(b *testing.B) { 22 | slice := testdata.BigStructSlice 23 | fn := func(i int, t testdata.BigStruct) { 24 | } 25 | 26 | b.Run("builtin For-Index", func(b *testing.B) { 27 | for n := 0; n < b.N; n++ { 28 | for i := 0; i < len(slice); i++ { 29 | fn(i, slice[i]) 30 | } 31 | } 32 | }) 33 | 34 | b.Run("builtin ForEach", func(b *testing.B) { 35 | for n := 0; n < b.N; n++ { 36 | for i, v := range slice { 37 | fn(i, v) 38 | } 39 | } 40 | }) 41 | 42 | b.Run("gofn.ForEach", func(b *testing.B) { 43 | for n := 0; n < b.N; n++ { 44 | gofn.ForEach(slice, fn) 45 | } 46 | }) 47 | 48 | b.Run("gofn.ForEachPtr", func(b *testing.B) { 49 | for n := 0; n < b.N; n++ { 50 | gofn.ForEachPtr(slice, func(i int, t *testdata.BigStruct) {}) 51 | } 52 | }) 53 | } 54 | -------------------------------------------------------------------------------- /internal/benchmark/num_fmt_test.go: -------------------------------------------------------------------------------- 1 | package benchmark 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/tiendc/gofn" 8 | ) 9 | 10 | // Benchmark_NumberUngroup 11 | // Result: 12 | // 13 | // Benchmark_NumberUngroup/gofn.NumberUngroup 14 | // Benchmark_NumberUngroup/gofn.NumberUngroup-10 2349999 480.3 ns/op 15 | // Benchmark_NumberUngroup/strings.ReplaceAll 16 | // Benchmark_NumberUngroup/strings.ReplaceAll-10 1516845 790.1 ns/op 17 | func Benchmark_NumberUngroup(b *testing.B) { 18 | groupingSep := byte(',') 19 | prepareData := func() []string { 20 | return []string{"123,4567", "1234,567", "123,4567,1234,567", "567,123,456", "123,456,712,34", 21 | "123,4567", "1234,567", "123,4567,1234,567", "567,123,456", "123,456,712,34", 22 | "123,4567", "1234,567", "123,4567,1234,567", "567,123,456", "123,456,712,34"} 23 | } 24 | 25 | b.Run("gofn.NumberFmtUngroup", func(b *testing.B) { 26 | for n := 0; n < b.N; n++ { 27 | s := prepareData() 28 | for _, ss := range s { 29 | gofn.NumberFmtUngroup(ss, groupingSep) 30 | } 31 | } 32 | }) 33 | 34 | b.Run("strings.ReplaceAll", func(b *testing.B) { 35 | for n := 0; n < b.N; n++ { 36 | s := prepareData() 37 | for _, ss := range s { 38 | strings.ReplaceAll(ss, string(groupingSep), "") 39 | } 40 | } 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /internal/benchmark/slice_equal_test.go: -------------------------------------------------------------------------------- 1 | package benchmark 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/tiendc/gofn" 8 | "github.com/tiendc/gofn/internal/testdata" 9 | ) 10 | 11 | // Benchmark_Slice_Equal 12 | // Result: 13 | // 14 | // Benchmark_Slice_Equal/StructSlice/Equal 15 | // Benchmark_Slice_Equal/StructSlice/Equal-8 510845715 2.047 ns/op 16 | // Benchmark_Slice_Equal/StructSlice/ContentEqual 17 | // Benchmark_Slice_Equal/StructSlice/ContentEqual-8 583167950 2.061 ns/op 18 | // Benchmark_Slice_Equal/StructSlice/DeepEqual 19 | // Benchmark_Slice_Equal/StructSlice/DeepEqual-8 15403771 79.19 ns/op 20 | // Benchmark_Slice_Equal/IntSlice/Equal 21 | // Benchmark_Slice_Equal/IntSlice/Equal-8 589706185 2.087 ns/op 22 | // Benchmark_Slice_Equal/IntSlice/ContentEqual 23 | // Benchmark_Slice_Equal/IntSlice/ContentEqual-8 523120755 2.194 ns/op 24 | // Benchmark_Slice_Equal/IntSlice/DeepEqual 25 | // Benchmark_Slice_Equal/IntSlice/DeepEqual-8 15243183 77.93 ns/op 26 | func Benchmark_Slice_Equal(b *testing.B) { 27 | srcStructSlice := testdata.BigStructSlice 28 | dstStructSlice := make([]testdata.BigStruct, 0, len(srcStructSlice)) 29 | copy(dstStructSlice, srcStructSlice) 30 | 31 | b.Run("StructSlice/Equal", func(b *testing.B) { 32 | for n := 0; n < b.N; n++ { 33 | gofn.Equal(srcStructSlice, dstStructSlice) 34 | } 35 | }) 36 | 37 | b.Run("StructSlice/ContentEqual", func(b *testing.B) { 38 | for n := 0; n < b.N; n++ { 39 | gofn.ContentEqual(srcStructSlice, dstStructSlice) 40 | } 41 | }) 42 | 43 | b.Run("StructSlice/DeepEqual", func(b *testing.B) { 44 | for n := 0; n < b.N; n++ { 45 | reflect.DeepEqual(srcStructSlice, dstStructSlice) 46 | } 47 | }) 48 | 49 | srcInt := testdata.IntSlice 50 | dstInt := make([]int, 0, len(srcInt)) 51 | copy(dstInt, srcInt) 52 | 53 | b.Run("IntSlice/Equal", func(b *testing.B) { 54 | for n := 0; n < b.N; n++ { 55 | gofn.Equal(srcInt, dstInt) 56 | } 57 | }) 58 | 59 | b.Run("IntSlice/ContentEqual", func(b *testing.B) { 60 | for n := 0; n < b.N; n++ { 61 | gofn.ContentEqual(srcInt, dstInt) 62 | } 63 | }) 64 | 65 | b.Run("IntSlice/DeepEqual", func(b *testing.B) { 66 | for n := 0; n < b.N; n++ { 67 | reflect.DeepEqual(srcInt, dstInt) 68 | } 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /internal/benchmark/sort_test.go: -------------------------------------------------------------------------------- 1 | package benchmark 2 | 3 | import ( 4 | "sort" 5 | "testing" 6 | 7 | "github.com/tiendc/gofn" 8 | ) 9 | 10 | // Benchmark_Sort 11 | // Result: 12 | // 13 | // Benchmark_Sort/sort.Ints 14 | // Benchmark_Sort/sort.Ints-8 1186268 920.1 ns/op 15 | // Benchmark_Sort/sort.Slice 16 | // Benchmark_Sort/sort.Slice-8 1268170 948.1 ns/op 17 | // Benchmark_Sort/gofn.Sort 18 | // Benchmark_Sort/gofn.Sort-8 1256199 967.0 ns/op 19 | func Benchmark_Sort(b *testing.B) { 20 | prepareData := func() []int { 21 | return []int{-112312, 2312, -12314, 0, 0, 12, 1, 31238, -312545, -6456 - 23423, 22 | 12312, 434, 545, 123, 7567, 3123, 23534, 45654, 0, 0, 1, 2, 3, 2, 1, 0, -123, -123, -123, 23 | -112312, 2312, -12314, 0, 0, 12, 1, 31238, -312545, -6456 - 23423, 24 | 12312, 434, 545, 123, 7567, 3123, 23534, 45654, 0, 0, 1, 2, 3, 2, 1, 0, -123, -123, -123} 25 | } 26 | 27 | b.Run("sort.Ints", func(b *testing.B) { 28 | for n := 0; n < b.N; n++ { 29 | sort.Ints(prepareData()) 30 | } 31 | }) 32 | 33 | b.Run("sort.Slice", func(b *testing.B) { 34 | for n := 0; n < b.N; n++ { 35 | s := prepareData() 36 | sort.Slice(s, func(i, j int) bool { 37 | return s[i] < s[j] 38 | }) 39 | } 40 | }) 41 | 42 | b.Run("gofn.Sort", func(b *testing.B) { 43 | for n := 0; n < b.N; n++ { 44 | gofn.Sort(prepareData()) 45 | } 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /internal/testdata/testdata.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | type BigStruct struct { 4 | Int int 5 | Int64 int64 6 | Bool bool 7 | String string 8 | } 9 | 10 | var ( 11 | BigStructSlice = []BigStruct{ 12 | { 13 | Int: 1234567, 14 | Int64: 98273984729384798, 15 | Bool: false, 16 | String: "The quick brown fox jumps over the lazy dog, The quick brown fox jumps over the lazy dog", 17 | }, 18 | { 19 | Int: 1234567, 20 | Int64: 98273984729384798, 21 | Bool: false, 22 | String: "The quick brown fox jumps over the lazy dog, The quick brown fox jumps over the lazy dog", 23 | }, 24 | { 25 | Int: 1234567, 26 | Int64: 98273984729384798, 27 | Bool: false, 28 | String: "The quick brown fox jumps over the lazy dog, The quick brown fox jumps over the lazy dog", 29 | }, 30 | { 31 | Int: 1234567, 32 | Int64: 98273984729384798, 33 | Bool: false, 34 | String: "The quick brown fox jumps over the lazy dog, The quick brown fox jumps over the lazy dog", 35 | }, 36 | { 37 | Int: 1234567, 38 | Int64: 98273984729384798, 39 | Bool: false, 40 | String: "The quick brown fox jumps over the lazy dog, The quick brown fox jumps over the lazy dog", 41 | }, 42 | { 43 | Int: 1234567, 44 | Int64: 98273984729384798, 45 | Bool: false, 46 | String: "The quick brown fox jumps over the lazy dog, The quick brown fox jumps over the lazy dog", 47 | }, 48 | { 49 | Int: 1234567, 50 | Int64: 98273984729384798, 51 | Bool: false, 52 | String: "The quick brown fox jumps over the lazy dog, The quick brown fox jumps over the lazy dog", 53 | }, 54 | { 55 | Int: 1234567, 56 | Int64: 98273984729384798, 57 | Bool: false, 58 | String: "The quick brown fox jumps over the lazy dog, The quick brown fox jumps over the lazy dog", 59 | }, 60 | { 61 | Int: 1234567, 62 | Int64: 98273984729384798, 63 | Bool: false, 64 | String: "The quick brown fox jumps over the lazy dog, The quick brown fox jumps over the lazy dog", 65 | }, 66 | { 67 | Int: 1234567, 68 | Int64: 98273984729384798, 69 | Bool: false, 70 | String: "The quick brown fox jumps over the lazy dog, The quick brown fox jumps over the lazy dog", 71 | }, 72 | } 73 | 74 | IntSet = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20} 75 | IntSlice = []int{1, 2, 2, 2, 3, 3, 4, 5, 6, 7, 7, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20} 76 | ) 77 | -------------------------------------------------------------------------------- /map.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | // MapEqual compares contents of 2 maps 4 | func MapEqual[K comparable, V comparable, M ~map[K]V](m1, m2 M) bool { 5 | if len(m1) != len(m2) { 6 | return false 7 | } 8 | for k, v1 := range m1 { 9 | if v2, ok := m2[k]; !ok || v1 != v2 { 10 | return false 11 | } 12 | } 13 | return true 14 | } 15 | 16 | // MapEqualBy compares contents of 2 maps 17 | func MapEqualBy[K comparable, V any, M ~map[K]V](m1, m2 M, equalCmp func(v1, v2 V) bool) bool { 18 | if len(m1) != len(m2) { 19 | return false 20 | } 21 | for k, v1 := range m1 { 22 | if v2, ok := m2[k]; !ok || !equalCmp(v1, v2) { 23 | return false 24 | } 25 | } 26 | return true 27 | } 28 | 29 | // Deprecated: use MapEqualBy instead 30 | func MapEqualPred[K comparable, V any, M ~map[K]V](m1, m2 M, equalCmp func(v1, v2 V) bool) bool { 31 | return MapEqualBy(m1, m2, equalCmp) 32 | } 33 | 34 | // MapContainKeys tests if a map contains one or more keys 35 | func MapContainKeys[K comparable, V any, M ~map[K]V](m M, keys ...K) bool { 36 | for _, k := range keys { 37 | if _, exists := m[k]; !exists { 38 | return false 39 | } 40 | } 41 | return true 42 | } 43 | 44 | // MapContainValues tests if a map contains one or more values (complexity is O(n)). 45 | // If you often need to check existence of map value, consider using bi-map data structure. 46 | func MapContainValues[K comparable, V comparable, M ~map[K]V](m M, values ...V) bool { 47 | for _, v := range values { 48 | found := false 49 | for _, x := range m { 50 | if x == v { 51 | found = true 52 | break 53 | } 54 | } 55 | if !found { 56 | return false 57 | } 58 | } 59 | return true 60 | } 61 | 62 | // MapIter iterates over entries of multiple maps 63 | func MapIter[K comparable, V any, M ~map[K]V](iterFunc func(K, V), mapSlice ...M) { 64 | for _, m := range mapSlice { 65 | for k, v := range m { 66 | iterFunc(k, v) 67 | } 68 | } 69 | } 70 | 71 | // MapKeys gets map keys as slice 72 | func MapKeys[K comparable, V any, M ~map[K]V](m M) []K { 73 | keys := make([]K, len(m)) 74 | i := 0 75 | for k := range m { 76 | keys[i] = k 77 | i++ 78 | } 79 | return keys 80 | } 81 | 82 | // MapValues gets map values as slice 83 | func MapValues[K comparable, V any](m map[K]V) []V { 84 | values := make([]V, len(m)) 85 | i := 0 86 | for _, v := range m { 87 | values[i] = v 88 | i++ 89 | } 90 | return values 91 | } 92 | 93 | // MapEntries returns a slice of map entries as Tuple2 type 94 | func MapEntries[K comparable, V any, M ~map[K]V](m M) []*Tuple2[K, V] { 95 | items := make([]*Tuple2[K, V], len(m)) 96 | i := 0 97 | for k, v := range m { 98 | items[i] = &Tuple2[K, V]{k, v} 99 | i++ 100 | } 101 | return items 102 | } 103 | 104 | // MapUpdate merges map content with another map. 105 | // Not change the target map, only change the source map. 106 | func MapUpdate[K comparable, V any, M ~map[K]V](m1, m2 M) M { 107 | if m1 == nil { 108 | m1 = make(M, len(m2)) 109 | } 110 | if m2 == nil { 111 | return m1 112 | } 113 | for k, v := range m2 { 114 | m1[k] = v 115 | } 116 | return m1 117 | } 118 | 119 | // MapUpdateExistingOnly update map existing items with another map. 120 | // Not change the target map, only change the source map. 121 | func MapUpdateExistingOnly[K comparable, V any, M ~map[K]V](m1, m2 M) M { 122 | if m1 == nil { 123 | return make(M, 0) 124 | } 125 | if m2 == nil { 126 | return m1 127 | } 128 | for k, v := range m2 { 129 | if _, existing := m1[k]; existing { 130 | m1[k] = v 131 | } 132 | } 133 | return m1 134 | } 135 | 136 | // MapUpdateNewOnly update map with another map and not override the existing values. 137 | // Not change the target map, only change the source map. 138 | func MapUpdateNewOnly[K comparable, V any, M ~map[K]V](m1, m2 M) M { 139 | if m1 == nil { 140 | return MapUpdate(make(M, len(m2)), m2) 141 | } 142 | if m2 == nil { 143 | return m1 144 | } 145 | for k, v := range m2 { 146 | if _, existing := m1[k]; !existing { 147 | m1[k] = v 148 | } 149 | } 150 | return m1 151 | } 152 | 153 | // MapGet gets the value for the key, if not exist, returns the default one 154 | func MapGet[K comparable, V any, M ~map[K]V](m M, k K, defaultVal V) V { 155 | if val, ok := m[k]; ok { 156 | return val 157 | } 158 | return defaultVal 159 | } 160 | 161 | // MapPop deletes and returns the value of the key if exists, returns the default one if not 162 | func MapPop[K comparable, V any, M ~map[K]V](m M, k K, defaultVal V) V { 163 | if val, ok := m[k]; ok { 164 | delete(m, k) 165 | return val 166 | } 167 | return defaultVal 168 | } 169 | 170 | // MapSetDefault sets default value for a key and returns the current value 171 | func MapSetDefault[K comparable, V any, M ~map[K]V](m M, k K, defaultVal V) V { 172 | if m == nil { 173 | var zero V 174 | return zero 175 | } 176 | if val, ok := m[k]; ok { 177 | return val 178 | } 179 | m[k] = defaultVal 180 | return defaultVal 181 | } 182 | 183 | // MapUnionKeys returns a list of unique keys that are collected from multiple maps 184 | func MapUnionKeys[K comparable, V any, M ~map[K]V](m1 M, ms ...M) []K { 185 | keys := MapKeys(m1) 186 | for _, m := range ms { 187 | keys = Union(keys, MapKeys(m)) 188 | } 189 | return keys 190 | } 191 | 192 | // MapIntersectionKeys returns a list of unique keys that exist in all maps 193 | func MapIntersectionKeys[K comparable, V any, M ~map[K]V](m1 M, ms ...M) []K { 194 | keys := MapKeys(m1) 195 | for _, m := range ms { 196 | keys = Intersection(keys, MapKeys(m)) 197 | } 198 | return keys 199 | } 200 | 201 | // MapDifferenceKeys returns 2 lists of keys that are differences of 2 maps 202 | func MapDifferenceKeys[K comparable, V any, M ~map[K]V](m1, m2 M) ([]K, []K) { 203 | return Difference(MapKeys(m1), MapKeys(m2)) 204 | } 205 | 206 | // MapCopy returns a copied a map 207 | func MapCopy[K comparable, V any, M ~map[K]V](m M) M { 208 | ret := make(M, len(m)) 209 | for k, v := range m { 210 | ret[k] = v 211 | } 212 | return ret 213 | } 214 | 215 | // MapPick returns a new map with picking up the specified keys only 216 | func MapPick[K comparable, V any, M ~map[K]V](m M, keys ...K) M { 217 | ret := make(M, len(keys)) 218 | for _, k := range keys { 219 | v, ok := m[k] 220 | if ok { 221 | ret[k] = v 222 | } 223 | } 224 | return ret 225 | } 226 | 227 | // MapOmit omits keys from a map 228 | func MapOmit[K comparable, V any, M ~map[K]V](m M, keys ...K) { 229 | for _, k := range keys { 230 | delete(m, k) 231 | } 232 | } 233 | 234 | // MapOmitCopy returns a new map with omitting the specified keys 235 | func MapOmitCopy[K comparable, V any, M ~map[K]V](m M, keys ...K) M { 236 | omitKeys := MapSliceToMapKeys(keys, struct{}{}) 237 | 238 | // Copy only keys not in the list 239 | ret := make(M, len(m)) 240 | for k, v := range m { 241 | _, ok := omitKeys[k] 242 | if !ok { 243 | ret[k] = v 244 | } 245 | } 246 | return ret 247 | } 248 | 249 | // MapCopyExcludeKeys returns a new map with omitting the specified keys. 250 | // Deprecated: Use MapOmit instead. 251 | func MapCopyExcludeKeys[K comparable, V any, M ~map[K]V](m M, keys ...K) M { 252 | return MapOmitCopy(m, keys...) 253 | } 254 | 255 | // MapReverse reverses a map by exchanging its keys and values. 256 | func MapReverse[K comparable, V comparable, M ~map[K]V, M2 map[V]K](m M) (M2, []K) { 257 | result := make(M2, len(m)) 258 | valDupKeys := map[K]struct{}{} 259 | for k, v := range m { 260 | if k2, exists := result[v]; exists { 261 | valDupKeys[k] = struct{}{} 262 | valDupKeys[k2] = struct{}{} 263 | } else { 264 | result[v] = k 265 | } 266 | } 267 | return result, MapKeys(valDupKeys) 268 | } 269 | -------------------------------------------------------------------------------- /map_test.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func Test_MapEqual(t *testing.T) { 10 | // Empty maps 11 | assert.True(t, MapEqual(map[int]bool{}, map[int]bool{})) 12 | 13 | // One is nil, one is empty 14 | assert.True(t, MapEqual(nil, map[int]int{})) 15 | assert.False(t, MapEqual(map[int]bool{}, map[int]bool{1: false})) 16 | 17 | assert.True(t, MapEqual(map[int]int{1: 1, 2: 2}, map[int]int{2: 2, 1: 1})) 18 | assert.False(t, MapEqual(map[int]int{1: 1, 2: 2}, map[int]int{2: 2, 1: 1, 3: 3})) 19 | assert.False(t, MapEqual(map[int]int{1: 1, 2: 2}, map[int]int{2: 2, 1: 11})) 20 | 21 | type st struct { 22 | Int int 23 | Str string 24 | } 25 | assert.True(t, MapEqual(map[int]st{1: {1, "1"}, 2: {2, "2"}}, 26 | map[int]st{2: {2, "2"}, 1: {1, "1"}})) 27 | assert.False(t, MapEqual(map[int]st{1: {1, "1"}, 2: {2, "2"}}, 28 | map[int]st{2: {2, "2"}, 1: {1, "1"}, 3: {3, "3"}})) 29 | } 30 | 31 | // nolint: gocritic 32 | func Test_MapEqualBy(t *testing.T) { 33 | // Empty maps 34 | assert.True(t, MapEqualBy(map[int]bool{}, map[int]bool{}, 35 | func(v1, v2 bool) bool { return v1 == v2 })) 36 | 37 | // One is nil, one is empty 38 | assert.True(t, MapEqualBy(nil, map[int]int{}, 39 | func(v1, v2 int) bool { return v1 == v2 })) 40 | assert.False(t, MapEqualBy(map[int]bool{}, map[int]bool{1: false}, 41 | func(v1, v2 bool) bool { return v1 == v2 })) 42 | 43 | assert.True(t, MapEqualBy(map[int]int{1: 1, 2: 2}, map[int]int{2: 2, 1: 1}, 44 | func(v1, v2 int) bool { return v1 == v2 })) 45 | assert.False(t, MapEqualBy(map[int]int{1: 1, 2: 2}, map[int]int{2: 2, 1: 1, 3: 3}, 46 | func(v1, v2 int) bool { return v1 == v2 })) 47 | assert.False(t, MapEqualBy(map[int]int{1: 1, 2: 2}, map[int]int{2: 2, 1: 11}, 48 | func(v1, v2 int) bool { return v1 == v2 })) 49 | 50 | type st struct { 51 | Int int 52 | Str string 53 | } 54 | assert.True(t, MapEqualBy(map[int]st{1: {1, "1"}, 2: {2, "2"}}, map[int]st{2: {2, "2"}, 1: {1, "1"}}, 55 | func(v1, v2 st) bool { return v1 == v2 })) 56 | assert.False(t, MapEqualBy(map[int]st{1: {1, "1"}, 2: {2, "2"}}, map[int]st{2: {2, "2"}, 1: {1, "1"}, 3: {3, "3"}}, 57 | func(v1, v2 st) bool { return v1 == v2 })) 58 | 59 | // Value is also a map 60 | assert.True(t, MapEqualBy(map[int]map[int]int{1: {1: 1}, 2: {2: 2}}, 61 | map[int]map[int]int{1: {1: 1}, 2: {2: 2}}, 62 | func(v1, v2 map[int]int) bool { return MapEqual(v1, v2) })) 63 | assert.False(t, MapEqualBy(map[int]map[int]int{1: {1: 1}, 2: {2: 2}}, 64 | map[int]map[int]int{1: {1: 1}, 2: {2: 2}, 3: {3: 3}}, 65 | func(v1, v2 map[int]int) bool { return MapEqual(v1, v2) })) 66 | } 67 | 68 | // nolint: gocritic 69 | func Test_MapEqualPred_Deprecated(t *testing.T) { 70 | assert.True(t, MapEqualPred(map[int]int{1: 1, 2: 2}, map[int]int{2: 2, 1: 1}, 71 | func(v1, v2 int) bool { return v1 == v2 })) 72 | assert.False(t, MapEqualPred(map[int]int{1: 1, 2: 2}, map[int]int{2: 2, 1: 1, 3: 3}, 73 | func(v1, v2 int) bool { return v1 == v2 })) 74 | assert.False(t, MapEqualPred(map[int]int{1: 1, 2: 2}, map[int]int{2: 2, 1: 11}, 75 | func(v1, v2 int) bool { return v1 == v2 })) 76 | } 77 | 78 | func Test_MapContainKeys(t *testing.T) { 79 | assert.False(t, MapContainKeys(map[bool]int{}, false, true)) 80 | 81 | assert.True(t, MapContainKeys(map[int]int{1: 1, 2: 2, 3: 3}, 1, 2)) 82 | assert.True(t, MapContainKeys(map[int]int{1: 1, 2: 2, 3: 3}, 1, 2, 3)) 83 | assert.False(t, MapContainKeys(map[int]int{1: 1, 2: 2, 3: 3}, 1, 2, 3, 4)) 84 | } 85 | 86 | func Test_MapContainValues(t *testing.T) { 87 | assert.False(t, MapContainValues(map[int]bool{}, false, true)) 88 | 89 | assert.True(t, MapContainValues(map[int]int{1: 1, 2: 2, 3: 3}, 1, 2)) 90 | assert.True(t, MapContainValues(map[int]int{1: 1, 2: 2, 3: 3}, 1, 2, 3)) 91 | assert.False(t, MapContainValues(map[int]int{1: 1, 2: 2, 3: 3}, 1, 2, 3, 4)) 92 | 93 | type st struct { 94 | Int int 95 | Str string 96 | } 97 | assert.True(t, MapContainValues(map[int]st{1: {1, "1"}, 2: {2, "2"}}, st{1, "1"}, st{2, "2"})) 98 | assert.False(t, MapContainValues(map[int]st{1: {1, "1"}, 2: {2, "2"}}, st{1, "1"}, st{2, "2"}, st{})) 99 | } 100 | 101 | func Test_MapIter(t *testing.T) { 102 | // No input map 103 | copy1 := map[int]int{} 104 | MapIter[int, int, map[int]int](func(k int, v int) { copy1[k] = v }) 105 | assert.Equal(t, map[int]int{}, copy1) 106 | 107 | // Empty input map 108 | copy2 := map[int]int{} 109 | MapIter(func(k int, v int) { copy2[k] = v }, map[int]int{}) 110 | assert.Equal(t, map[int]int{}, copy2) 111 | 112 | // Single map input 113 | copy3 := map[int]int{} 114 | MapIter(func(k int, v int) { copy3[k] = v }, map[int]int{1: 11, 2: 22, 3: 33}) 115 | assert.Equal(t, map[int]int{1: 11, 2: 22, 3: 33}, copy3) 116 | 117 | // Multiple maps input 118 | copy4 := map[int]int{} 119 | MapIter(func(k int, v int) { copy4[k] = v }, 120 | map[int]int{1: 11, 2: 22, 3: 33}, map[int]int{4: 44, 5: 55}) 121 | assert.Equal(t, map[int]int{1: 11, 2: 22, 3: 33, 4: 44, 5: 55}, copy4) 122 | } 123 | 124 | func Test_MapKeys(t *testing.T) { 125 | assert.Equal(t, []int{}, MapKeys[int, bool, map[int]bool](nil)) 126 | assert.Equal(t, []int{}, MapKeys(map[int]bool{})) 127 | 128 | assert.Equal(t, []int{1}, MapKeys(map[int]int{1: 11})) 129 | 130 | // NOTE: order of result slice is non-deterministic 131 | assert.True(t, ContentEqual([]int{1, 2}, MapKeys(map[int]int{1: 11, 2: 22}))) 132 | } 133 | 134 | func Test_MapValues(t *testing.T) { 135 | assert.Equal(t, []bool{}, MapValues[int, bool](nil)) 136 | assert.Equal(t, []int{}, MapValues(map[int]int{})) 137 | 138 | assert.Equal(t, []int{11}, MapValues(map[int]int{1: 11})) 139 | // NOTE: order of result slice is non-deterministic 140 | assert.True(t, ContentEqual([]int{11, 22}, MapValues(map[int]int{1: 11, 2: 22}))) 141 | } 142 | 143 | func Test_MapEntries(t *testing.T) { 144 | assert.Equal(t, []*Tuple2[int, bool]{}, MapEntries[int, bool, map[int]bool](nil)) 145 | assert.Equal(t, []*Tuple2[int, int]{}, MapEntries(map[int]int{})) 146 | 147 | assert.Equal(t, []*Tuple2[int, int]{{1, 11}}, MapEntries(map[int]int{1: 11})) 148 | assert.True(t, ContentEqualPtr([]*Tuple2[int, int]{{1, 11}, {2, 22}}, MapEntries(map[int]int{1: 11, 2: 22}))) 149 | } 150 | 151 | func Test_MapUpdate(t *testing.T) { 152 | // Update with a nil map 153 | assert.Equal(t, map[int]bool{}, MapUpdate(nil, map[int]bool{})) 154 | assert.Equal(t, map[int]bool{1: false, 2: true}, MapUpdate(map[int]bool{1: false, 2: true}, nil)) 155 | // Update with an empty map 156 | assert.Equal(t, map[int]bool{1: false, 2: true}, MapUpdate(map[int]bool{1: false, 2: true}, map[int]bool{})) 157 | 158 | // Merge 2 maps 159 | assert.Equal(t, map[int]string{1: "one", 2: "two", 3: "three"}, 160 | MapUpdate(map[int]string{2: "two"}, map[int]string{1: "one", 3: "three"})) 161 | // Merge 2 maps with override 162 | assert.Equal(t, map[int]string{1: "one", 2: "TWO", 3: "three"}, 163 | MapUpdate(map[int]string{2: "two"}, map[int]string{1: "one", 2: "TWO", 3: "three"})) 164 | 165 | // Derived type 166 | type Map map[int]string 167 | assert.Equal(t, Map{1: "one", 2: "two", 3: "three"}, 168 | MapUpdate(Map{2: "two"}, map[int]string{1: "one", 3: "three"})) 169 | } 170 | 171 | func Test_MapUpdateExistingOnly(t *testing.T) { 172 | // Update with a nil map 173 | assert.Equal(t, map[int]bool{}, MapUpdateExistingOnly(nil, map[int]bool{})) 174 | assert.Equal(t, map[int]bool{}, MapUpdateExistingOnly(map[int]bool{}, nil)) 175 | assert.Equal(t, map[int]int{}, MapUpdateExistingOnly(map[int]int{}, map[int]int{1: 1})) 176 | assert.Equal(t, map[int]int{1: 1}, MapUpdateExistingOnly(map[int]int{1: 1}, map[int]int{})) 177 | assert.Equal(t, map[int]int{1: 1, 2: 2}, MapUpdateExistingOnly(map[int]int{1: 1, 2: 2}, map[int]int{3: 3, 4: 4})) 178 | assert.Equal(t, map[int]int{1: 1, 2: 22}, MapUpdateExistingOnly(map[int]int{1: 1, 2: 2}, map[int]int{3: 3, 2: 22})) 179 | } 180 | 181 | func Test_MapUpdateNewOnly(t *testing.T) { 182 | // Update with a nil map 183 | assert.Equal(t, map[int]bool{}, MapUpdateNewOnly(nil, map[int]bool{})) 184 | assert.Equal(t, map[int]bool{}, MapUpdateNewOnly(map[int]bool{}, nil)) 185 | assert.Equal(t, map[int]int{}, MapUpdateNewOnly(map[int]int{}, map[int]int{})) 186 | assert.Equal(t, map[int]int{1: 1}, MapUpdateNewOnly(map[int]int{1: 1}, map[int]int{1: 11})) 187 | assert.Equal(t, map[int]int{1: 1, 2: 2}, MapUpdateNewOnly(map[int]int{1: 1, 2: 2}, map[int]int{1: 11, 2: 22})) 188 | assert.Equal(t, map[int]int{1: 1, 2: 2, 3: 3}, MapUpdateNewOnly(map[int]int{1: 1, 2: 2}, map[int]int{3: 3, 2: 22})) 189 | } 190 | 191 | func Test_MapGet(t *testing.T) { 192 | assert.Equal(t, true, MapGet[int, bool, map[int]bool](nil, 1, true)) 193 | assert.Equal(t, 11, MapGet(map[int]int{}, 1, 11)) 194 | 195 | assert.Equal(t, 11, MapGet(map[int]int{1: 11}, 1, 100)) 196 | assert.Equal(t, 100, MapGet(map[int]int{1: 11, 2: 22}, 3, 100)) 197 | } 198 | 199 | func Test_MapPop(t *testing.T) { 200 | assert.Equal(t, true, MapPop[int, bool, map[int]bool](nil, 1, true)) 201 | assert.Equal(t, 11, MapPop(map[int]int{}, 1, 11)) 202 | 203 | m1 := map[int]int{1: 11} 204 | assert.Equal(t, 11, MapPop(m1, 1, 100)) 205 | assert.Equal(t, map[int]int{}, m1) 206 | m2 := map[int]int{1: 11, 2: 22} 207 | assert.Equal(t, 100, MapPop(m2, 3, 100)) 208 | assert.Equal(t, map[int]int{1: 11, 2: 22}, m2) 209 | } 210 | 211 | func Test_MapSetDefault(t *testing.T) { 212 | assert.Equal(t, false, MapSetDefault[int, bool, map[int]bool](nil, 1, true)) 213 | assert.Equal(t, 11, MapSetDefault(map[int]int{}, 1, 11)) 214 | 215 | m1 := map[int]int{1: 11} 216 | assert.Equal(t, 11, MapSetDefault(m1, 1, 100)) 217 | assert.Equal(t, map[int]int{1: 11}, m1) 218 | m2 := map[int]int{1: 11} 219 | assert.Equal(t, 22, MapSetDefault(m2, 2, 22)) 220 | assert.Equal(t, map[int]int{1: 11, 2: 22}, m2) 221 | } 222 | 223 | func Test_MapUnionKeys(t *testing.T) { 224 | assert.Equal(t, []int{}, MapUnionKeys[int, int, map[int]int](nil, nil)) 225 | assert.Equal(t, []int{}, MapUnionKeys(nil, map[int]int{})) 226 | assert.Equal(t, []int{1}, MapUnionKeys(map[int]int{1: 11}, nil)) 227 | 228 | assert.True(t, ContentEqual([]int{1, 2, 3, 4}, 229 | MapUnionKeys(map[int]int{1: 11, 2: 22}, map[int]int{3: 33, 4: 44}))) 230 | assert.True(t, ContentEqual([]string{"1", "2", "3", "4"}, 231 | MapUnionKeys(map[string]int{"1": 11, "2": 22}, map[string]int{"3": 33, "4": 44}))) 232 | } 233 | 234 | func Test_MapIntersectionKeys(t *testing.T) { 235 | assert.Equal(t, []int{}, MapIntersectionKeys[int, int, map[int]int](nil, nil)) 236 | assert.Equal(t, []int{}, MapIntersectionKeys(nil, map[int]int{})) 237 | assert.Equal(t, []int{}, MapIntersectionKeys(map[int]int{1: 11}, nil)) 238 | 239 | assert.True(t, ContentEqual([]int{}, 240 | MapIntersectionKeys(map[int]int{1: 11, 2: 22}, map[int]int{3: 33, 4: 44}))) 241 | assert.True(t, ContentEqual([]string{"2"}, 242 | MapIntersectionKeys(map[string]int{"1": 11, "2": 22}, map[string]int{"3": 33, "2": 22}))) 243 | } 244 | 245 | func Test_MapDifferenceKeys(t *testing.T) { 246 | l, r := MapDifferenceKeys[int, int, map[int]int](nil, nil) 247 | assert.Equal(t, []int{}, l) 248 | assert.Equal(t, []int{}, r) 249 | l, r = MapDifferenceKeys(nil, map[int]int{}) 250 | assert.Equal(t, []int{}, l) 251 | assert.Equal(t, []int{}, r) 252 | l, r = MapDifferenceKeys(map[int]int{1: 11}, nil) 253 | assert.Equal(t, []int{1}, l) 254 | assert.Equal(t, []int{}, r) 255 | 256 | // Derived types 257 | type MapII map[int]int 258 | type MapSI map[string]int 259 | 260 | l, r = MapDifferenceKeys(map[int]int{1: 11, 2: 22}, MapII{3: 33, 4: 44}) 261 | assert.True(t, ContentEqual([]int{1, 2}, l)) 262 | assert.True(t, ContentEqual([]int{3, 4}, r)) 263 | 264 | l2, r2 := MapDifferenceKeys(MapSI{"1": 11, "2": 22}, map[string]int{"3": 33, "2": 22}) 265 | assert.True(t, ContentEqual([]string{"1"}, l2)) 266 | assert.True(t, ContentEqual([]string{"3"}, r2)) 267 | } 268 | 269 | func Test_MapCopy(t *testing.T) { 270 | // Nil/empty maps 271 | assert.Equal(t, map[int]int{}, MapCopy(map[int]int(nil))) 272 | assert.Equal(t, map[int]int{}, MapCopy(map[int]int{})) 273 | 274 | assert.True(t, MapEqual(map[int]int{1: 11, 2: 22}, MapCopy(map[int]int{1: 11, 2: 22}))) 275 | } 276 | 277 | func Test_MapPick(t *testing.T) { 278 | // Nil/empty maps 279 | assert.Equal(t, map[int]int{}, MapPick(map[int]int(nil))) 280 | assert.Equal(t, map[int]int{}, MapPick(map[int]int{}, 1, 2, 3)) 281 | 282 | assert.True(t, MapEqual(map[int]int{}, MapPick(map[int]int{1: 11, 2: 22}))) 283 | assert.True(t, MapEqual(map[int]int{2: 22}, MapPick(map[int]int{1: 11, 2: 22}, 2, 3, 2))) 284 | } 285 | 286 | func Test_MapOmit(t *testing.T) { 287 | // Nil/empty maps 288 | MapOmit(map[int]int(nil)) 289 | m1 := map[int]int{} 290 | MapOmit(m1) 291 | assert.Equal(t, map[int]int{}, m1) 292 | 293 | m2 := map[int]int{1: 11, 2: 22, 3: 33} 294 | MapOmit(m2, 0) 295 | assert.Equal(t, 3, len(m2)) 296 | MapOmit(m2, 0, 1, 3) 297 | assert.Equal(t, map[int]int{2: 22}, m2) 298 | } 299 | 300 | func Test_MapOmitCopy(t *testing.T) { 301 | // Nil/empty maps 302 | assert.Equal(t, map[int]int{}, MapOmitCopy(map[int]int(nil))) 303 | assert.Equal(t, map[int]int{}, MapOmitCopy(map[int]int{}, 1, 2, 3)) 304 | 305 | assert.True(t, MapEqual(map[int]int{1: 11, 2: 22}, MapOmitCopy(map[int]int{1: 11, 2: 22}))) 306 | assert.True(t, MapEqual(map[int]int{1: 11}, MapOmitCopy(map[int]int{1: 11, 2: 22}, 2, 3, 2))) 307 | } 308 | 309 | func Test_MapCopyExcludeKeys(t *testing.T) { 310 | m := MapCopyExcludeKeys(map[int]int{1: 11, 2: 22}, 2, 3) 311 | assert.True(t, MapEqual(map[int]int{1: 11}, m)) 312 | } 313 | 314 | func Test_MapReverse(t *testing.T) { 315 | // Nil/Empty map 316 | m, dupKeys := MapReverse((map[int]int)(nil)) 317 | assert.Equal(t, map[int]int{}, m) 318 | assert.Equal(t, []int{}, dupKeys) 319 | m, dupKeys = MapReverse(map[int]int{}) 320 | assert.Equal(t, map[int]int{}, m) 321 | assert.Equal(t, []int{}, dupKeys) 322 | 323 | // No value duplications in the input 324 | m, dupKeys = MapReverse(map[int]int{1: 11, 2: 22, 3: 33}) 325 | assert.Equal(t, map[int]int{11: 1, 22: 2, 33: 3}, m) 326 | assert.Equal(t, []int{}, dupKeys) 327 | 328 | // Has value duplications in the input 329 | m, dupKeys = MapReverse(map[int]int{1: 11, 2: 11, 3: 33}) 330 | assert.True(t, m[11] == 1 || m[11] == 2) 331 | assert.True(t, ContentEqual([]int{1, 2}, dupKeys)) 332 | m, dupKeys = MapReverse(map[int]int{1: 11, 2: 11, 3: 33, 4: 33, 5: 55}) 333 | assert.True(t, m[11] == 1 || m[11] == 2) 334 | assert.True(t, m[33] == 3 || m[33] == 4) 335 | assert.True(t, m[55] == 5) 336 | assert.True(t, ContentEqual([]int{1, 2, 3, 4}, dupKeys)) 337 | } 338 | -------------------------------------------------------------------------------- /math.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | // All returns true if all given values are evaluated as `true` 4 | func All[T comparable](s ...T) bool { 5 | var zeroT T 6 | for i := range s { 7 | if s[i] == zeroT { 8 | return false 9 | } 10 | } 11 | return true 12 | } 13 | 14 | // Any returns true if at least one of given values is evaluated as `true` 15 | func Any[T comparable](s ...T) bool { 16 | var zeroT T 17 | for i := range s { 18 | if s[i] != zeroT { 19 | return true 20 | } 21 | } 22 | return false 23 | } 24 | 25 | // Abs calculates absolute value of an integer. 26 | // Ref: http://cavaliercoder.com/blog/optimized-abs-for-int64-in-go.html. 27 | // Note: if inputs MinInt64, the result is negative. 28 | func Abs(n int64) int64 { 29 | y := n >> 63 //nolint:mnd 30 | return (n ^ y) - y 31 | } 32 | 33 | // Clamp clamps number within the inclusive lower and upper bounds. 34 | func Clamp[T NumberExt | StringExt](value, min, max T) T { 35 | if min > max { 36 | min, max = max, min 37 | } 38 | if value < min { 39 | return min 40 | } 41 | if value > max { 42 | return max 43 | } 44 | return value 45 | } 46 | 47 | // Product calculates product value of slice elements 48 | func Product[T NumberExt | ComplexExt](s ...T) T { 49 | if len(s) == 0 { 50 | return 0 51 | } 52 | product := T(1) 53 | for _, v := range s { 54 | product *= v 55 | } 56 | return product 57 | } 58 | 59 | // ProductAs calculates product value with conversion to another type. 60 | // Type size of the result should be wider than the input's. 61 | // E.g. product := ProductAs[int64](int32Slice...) 62 | func ProductAs[U, T NumberExt](s ...T) U { 63 | if len(s) == 0 { 64 | return 0 65 | } 66 | product := U(1) 67 | for _, v := range s { 68 | product *= U(v) 69 | } 70 | return product 71 | } 72 | 73 | // Sum calculates sum of slice items 74 | func Sum[T NumberExt | ComplexExt](s ...T) T { 75 | var sum T 76 | for _, v := range s { 77 | sum += v 78 | } 79 | return sum 80 | } 81 | 82 | // SumAs calculates sum value with conversion to another type. 83 | // Type size of the result should be wider than the input's. 84 | // E.g. sum := SumAs[int64](int32Slice...) 85 | func SumAs[U, T NumberExt](s ...T) U { 86 | var sum U 87 | for _, v := range s { 88 | sum += U(v) 89 | } 90 | return sum 91 | } 92 | 93 | // Min find the minimum value in the list 94 | func Min[T NumberExt | StringExt](v1 T, s ...T) T { 95 | min := v1 96 | for i := range s { 97 | if s[i] < min { 98 | min = s[i] 99 | } 100 | } 101 | return min 102 | } 103 | 104 | // MinIn find the minimum value in the list. 105 | // Use min := Must(MinIn(slice)) to panic on error. 106 | func MinIn[T NumberExt | StringExt, S ~[]T](s S) (T, error) { 107 | if len(s) == 0 { 108 | var zeroT T 109 | return zeroT, ErrEmpty 110 | } 111 | min := s[0] 112 | for i := range s { 113 | if s[i] < min { 114 | min = s[i] 115 | } 116 | } 117 | return min, nil 118 | } 119 | 120 | // MinInBy find the minimum value in the list 121 | func MinInBy[T any, S ~[]T](s S, lessFunc func(a, b T) bool) (T, error) { 122 | if len(s) == 0 { 123 | var zeroT T 124 | return zeroT, ErrEmpty 125 | } 126 | min := s[0] 127 | for i := range s { 128 | if lessFunc(s[i], min) { 129 | min = s[i] 130 | } 131 | } 132 | return min, nil 133 | } 134 | 135 | // Deprecated: use MinInBy 136 | func MinInPred[T any, S ~[]T](s S, lessFunc func(a, b T) bool) (T, error) { 137 | return MinInBy(s, lessFunc) 138 | } 139 | 140 | // Max find the maximum value in the list 141 | func Max[T NumberExt | StringExt](v1 T, s ...T) T { 142 | max := v1 143 | for i := range s { 144 | if s[i] > max { 145 | max = s[i] 146 | } 147 | } 148 | return max 149 | } 150 | 151 | // MaxIn finds the maximum value in the list. 152 | // Use max := Must(MaxIn(slice)) to panic on error. 153 | func MaxIn[T NumberExt | StringExt, S ~[]T](s S) (T, error) { 154 | if len(s) == 0 { 155 | var zeroT T 156 | return zeroT, ErrEmpty 157 | } 158 | max := s[0] 159 | for i := range s { 160 | if s[i] > max { 161 | max = s[i] 162 | } 163 | } 164 | return max, nil 165 | } 166 | 167 | // MaxInBy finds the maximum value in the list 168 | func MaxInBy[T any, S ~[]T](s S, lessFunc func(a, b T) bool) (T, error) { 169 | if len(s) == 0 { 170 | var zeroT T 171 | return zeroT, ErrEmpty 172 | } 173 | max := s[0] 174 | for i := range s { 175 | if lessFunc(max, s[i]) { 176 | max = s[i] 177 | } 178 | } 179 | return max, nil 180 | } 181 | 182 | // Deprecated: use MaxInBy 183 | func MaxInPred[T any, S ~[]T](s S, lessFunc func(a, b T) bool) (T, error) { 184 | return MaxInBy(s, lessFunc) 185 | } 186 | 187 | // MinMax finds the minimum and maximum values in the list 188 | func MinMax[T NumberExt | StringExt](v1 T, s ...T) (T, T) { 189 | min := v1 190 | max := v1 191 | for i := range s { 192 | if s[i] < min { 193 | min = s[i] 194 | } 195 | if s[i] > max { 196 | max = s[i] 197 | } 198 | } 199 | return min, max 200 | } 201 | -------------------------------------------------------------------------------- /math_test.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | import ( 4 | "errors" 5 | "math" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func Test_All(t *testing.T) { 12 | assert.True(t, All[int]()) 13 | assert.True(t, All[bool]()) 14 | assert.True(t, All(true, true, true)) 15 | assert.True(t, All(1, -1, 2)) 16 | 17 | assert.False(t, All(true, false, true)) 18 | assert.False(t, All(1, -1, 2, 0)) 19 | } 20 | 21 | func Test_Any(t *testing.T) { 22 | assert.True(t, Any(true, false, false)) 23 | assert.True(t, Any(0, -1, 2, 0)) 24 | assert.True(t, Any(0, -1, 0, 0)) 25 | 26 | assert.False(t, Any[int]()) 27 | assert.False(t, Any[bool]()) 28 | } 29 | 30 | func Test_Abs(t *testing.T) { 31 | assert.Equal(t, int64(0), Abs(-0)) 32 | assert.Equal(t, int64(100), Abs(100)) 33 | assert.Equal(t, int64(100), Abs(-100)) 34 | assert.Equal(t, int64(-math.MinInt32), Abs(math.MinInt32)) 35 | assert.Equal(t, int64(math.MaxInt32), Abs(math.MaxInt32)) 36 | assert.Equal(t, int64(math.MaxInt64), Abs(math.MaxInt64)) 37 | 38 | // Special case 39 | assert.Equal(t, int64(math.MinInt64), Abs(math.MinInt64)) 40 | } 41 | 42 | func Test_Clamp(t *testing.T) { 43 | assert.Equal(t, 3, Clamp(1, 3, 5)) 44 | assert.Equal(t, 5, Clamp(7, 3, 5)) 45 | assert.Equal(t, 3, Clamp(3, 3, 5)) 46 | assert.Equal(t, 5, Clamp(5, 3, 5)) 47 | assert.Equal(t, 4, Clamp(4, 3, 5)) 48 | 49 | // Incorrect position of min and max 50 | assert.Equal(t, 4, Clamp(4, 5, 3)) 51 | // Negative numbers 52 | assert.Equal(t, -5, Clamp(-7, -3, -5)) 53 | 54 | // Clamp string 55 | assert.Equal(t, "g", Clamp("a", "g", "z")) 56 | assert.Equal(t, "p", Clamp("p", "g", "z")) 57 | assert.Equal(t, "p", Clamp("p", "z", "g")) 58 | } 59 | 60 | func Test_Product(t *testing.T) { 61 | assert.Equal(t, 0, Product[int]()) 62 | assert.Equal(t, -6, Product(1, 2, 3, -1)) 63 | assert.Equal(t, 0, Product(1, 2, 3, -1, 0)) 64 | assert.Equal(t, int8(-6), Product[int8](1, 2, 3, -1)) 65 | } 66 | 67 | func Test_ProductAs(t *testing.T) { 68 | assert.Equal(t, 0, ProductAs[int, int]()) 69 | assert.Equal(t, -6, ProductAs[int](1, 2, 3, -1)) 70 | assert.Equal(t, 0, ProductAs[int](1, 2, 3, -1, 0)) 71 | assert.Equal(t, int64(6000000000), ProductAs[int64](int32(1000), int32(2000), int32(3000))) 72 | // Overflow 73 | assert.Equal(t, int32(1705032704), ProductAs[int32](int32(1000), int32(2000), int32(3000))) 74 | } 75 | 76 | func Test_Sum(t *testing.T) { 77 | assert.Equal(t, 0, Sum[int]()) 78 | assert.Equal(t, 5, Sum(1, 2, 3, -1)) 79 | assert.Equal(t, int8(5), Sum[int8](1, 2, 3, -1)) 80 | } 81 | 82 | func Test_SumAs(t *testing.T) { 83 | assert.Equal(t, 0, SumAs[int, int]()) 84 | assert.Equal(t, 5, SumAs[int](1, 2, 3, -1)) 85 | assert.Equal(t, int64(5000000000), SumAs[int64](int32(1000000000), int32(2000000000), int32(2000000000))) 86 | // Overflow 87 | assert.Equal(t, int32(705032704), SumAs[int32](int32(1000000000), int32(2000000000), int32(2000000000))) 88 | } 89 | 90 | func Test_Min(t *testing.T) { 91 | assert.Equal(t, -10, Min(0, 2, -10, -5, 3, 5)) 92 | assert.Equal(t, float32(-0.2), Min[float32](0.1, -0.2, 0, 0, -0.2, 10)) 93 | assert.Equal(t, "", Min("", "1", "A", "a")) 94 | assert.Equal(t, "Abc", Min("Abc", "aBC")) 95 | } 96 | 97 | func Test_MinIn(t *testing.T) { 98 | // Nil/Empty slices 99 | m1, err := MinIn[uint64]([]uint64(nil)) 100 | assert.True(t, m1 == 0 && errors.Is(err, ErrEmpty)) 101 | m2, err := MinIn([]int{}) 102 | assert.True(t, m2 == 0 && errors.Is(err, ErrEmpty)) 103 | 104 | m2, err = MinIn([]int{0, 2, -10, -5, 3, 5}) 105 | assert.True(t, err == nil && m2 == -10) 106 | 107 | // Float type 108 | m3, err := MinIn([]float32{0.1, -0.2, 0, 0, -0.2, 10}) 109 | assert.True(t, err == nil && m3 == -0.2) 110 | 111 | // String type 112 | m4, err := MinIn([]string{"", "1", "A", "a"}) 113 | assert.True(t, err == nil && m4 == "") 114 | m4, err = MinIn([]string{"Abc", "aBC"}) 115 | assert.True(t, err == nil && m4 == "Abc") 116 | } 117 | 118 | func Test_MinInBy(t *testing.T) { 119 | // Nil/Empty slices 120 | m1, err := MinInBy[uint64]([]uint64(nil), func(v1, v2 uint64) bool { return v1 < v2 }) 121 | assert.True(t, m1 == 0 && errors.Is(err, ErrEmpty)) 122 | m2, err := MinInBy([]int{}, func(v1, v2 int) bool { return v1 < v2 }) 123 | assert.True(t, m2 == 0 && errors.Is(err, ErrEmpty)) 124 | 125 | m2, err = MinInBy([]int{0, 2, -10, -5, 3, 5}, func(v1, v2 int) bool { return v1 < v2 }) 126 | assert.True(t, err == nil && m2 == -10) 127 | 128 | // Float type 129 | m3, err := MinInBy([]float32{0.1, -0.2, 0, 0, -0.2, 10}, func(v1, v2 float32) bool { return v1 < v2 }) 130 | assert.True(t, err == nil && m3 == -0.2) 131 | 132 | // String type 133 | m4, err := MinInBy([]string{"", "1", "A", "a"}, func(v1, v2 string) bool { return v1 < v2 }) 134 | assert.True(t, err == nil && m4 == "") 135 | m4, err = MinInBy([]string{"Abc", "aBC"}, func(v1, v2 string) bool { return v1 < v2 }) 136 | assert.True(t, err == nil && m4 == "Abc") 137 | 138 | // Struct type 139 | type st struct { 140 | Int int8 141 | } 142 | m5, err := MinInBy([]st{{0}, {1}, {10}, {20}, {10}}, func(v1, v2 st) bool { return v1.Int < v2.Int }) 143 | assert.True(t, err == nil && m5 == st{0}) 144 | } 145 | 146 | func Test_MinInPred_Deprecated(t *testing.T) { 147 | m1, err := MinInPred([]int{0, 2, -10, -5, 3, 5}, func(v1, v2 int) bool { return v1 < v2 }) 148 | assert.True(t, err == nil && m1 == -10) 149 | } 150 | 151 | func Test_Max(t *testing.T) { 152 | assert.Equal(t, 30, Max(0, 2, -10, -5, 30, 5, 30)) 153 | assert.Equal(t, 10.11, Max[float64](0.1, -0.2, 10.1, 0, -0.2, 10, 10.11)) 154 | assert.Equal(t, "a", Max("", "1", "A", "a")) 155 | assert.Equal(t, "aBC", Max("Abc", "aBC")) 156 | } 157 | 158 | func Test_MaxIn(t *testing.T) { 159 | // Nil/Empty slices 160 | m1, err := MaxIn[uint64]([]uint64(nil)) 161 | assert.True(t, m1 == 0 && errors.Is(err, ErrEmpty)) 162 | m2, err := MaxIn([]int{}) 163 | assert.True(t, m2 == 0 && errors.Is(err, ErrEmpty)) 164 | 165 | m2, err = MaxIn([]int{0, 2, -10, -5, 30, 5, 30}) 166 | assert.True(t, err == nil && m2 == 30) 167 | 168 | // Float type 169 | m3, err := MaxIn([]float32{0.1, -0.2, 10.1, 0, -0.2, 10, 10.11}) 170 | assert.True(t, err == nil && m3 == 10.11) 171 | 172 | // String type 173 | m4, err := MaxIn([]string{"", "1", "A", "a"}) 174 | assert.True(t, err == nil && m4 == "a") 175 | m4, err = MaxIn([]string{"Abc", "aBC"}) 176 | assert.True(t, err == nil && m4 == "aBC") 177 | } 178 | 179 | func Test_MaxInBy(t *testing.T) { 180 | // Nil/Empty slices 181 | m1, err := MaxInBy[uint64]([]uint64(nil), func(v1, v2 uint64) bool { return v1 < v2 }) 182 | assert.True(t, m1 == 0 && errors.Is(err, ErrEmpty)) 183 | m2, err := MaxInBy([]int{}, func(v1, v2 int) bool { return v1 < v2 }) 184 | assert.True(t, m2 == 0 && errors.Is(err, ErrEmpty)) 185 | 186 | m2, err = MaxInBy([]int{0, 2, -10, -5, 30, 5, 30}, func(v1, v2 int) bool { return v1 < v2 }) 187 | assert.True(t, err == nil && m2 == 30) 188 | 189 | // Float type 190 | m3, err := MaxInBy([]float32{0.1, -0.2, 10.1, 0, -0.2, 10, 10.11}, func(v1, v2 float32) bool { return v1 < v2 }) 191 | assert.True(t, err == nil && m3 == 10.11) 192 | 193 | // String type 194 | m4, err := MaxInBy([]string{"", "1", "A", "a"}, func(v1, v2 string) bool { return v1 < v2 }) 195 | assert.True(t, err == nil && m4 == "a") 196 | m4, err = MaxInBy([]string{"Abc", "aBC"}, func(v1, v2 string) bool { return v1 < v2 }) 197 | assert.True(t, err == nil && m4 == "aBC") 198 | 199 | // Struct type 200 | type st struct { 201 | Int int8 202 | } 203 | m5, err := MaxInBy([]st{{0}, {1}, {10}, {20}, {10}}, func(v1, v2 st) bool { return v1.Int < v2.Int }) 204 | assert.True(t, err == nil && m5 == st{20}) 205 | } 206 | 207 | func Test_MaxInPred_Deprecated(t *testing.T) { 208 | m1, err := MaxInPred([]int{0, 2, -10, -5, 30, 5, 30}, func(v1, v2 int) bool { return v1 < v2 }) 209 | assert.True(t, err == nil && m1 == 30) 210 | } 211 | 212 | func Test_MinMax(t *testing.T) { 213 | m1, m2 := MinMax(0, 2, -10, -5, 3, 5) 214 | assert.True(t, m1 == -10 && m2 == 5) 215 | f1, f2 := MinMax[float32](0.1, -0.2, 0, 0, -0.2, 10) 216 | assert.True(t, f1 == -0.2 && f2 == 10) 217 | s1, s2 := MinMax("", "1", "A", "a") 218 | assert.True(t, s1 == "" && s2 == "a") 219 | } 220 | -------------------------------------------------------------------------------- /number.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | "unsafe" 8 | ) 9 | 10 | const ( 11 | base10 = 10 12 | byte2Bits = 8 13 | 14 | fractionSep = '.' 15 | noFractionSep = byte(0) 16 | groupSep = ',' 17 | ) 18 | 19 | func ParseIntEx[T IntExt](s string, base int) (T, error) { 20 | var zeroT T 21 | v, err := strconv.ParseInt(s, base, int(unsafe.Sizeof(zeroT)*byte2Bits)) //nolint:gosec 22 | if err == nil { 23 | return T(v), nil 24 | } 25 | return zeroT, err // nolint: wrapcheck 26 | } 27 | 28 | func ParseInt[T IntExt](s string) (T, error) { 29 | return ParseIntEx[T](s, base10) 30 | } 31 | 32 | // ParseIntUngroup omit all grouping commas then parse the string value 33 | func ParseIntUngroup[T IntExt](s string) (T, error) { 34 | return ParseIntEx[T](NumberFmtUngroup(s, groupSep), base10) 35 | } 36 | 37 | func ParseIntDef[T IntExt](s string, defaultVal T) T { 38 | v, err := strconv.ParseInt(s, base10, int(unsafe.Sizeof(defaultVal)*byte2Bits)) //nolint:gosec 39 | if err == nil { 40 | return T(v) 41 | } 42 | return defaultVal 43 | } 44 | 45 | func ParseUintEx[T UIntExt](s string, base int) (T, error) { 46 | var zeroT T 47 | v, err := strconv.ParseUint(s, base, int(unsafe.Sizeof(zeroT)*byte2Bits)) //nolint:gosec 48 | if err == nil { 49 | return T(v), nil 50 | } 51 | return zeroT, err // nolint: wrapcheck 52 | } 53 | 54 | func ParseUint[T UIntExt](s string) (T, error) { 55 | return ParseUintEx[T](s, base10) 56 | } 57 | 58 | // ParseUintUngroup omit all grouping commas then parse the string value 59 | func ParseUintUngroup[T UIntExt](s string) (T, error) { 60 | return ParseUintEx[T](NumberFmtUngroup(s, groupSep), base10) 61 | } 62 | 63 | func ParseUintDef[T UIntExt](s string, defaultVal T) T { 64 | v, err := strconv.ParseUint(s, base10, int(unsafe.Sizeof(defaultVal)*byte2Bits)) //nolint:gosec 65 | if err == nil { 66 | return T(v) 67 | } 68 | return defaultVal 69 | } 70 | 71 | func ParseFloat[T FloatExt](s string) (T, error) { 72 | var zeroT T 73 | v, err := strconv.ParseFloat(s, int(unsafe.Sizeof(zeroT)*byte2Bits)) //nolint:gosec 74 | if err == nil { 75 | return T(v), nil 76 | } 77 | return zeroT, err // nolint: wrapcheck 78 | } 79 | 80 | // ParseFloatUngroup omit all grouping commas then parse the string value 81 | func ParseFloatUngroup[T FloatExt](s string) (T, error) { 82 | return ParseFloat[T](NumberFmtUngroup(s, groupSep)) 83 | } 84 | 85 | func ParseFloatDef[T FloatExt](s string, defaultVal T) T { 86 | v, err := strconv.ParseFloat(s, int(unsafe.Sizeof(defaultVal)*byte2Bits)) //nolint:gosec 87 | if err == nil { 88 | return T(v) 89 | } 90 | return defaultVal 91 | } 92 | 93 | func FormatIntEx[T IntExt](v T, format string) string { 94 | return fmt.Sprintf(format, v) 95 | } 96 | 97 | func FormatInt[T IntExt](v T) string { 98 | return strconv.FormatInt(int64(v), base10) 99 | } 100 | 101 | // FormatIntGroup format the value then group the decimal using comma 102 | func FormatIntGroup[T IntExt](v T) string { 103 | s := strconv.FormatInt(int64(v), base10) 104 | return NumberFmtGroup(s, noFractionSep, groupSep) 105 | } 106 | 107 | func FormatUintEx[T UIntExt](v T, format string) string { 108 | return fmt.Sprintf(format, v) 109 | } 110 | 111 | func FormatUint[T UIntExt](v T) string { 112 | return strconv.FormatUint(uint64(v), base10) 113 | } 114 | 115 | // FormatUintGroup format the value then group the decimal using comma 116 | func FormatUintGroup[T UIntExt](v T) string { 117 | return NumberFmtGroup(strconv.FormatUint(uint64(v), base10), noFractionSep, groupSep) 118 | } 119 | 120 | func FormatFloatEx[T FloatExt](v T, format string) string { 121 | return fmt.Sprintf(format, v) 122 | } 123 | 124 | func FormatFloat[T FloatExt](v T) string { 125 | return fmt.Sprintf("%f", v) 126 | } 127 | 128 | // FormatFloatGroup format the value then group the decimal using comma 129 | func FormatFloatGroup[T FloatExt](v T) string { 130 | return NumberFmtGroup(fmt.Sprintf("%f", v), fractionSep, groupSep) 131 | } 132 | 133 | // FormatFloatGroupEx format the value then group the decimal using comma 134 | func FormatFloatGroupEx[T FloatExt](v T, format string) string { 135 | return NumberFmtGroup(fmt.Sprintf(format, v), fractionSep, groupSep) 136 | } 137 | 138 | // NumberFmtGroup separate decimal groups in the value string 139 | func NumberFmtGroup(num string, fractionSep, groupSep byte) string { 140 | if len(num) < 4 { //nolint:mnd 141 | return num 142 | } 143 | // Format as integer 144 | if fractionSep == 0 { 145 | return numberPartFmtGroup(num, groupSep) 146 | } 147 | // Format as real number 148 | fractionIndex := strings.IndexByte(num, fractionSep) 149 | if fractionIndex >= 0 { 150 | return numberPartFmtGroup(num[:fractionIndex], groupSep) + num[fractionIndex:] 151 | } 152 | return numberPartFmtGroup(num, groupSep) 153 | } 154 | 155 | // NumberFmtUngroup ungroup the value string 156 | func NumberFmtUngroup(num string, groupSep byte) string { 157 | ret := make([]byte, 0, len(num)) 158 | for i := range num { 159 | if num[i] == groupSep { 160 | continue 161 | } 162 | ret = append(ret, num[i]) 163 | } 164 | return string(ret) 165 | } 166 | 167 | func numberPartFmtGroup(s string, groupSep byte) string { 168 | if groupSep == 0 || !stringIsInteger(s, true) { 169 | return s 170 | } 171 | buf := make([]byte, 0, len(s)+5) //nolint:mnd 172 | ch := s[0] 173 | if ch == '-' { 174 | buf = append(buf, ch) 175 | s = s[1:] 176 | } 177 | start := len(s) % 3 //nolint:mnd 178 | if start == 0 { 179 | start = 3 180 | } 181 | for i := range s { 182 | ch = s[i] 183 | if i != 0 && i == start { 184 | buf = append(buf, groupSep) 185 | start += 3 186 | } 187 | buf = append(buf, ch) 188 | } 189 | return string(buf) 190 | } 191 | 192 | func stringIsInteger(s string, allowSign bool) bool { 193 | length := len(s) 194 | if length == 0 { 195 | return false 196 | } 197 | if s[0] == '-' { 198 | if !allowSign || length == 1 { 199 | return false 200 | } 201 | s = s[1:] 202 | } 203 | for _, ch := range s { 204 | if ch < '0' || ch > '9' { 205 | return false 206 | } 207 | } 208 | return true 209 | } 210 | -------------------------------------------------------------------------------- /number_cast.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | import ( 4 | "math" 5 | ) 6 | 7 | // ToSafeInt8 safely cast the value to type int8 (not using reflection) 8 | func ToSafeInt8[T IntExt | UIntExt](v T) (int8, error) { 9 | if (v >= 0 && uint64(v) > math.MaxInt8) || (v < 0 && int64(v) < math.MinInt8) { 10 | return int8(v), ErrOverflow 11 | } 12 | return int8(v), nil 13 | } 14 | 15 | // ToSafeInt16 safely cast the value to type int16 (not using reflection) 16 | func ToSafeInt16[T IntExt | UIntExt](v T) (int16, error) { 17 | if (v >= 0 && uint64(v) > math.MaxInt16) || (v < 0 && int64(v) < math.MinInt16) { 18 | return int16(v), ErrOverflow 19 | } 20 | return int16(v), nil 21 | } 22 | 23 | // ToSafeInt32 safely cast the value to type int32 (not using reflection) 24 | func ToSafeInt32[T IntExt | UIntExt](v T) (int32, error) { 25 | if (v >= 0 && uint64(v) > math.MaxInt32) || (v < 0 && int64(v) < math.MinInt32) { 26 | return int32(v), ErrOverflow 27 | } 28 | return int32(v), nil 29 | } 30 | 31 | // ToSafeInt64 safely cast the value to type int64 (not using reflection) 32 | func ToSafeInt64[T IntExt | UIntExt](v T) (int64, error) { 33 | if v >= 0 && uint64(v) > math.MaxInt64 { 34 | return int64(v), ErrOverflow 35 | } 36 | // If v < 0, it's always safe to cast to int64 37 | return int64(v), nil 38 | } 39 | 40 | // ToSafeUint8 safely cast the value to type uint8 (not using reflection) 41 | func ToSafeUint8[T IntExt | UIntExt](v T) (uint8, error) { 42 | if v < 0 || uint64(v) > math.MaxUint8 { 43 | return uint8(v), ErrOverflow 44 | } 45 | return uint8(v), nil 46 | } 47 | 48 | // ToSafeUint16 safely cast the value to type uint16 (not using reflection) 49 | func ToSafeUint16[T IntExt | UIntExt](v T) (uint16, error) { 50 | if v < 0 || uint64(v) > math.MaxUint16 { 51 | return uint16(v), ErrOverflow 52 | } 53 | return uint16(v), nil 54 | } 55 | 56 | // ToSafeUint32 safely cast the value to type uint32 (not using reflection) 57 | func ToSafeUint32[T IntExt | UIntExt](v T) (uint32, error) { 58 | if v < 0 || uint64(v) > math.MaxUint32 { 59 | return uint32(v), ErrOverflow 60 | } 61 | return uint32(v), nil 62 | } 63 | 64 | // ToSafeUint64 safely cast the value to type uint64 (not using reflection) 65 | func ToSafeUint64[T IntExt | UIntExt](v T) (uint64, error) { 66 | if v < 0 { 67 | return uint64(v), ErrOverflow 68 | } 69 | return uint64(v), nil 70 | } 71 | -------------------------------------------------------------------------------- /number_test.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "strconv" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func Test_ParseInt(t *testing.T) { 13 | i8, e := ParseInt[int8]("127") 14 | assert.True(t, e == nil && i8 == int8(127)) 15 | i8, e = ParseInt[int8]("-128") 16 | assert.True(t, e == nil && i8 == int8(-128)) 17 | _, e = ParseInt[int8]("128") 18 | assert.ErrorIs(t, e, strconv.ErrRange) 19 | _, e = ParseInt[int8]("-129") 20 | assert.ErrorIs(t, e, strconv.ErrRange) 21 | 22 | i16, e := ParseInt[int16]("32767") 23 | assert.True(t, e == nil && i16 == int16(32767)) 24 | i16, e = ParseInt[int16]("-32768") 25 | assert.True(t, e == nil && i16 == int16(-32768)) 26 | _, e = ParseInt[int16]("32768") 27 | assert.ErrorIs(t, e, strconv.ErrRange) 28 | _, e = ParseInt[int16]("-32769") 29 | assert.ErrorIs(t, e, strconv.ErrRange) 30 | 31 | i32, e := ParseInt[int32]("2147483647") 32 | assert.True(t, e == nil && i32 == int32(2147483647)) 33 | i32, e = ParseInt[int32]("-2147483648") 34 | assert.True(t, e == nil && i32 == int32(-2147483648)) 35 | _, e = ParseInt[int32]("2147483648") 36 | assert.ErrorIs(t, e, strconv.ErrRange) 37 | _, e = ParseInt[int32]("-2147483649") 38 | assert.ErrorIs(t, e, strconv.ErrRange) 39 | 40 | i64, e := ParseInt[int64]("9223372036854775807") 41 | assert.True(t, e == nil && i64 == int64(9223372036854775807)) 42 | i64, e = ParseInt[int64]("-9223372036854775808") 43 | assert.True(t, e == nil && i64 == int64(-9223372036854775808)) 44 | _, e = ParseInt[int64]("9223372036854775808") 45 | assert.ErrorIs(t, e, strconv.ErrRange) 46 | _, e = ParseInt[int64]("-9223372036854775809") 47 | assert.ErrorIs(t, e, strconv.ErrRange) 48 | 49 | // Custom type 50 | type X8 int8 51 | x8, e := ParseInt[X8]("127") 52 | assert.True(t, e == nil && x8 == X8(127)) 53 | x8, e = ParseInt[X8]("-128") 54 | assert.True(t, e == nil && x8 == X8(-128)) 55 | _, e = ParseInt[X8]("128") 56 | assert.ErrorIs(t, e, strconv.ErrRange) 57 | _, e = ParseInt[X8]("-129") 58 | assert.ErrorIs(t, e, strconv.ErrRange) 59 | } 60 | 61 | func Test_ParseIntUngroup(t *testing.T) { 62 | i32, e := ParseIntUngroup[int32]("2147483647") 63 | assert.True(t, e == nil && i32 == int32(2147483647)) 64 | i32, e = ParseIntUngroup[int32]("2,147") 65 | assert.True(t, e == nil && i32 == int32(2147)) 66 | i32, e = ParseIntUngroup[int32]("-2,147,483,647") 67 | assert.True(t, e == nil && i32 == int32(-2147483647)) 68 | _, e = ParseIntUngroup[int32]("-2,147,483,649") 69 | assert.ErrorIs(t, e, strconv.ErrRange) 70 | } 71 | 72 | func Test_ParseIntDef(t *testing.T) { 73 | i8 := ParseIntDef("127", int8(120)) 74 | assert.True(t, i8 == int8(127)) 75 | i8 = ParseIntDef("-129", int8(100)) // overflow, return default 76 | assert.True(t, i8 == int8(100)) 77 | 78 | i16 := ParseIntDef("-32768", int16(100)) 79 | assert.True(t, i16 == int16(-32768)) 80 | i16 = ParseIntDef("32768", int16(100)) // overflow, return default 81 | assert.True(t, i16 == int16(100)) 82 | 83 | i32 := ParseIntDef("2147483647", int32(100)) 84 | assert.True(t, i32 == int32(2147483647)) 85 | i32 = ParseIntDef("-2147483649", int32(-100)) // overflow, return default 86 | assert.True(t, i32 == int32(-100)) 87 | 88 | i64 := ParseIntDef("-9223372036854775808", int64(100)) 89 | assert.True(t, i64 == int64(-9223372036854775808)) 90 | i64 = ParseIntDef("9223372036854775808", int64(-100)) // overflow, return default 91 | assert.True(t, i64 == int64(-100)) 92 | 93 | // Custom type 94 | type X32 int32 95 | x32 := ParseIntDef("2147483647", X32(100)) 96 | assert.True(t, x32 == X32(2147483647)) 97 | x32 = ParseIntDef("-2147483649", X32(-100)) // overflow, return default 98 | assert.True(t, x32 == X32(-100)) 99 | } 100 | 101 | func Test_ParseUint(t *testing.T) { 102 | i8, e := ParseUint[uint8]("255") 103 | assert.True(t, e == nil && i8 == uint8(255)) 104 | i8, e = ParseUint[uint8]("0") 105 | assert.True(t, e == nil && i8 == uint8(0)) 106 | _, e = ParseUint[uint8]("256") 107 | assert.ErrorIs(t, e, strconv.ErrRange) 108 | _, e = ParseUint[uint8]("-1") 109 | assert.ErrorIs(t, e, strconv.ErrSyntax) 110 | 111 | i16, e := ParseUint[uint16]("65535") 112 | assert.True(t, e == nil && i16 == uint16(65535)) 113 | i16, e = ParseUint[uint16]("0") 114 | assert.True(t, e == nil && i16 == uint16(0)) 115 | _, e = ParseUint[uint16]("65536") 116 | assert.ErrorIs(t, e, strconv.ErrRange) 117 | _, e = ParseUint[uint16]("-1") 118 | assert.ErrorIs(t, e, strconv.ErrSyntax) 119 | 120 | i32, e := ParseUint[uint32]("4294967295") 121 | assert.True(t, e == nil && i32 == uint32(4294967295)) 122 | i32, e = ParseUint[uint32]("0") 123 | assert.True(t, e == nil && i32 == uint32(0)) 124 | _, e = ParseUint[uint32]("4294967296") 125 | assert.ErrorIs(t, e, strconv.ErrRange) 126 | _, e = ParseUint[uint32]("-1") 127 | assert.ErrorIs(t, e, strconv.ErrSyntax) 128 | 129 | i64, e := ParseUint[uint64]("18446744073709551615") 130 | assert.True(t, e == nil && i64 == uint64(18446744073709551615)) 131 | i64, e = ParseUint[uint64]("0") 132 | assert.True(t, e == nil && i64 == uint64(0)) 133 | _, e = ParseUint[uint64]("18446744073709551616") 134 | assert.ErrorIs(t, e, strconv.ErrRange) 135 | _, e = ParseUint[uint64]("-1") 136 | assert.ErrorIs(t, e, strconv.ErrSyntax) 137 | 138 | // Custom type 139 | type X32 uint32 140 | x32, e := ParseUint[X32]("4294967295") 141 | assert.True(t, e == nil && x32 == X32(4294967295)) 142 | x32, e = ParseUint[X32]("0") 143 | assert.True(t, e == nil && x32 == X32(0)) 144 | _, e = ParseUint[X32]("4294967296") 145 | assert.ErrorIs(t, e, strconv.ErrRange) 146 | _, e = ParseUint[X32]("-1") 147 | assert.ErrorIs(t, e, strconv.ErrSyntax) 148 | } 149 | 150 | func Test_ParseUintUngroup(t *testing.T) { 151 | i32, e := ParseUintUngroup[uint32]("2147483647") 152 | assert.True(t, e == nil && i32 == uint32(2147483647)) 153 | i32, e = ParseUintUngroup[uint32]("2,147") 154 | assert.True(t, e == nil && i32 == uint32(2147)) 155 | i32, e = ParseUintUngroup[uint32]("2,147,483,647") 156 | assert.True(t, e == nil && i32 == uint32(2147483647)) 157 | _, e = ParseUintUngroup[uint32]("4,294,967,296") 158 | assert.ErrorIs(t, e, strconv.ErrRange) 159 | } 160 | 161 | func Test_ParseUintDef(t *testing.T) { 162 | i8 := ParseUintDef("255", uint8(120)) 163 | assert.True(t, i8 == uint8(255)) 164 | i8 = ParseUintDef("-1", uint8(100)) // overflow, return default 165 | assert.True(t, i8 == uint8(100)) 166 | 167 | i16 := ParseUintDef("65535", uint16(100)) 168 | assert.True(t, i16 == uint16(65535)) 169 | i16 = ParseUintDef("65536", uint16(100)) // overflow, return default 170 | assert.True(t, i16 == uint16(100)) 171 | 172 | i32 := ParseUintDef("4294967295", uint32(100)) 173 | assert.True(t, i32 == uint32(4294967295)) 174 | i32 = ParseUintDef("4294967296", uint32(100)) // overflow, return default 175 | assert.True(t, i32 == uint32(100)) 176 | 177 | i64 := ParseUintDef("18446744073709551615", uint64(100)) 178 | assert.True(t, i64 == uint64(18446744073709551615)) 179 | i64 = ParseUintDef("18446744073709551616", uint64(100)) // overflow, return default 180 | assert.True(t, i64 == uint64(100)) 181 | 182 | // Custom type 183 | type X64 uint64 184 | x64 := ParseUintDef("18446744073709551615", X64(100)) 185 | assert.True(t, x64 == X64(18446744073709551615)) 186 | x64 = ParseUintDef("18446744073709551616", X64(100)) // overflow, return default 187 | assert.True(t, x64 == X64(100)) 188 | } 189 | 190 | func Test_ParseFloat(t *testing.T) { 191 | f32, e := ParseFloat[float32]("123456.123456") 192 | assert.True(t, e == nil && f32 == float32(123456.123456)) 193 | f32, e = ParseFloat[float32]("3.40282346638528859811704183484516925440e+38") 194 | assert.True(t, e == nil && f32 == math.MaxFloat32) 195 | f32, e = ParseFloat[float32]("123456.1234567") // out of precision 196 | assert.True(t, e == nil && f32 == float32(123456.123456)) 197 | _, e = ParseFloat[float32]("4.40282346638528859811704183484516925440e+38") // overflow 198 | assert.ErrorIs(t, e, strconv.ErrRange) 199 | 200 | f64, e := ParseFloat[float64]("123456789.123456789") 201 | assert.True(t, e == nil && f64 == float64(123456789.123456789)) 202 | f64, e = ParseFloat[float64]("1.79769313486231570814527423731704356798070e+308") 203 | assert.True(t, e == nil && f64 == math.MaxFloat64) 204 | _, e = ParseFloat[float64]("2.79769313486231570814527423731704356798070e+308") // overflow 205 | assert.ErrorIs(t, e, strconv.ErrRange) 206 | } 207 | 208 | func Test_ParseFloatUngroup(t *testing.T) { 209 | f32, e := ParseFloatUngroup[float32]("123456.123456") 210 | assert.True(t, e == nil && f32 == float32(123456.123456)) 211 | f32, e = ParseFloatUngroup[float32]("123,456.123,456") 212 | assert.True(t, e == nil && f32 == float32(123456.123456)) 213 | } 214 | 215 | func Test_ParseFloatDef(t *testing.T) { 216 | f32 := ParseFloatDef("123456.123456", float32(1)) 217 | assert.True(t, f32 == float32(123456.123456)) 218 | f32 = ParseFloatDef("3.40282346638528859811704183484516925440e+38", float32(0)) 219 | assert.True(t, f32 == math.MaxFloat32) 220 | f32 = ParseFloatDef("123456.1234567", float32(100)) // out of precision 221 | assert.True(t, f32 == float32(123456.123456)) 222 | f32 = ParseFloatDef("4.40282346638528859811704183484516925440e+38", float32(1)) // overflow 223 | assert.True(t, f32 == float32(1)) 224 | 225 | f64 := ParseFloatDef("123456789.123456789", float64(1)) 226 | assert.True(t, f64 == float64(123456789.123456789)) 227 | f64 = ParseFloatDef("1.79769313486231570814527423731704356798070e+308", float64(100)) 228 | assert.True(t, f64 == math.MaxFloat64) 229 | f64 = ParseFloatDef("2.79769313486231570814527423731704356798070e+308", float64(1.2)) // overflow 230 | assert.True(t, f64 == float64(1.2)) 231 | } 232 | 233 | func Test_FormatInt(t *testing.T) { 234 | assert.Equal(t, "127", FormatInt(int8(127))) 235 | assert.Equal(t, "-2147483647", FormatInt(int32(-2147483647))) 236 | assert.Equal(t, "9223372036854775807", FormatInt(int64(9223372036854775807))) 237 | assert.Equal(t, "-9223372036854775807", FormatInt(int64(-9223372036854775807))) 238 | } 239 | 240 | func Test_FormatIntEx(t *testing.T) { 241 | assert.Equal(t, "127", FormatIntEx(int8(127), "%d")) 242 | assert.Equal(t, "00127", FormatIntEx(int8(127), "%05d")) 243 | assert.Equal(t, "-2147483647", FormatIntEx(int32(-2147483647), "%d")) 244 | assert.Equal(t, "9223372036854775807", FormatIntEx(int64(9223372036854775807), "%d")) 245 | assert.Equal(t, "-7fffffffffffffff", FormatIntEx(int64(-9223372036854775807), "%x")) 246 | } 247 | 248 | func Test_FormatIntGroup(t *testing.T) { 249 | assert.Equal(t, "127", FormatIntGroup(int8(127))) 250 | assert.Equal(t, "-2,147,483,647", FormatIntGroup(int32(-2147483647))) 251 | assert.Equal(t, "9,223,372,036,854,775,807", FormatIntGroup(int64(9223372036854775807))) 252 | assert.Equal(t, "-9,223,372,036,854,775,807", FormatIntGroup(int64(-9223372036854775807))) 253 | } 254 | 255 | func Test_FormatUintEx(t *testing.T) { 256 | assert.Equal(t, "127", FormatUintEx(uint8(127), "%d")) 257 | assert.Equal(t, "00127", FormatUintEx(uint8(127), "%05d")) 258 | assert.Equal(t, "9223372036854775807", FormatUintEx(uint64(9223372036854775807), "%d")) 259 | } 260 | 261 | func Test_FormatUint(t *testing.T) { 262 | assert.Equal(t, "127", FormatUint(uint8(127))) 263 | assert.Equal(t, "2147483647", FormatUint(uint32(2147483647))) 264 | assert.Equal(t, "9223372036854775807", FormatUint(uint64(9223372036854775807))) 265 | } 266 | 267 | func Test_FormatUintGroup(t *testing.T) { 268 | assert.Equal(t, "255", FormatUintGroup(uint8(255))) 269 | assert.Equal(t, "2,147,483,647", FormatUintGroup(uint32(2147483647))) 270 | assert.Equal(t, "9,223,372,036,854,775,807", FormatUintGroup(uint64(9223372036854775807))) 271 | } 272 | 273 | func Test_FormatFloatEx(t *testing.T) { 274 | assert.Equal(t, "1234567.125", FormatFloatEx(float32(1234567.1234567), "%.3f")) 275 | assert.Equal(t, "1234567.123", FormatFloatEx(float64(1234567.1234567), "%.3f")) 276 | } 277 | 278 | func Test_FormatFloat(t *testing.T) { 279 | assert.Equal(t, fmt.Sprintf("%f", float32(1234567.1234567)), FormatFloat(float32(1234567.1234567))) 280 | assert.Equal(t, fmt.Sprintf("%f", math.MaxFloat32), FormatFloat(math.MaxFloat32)) 281 | assert.Equal(t, fmt.Sprintf("%f", float64(1234567.1234567)), FormatFloat(float64(1234567.1234567))) 282 | assert.Equal(t, fmt.Sprintf("%f", math.MaxFloat64), FormatFloat(math.MaxFloat64)) 283 | } 284 | 285 | func Test_FormatFloatGroup(t *testing.T) { 286 | assert.Equal(t, "1,234,567.125000", FormatFloatGroup(float32(1234567.1234567))) 287 | assert.Equal(t, "123,456,789.123457", FormatFloatGroup(float64(123456789.123456789))) 288 | } 289 | 290 | func Test_FormatFloatGroupEx(t *testing.T) { 291 | assert.Equal(t, "1,234,567.125", FormatFloatGroupEx(float32(1234567.1234567), "%.3f")) 292 | assert.Equal(t, "123,456,789.123", FormatFloatGroupEx(float64(123456789.123456789), "%.3f")) 293 | } 294 | 295 | func Test_NumberGroups(t *testing.T) { 296 | testCases := []struct { 297 | ungroup string 298 | grouped string 299 | }{ 300 | {"abc123", "abc123"}, 301 | {"1", "1"}, 302 | {"12", "12"}, 303 | {"123", "123"}, 304 | {"1234", "1,234"}, 305 | {"12345", "12,345"}, 306 | {"123456", "123,456"}, 307 | {"1234567", "1,234,567"}, 308 | {"12345678", "12,345,678"}, 309 | {"123456789", "123,456,789"}, 310 | {"1234567890", "1,234,567,890"}, 311 | 312 | {"-1", "-1"}, 313 | {"-12", "-12"}, 314 | {"-123", "-123"}, 315 | {"-1234", "-1,234"}, 316 | {"-12345", "-12,345"}, 317 | {"-123456", "-123,456"}, 318 | {"-1234567", "-1,234,567"}, 319 | {"-12345678", "-12,345,678"}, 320 | {"-123456789", "-123,456,789"}, 321 | {"-1234567890", "-1,234,567,890"}, 322 | 323 | {"1.1", "1.1"}, 324 | {"12.12", "12.12"}, 325 | {"123.123", "123.123"}, 326 | {"1234.1234", "1,234.1234"}, 327 | {"12345.12345", "12,345.12345"}, 328 | {"123456.123456", "123,456.123456"}, 329 | {"1234567.1234567", "1,234,567.1234567"}, 330 | {"12345678.12345678", "12,345,678.12345678"}, 331 | {"123456789.123456789", "123,456,789.123456789"}, 332 | {"1234567890.1234567890", "1,234,567,890.1234567890"}, 333 | 334 | {"-1.1", "-1.1"}, 335 | {"-12.12", "-12.12"}, 336 | {"-123.123", "-123.123"}, 337 | {"-1234.1234", "-1,234.1234"}, 338 | {"-12345.12345", "-12,345.12345"}, 339 | {"-123456.123456", "-123,456.123456"}, 340 | {"-1234567.1234567", "-1,234,567.1234567"}, 341 | {"-12345678.12345678", "-12,345,678.12345678"}, 342 | {"-123456789.123456789", "-123,456,789.123456789"}, 343 | {"-1234567890.1234567890", "-1,234,567,890.1234567890"}, 344 | } 345 | for i, tc := range testCases { 346 | assert.Equal(t, tc.grouped, NumberFmtGroup(tc.ungroup, fractionSep, groupSep), fmt.Sprintf("group #%d", i)) 347 | assert.Equal(t, tc.ungroup, NumberFmtUngroup(tc.grouped, groupSep), fmt.Sprintf("ungroup #%d", i)) 348 | } 349 | } 350 | 351 | func Test_stringIsInteger(t *testing.T) { 352 | assert.False(t, stringIsInteger("", true)) 353 | assert.False(t, stringIsInteger("-", true)) 354 | assert.False(t, stringIsInteger("-123", false)) 355 | assert.False(t, stringIsInteger("1.23", false)) 356 | assert.False(t, stringIsInteger("1e3", false)) 357 | 358 | assert.True(t, stringIsInteger("123", true)) 359 | assert.True(t, stringIsInteger("123", false)) 360 | assert.True(t, stringIsInteger("-123", true)) 361 | } 362 | -------------------------------------------------------------------------------- /rand.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | import "math/rand" 4 | 5 | // RandChoiceMaker a struct for picking up items randomly from a list of items 6 | type RandChoiceMaker[T any] struct { 7 | source []*T // Use pointers to slice items to gain more performance when item type is struct 8 | intNFunc func(n int) int 9 | } 10 | 11 | // Next gets the next item randomly from the source. 12 | // If there is no item in the source, returns false as the 2nd value. 13 | func (m *RandChoiceMaker[T]) Next() (T, bool) { 14 | count := len(m.source) 15 | if count == 0 { 16 | var defaultVal T 17 | return defaultVal, false 18 | } 19 | index := m.intNFunc(count) 20 | result := m.source[index] 21 | switch index { 22 | case 0: 23 | m.source = m.source[1:] 24 | case count - 1: 25 | m.source = m.source[:count-1] 26 | default: 27 | m.source = append(m.source[:index], m.source[index+1:]...) 28 | } 29 | return *result, true 30 | } 31 | 32 | // HasNext checks to see the maker has items to return 33 | func (m *RandChoiceMaker[T]) HasNext() bool { 34 | return len(m.source) > 0 35 | } 36 | 37 | func NewRandChoiceMaker[T any, S ~[]T](s S, randFuncs ...func(n int) int) RandChoiceMaker[T] { 38 | var source []*T 39 | for i := range s { 40 | source = append(source, &s[i]) 41 | } 42 | var randFunc func(int) int 43 | if len(randFuncs) > 0 { 44 | randFunc = randFuncs[0] 45 | } else { 46 | randFunc = rand.Intn 47 | } 48 | return RandChoiceMaker[T]{source: source, intNFunc: randFunc} 49 | } 50 | 51 | // RandChoice picks up an item randomly from a slice 52 | func RandChoice[T any](s ...T) (T, bool) { 53 | if len(s) == 0 { 54 | var defaultVal T 55 | return defaultVal, false 56 | } 57 | return s[rand.Intn(len(s))], true //nolint:gosec 58 | } 59 | -------------------------------------------------------------------------------- /rand_test.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func Test_RandChoice(t *testing.T) { 11 | // Empty input 12 | v1, b1 := RandChoice[int]() 13 | assert.Equal(t, 0, v1) 14 | assert.False(t, b1) 15 | // One item input 16 | v2, b2 := RandChoice[float32](1.1) 17 | assert.Equal(t, float32(1.1), v2) 18 | assert.True(t, b2) 19 | // Multiple items input 20 | v3, b3 := RandChoice[string]("1", "2", "3") 21 | assert.True(t, Contain([]string{"1", "2", "3"}, v3)) 22 | assert.True(t, b3) 23 | } 24 | 25 | func Test_RandChoiceMaker_Next(t *testing.T) { 26 | // Empty input 27 | maker1 := NewRandChoiceMaker[int]([]int(nil)) 28 | assert.False(t, maker1.HasNext()) 29 | v1, b1 := maker1.Next() 30 | assert.Equal(t, 0, v1) 31 | assert.False(t, b1) 32 | 33 | // One item input 34 | maker2 := NewRandChoiceMaker([]float32{1.1}) 35 | assert.True(t, maker2.HasNext()) 36 | v2, b2 := maker2.Next() 37 | assert.False(t, maker2.HasNext()) 38 | assert.Equal(t, float32(1.1), v2) 39 | assert.True(t, b2) 40 | 41 | // Multiple items input (with using custom rand function) 42 | maker3 := NewRandChoiceMaker([]string{"1", "2", "3"}, rand.Intn) 43 | assert.True(t, maker3.HasNext()) 44 | v30, b30 := maker3.Next() 45 | v31, b31 := maker3.Next() 46 | v32, b32 := maker3.Next() 47 | assert.False(t, maker3.HasNext()) 48 | v33, b33 := maker3.Next() 49 | v34, b34 := maker3.Next() 50 | assert.True(t, b30 && b31 && b32) 51 | assert.False(t, b33 || b34) 52 | v3 := []string{v30, v31, v32, v33, v34} 53 | Sort(v3) 54 | assert.Equal(t, []string{"", "", "1", "2", "3"}, v3) 55 | } 56 | -------------------------------------------------------------------------------- /slice_algo.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | // Compact excludes all zero items in a slice 4 | func Compact[T comparable, S ~[]T](s S) S { 5 | result := make(S, 0, len(s)) 6 | var zeroT T 7 | for _, v := range s { 8 | if v == zeroT { 9 | continue 10 | } 11 | result = append(result, v) 12 | } 13 | return result 14 | } 15 | 16 | // Drop returns a copied slice with dropping items in the list 17 | func Drop[T comparable, S ~[]T](a S, values ...T) S { 18 | return FilterNIN(a, values...) 19 | } 20 | 21 | // Reverse reverses slice content, this modifies the slice 22 | func Reverse[T any, S ~[]T](s S) S { 23 | if len(s) == 0 { 24 | return s 25 | } 26 | for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { 27 | s[i], s[j] = s[j], s[i] 28 | } 29 | return s 30 | } 31 | 32 | // ReverseCopy returns a new slice which has content in reversed order 33 | func ReverseCopy[T any, S ~[]T](s S) S { 34 | result := make(S, len(s)) 35 | for i, j := 0, len(s)-1; j >= 0; j-- { 36 | result[i] = s[j] 37 | i++ 38 | } 39 | return result 40 | } 41 | 42 | // Shuffle items of a slice and returns a new slice 43 | func Shuffle[T any, S ~[]T](s S, randFuncs ...func(n int) int) S { 44 | if len(s) <= 1 { 45 | return append(S{}, s...) 46 | } 47 | maker := NewRandChoiceMaker(s, randFuncs...) 48 | result := make(S, 0, len(s)) 49 | for { 50 | item, valid := maker.Next() 51 | if !valid { 52 | break 53 | } 54 | result = append(result, item) 55 | } 56 | return result 57 | } 58 | 59 | // Chunk splits slice content into chunks by chunk size 60 | func Chunk[T any, S ~[]T](s S, chunkSize int) []S { 61 | if chunkSize <= 0 { 62 | return []S{} 63 | } 64 | 65 | chunks := make([]S, 0, len(s)/chunkSize+1) 66 | for { 67 | if len(s) == 0 { 68 | break 69 | } 70 | if len(s) < chunkSize { 71 | chunkSize = len(s) 72 | } 73 | chunks = append(chunks, s[0:chunkSize]) 74 | s = s[chunkSize:] 75 | } 76 | return chunks 77 | } 78 | 79 | // ChunkByPieces splits slice content into chunks by number of pieces 80 | func ChunkByPieces[T any, S ~[]T](s S, chunkCount int) []S { 81 | if chunkCount <= 0 { 82 | return []S{} 83 | } 84 | chunkSize := len(s) / chunkCount 85 | if chunkSize*chunkCount < len(s) { 86 | chunkSize++ 87 | } 88 | 89 | return Chunk(s, chunkSize) 90 | } 91 | 92 | // Reduce reduces a slice to a value 93 | func Reduce[T any, S ~[]T](s S, reduceFunc func(accumulator, currentValue T) T) T { 94 | length := len(s) 95 | if length == 0 { 96 | var zeroT T 97 | return zeroT 98 | } 99 | accumulator := s[0] 100 | for i := 1; i < length; i++ { 101 | accumulator = reduceFunc(accumulator, s[i]) 102 | } 103 | return accumulator 104 | } 105 | 106 | // ReduceEx reduces a slice to a value with custom initial value 107 | func ReduceEx[T any, U any, S ~[]T]( 108 | s S, 109 | reduceFunc func(accumulator U, currentValue T, currentIndex int) U, 110 | initVal U, 111 | ) U { 112 | accumulator := initVal 113 | for i, v := range s { 114 | accumulator = reduceFunc(accumulator, v, i) 115 | } 116 | return accumulator 117 | } 118 | 119 | // ReduceReverse reduces a slice to a value 120 | func ReduceReverse[T any, S ~[]T](s S, reduceFunc func(accumulator, currentValue T) T) T { 121 | length := len(s) 122 | if length == 0 { 123 | var zeroT T 124 | return zeroT 125 | } 126 | accumulator := s[length-1] 127 | for i := length - 2; i >= 0; i-- { //nolint:mnd 128 | accumulator = reduceFunc(accumulator, s[i]) 129 | } 130 | return accumulator 131 | } 132 | 133 | // ReduceReverseEx reduces a slice to a value with custom initial value 134 | func ReduceReverseEx[T any, U any, S ~[]T]( 135 | s S, 136 | reduceFunc func(accumulator U, currentValue T, currentIndex int) U, 137 | initVal U, 138 | ) U { 139 | accumulator := initVal 140 | for i := len(s) - 1; i >= 0; i-- { 141 | accumulator = reduceFunc(accumulator, s[i], i) 142 | } 143 | return accumulator 144 | } 145 | 146 | // Partition splits slice items into 2 lists. 147 | func Partition[T any, S ~[]T](s S, partitionFunc func(T, int) bool) (S, S) { 148 | partition0, partitionRemaining := S{}, S{} 149 | for i, v := range s { 150 | if partitionFunc(v, i) { 151 | partition0 = append(partition0, v) 152 | } else { 153 | partitionRemaining = append(partitionRemaining, v) 154 | } 155 | } 156 | return partition0, partitionRemaining 157 | } 158 | 159 | // PartitionN splits slice items into N lists. 160 | // partitionFunc should return index of the partition to put the corresponding item into. 161 | func PartitionN[T any, S ~[]T](s S, numPartitions uint, partitionFunc func(T, int) int) []S { 162 | if numPartitions <= 0 { 163 | return []S{} 164 | } 165 | partitions := make([]S, numPartitions) 166 | for i := range partitions { 167 | partitions[i] = S{} 168 | } 169 | lastIndex := int(numPartitions) - 1 //nolint:gosec 170 | for i, v := range s { 171 | pIndex := partitionFunc(v, i) 172 | if pIndex < 0 || pIndex > lastIndex { 173 | pIndex = lastIndex 174 | } 175 | partitions[pIndex] = append(partitions[pIndex], v) 176 | } 177 | return partitions 178 | } 179 | 180 | // Union returns all unique values from multiple slices 181 | func Union[T comparable, S ~[]T](a, b S) S { 182 | lenA, lenB := len(a), len(b) 183 | if lenA == 0 { 184 | return ToSet(b) 185 | } 186 | if lenB == 0 { 187 | return ToSet(a) 188 | } 189 | 190 | seen := make(map[T]struct{}, lenA+lenB) 191 | result := make(S, 0, lenA+lenB) 192 | 193 | for i := 0; i < lenA; i++ { 194 | v := a[i] 195 | if _, ok := seen[v]; ok { 196 | continue 197 | } 198 | seen[v] = struct{}{} 199 | result = append(result, v) 200 | } 201 | for i := 0; i < lenB; i++ { 202 | v := b[i] 203 | if _, ok := seen[v]; ok { 204 | continue 205 | } 206 | seen[v] = struct{}{} 207 | result = append(result, v) 208 | } 209 | 210 | return result 211 | } 212 | 213 | // UnionBy returns all unique values from multiple slices with key function 214 | func UnionBy[T any, K comparable, S ~[]T](a, b S, keyFunc func(t T) K) S { 215 | lenA, lenB := len(a), len(b) 216 | if lenA == 0 { 217 | return ToSetBy(b, keyFunc) 218 | } 219 | if lenB == 0 { 220 | return ToSetBy(a, keyFunc) 221 | } 222 | 223 | seen := make(map[K]struct{}, lenA+lenB) 224 | result := make(S, 0, lenA+lenB) 225 | 226 | for i := 0; i < lenA; i++ { 227 | v := a[i] 228 | k := keyFunc(v) 229 | if _, ok := seen[k]; ok { 230 | continue 231 | } 232 | seen[k] = struct{}{} 233 | result = append(result, v) 234 | } 235 | for i := 0; i < lenB; i++ { 236 | v := b[i] 237 | k := keyFunc(v) 238 | if _, ok := seen[k]; ok { 239 | continue 240 | } 241 | seen[k] = struct{}{} 242 | result = append(result, v) 243 | } 244 | 245 | return result 246 | } 247 | 248 | // Deprecated: use UnionBy instead 249 | func UnionPred[T any, K comparable, S ~[]T](a, b S, keyFunc func(t T) K) S { 250 | return UnionBy(a, b, keyFunc) 251 | } 252 | 253 | // Intersection returns all unique shared values from multiple slices 254 | func Intersection[T comparable, S ~[]T](a, b S) S { 255 | lenA, lenB := len(a), len(b) 256 | if lenA == 0 || lenB == 0 { 257 | return S{} 258 | } 259 | 260 | seen := make(map[T]*bool, lenA) 261 | result := make(S, 0, Min(lenA, lenB)) 262 | 263 | for i := 0; i < lenA; i++ { 264 | active := true 265 | seen[a[i]] = &active 266 | } 267 | for i := 0; i < lenB; i++ { 268 | v := b[i] 269 | if active, ok := seen[v]; ok && *active { 270 | result = append(result, v) 271 | *active = false 272 | } 273 | } 274 | 275 | return result 276 | } 277 | 278 | // IntersectionBy returns all unique shared values from multiple slices with key function 279 | func IntersectionBy[T any, K comparable, S ~[]T](a, b S, keyFunc func(t T) K) S { 280 | lenA, lenB := len(a), len(b) 281 | if lenA == 0 || lenB == 0 { 282 | return S{} 283 | } 284 | 285 | seen := make(map[K]*bool, lenA) 286 | result := make(S, 0, Min(lenA, lenB)) 287 | 288 | for i := 0; i < lenA; i++ { 289 | active := true 290 | seen[keyFunc(a[i])] = &active 291 | } 292 | for i := 0; i < lenB; i++ { 293 | v := b[i] 294 | k := keyFunc(v) 295 | if active, ok := seen[k]; ok && *active { 296 | result = append(result, v) 297 | *active = false 298 | } 299 | } 300 | 301 | return result 302 | } 303 | 304 | // Deprecated: use IntersectionBy instead 305 | func IntersectionPred[T any, K comparable, S ~[]T](a, b S, keyFunc func(t T) K) S { 306 | return IntersectionBy(a, b, keyFunc) 307 | } 308 | 309 | // Difference calculates the differences between two slices. 310 | // The first result list contains all the values exist in the left list, but the right. 311 | // The second result list contains all the values exist in the right list, but the left. 312 | // NOTE: this function does not return unique values. 313 | func Difference[T comparable, S ~[]T](a, b S) (S, S) { 314 | lenA, lenB := len(a), len(b) 315 | if lenA == 0 || lenB == 0 { 316 | return append(S{}, a...), append(S{}, b...) 317 | } 318 | 319 | leftDiff, rightDiff := S{}, S{} 320 | leftMap, rightMap := MapSliceToMapKeys(a, struct{}{}), MapSliceToMapKeys(b, struct{}{}) 321 | 322 | for _, v := range a { 323 | if _, ok := rightMap[v]; !ok { 324 | leftDiff = append(leftDiff, v) 325 | } 326 | } 327 | for _, v := range b { 328 | if _, ok := leftMap[v]; !ok { 329 | rightDiff = append(rightDiff, v) 330 | } 331 | } 332 | 333 | return leftDiff, rightDiff 334 | } 335 | 336 | // DifferenceBy calculates the differences between two slices using special key function. 337 | // NOTE: this function does not return unique values. 338 | func DifferenceBy[T any, K comparable, S ~[]T](a, b S, keyFunc func(t T) K) (S, S) { 339 | lenA, lenB := len(a), len(b) 340 | if lenA == 0 || lenB == 0 { 341 | return append(S{}, a...), append(S{}, b...) 342 | } 343 | 344 | leftDiff, rightDiff := S{}, S{} 345 | leftMap, rightMap := make(map[K]struct{}, lenA), make(map[K]struct{}, lenB) 346 | 347 | for _, v := range a { 348 | leftMap[keyFunc(v)] = struct{}{} 349 | } 350 | for _, v := range b { 351 | rightMap[keyFunc(v)] = struct{}{} 352 | } 353 | 354 | for _, v := range a { 355 | if _, ok := rightMap[keyFunc(v)]; !ok { 356 | leftDiff = append(leftDiff, v) 357 | } 358 | } 359 | for _, v := range b { 360 | if _, ok := leftMap[keyFunc(v)]; !ok { 361 | rightDiff = append(rightDiff, v) 362 | } 363 | } 364 | 365 | return leftDiff, rightDiff 366 | } 367 | 368 | // Deprecated: use DifferenceBy instead 369 | func DifferencePred[T any, K comparable, S ~[]T](a, b S, keyFunc func(t T) K) (S, S) { 370 | return DifferenceBy(a, b, keyFunc) 371 | } 372 | 373 | // Flatten flattens 2-dimensional slices. 374 | // E.g. Flatten([1,2,3], [3,4,5]) -> [1,2,3,3,4,5]. 375 | func Flatten[T any, S ~[]T](s ...S) S { 376 | result := make(S, 0, len(s)*5) //nolint:mnd 377 | for _, innerSlice := range s { 378 | result = append(result, innerSlice...) 379 | } 380 | return result 381 | } 382 | 383 | // Flatten3 flattens 3-dimensional slices 384 | func Flatten3[T any, S ~[]T, SS ~[]S](s ...SS) S { 385 | result := make(S, 0, len(s)*30) //nolint:mnd 386 | for _, innerSlice := range s { 387 | for _, mostInnerSlice := range innerSlice { 388 | result = append(result, mostInnerSlice...) 389 | } 390 | } 391 | return result 392 | } 393 | 394 | // Zip combines values from 2 slices by each position 395 | func Zip[T1, T2 any, S1 ~[]T1, S2 ~[]T2](slice1 S1, slice2 S2) []*Tuple2[T1, T2] { 396 | minLen := len(slice1) 397 | if minLen > len(slice2) { 398 | minLen = len(slice2) 399 | } 400 | result := make([]*Tuple2[T1, T2], minLen) 401 | for i := 0; i < minLen; i++ { 402 | result[i] = &Tuple2[T1, T2]{slice1[i], slice2[i]} 403 | } 404 | return result 405 | } 406 | 407 | // Zip3 combines values from 3 slices by each position 408 | func Zip3[T1, T2, T3 any, S1 ~[]T1, S2 ~[]T2, S3 ~[]T3](slice1 S1, slice2 S2, slice3 S3) []*Tuple3[T1, T2, T3] { 409 | minLen := Min(len(slice1), len(slice2), len(slice3)) 410 | result := make([]*Tuple3[T1, T2, T3], minLen) 411 | for i := 0; i < minLen; i++ { 412 | result[i] = &Tuple3[T1, T2, T3]{slice1[i], slice2[i], slice3[i]} 413 | } 414 | return result 415 | } 416 | 417 | // Zip4 combines values from 4 slices by each position 418 | func Zip4[T1, T2, T3, T4 any, S1 ~[]T1, S2 ~[]T2, S3 ~[]T3, S4 ~[]T4]( 419 | slice1 S1, slice2 S2, slice3 S3, slice4 S4, 420 | ) []*Tuple4[T1, T2, T3, T4] { 421 | minLen := Min(len(slice1), len(slice2), len(slice3), len(slice4)) 422 | result := make([]*Tuple4[T1, T2, T3, T4], minLen) 423 | for i := 0; i < minLen; i++ { 424 | result[i] = &Tuple4[T1, T2, T3, T4]{slice1[i], slice2[i], slice3[i], slice4[i]} 425 | } 426 | return result 427 | } 428 | 429 | // Zip5 combines values from 4 slices by each position 430 | func Zip5[T1, T2, T3, T4, T5 any, S1 ~[]T1, S2 ~[]T2, S3 ~[]T3, S4 ~[]T4, S5 ~[]T5]( 431 | slice1 S1, slice2 S2, slice3 S3, slice4 S4, slice5 S5, 432 | ) []*Tuple5[T1, T2, T3, T4, T5] { 433 | minLen := Min(len(slice1), len(slice2), len(slice3), len(slice4), len(slice5)) 434 | result := make([]*Tuple5[T1, T2, T3, T4, T5], minLen) 435 | for i := 0; i < minLen; i++ { 436 | result[i] = &Tuple5[T1, T2, T3, T4, T5]{slice1[i], slice2[i], slice3[i], slice4[i], slice5[i]} 437 | } 438 | return result 439 | } 440 | -------------------------------------------------------------------------------- /slice_algo_test.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | import ( 4 | "math/rand" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func Test_Compact(t *testing.T) { 12 | assert.Equal(t, []int{1, -1}, Compact([]int{1, 0, -1})) 13 | assert.Equal(t, []bool{true, true, true}, Compact([]bool{true, true, false, false, true})) 14 | assert.Equal(t, []string{"1", "2"}, Compact([]string{"1", "", "2"})) 15 | 16 | type St struct { 17 | Int int 18 | Str string 19 | } 20 | assert.Equal(t, []St{{1, "1"}, {2, "2"}}, Compact([]St{{1, "1"}, {}, {2, "2"}})) 21 | } 22 | 23 | func Test_Drop(t *testing.T) { 24 | // Nil/empty source slice 25 | assert.Equal(t, []int{}, Drop([]int(nil))) 26 | assert.Equal(t, []int{}, Drop([]int{})) 27 | 28 | assert.Equal(t, []int{1, 4}, Drop([]int{1, 2, 3, 4, 5}, 5, 3, 2, 7)) 29 | } 30 | 31 | func Test_Reverse(t *testing.T) { 32 | assert.Equal(t, []int{}, Reverse([]int{})) 33 | assert.Equal(t, []int64{1}, Reverse([]int64{1})) 34 | assert.Equal(t, []int{3, 2, 1}, Reverse([]int{1, 2, 3})) 35 | 36 | s := []int{-1, -2, 0, 1, 2} 37 | Reverse(s) 38 | assert.Equal(t, []int{2, 1, 0, -2, -1}, s) 39 | } 40 | 41 | func Test_ReverseCopy(t *testing.T) { 42 | assert.Equal(t, []int{}, ReverseCopy([]int{})) 43 | assert.Equal(t, []int64{1}, ReverseCopy([]int64{1})) 44 | assert.Equal(t, []int{3, 2, 1}, ReverseCopy([]int{1, 2, 3})) 45 | 46 | s := []int{-1, -2, 0, 1, 2} 47 | s2 := ReverseCopy(s) 48 | assert.Equal(t, []int{-1, -2, 0, 1, 2}, s) 49 | assert.Equal(t, []int{2, 1, 0, -2, -1}, s2) 50 | } 51 | 52 | func Test_Shuffle(t *testing.T) { 53 | // Empty input slice 54 | s1 := Shuffle[int]([]int(nil)) 55 | assert.Equal(t, []int{}, s1) 56 | // One item input 57 | s2 := Shuffle([]float32{1.1}) 58 | assert.Equal(t, []float32{1.1}, s2) 59 | // Multiple items input (with using custom rand function) 60 | s3 := Shuffle([]string{"1", "2", "3"}, rand.Intn) 61 | Sort(s3) 62 | assert.Equal(t, []string{"1", "2", "3"}, s3) 63 | } 64 | 65 | func Test_Chunk(t *testing.T) { 66 | // Empty input 67 | chunks := Chunk([]int{}, 5) 68 | assert.True(t, len(chunks) == 0) 69 | 70 | // Nil input 71 | chunks = Chunk[int]([]int(nil), 5) 72 | assert.True(t, len(chunks) == 0) 73 | 74 | // Chunk size greater than input size 75 | chunks = Chunk([]int{1, 2, 3}, 5) 76 | assert.True(t, len(chunks) == 1 && reflect.DeepEqual(chunks[0], []int{1, 2, 3})) 77 | 78 | // Normal case 79 | chunks = Chunk([]int{1, 2, 3, 4, 5}, 2) 80 | assert.True(t, len(chunks) == 3 && 81 | len(chunks[0]) == 2 && reflect.DeepEqual(chunks[0], []int{1, 2}) && 82 | len(chunks[1]) == 2 && reflect.DeepEqual(chunks[1], []int{3, 4}) && 83 | len(chunks[2]) == 1 && reflect.DeepEqual(chunks[2], []int{5})) 84 | } 85 | 86 | func Test_ChunkByPieces(t *testing.T) { 87 | // Empty input 88 | chunks := ChunkByPieces([]int{}, 5) 89 | assert.True(t, len(chunks) == 0) 90 | 91 | // Nil input 92 | chunks = ChunkByPieces[int]([]int(nil), 5) 93 | assert.True(t, len(chunks) == 0) 94 | 95 | // Chunk count is zero 96 | chunks = ChunkByPieces([]int{1, 2, 3}, 0) 97 | assert.Equal(t, [][]int{}, chunks) 98 | 99 | // Chunk count greater than input size 100 | chunks = ChunkByPieces([]int{1, 2, 3}, 5) 101 | assert.True(t, len(chunks) == 3 && 102 | len(chunks[0]) == 1 && reflect.DeepEqual(chunks[0], []int{1}) && 103 | len(chunks[1]) == 1 && reflect.DeepEqual(chunks[1], []int{2}) && 104 | len(chunks[2]) == 1 && reflect.DeepEqual(chunks[2], []int{3})) 105 | 106 | // Normal case 107 | chunks = ChunkByPieces([]int{1, 2, 3, 4, 5}, 2) 108 | assert.True(t, len(chunks) == 2 && 109 | len(chunks[0]) == 3 && reflect.DeepEqual(chunks[0], []int{1, 2, 3}) && 110 | len(chunks[1]) == 2 && reflect.DeepEqual(chunks[1], []int{4, 5})) 111 | } 112 | 113 | func Test_Reduce(t *testing.T) { 114 | // Empty slice 115 | assert.Equal(t, 0, Reduce[int]([]int{}, func(acc, v int) int { return acc + v })) 116 | // Slice has 1 element 117 | assert.Equal(t, 1, Reduce[int]([]int{1}, func(acc, v int) int { return acc + v })) 118 | 119 | assert.Equal(t, 6, Reduce[int]([]int{1, 2, 3}, func(acc, v int) int { return acc + v })) 120 | assert.Equal(t, 8, Reduce[int]([]int{1, 2, 4}, func(acc, v int) int { return acc * v })) 121 | assert.Equal(t, 0, Reduce[int]([]int{1, 2, 0}, func(acc, v int) int { return acc * v })) 122 | } 123 | 124 | func Test_ReduceEx(t *testing.T) { 125 | assert.Equal(t, 7, ReduceEx[int]([]int{1, 2, 3}, func(acc, v, i int) int { return acc + v }, 1)) 126 | assert.Equal(t, 8, ReduceEx[int]([]int{1, 2, 4}, func(acc, v, i int) int { return acc * v }, 1)) 127 | assert.Equal(t, 0, ReduceEx[int]([]int{1, 2, 0}, func(acc, v, i int) int { return acc * v }, 1)) 128 | } 129 | 130 | func Test_ReduceReverse(t *testing.T) { 131 | // Empty slice 132 | assert.Equal(t, 0, ReduceReverse[int]([]int{}, func(acc, v int) int { return acc + v })) 133 | // Slice has 1 element 134 | assert.Equal(t, 1, ReduceReverse[int]([]int{1}, func(acc, v int) int { return acc + v })) 135 | 136 | assert.Equal(t, 6, ReduceReverse[int]([]int{1, 2, 3}, func(acc, v int) int { return acc + v })) 137 | assert.Equal(t, 8, ReduceReverse[int]([]int{1, 2, 4}, func(acc, v int) int { return acc * v })) 138 | assert.Equal(t, 0, ReduceReverse[int]([]int{1, 2, 0}, func(acc, v int) int { return acc * v })) 139 | } 140 | 141 | func Test_ReduceReverseEx(t *testing.T) { 142 | assert.Equal(t, 7, ReduceReverseEx[int]([]int{1, 2, 3}, func(acc, v, i int) int { return acc + v }, 1)) 143 | assert.Equal(t, 8, ReduceReverseEx[int]([]int{1, 2, 4}, func(acc, v, i int) int { return acc * v }, 1)) 144 | assert.Equal(t, 0, ReduceReverseEx[int]([]int{1, 2, 0}, func(acc, v, i int) int { return acc * v }, 1)) 145 | } 146 | 147 | func Test_Partition(t *testing.T) { 148 | // Nil/empty input 149 | s1, s2 := Partition([]int(nil), func(i int, _ int) bool { return i%2 == 0 }) 150 | assert.Equal(t, []int{}, s1) 151 | assert.Equal(t, []int{}, s2) 152 | s1, s2 = Partition([]int{}, func(i int, _ int) bool { return i%2 == 0 }) 153 | assert.Equal(t, []int{}, s1) 154 | assert.Equal(t, []int{}, s2) 155 | 156 | s1, s2 = Partition([]int{3, 5, 7, 3}, func(i int, _ int) bool { return i%2 == 1 }) 157 | assert.Equal(t, []int{3, 5, 7, 3}, s1) 158 | assert.Equal(t, []int{}, s2) 159 | 160 | s1, s2 = Partition([]int{3, 5, 7, 3}, func(i int, _ int) bool { return i%2 == 0 }) 161 | assert.Equal(t, []int{}, s1) 162 | assert.Equal(t, []int{3, 5, 7, 3}, s2) 163 | 164 | s1, s2 = Partition([]int{3, 2, 5, 0, 0, 7, 3, 4}, func(i int, _ int) bool { return i%2 == 0 }) 165 | assert.Equal(t, []int{2, 0, 0, 4}, s1) 166 | assert.Equal(t, []int{3, 5, 7, 3}, s2) 167 | } 168 | 169 | func Test_PartitionN(t *testing.T) { 170 | // Zero partition 171 | p := PartitionN([]int{1, 2, 3}, 0, func(i int, _ int) int { return i % 2 }) 172 | assert.Equal(t, 0, len(p)) 173 | 174 | // Nil/empty input 175 | p = PartitionN([]int(nil), 3, func(i int, _ int) int { return i % 2 }) 176 | assert.Equal(t, []int{}, p[0]) 177 | assert.Equal(t, []int{}, p[1]) 178 | assert.Equal(t, []int{}, p[2]) 179 | p = PartitionN([]int{}, 3, func(i int, _ int) int { return i % 2 }) 180 | assert.Equal(t, []int{}, p[0]) 181 | assert.Equal(t, []int{}, p[1]) 182 | assert.Equal(t, []int{}, p[2]) 183 | 184 | p = PartitionN([]int{20, 30, 40, 50, 5, -1, 15, 10}, 3, func(i int, _ int) int { return i / 10 }) 185 | assert.Equal(t, []int{5, -1}, p[0]) 186 | assert.Equal(t, []int{15, 10}, p[1]) 187 | assert.Equal(t, []int{20, 30, 40, 50}, p[2]) 188 | } 189 | 190 | func Test_Union(t *testing.T) { 191 | assert.Equal(t, []int{}, Union[int]([]int(nil), nil)) 192 | assert.Equal(t, []int{}, Union(nil, []int{})) 193 | assert.Equal(t, []int{1}, Union([]int{1}, nil)) 194 | 195 | assert.Equal(t, []int{1, 2, 3, 4}, Union([]int{1, 2}, []int{3, 4})) 196 | assert.Equal(t, []int{1, 2, 3, 4}, Union([]int{1, 2, 3, 2}, []int{1, 2, 4, 3})) 197 | assert.Equal(t, []string{"1", "2", "3", "4"}, Union([]string{"1", "2", "3", "2"}, []string{"1", "2", "4", "3"})) 198 | } 199 | 200 | // nolint: forcetypeassert 201 | func Test_UnionBy(t *testing.T) { 202 | assert.Equal(t, []any{}, UnionBy[any]([]any(nil), nil, func(t any) int { return t.(int) })) 203 | assert.Equal(t, []any{}, UnionBy(nil, []any{}, func(t any) int { return t.(int) })) 204 | assert.Equal(t, []any{1}, UnionBy([]any{1}, nil, func(t any) int { return t.(int) })) 205 | 206 | assert.Equal(t, []any{1, 2, 3, 4}, UnionBy([]any{1, 2}, []any{3, 4}, 207 | func(t any) int { return t.(int) })) 208 | assert.Equal(t, []any{1, 2, 3, 4}, UnionBy([]any{1, 2, 3, 2}, []any{1, 2, 4, 3}, 209 | func(t any) int { return t.(int) })) 210 | assert.Equal(t, []any{"1", "2", "3", "4"}, 211 | UnionBy([]any{"1", "2", "3", "2"}, []any{"1", "2", "4", "3"}, 212 | func(t any) string { return t.(string) })) 213 | } 214 | 215 | // nolint: forcetypeassert 216 | func Test_UnionPred_Deprecated(t *testing.T) { 217 | assert.Equal(t, []any{1, 2, 3, 4}, UnionPred([]any{1, 2}, []any{3, 4}, 218 | func(t any) int { return t.(int) })) 219 | assert.Equal(t, []any{1, 2, 3, 4}, UnionPred([]any{1, 2, 3, 2}, []any{1, 2, 4, 3}, 220 | func(t any) int { return t.(int) })) 221 | } 222 | 223 | func Test_Intersection(t *testing.T) { 224 | assert.Equal(t, []int{}, Intersection[int]([]int(nil), nil)) 225 | assert.Equal(t, []int{}, Intersection(nil, []int{})) 226 | assert.Equal(t, []int{}, Intersection([]int{1}, nil)) 227 | assert.Equal(t, []int{}, Intersection(nil, []int{1})) 228 | 229 | assert.Equal(t, []int{}, Intersection([]int{1, 2}, []int{3, 4})) 230 | assert.Equal(t, []int{1, 2, 3}, Intersection([]int{1, 2, 3, 2}, []int{1, 2, 4, 3})) 231 | assert.Equal(t, []string{"1", "2", "3"}, Intersection([]string{"1", "2", "3", "2"}, []string{"1", "2", "4", "3"})) 232 | } 233 | 234 | // nolint: forcetypeassert 235 | func Test_IntersectionBy(t *testing.T) { 236 | assert.Equal(t, []any{}, IntersectionBy[any]([]any(nil), nil, func(t any) int { return t.(int) })) 237 | assert.Equal(t, []any{}, IntersectionBy(nil, []any{}, func(t any) int { return t.(int) })) 238 | assert.Equal(t, []any{}, IntersectionBy([]any{1}, nil, func(t any) int { return t.(int) })) 239 | assert.Equal(t, []any{}, IntersectionBy(nil, []any{1}, func(t any) int { return t.(int) })) 240 | 241 | assert.Equal(t, []any{}, IntersectionBy([]any{1, 2}, []any{3, 4}, 242 | func(t any) int { return t.(int) })) 243 | assert.Equal(t, []any{1, 2, 3}, IntersectionBy([]any{1, 2, 3, 2}, []any{1, 2, 4, 3}, 244 | func(t any) int { return t.(int) })) 245 | assert.Equal(t, []any{"1", "2", "3"}, 246 | IntersectionBy([]any{"1", "2", "3", "2"}, []any{"1", "2", "4", "3"}, 247 | func(t any) string { return t.(string) })) 248 | } 249 | 250 | // nolint: forcetypeassert 251 | func Test_IntersectionPred_Deprecated(t *testing.T) { 252 | assert.Equal(t, []any{}, IntersectionPred([]any{1, 2}, []any{3, 4}, 253 | func(t any) int { return t.(int) })) 254 | assert.Equal(t, []any{1, 2, 3}, IntersectionPred([]any{1, 2, 3, 2}, []any{1, 2, 4, 3}, 255 | func(t any) int { return t.(int) })) 256 | } 257 | 258 | func Test_Difference(t *testing.T) { 259 | l, r := Difference[int]([]int(nil), nil) 260 | assert.Equal(t, []int{}, l) 261 | assert.Equal(t, []int{}, r) 262 | l, r = Difference([]int{}, nil) 263 | assert.Equal(t, []int{}, l) 264 | assert.Equal(t, []int{}, r) 265 | 266 | l, r = Difference([]int{1, 2}, []int{3, 4}) 267 | assert.Equal(t, []int{1, 2}, l) 268 | assert.Equal(t, []int{3, 4}, r) 269 | 270 | l, r = Difference([]int{1, 2, 3, 2}, []int{1, 4, 4, 3}) 271 | assert.Equal(t, []int{2, 2}, l) 272 | assert.Equal(t, []int{4, 4}, r) 273 | 274 | l2, r2 := Difference([]string{"1", "2", "3", "2"}, []string{"1", "4", "2", "", "3"}) 275 | assert.Equal(t, []string{}, l2) 276 | assert.Equal(t, []string{"4", ""}, r2) 277 | } 278 | 279 | // nolint: forcetypeassert 280 | func Test_DifferenceBy(t *testing.T) { 281 | l, r := DifferenceBy[any]([]any(nil), nil, func(t any) int { return t.(int) }) 282 | assert.Equal(t, []any{}, l) 283 | assert.Equal(t, []any{}, r) 284 | l, r = DifferenceBy([]any{}, nil, func(t any) int { return t.(int) }) 285 | assert.Equal(t, []any{}, l) 286 | assert.Equal(t, []any{}, r) 287 | 288 | l, r = DifferenceBy([]any{1, 2}, []any{3, 4}, func(t any) int { return t.(int) }) 289 | assert.Equal(t, []any{1, 2}, l) 290 | assert.Equal(t, []any{3, 4}, r) 291 | 292 | l, r = DifferenceBy([]any{1, 2, 3, 2}, []any{1, 4, 4, 3}, func(t any) int { return t.(int) }) 293 | assert.Equal(t, []any{2, 2}, l) 294 | assert.Equal(t, []any{4, 4}, r) 295 | 296 | l, r = DifferenceBy([]any{"1", "2", "3", "2"}, []any{"1", "4", "2", "", "3"}, 297 | func(t any) string { return t.(string) }) 298 | assert.Equal(t, []any{}, l) 299 | assert.Equal(t, []any{"4", ""}, r) 300 | } 301 | 302 | // nolint: forcetypeassert 303 | func Test_DifferencePred_Deprecated(t *testing.T) { 304 | l, r := DifferencePred([]any{1, 2}, []any{3, 4}, func(t any) int { return t.(int) }) 305 | assert.Equal(t, []any{1, 2}, l) 306 | assert.Equal(t, []any{3, 4}, r) 307 | 308 | l, r = DifferencePred([]any{1, 2, 3, 2}, []any{1, 4, 4, 3}, func(t any) int { return t.(int) }) 309 | assert.Equal(t, []any{2, 2}, l) 310 | assert.Equal(t, []any{4, 4}, r) 311 | } 312 | 313 | func Test_Flatten(t *testing.T) { 314 | assert.True(t, reflect.DeepEqual([]int{}, Flatten([]int{}, []int{}))) 315 | assert.True(t, reflect.DeepEqual([]int{1}, Flatten([]int{}, []int{1}))) 316 | assert.True(t, reflect.DeepEqual([]int{1, 2, 3}, Flatten([]int{1, 2}, []int{3}))) 317 | assert.True(t, reflect.DeepEqual([]int64{1, 2, 3, 4, 0}, Flatten([]int64{1, 2}, []int64{3, 4}, []int64{0}))) 318 | } 319 | 320 | func Test_Flatten3(t *testing.T) { 321 | assert.True(t, reflect.DeepEqual([]int{}, Flatten3([][]int{}, [][]int{}))) 322 | assert.True(t, reflect.DeepEqual([]int{1}, Flatten3([][]int{{1}}, [][]int{}))) 323 | assert.True(t, reflect.DeepEqual([]int{1, 2, 3, 4}, Flatten3([][]int{{1, 2}, {3, 4}}))) 324 | assert.True(t, reflect.DeepEqual([]int{1, 2, 3, 4, 0}, Flatten3([][]int{{1, 2}, {3, 4}}, [][]int{{0}}))) 325 | } 326 | 327 | func Test_Zip(t *testing.T) { 328 | assert.Equal(t, []*Tuple2[int, bool]{}, Zip([]int{}, []bool{})) 329 | assert.Equal(t, []*Tuple2[int, bool]{}, Zip([]int{1, 2, 3}, []bool{})) 330 | assert.Equal(t, []*Tuple2[int, string]{{1, "1"}, {2, "2"}}, Zip([]int{1, 2, 3}, []string{"1", "2"})) 331 | } 332 | 333 | func Test_Zip3(t *testing.T) { 334 | assert.Equal(t, []*Tuple3[int, bool, int32]{}, Zip3([]int{}, []bool{}, []int32{})) 335 | assert.Equal(t, []*Tuple3[int, bool, int32]{}, Zip3([]int{1, 2, 3}, []bool{}, []int32{4, 5})) 336 | assert.Equal(t, []*Tuple3[int, int, int]{{1, 11, 111}, {2, 22, 222}}, 337 | Zip3([]int{1, 2, 3, 4}, []int{11, 22, 33}, []int{111, 222})) 338 | assert.Equal(t, []*Tuple3[int, string, bool]{{1, "1", true}, {2, "2", false}}, 339 | Zip3([]int{1, 2, 3}, []string{"1", "2"}, []bool{true, false, false})) 340 | } 341 | 342 | func Test_Zip4(t *testing.T) { 343 | assert.Equal(t, 344 | []*Tuple4[int, bool, int32, float32]{}, 345 | Zip4([]int{}, []bool{}, []int32{}, []float32{})) 346 | assert.Equal(t, 347 | []*Tuple4[int, bool, int32, float32]{}, 348 | Zip4([]int{1, 2, 3}, []bool{}, []int32{4, 5}, []float32{1.1, 2.2})) 349 | assert.Equal(t, 350 | []*Tuple4[int, int, int, int]{ 351 | {1, 11, 111, 1111}, {2, 22, 222, 2222}, 352 | }, 353 | Zip4([]int{1, 2, 3, 4}, []int{11, 22, 33}, []int{111, 222}, []int{1111, 2222, 3333})) 354 | assert.Equal(t, 355 | []*Tuple4[int, string, bool, int]{ 356 | {1, "1", true, 11}, {2, "2", false, 22}, 357 | }, 358 | Zip4([]int{1, 2, 3}, []string{"1", "2"}, []bool{true, false, false}, []int{11, 22, 32})) 359 | } 360 | 361 | func Test_Zip5(t *testing.T) { 362 | assert.Equal(t, 363 | []*Tuple5[int, bool, int32, float32, string]{}, 364 | Zip5([]int{}, []bool{}, []int32{}, []float32{}, []string{})) 365 | assert.Equal(t, 366 | []*Tuple5[int, bool, int32, float32, string]{}, 367 | Zip5([]int{1, 2, 3}, []bool{}, []int32{4, 5}, []float32{1.1, 2.2}, []string{"1"})) 368 | assert.Equal(t, 369 | []*Tuple5[int, int, int, int, int]{ 370 | {1, 11, 111, 1111, 11111}, {2, 22, 222, 2222, 22222}, 371 | }, 372 | Zip5([]int{1, 2, 3, 4}, []int{11, 22, 33}, []int{111, 222}, []int{1111, 2222, 3333}, []int{11111, 22222, 33333})) 373 | assert.Equal(t, 374 | []*Tuple5[int, string, bool, int, int]{ 375 | {1, "1", true, 11, 111}, {2, "2", false, 22, 222}, 376 | }, 377 | Zip5([]int{1, 2, 3}, []string{"1", "2"}, []bool{true, false, false}, []int{11, 22, 33}, []int{111, 222, 333})) 378 | } 379 | -------------------------------------------------------------------------------- /slice_conv.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | // ToIfaceSlice convert a slice to a slice of interface 4 | func ToIfaceSlice[T any, S ~[]T](s S) []any { 5 | result := make([]any, len(s)) 6 | for i := range s { 7 | result[i] = s[i] 8 | } 9 | return result 10 | } 11 | 12 | // Deprecated: use ToIfaceSlice instead 13 | func ToIntfSlice[T any, S ~[]T](s S) []any { 14 | return ToIfaceSlice(s) 15 | } 16 | 17 | // ToStringSlice converts str-approximate slice to string slice 18 | func ToStringSlice[U, T ~string, S ~[]T](slice S) []U { 19 | result := make([]U, len(slice)) 20 | for i := range slice { 21 | result[i] = U(slice[i]) 22 | } 23 | return result 24 | } 25 | 26 | // ToNumberSlice converts int-approximate slice to int slice 27 | func ToNumberSlice[U, T NumberExt, S ~[]T](slice S) []U { 28 | result := make([]U, len(slice)) 29 | for i := range slice { 30 | result[i] = U(slice[i]) 31 | } 32 | return result 33 | } 34 | 35 | // ToSlice returns a slice for individual input arguments 36 | func ToSlice[T any](s ...T) []T { 37 | if s == nil { 38 | return []T{} 39 | } 40 | return s 41 | } 42 | 43 | // ToPtrSlice returns a slice of pointers point to the input slice's elements 44 | func ToPtrSlice[T any, S ~[]T](s S) []*T { 45 | result := make([]*T, len(s)) 46 | for i := range s { 47 | result[i] = &s[i] 48 | } 49 | return result 50 | } 51 | -------------------------------------------------------------------------------- /slice_conv_test.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func Test_ToIfaceSlice(t *testing.T) { 10 | assert.Equal(t, []any{}, ToIfaceSlice([]int{})) 11 | assert.Equal(t, []any{"one"}, ToIfaceSlice([]string{"one"})) 12 | assert.Equal(t, []any{1, 2, 3}, ToIfaceSlice([]int{1, 2, 3})) 13 | assert.Equal(t, []any{float32(1.1), float32(2.2), float32(3.3)}, ToIfaceSlice([]float32{1.1, 2.2, 3.3})) 14 | assert.Equal(t, []any{"one", 2, 3.3}, ToIfaceSlice([]any{"one", 2, 3.3})) 15 | } 16 | 17 | func Test_ToIntfSlice_Deprecated(t *testing.T) { 18 | assert.Equal(t, []any{1, 2, 3}, ToIntfSlice([]int{1, 2, 3})) 19 | assert.Equal(t, []any{"one", 2, 3.3}, ToIntfSlice([]any{"one", 2, 3.3})) 20 | } 21 | 22 | func Test_ToStringSlice(t *testing.T) { 23 | type StrType string 24 | assert.Equal(t, []string{}, ToStringSlice[string]([]StrType{})) 25 | assert.Equal(t, []string{"one", "two"}, ToStringSlice[string]([]StrType{"one", "two"})) 26 | } 27 | 28 | func Test_ToNumberSlice(t *testing.T) { 29 | type NumType int 30 | assert.Equal(t, []int16{}, ToNumberSlice[int16]([]NumType{})) 31 | assert.Equal(t, []int16{1, -1, 0, -32768, 32767}, ToNumberSlice[int16]([]NumType{1, 65535, 65536, 32768, 32767})) 32 | assert.Equal(t, []int64{-1, 65535}, ToNumberSlice[int64]([]NumType{-1, 65535})) 33 | } 34 | 35 | func Test_ToSlice(t *testing.T) { 36 | assert.Equal(t, []int{}, ToSlice[int]()) 37 | assert.Equal(t, []int{1}, ToSlice[int](1)) 38 | assert.Equal(t, []string{"1", "2", "3"}, ToSlice("1", "2", "3")) 39 | } 40 | 41 | func Test_ToPtrSlice(t *testing.T) { 42 | assert.Equal(t, []*int{}, ToPtrSlice([]int(nil))) 43 | s1 := []int{1, 2, 3} 44 | assert.Equal(t, []*int{&s1[0], &s1[1], &s1[2]}, ToPtrSlice(s1)) 45 | } 46 | -------------------------------------------------------------------------------- /slice_filter.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | import ( 4 | "strings" 5 | "unsafe" 6 | ) 7 | 8 | // Filter filters slice elements with condition. 9 | func Filter[T any, S ~[]T](s S, filterFunc func(t T) bool) S { 10 | result := make(S, 0, len(s)) 11 | for i := range s { 12 | if filterFunc(s[i]) { 13 | result = append(result, s[i]) 14 | } 15 | } 16 | return result 17 | } 18 | 19 | // FilterPtr filters slice elements using pointer in callback. 20 | // This function is faster than Filter() when used on slices of structs. 21 | func FilterPtr[T any, S ~[]T](s S, filterFunc func(t *T) bool) S { 22 | result := make(S, 0, len(s)) 23 | for i := range s { 24 | if filterFunc(&s[i]) { 25 | result = append(result, s[i]) 26 | } 27 | } 28 | return result 29 | } 30 | 31 | // FilterLT returns all values which are less than the specified value 32 | func FilterLT[T NumberExt | StringExt, S ~[]T](s S, v T) S { 33 | return Filter(s, func(t T) bool { return t < v }) 34 | } 35 | 36 | // FilterLTE returns all values which are less than or equal to the specified value 37 | func FilterLTE[T NumberExt | StringExt, S ~[]T](s S, v T) S { 38 | return Filter(s, func(t T) bool { return t <= v }) 39 | } 40 | 41 | // FilterGT returns all values which are greater than the specified value 42 | func FilterGT[T NumberExt | StringExt, S ~[]T](s S, v T) S { 43 | return Filter(s, func(t T) bool { return t > v }) 44 | } 45 | 46 | // FilterGTE returns all values which are greater than or equal to the specified value 47 | func FilterGTE[T NumberExt | StringExt, S ~[]T](s S, v T) S { 48 | return Filter(s, func(t T) bool { return t >= v }) 49 | } 50 | 51 | // FilterNE returns all values which are not equal to the specified value 52 | func FilterNE[T comparable, S ~[]T](s S, v T) S { 53 | return Filter(s, func(t T) bool { return t != v }) 54 | } 55 | 56 | // FilterIN returns all values which are present in the specified list 57 | func FilterIN[T comparable, S ~[]T](s S, v ...T) S { 58 | m := make(map[T]struct{}, len(v)) 59 | for _, t := range v { 60 | m[t] = struct{}{} 61 | } 62 | return Filter(s, func(t T) bool { 63 | _, ok := m[t] 64 | return ok 65 | }) 66 | } 67 | 68 | // FilterNIN returns all values which are not present in the specified list 69 | func FilterNIN[T comparable, S ~[]T](s S, v ...T) S { 70 | m := make(map[T]struct{}, len(v)) 71 | for _, t := range v { 72 | m[t] = struct{}{} 73 | } 74 | return Filter(s, func(t T) bool { 75 | _, ok := m[t] 76 | return !ok 77 | }) 78 | } 79 | 80 | // FilterLIKE returns all strings which contain the specified substring. 81 | // Don't use wildcard in the input string. 82 | // For example: FilterLIKE(names, "tom"). 83 | func FilterLIKE[T StringExt, S ~[]T](s S, v string) S { 84 | if len(v) == 0 { 85 | return S{} 86 | } 87 | return Filter(s, func(t T) bool { 88 | return strings.Contains(*(*string)(unsafe.Pointer(&t)), v) 89 | }) 90 | } 91 | 92 | // FilterILIKE returns all strings which contain the specified substring with case-insensitive 93 | func FilterILIKE[T StringExt, S ~[]T](s S, v string) S { 94 | if len(v) == 0 { 95 | return S{} 96 | } 97 | v = strings.ToLower(v) 98 | return Filter(s, func(t T) bool { 99 | return strings.Contains(strings.ToLower(*(*string)(unsafe.Pointer(&t))), v) 100 | }) 101 | } 102 | -------------------------------------------------------------------------------- /slice_filter_test.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func Test_Filter(t *testing.T) { 10 | // Nil/Empty slices 11 | assert.Equal(t, []int{}, Filter[int]([]int(nil), func(v int) bool { return true })) 12 | assert.Equal(t, []float32{}, Filter([]float32{}, func(v float32) bool { return true })) 13 | 14 | assert.Equal(t, []int{-4, 0}, 15 | Filter([]int{2, -4, 6, 0}, func(v int) bool { return v <= 0 })) 16 | assert.Equal(t, []float32{2, 6, 0.0001}, 17 | Filter([]float32{2, -4, 6, 0.0001}, func(v float32) bool { return v > 0 })) 18 | assert.Equal(t, []string{"one", "two"}, 19 | Filter([]string{"one", "", "two"}, func(v string) bool { return len(v) > 0 })) 20 | } 21 | 22 | func Test_FilterPtr(t *testing.T) { 23 | // Nil/Empty slices 24 | assert.Equal(t, []int{}, FilterPtr[int]([]int(nil), func(v *int) bool { return true })) 25 | assert.Equal(t, []float32{}, FilterPtr([]float32{}, func(v *float32) bool { return true })) 26 | 27 | assert.Equal(t, []int{-4, 0}, 28 | FilterPtr([]int{2, -4, 6, 0}, func(v *int) bool { return *v <= 0 })) 29 | assert.Equal(t, []float32{2, 6, 0.0001}, 30 | FilterPtr([]float32{2, -4, 6, 0.0001}, func(v *float32) bool { return *v > 0 })) 31 | assert.Equal(t, []string{"one", "two"}, 32 | FilterPtr([]string{"one", "", "two"}, func(v *string) bool { return len(*v) > 0 })) 33 | } 34 | 35 | func Test_FilterLT(t *testing.T) { 36 | // Nil/Empty slices 37 | assert.Equal(t, []int{}, FilterLT[int]([]int(nil), 0)) 38 | assert.Equal(t, []int{}, FilterLT([]int{}, 0)) 39 | 40 | assert.Equal(t, []int{-10, -3}, FilterLT([]int{0, -10, 1, -3, 0, 7}, 0)) 41 | assert.Equal(t, []string{"Ab"}, FilterLT([]string{"Ab", "cd", "a"}, "a")) 42 | } 43 | 44 | func Test_FilterLTE(t *testing.T) { 45 | // Nil/Empty slices 46 | assert.Equal(t, []int{}, FilterLTE[int]([]int(nil), 0)) 47 | assert.Equal(t, []int{}, FilterLTE([]int{}, 0)) 48 | 49 | assert.Equal(t, []int{0, -10, -3, 0}, FilterLTE([]int{0, -10, 1, -3, 0, 7}, 0)) 50 | assert.Equal(t, []string{"Ab", "a"}, FilterLTE([]string{"Ab", "cd", "a"}, "a")) 51 | } 52 | 53 | func Test_FilterGT(t *testing.T) { 54 | // Nil/Empty slices 55 | assert.Equal(t, []int{}, FilterGT[int]([]int(nil), 0)) 56 | assert.Equal(t, []int{}, FilterGT([]int{}, 0)) 57 | 58 | assert.Equal(t, []int{1, 7}, FilterGT([]int{0, -10, 1, -3, 0, 7}, 0)) 59 | assert.Equal(t, []string{"cd"}, FilterGT([]string{"Ab", "cd", "a"}, "a")) 60 | } 61 | 62 | func Test_FilterGTE(t *testing.T) { 63 | // Nil/Empty slices 64 | assert.Equal(t, []int{}, FilterGTE[int]([]int(nil), 0)) 65 | assert.Equal(t, []int{}, FilterGTE([]int{}, 0)) 66 | 67 | assert.Equal(t, []int{0, 1, 0, 7}, FilterGTE([]int{0, -10, 1, -3, 0, 7}, 0)) 68 | assert.Equal(t, []string{"cd", "a"}, FilterGTE([]string{"Ab", "cd", "a"}, "a")) 69 | } 70 | 71 | func Test_FilterNE(t *testing.T) { 72 | // Nil/Empty slices 73 | assert.Equal(t, []int{}, FilterNE[int]([]int(nil), 0)) 74 | assert.Equal(t, []int{}, FilterNE([]int{}, 0)) 75 | 76 | assert.Equal(t, []int{-10, 1, -3, 7}, FilterNE([]int{0, -10, 1, -3, 0, 7}, 0)) 77 | assert.Equal(t, []string{"Ab", "cd"}, FilterNE([]string{"Ab", "cd", "a"}, "a")) 78 | } 79 | 80 | func Test_FilterIN(t *testing.T) { 81 | // Nil/Empty slices 82 | assert.Equal(t, []int{}, FilterIN[int]([]int(nil), 0)) 83 | assert.Equal(t, []int{}, FilterIN([]int{}, 0)) 84 | 85 | assert.Equal(t, []int{1, 7}, FilterIN([]int{0, -10, 1, -3, 0, 7}, 1, 3, 7)) 86 | assert.Equal(t, []string{"a"}, FilterIN([]string{"Ab", "cd", "a"}, "a", "b")) 87 | } 88 | 89 | func Test_FilterNIN(t *testing.T) { 90 | // Nil/Empty slices 91 | assert.Equal(t, []int{}, FilterNIN[int]([]int(nil), 0)) 92 | assert.Equal(t, []int{}, FilterNIN([]int{}, 0)) 93 | 94 | assert.Equal(t, []int{-10, 1, -3}, FilterNIN([]int{0, -10, 1, -3, 0, 7}, 3, 7, 0)) 95 | assert.Equal(t, []string{"Ab"}, FilterNIN([]string{"Ab", "cd", "a"}, "a", "b", "cd")) 96 | } 97 | 98 | func Test_FilterLIKE(t *testing.T) { 99 | // Nil/Empty slices 100 | assert.Equal(t, []string{}, FilterLIKE[string]([]string(nil), "")) 101 | assert.Equal(t, []string{}, FilterLIKE([]string{}, "")) 102 | 103 | assert.Equal(t, []string{}, 104 | FilterLIKE([]string{"*Abc*", "*abc*", "abc*", "*abc"}, "ac")) 105 | assert.Equal(t, []string{"*abc*", "abc*", "*abc"}, 106 | FilterLIKE([]string{"*Abc*", "*abc*", "abc*", "*abc"}, "abc")) 107 | assert.Equal(t, []string{"*Abc*"}, 108 | FilterLIKE([]string{"*Abc*", "*abc*", "abc*", "*abc"}, "Abc")) 109 | 110 | type X string 111 | assert.Equal(t, []X{}, 112 | FilterLIKE([]X{"*Abc*", "*abc*", "abc*", "*abc"}, "ac")) 113 | assert.Equal(t, []X{"*abc*", "abc*", "*abc"}, 114 | FilterLIKE([]X{"*Abc*", "*abc*", "abc*", "*abc"}, "abc")) 115 | assert.Equal(t, []X{"*Abc*"}, 116 | FilterLIKE([]X{"*Abc*", "*abc*", "abc*", "*abc"}, "Abc")) 117 | } 118 | 119 | func Test_FilterILIKE(t *testing.T) { 120 | // Nil/Empty slices 121 | assert.Equal(t, []string{}, FilterILIKE[string]([]string(nil), "")) 122 | assert.Equal(t, []string{}, FilterILIKE([]string{}, "")) 123 | 124 | assert.Equal(t, []string{}, 125 | FilterILIKE([]string{"*Abc*", "*abc*", "abc*", "*abc"}, "aC")) 126 | assert.Equal(t, []string{"*Abc*", "*abc*", "abc*", "*abc"}, 127 | FilterILIKE([]string{"*Abc*", "*abc*", "abc*", "*abc"}, "abc")) 128 | assert.Equal(t, []string{"*Abc*", "*abc*", "abc*", "*abc"}, 129 | FilterILIKE([]string{"*Abc*", "*abc*", "abc*", "*abc"}, "AbC")) 130 | 131 | type X string 132 | assert.Equal(t, []X{}, 133 | FilterILIKE([]X{"*Abc*", "*abc*", "abc*", "*abc"}, "aC")) 134 | assert.Equal(t, []X{"*Abc*", "*abc*", "abc*", "*abc"}, 135 | FilterILIKE([]X{"*Abc*", "*abc*", "abc*", "*abc"}, "abc")) 136 | assert.Equal(t, []X{"*Abc*", "*abc*", "abc*", "*abc"}, 137 | FilterILIKE([]X{"*Abc*", "*abc*", "abc*", "*abc"}, "AbC")) 138 | } 139 | -------------------------------------------------------------------------------- /slice_iter.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | // ForEach iterates over slice items. 4 | // For more advanced requirements, see Iter. 5 | func ForEach[T any, S ~[]T](s S, pred func(i int, t T)) { 6 | for i := range s { 7 | pred(i, s[i]) 8 | } 9 | } 10 | 11 | // ForEachPtr iterates over pointers to slice items. 12 | // You can use this to achieve more performance if you have a slice of big structs. 13 | // For more advanced requirements, see IterPtr. 14 | func ForEachPtr[T any, S ~[]T](s S, pred func(i int, t *T)) { 15 | for i := range s { 16 | pred(i, &s[i]) 17 | } 18 | } 19 | 20 | // ForEachReverse iterates over slice items from the end. 21 | // For more advanced requirements, see IterReverse. 22 | func ForEachReverse[T any, S ~[]T](s S, pred func(i int, t T)) { 23 | for i := len(s) - 1; i >= 0; i-- { 24 | pred(i, s[i]) 25 | } 26 | } 27 | 28 | // ForEachPtrReverse iterates over pointers to slice items from the end. 29 | // For more advanced requirements, see IterPtrReverse. 30 | func ForEachPtrReverse[T any, S ~[]T](s S, pred func(i int, t *T)) { 31 | for i := len(s) - 1; i >= 0; i-- { 32 | pred(i, &s[i]) 33 | } 34 | } 35 | 36 | // Iter iterates over items from multiple slices with ability to stop. 37 | // When the `iterFunc` function returns false, the iteration stops. 38 | func Iter[T any, S ~[]T](iterFunc func(index int, v T) bool, slices ...S) { 39 | global := 0 40 | for _, s := range slices { 41 | for _, v := range s { 42 | if !iterFunc(global, v) { 43 | return 44 | } 45 | global++ 46 | } 47 | } 48 | } 49 | 50 | // IterPtr iterates over pointers to items from multiple slices with ability to stop. 51 | // When the `iterFunc` function returns false, the iterating stops. 52 | func IterPtr[T any, S ~[]T](iterFunc func(index int, v *T) bool, slices ...S) { 53 | global := 0 54 | for _, s := range slices { 55 | for j := range s { 56 | if !iterFunc(global, &s[j]) { 57 | return 58 | } 59 | global++ 60 | } 61 | } 62 | } 63 | 64 | // IterReverse iterates over items from multiple slices from the end with ability to stop. 65 | // When the `iterFunc` function returns false, the iteration stops. 66 | func IterReverse[T any, S ~[]T](iterFunc func(index int, v T) bool, slices ...S) { 67 | global := -1 68 | for _, s := range slices { 69 | global += len(s) 70 | } 71 | for i := len(slices) - 1; i >= 0; i-- { 72 | s := slices[i] 73 | for j := len(s) - 1; j >= 0; j-- { 74 | if !iterFunc(global, s[j]) { 75 | return 76 | } 77 | global-- 78 | } 79 | } 80 | } 81 | 82 | // IterPtrReverse iterates over pointers to items from multiple slices with ability to stop. 83 | // When the `iterFunc` function returns false, the iteration stops. 84 | func IterPtrReverse[T any, S ~[]T](iterFunc func(index int, v *T) bool, slices ...S) { 85 | global := -1 86 | for _, s := range slices { 87 | global += len(s) 88 | } 89 | for i := len(slices) - 1; i >= 0; i-- { 90 | s := slices[i] 91 | for j := len(s) - 1; j >= 0; j-- { 92 | if !iterFunc(global, &s[j]) { 93 | return 94 | } 95 | global-- 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /slice_iter_test.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func Test_ForEach(t *testing.T) { 10 | copy1 := []int{} 11 | ForEach[int]([]int(nil), func(i int, t int) { copy1 = append(copy1, t) }) 12 | assert.Equal(t, []int{}, copy1) 13 | 14 | copy2 := []int64{} 15 | ForEach([]int64{1, 2, 3}, func(i int, t int64) { copy2 = append(copy2, t) }) 16 | assert.Equal(t, []int64{1, 2, 3}, copy2) 17 | } 18 | 19 | func Test_ForEachPtr(t *testing.T) { 20 | copy1 := []int{} 21 | ForEachPtr[int]([]int(nil), func(i int, t *int) { copy1 = append(copy1, *t) }) 22 | assert.Equal(t, []int{}, copy1) 23 | 24 | copy2 := []int64{} 25 | ForEachPtr([]int64{1, 2, 3}, func(i int, t *int64) { copy2 = append(copy2, *t) }) 26 | assert.Equal(t, []int64{1, 2, 3}, copy2) 27 | } 28 | 29 | func Test_ForEachReverse(t *testing.T) { 30 | copy1 := []int{} 31 | ForEachReverse[int]([]int(nil), func(i int, t int) { copy1 = append(copy1, t) }) 32 | assert.Equal(t, []int{}, copy1) 33 | 34 | copy2 := []int64{} 35 | ForEachReverse([]int64{1, 2, 3}, func(i int, t int64) { copy2 = append(copy2, t) }) 36 | assert.Equal(t, []int64{3, 2, 1}, copy2) 37 | } 38 | 39 | func Test_ForEachPtrReverse(t *testing.T) { 40 | copy1 := []int{} 41 | ForEachPtrReverse[int]([]int(nil), func(i int, t *int) { copy1 = append(copy1, *t) }) 42 | assert.Equal(t, []int{}, copy1) 43 | 44 | copy2 := []int64{} 45 | ForEachPtrReverse([]int64{1, 2, 3}, func(i int, t *int64) { copy2 = append(copy2, *t) }) 46 | assert.Equal(t, []int64{3, 2, 1}, copy2) 47 | } 48 | 49 | func Test_Iter(t *testing.T) { 50 | // No slice input 51 | copy1 := []int{} 52 | Iter[int, []int](func(i int, t int) bool { copy1 = append(copy1, t); return true }) 53 | assert.Equal(t, []int{}, copy1) 54 | 55 | // Empty slice input 56 | copy2 := []int{} 57 | Iter(func(i int, t int) bool { copy2 = append(copy2, t); return true }, []int{}) 58 | assert.Equal(t, []int{}, copy2) 59 | 60 | // Single slice 61 | copy3 := []int{} 62 | index3 := []int{} 63 | Iter(func(i int, t int) bool { copy3 = append(copy3, t); index3 = append(index3, i); return true }, 64 | []int{1, 2, 3}) 65 | assert.Equal(t, []int{1, 2, 3}, copy3) 66 | assert.Equal(t, []int{0, 1, 2}, index3) 67 | 68 | // Multiple slices 69 | copy4 := []int32{} 70 | index4 := []int{} 71 | Iter(func(i int, t int32) bool { copy4 = append(copy4, t); index4 = append(index4, i); return true }, 72 | []int32{1, 2, 3}, []int32{}, []int32{4, 5}) 73 | assert.Equal(t, []int32{1, 2, 3, 4, 5}, copy4) 74 | assert.Equal(t, []int{0, 1, 2, 3, 4}, index4) 75 | 76 | // With stop 77 | copy5 := []string{} 78 | index5 := []int{} 79 | Iter(func(i int, t string) bool { copy5 = append(copy5, t); index5 = append(index5, i); return i < 3 }, 80 | []string{"1", "2", "3"}, []string{}, []string{"4", "5"}) 81 | assert.Equal(t, []string{"1", "2", "3", "4"}, copy5) 82 | assert.Equal(t, []int{0, 1, 2, 3}, index5) 83 | } 84 | 85 | func Test_IterPtr(t *testing.T) { 86 | // No slice input 87 | copy1 := []int{} 88 | IterPtr[int, []int](func(i int, t *int) bool { copy1 = append(copy1, *t); return true }) 89 | assert.Equal(t, []int{}, copy1) 90 | 91 | // Empty slice input 92 | copy2 := []int{} 93 | IterPtr(func(i int, t *int) bool { copy2 = append(copy2, *t); return true }, []int{}) 94 | assert.Equal(t, []int{}, copy2) 95 | 96 | // Single slice 97 | copy3 := []int{} 98 | index3 := []int{} 99 | IterPtr(func(i int, t *int) bool { copy3 = append(copy3, *t); index3 = append(index3, i); return true }, 100 | []int{1, 2, 3}) 101 | assert.Equal(t, []int{1, 2, 3}, copy3) 102 | assert.Equal(t, []int{0, 1, 2}, index3) 103 | 104 | // Multiple slices 105 | copy4 := []int32{} 106 | index4 := []int{} 107 | IterPtr(func(i int, t *int32) bool { copy4 = append(copy4, *t); index4 = append(index4, i); return true }, 108 | []int32{1, 2, 3}, []int32{}, []int32{4, 5}) 109 | assert.Equal(t, []int32{1, 2, 3, 4, 5}, copy4) 110 | assert.Equal(t, []int{0, 1, 2, 3, 4}, index4) 111 | 112 | // With stop 113 | copy5 := []string{} 114 | index5 := []int{} 115 | IterPtr(func(i int, t *string) bool { copy5 = append(copy5, *t); index5 = append(index5, i); return i < 3 }, 116 | []string{"1", "2", "3"}, []string{}, []string{"4", "5"}) 117 | assert.Equal(t, []string{"1", "2", "3", "4"}, copy5) 118 | assert.Equal(t, []int{0, 1, 2, 3}, index5) 119 | } 120 | 121 | func Test_IterReverse(t *testing.T) { 122 | // No slice input 123 | copy1 := []int{} 124 | IterReverse[int, []int](func(i int, t int) bool { copy1 = append(copy1, t); return true }) 125 | assert.Equal(t, []int{}, copy1) 126 | 127 | // Empty slice input 128 | copy2 := []int{} 129 | IterReverse(func(i int, t int) bool { copy2 = append(copy2, t); return true }, []int{}) 130 | assert.Equal(t, []int{}, copy2) 131 | 132 | // Single slice 133 | copy3 := []int{} 134 | index3 := []int{} 135 | IterReverse(func(i int, t int) bool { copy3 = append(copy3, t); index3 = append(index3, i); return true }, 136 | []int{1, 2, 3}) 137 | assert.Equal(t, []int{3, 2, 1}, copy3) 138 | assert.Equal(t, []int{2, 1, 0}, index3) 139 | 140 | // Multiple slices 141 | copy4 := []int32{} 142 | index4 := []int{} 143 | IterReverse(func(i int, t int32) bool { copy4 = append(copy4, t); index4 = append(index4, i); return true }, 144 | []int32{1, 2, 3}, []int32{}, []int32{4, 5}) 145 | assert.Equal(t, []int32{5, 4, 3, 2, 1}, copy4) 146 | assert.Equal(t, []int{4, 3, 2, 1, 0}, index4) 147 | 148 | // With stop 149 | copy5 := []string{} 150 | index5 := []int{} 151 | IterReverse(func(i int, t string) bool { copy5 = append(copy5, t); index5 = append(index5, i); return i > 3 }, 152 | []string{"1", "2", "3"}, []string{}, []string{"4", "5"}) 153 | assert.Equal(t, []string{"5", "4"}, copy5) 154 | assert.Equal(t, []int{4, 3}, index5) 155 | } 156 | 157 | func Test_IterPtrReverse(t *testing.T) { 158 | // No slice input 159 | copy1 := []int{} 160 | IterPtrReverse[int, []int](func(i int, t *int) bool { copy1 = append(copy1, *t); return true }) 161 | assert.Equal(t, []int{}, copy1) 162 | 163 | // Empty slice input 164 | copy2 := []int{} 165 | IterPtrReverse(func(i int, t *int) bool { copy2 = append(copy2, *t); return true }, []int{}) 166 | assert.Equal(t, []int{}, copy2) 167 | 168 | // Single slice 169 | copy3 := []int{} 170 | index3 := []int{} 171 | IterPtrReverse(func(i int, t *int) bool { copy3 = append(copy3, *t); index3 = append(index3, i); return true }, 172 | []int{1, 2, 3}) 173 | assert.Equal(t, []int{3, 2, 1}, copy3) 174 | assert.Equal(t, []int{2, 1, 0}, index3) 175 | 176 | // Multiple slices 177 | copy4 := []int32{} 178 | index4 := []int{} 179 | IterPtrReverse(func(i int, t *int32) bool { copy4 = append(copy4, *t); index4 = append(index4, i); return true }, 180 | []int32{1, 2, 3}, []int32{}, []int32{4, 5}) 181 | assert.Equal(t, []int32{5, 4, 3, 2, 1}, copy4) 182 | assert.Equal(t, []int{4, 3, 2, 1, 0}, index4) 183 | 184 | // With stop 185 | copy5 := []string{} 186 | index5 := []int{} 187 | IterPtrReverse(func(i int, t *string) bool { copy5 = append(copy5, *t); index5 = append(index5, i); return i > 3 }, 188 | []string{"1", "2", "3"}, []string{}, []string{"4", "5"}) 189 | assert.Equal(t, []string{"5", "4"}, copy5) 190 | assert.Equal(t, []int{4, 3}, index5) 191 | } 192 | -------------------------------------------------------------------------------- /slice_sort.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | import "sort" 4 | 5 | // Sort sorts slice values 6 | func Sort[T NumberExt | StringExt, S ~[]T](s S) S { 7 | sort.Slice(s, func(i, j int) bool { return s[i] < s[j] }) 8 | return s 9 | } 10 | 11 | // SortDesc sorts slice values in descending order 12 | func SortDesc[T NumberExt | StringExt, S ~[]T](s S) S { 13 | sort.Slice(s, func(i, j int) bool { return s[i] > s[j] }) 14 | return s 15 | } 16 | 17 | // SortEx sorts slice values 18 | func SortEx[T any, S ~[]T](s S, less func(i, j int) bool) S { 19 | sort.Slice(s, less) 20 | return s 21 | } 22 | 23 | // SortStable sorts slice values 24 | func SortStable[T NumberExt | StringExt, S ~[]T](s S) S { 25 | sort.SliceStable(s, func(i, j int) bool { return s[i] < s[j] }) 26 | return s 27 | } 28 | 29 | // SortStableDesc sorts slice values in descending order 30 | func SortStableDesc[T NumberExt | StringExt, S ~[]T](s S) S { 31 | sort.SliceStable(s, func(i, j int) bool { return s[i] > s[j] }) 32 | return s 33 | } 34 | 35 | // SortStableEx sorts slice values 36 | func SortStableEx[T any, S ~[]T](s S, less func(i, j int) bool) S { 37 | sort.SliceStable(s, less) 38 | return s 39 | } 40 | 41 | // IsSorted checks if a slice is sorted 42 | func IsSorted[T NumberExt | StringExt, S ~[]T](s S) bool { 43 | return sort.SliceIsSorted(s, func(i, j int) bool { return s[i] < s[j] }) 44 | } 45 | 46 | // IsSortedDesc checks if a slice is sorted in descending order 47 | func IsSortedDesc[T NumberExt | StringExt, S ~[]T](s S) bool { 48 | return sort.SliceIsSorted(s, func(i, j int) bool { return s[i] > s[j] }) 49 | } 50 | -------------------------------------------------------------------------------- /slice_sort_test.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func Test_Sort(t *testing.T) { 10 | assert.Equal(t, []int{}, Sort([]int{})) 11 | assert.Equal(t, []uint32{1}, Sort([]uint32{1})) 12 | assert.Equal(t, []int8{-10, 0, 10, 11, 120}, Sort([]int8{0, -10, 120, 11, 10})) 13 | assert.Equal(t, []float64{-10.3, 0, 10.55, 11.1, 120.120}, Sort([]float64{0, -10.3, 120.120, 11.1, 10.55})) 14 | } 15 | 16 | func Test_SortDesc(t *testing.T) { 17 | assert.Equal(t, []int{}, SortDesc([]int{})) 18 | assert.Equal(t, []uint32{1}, SortDesc([]uint32{1})) 19 | assert.Equal(t, []int8{120, 11, 10, 0, -10}, SortDesc([]int8{0, -10, 120, 11, 10})) 20 | assert.Equal(t, []float64{120.120, 11.1, 10.55, 0, -10.3}, SortDesc([]float64{0, -10.3, 120.120, 11.1, 10.55})) 21 | } 22 | 23 | func Test_SortEx(t *testing.T) { 24 | s := []int{0, -3, 1, 2, -2} 25 | assert.Equal(t, []int{-3, -2, 0, 1, 2}, SortEx(s, func(i, j int) bool { return s[i] < s[j] })) 26 | } 27 | 28 | func Test_SortStable(t *testing.T) { 29 | assert.Equal(t, []int{}, SortStable([]int{})) 30 | assert.Equal(t, []uint32{1}, SortStable([]uint32{1})) 31 | assert.Equal(t, []int8{-10, 0, 10, 11, 120}, SortStable([]int8{0, -10, 120, 11, 10})) 32 | assert.Equal(t, []float64{-10.3, 0, 10.55, 11.1, 120.120}, SortStable([]float64{0, -10.3, 120.120, 11.1, 10.55})) 33 | } 34 | 35 | func Test_SortStableDesc(t *testing.T) { 36 | assert.Equal(t, []int{}, SortStableDesc([]int{})) 37 | assert.Equal(t, []uint32{1}, SortStableDesc([]uint32{1})) 38 | assert.Equal(t, []int8{120, 11, 10, 0, -10}, SortStableDesc([]int8{0, -10, 120, 11, 10})) 39 | assert.Equal(t, []float64{120.120, 11.1, 10.55, 0, -10.3}, SortStableDesc([]float64{0, -10.3, 120.120, 11.1, 10.55})) 40 | } 41 | 42 | func Test_SortStableEx(t *testing.T) { 43 | s := []int{0, -3, 1, 2, -2} 44 | assert.Equal(t, []int{-3, -2, 0, 1, 2}, SortStableEx(s, func(i, j int) bool { return s[i] < s[j] })) 45 | } 46 | 47 | func Test_IsSorted(t *testing.T) { 48 | assert.True(t, IsSorted([]int{})) 49 | assert.True(t, IsSorted([]uint32{1})) 50 | assert.True(t, IsSorted([]int8{-10, 0, 10, 11, 120})) 51 | assert.True(t, IsSorted([]float64{-10.3, 0, 10.55, 11.1, 120.120})) 52 | } 53 | 54 | func Test_IsSortedDesc(t *testing.T) { 55 | assert.True(t, IsSortedDesc([]int{})) 56 | assert.True(t, IsSortedDesc([]uint32{1})) 57 | assert.True(t, IsSortedDesc([]int8{120, 11, 10, 0, -10})) 58 | assert.True(t, IsSortedDesc([]float64{120.120, 11.1, 10.55, 0, -10.3})) 59 | } 60 | -------------------------------------------------------------------------------- /slice_subset.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | // ContainSlice tests if a slice contains a slice 4 | func ContainSlice[T comparable, S ~[]T](a, b S) bool { 5 | return IndexOfSlice(a, b) >= 0 6 | } 7 | 8 | // IndexOfSlice gets index of sub-slice in slice. 9 | // Returns -1 if not found. 10 | func IndexOfSlice[T comparable, S ~[]T](a, sub S) int { 11 | lengthA := len(a) 12 | lengthSub := len(sub) 13 | if lengthSub == 0 || lengthA < lengthSub { 14 | return -1 15 | } 16 | sub1st := sub[0] 17 | for i, max := 0, lengthA-lengthSub; i <= max; i++ { 18 | if a[i] == sub1st { 19 | found := true 20 | for j := 1; j < lengthSub; j++ { 21 | if a[i+j] != sub[j] { 22 | found = false 23 | break 24 | } 25 | } 26 | if found { 27 | return i 28 | } 29 | } 30 | } 31 | return -1 32 | } 33 | 34 | // LastIndexOfSlice gets last index of sub-slice in slice 35 | // Returns -1 if not found 36 | func LastIndexOfSlice[T comparable, S ~[]T](a, sub S) int { 37 | lengthA := len(a) 38 | lengthSub := len(sub) 39 | if lengthSub == 0 || lengthA < lengthSub { 40 | return -1 41 | } 42 | sub1st := sub[0] 43 | for i := lengthA - lengthSub; i >= 0; i-- { 44 | if a[i] == sub1st { 45 | found := true 46 | for j := 1; j < lengthSub; j++ { 47 | if a[i+j] != sub[j] { 48 | found = false 49 | break 50 | } 51 | } 52 | if found { 53 | return i 54 | } 55 | } 56 | } 57 | return -1 58 | } 59 | 60 | // SubSlice gets sub slice from a slice. 61 | // Passing negative numbers to get items from the end of the slice. 62 | // For example, using start=-1, end=-2 to get the last item of the slice 63 | // end param is exclusive. 64 | func SubSlice[T any, S ~[]T](s S, start, end int) S { 65 | length := len(s) 66 | if length == 0 { 67 | return S{} 68 | } 69 | 70 | for start < 0 { 71 | start += length 72 | } 73 | if start > length { 74 | start = length 75 | } 76 | for end < 0 { 77 | end += length 78 | } 79 | if end > length { 80 | end = length 81 | } 82 | 83 | if start > end { 84 | // NOTE: end is exclusive 85 | return s[end+1 : start+1] 86 | } 87 | return s[start:end] 88 | } 89 | -------------------------------------------------------------------------------- /slice_subset_test.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func Test_ContainSlice(t *testing.T) { 10 | assert.False(t, ContainSlice([]int{}, nil)) 11 | assert.False(t, ContainSlice([]string{"one"}, []string{})) 12 | assert.False(t, ContainSlice([]string{"one", "two"}, []string{"Two"})) 13 | assert.False(t, ContainSlice([]int64{1, 2, 3}, []int64{1, 2, 3, 4})) 14 | assert.False(t, ContainSlice([]uint{0, 1, 2, 3, 4, 5}, []uint{3, 4, 5, 6})) 15 | assert.False(t, ContainSlice([]float32{1.1, 2.2, 3.3}, []float32{2.2, 3.31})) 16 | 17 | assert.True(t, ContainSlice([]int{1}, []int{1})) 18 | assert.True(t, ContainSlice([]int{0, 1, 2}, []int{2})) 19 | assert.True(t, ContainSlice([]int{0, 1, 2, 0, 1, 2, 3}, []int{0, 1, 2})) 20 | assert.True(t, ContainSlice([]string{"one", ""}, []string{""})) 21 | assert.True(t, ContainSlice([]string{"one", "two", "three"}, []string{"one", "two"})) 22 | assert.True(t, ContainSlice([]int64{1, 2, 3}, []int64{1, 2, 3})) 23 | assert.True(t, ContainSlice([]uint{0, 1, 1, 1, 1}, []uint{1})) 24 | } 25 | 26 | func Test_IndexOfSlice(t *testing.T) { 27 | assert.Equal(t, -1, IndexOfSlice([]int{}, nil)) 28 | assert.Equal(t, -1, IndexOfSlice([]string{"one"}, []string{})) 29 | assert.Equal(t, -1, IndexOfSlice([]string{"one", "two"}, []string{"Two"})) 30 | assert.Equal(t, -1, IndexOfSlice([]int64{1, 2, 3}, []int64{1, 2, 3, 4})) 31 | assert.Equal(t, -1, IndexOfSlice([]uint{0, 1, 2, 3, 4, 5}, []uint{3, 4, 5, 6})) 32 | assert.Equal(t, -1, IndexOfSlice([]float32{1.1, 2.2, 3.3}, []float32{2.2, 3.31})) 33 | 34 | assert.Equal(t, 0, IndexOfSlice([]int{1}, []int{1})) 35 | assert.Equal(t, 2, IndexOfSlice([]int{0, 1, 2}, []int{2})) 36 | assert.Equal(t, 0, IndexOfSlice([]int{0, 1, 2, 0, 1, 2, 3}, []int{0, 1, 2})) 37 | assert.Equal(t, 1, IndexOfSlice([]string{"one", ""}, []string{""})) 38 | assert.Equal(t, 0, IndexOfSlice([]string{"one", "two", "three"}, []string{"one", "two"})) 39 | assert.Equal(t, 0, IndexOfSlice([]int64{1, 2, 3}, []int64{1, 2, 3})) 40 | assert.Equal(t, 1, IndexOfSlice([]uint{0, 1, 1, 1, 1}, []uint{1})) 41 | } 42 | 43 | func Test_LastIndexOfSlice(t *testing.T) { 44 | assert.Equal(t, -1, LastIndexOfSlice([]int{}, nil)) 45 | assert.Equal(t, -1, LastIndexOfSlice([]string{"one"}, []string{})) 46 | assert.Equal(t, -1, LastIndexOfSlice([]string{"one", "two"}, []string{"Two"})) 47 | assert.Equal(t, -1, LastIndexOfSlice([]int64{1, 2, 3}, []int64{1, 2, 3, 4})) 48 | assert.Equal(t, -1, LastIndexOfSlice([]uint{0, 1, 2, 3, 4, 5}, []uint{3, 4, 5, 6})) 49 | assert.Equal(t, -1, LastIndexOfSlice([]float32{1.1, 2.2, 3.3}, []float32{2.2, 3.31})) 50 | 51 | assert.Equal(t, 0, LastIndexOfSlice([]int{1}, []int{1})) 52 | assert.Equal(t, 2, LastIndexOfSlice([]int{0, 1, 2}, []int{2})) 53 | assert.Equal(t, 3, LastIndexOfSlice([]int{0, 1, 2, 0, 1, 2, 3}, []int{0, 1, 2})) 54 | assert.Equal(t, 2, LastIndexOfSlice([]string{"", "one", ""}, []string{""})) 55 | assert.Equal(t, 0, LastIndexOfSlice([]string{"one", "two", "three"}, []string{"one", "two"})) 56 | assert.Equal(t, 0, LastIndexOfSlice([]int64{1, 2, 3}, []int64{1, 2, 3})) 57 | assert.Equal(t, 4, LastIndexOfSlice([]uint{0, 1, 1, 1, 1}, []uint{1})) 58 | } 59 | 60 | func Test_SubSlice(t *testing.T) { 61 | assert.Equal(t, []int{}, SubSlice([]int{}, 0, 100)) 62 | assert.Equal(t, []int{}, SubSlice([]int{1, 2, 3}, 10, 100)) 63 | assert.Equal(t, []int{2, 3}, SubSlice([]int{1, 2, 3}, 1, 100)) 64 | assert.Equal(t, []int{3}, SubSlice([]int{1, 2, 3}, -1, 100)) 65 | assert.Equal(t, []int{2, 3}, SubSlice([]int{1, 2, 3}, -1, -3)) 66 | } 67 | -------------------------------------------------------------------------------- /slice_transform.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | // MapSlice transforms a slice to another with map function 4 | func MapSlice[T any, U any, S ~[]T](s S, mapFunc func(T) U) []U { 5 | result := make([]U, len(s)) 6 | for i := range s { 7 | result[i] = mapFunc(s[i]) 8 | } 9 | return result 10 | } 11 | 12 | // MapSliceEx transforms a slice to another with map function and error handling 13 | func MapSliceEx[T any, U any, S ~[]T](s S, mapFunc func(T) (U, error)) ([]U, error) { 14 | result := make([]U, 0, len(s)) 15 | for i := range s { 16 | v, err := mapFunc(s[i]) 17 | if err != nil { 18 | return nil, err 19 | } 20 | result = append(result, v) 21 | } 22 | return result, nil 23 | } 24 | 25 | // MapSliceToMap transforms a slice to a map with map function 26 | func MapSliceToMap[T any, K comparable, V any, S ~[]T](s S, mapFunc func(T) (K, V)) map[K]V { 27 | result := make(map[K]V, len(s)) 28 | for i := range s { 29 | k, v := mapFunc(s[i]) 30 | result[k] = v 31 | } 32 | return result 33 | } 34 | 35 | // MapSliceToMapEx transforms a slice to a map with map function and error handling 36 | func MapSliceToMapEx[T any, K comparable, V any, S ~[]T](s S, mapFunc func(T) (K, V, error)) (map[K]V, error) { 37 | result := make(map[K]V, len(s)) 38 | for i := range s { 39 | k, v, err := mapFunc(s[i]) 40 | if err != nil { 41 | return nil, err 42 | } 43 | result[k] = v 44 | } 45 | return result, nil 46 | } 47 | 48 | // MapSliceToMapKeys transforms a slice to a map using slice items as map keys. 49 | // For example: MapSliceToMapKeys(s, struct{}{}) -> map[T]struct{} 50 | func MapSliceToMapKeys[T comparable, V any, S ~[]T](s S, defaultVal V) map[T]V { 51 | if len(s) == 0 { 52 | return map[T]V{} 53 | } 54 | ret := make(map[T]V, len(s)) 55 | for _, v := range s { 56 | ret[v] = defaultVal 57 | } 58 | return ret 59 | } 60 | -------------------------------------------------------------------------------- /slice_transform_test.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | import ( 4 | "strconv" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func Test_MapSlice(t *testing.T) { 11 | // Nil/Empty maps 12 | assert.Equal(t, []float32{}, MapSlice[int, float32]([]int(nil), func(v int) float32 { return float32(v) })) 13 | assert.Equal(t, []float32{}, MapSlice([]int{}, func(v int) float32 { return float32(v) })) 14 | 15 | // []int -> []int 16 | assert.Equal(t, []int{2, 4, 6}, MapSlice([]int{1, 2, 3}, func(v int) int { return v * 2 })) 17 | // []int -> []float32 18 | assert.Equal(t, []float32{2, 4, 6}, MapSlice([]int{1, 2, 3}, func(v int) float32 { return float32(v * 2) })) 19 | } 20 | 21 | func Test_MapSliceEx(t *testing.T) { 22 | // Nil/Empty maps 23 | r1, e := MapSliceEx[int, float32]([]int(nil), func(v int) (float32, error) { return float32(v), nil }) 24 | assert.Nil(t, e) 25 | assert.Equal(t, []float32{}, r1) 26 | r2, e := MapSliceEx([]int{}, func(v int) (float32, error) { return float32(v), nil }) 27 | assert.Nil(t, e) 28 | assert.Equal(t, []float32{}, r2) 29 | 30 | // []string -> []int 31 | r3, e := MapSliceEx([]string{"1", "2", "3"}, ParseInt[int]) 32 | assert.Nil(t, e) 33 | assert.Equal(t, []int{1, 2, 3}, r3) 34 | 35 | // []string -> []int with error 36 | _, e = MapSliceEx([]string{"1", "a", "3"}, ParseInt[int]) 37 | assert.ErrorIs(t, e, strconv.ErrSyntax) 38 | } 39 | 40 | func Test_MapSliceToMap(t *testing.T) { 41 | // Nil/Empty maps 42 | assert.Equal(t, map[int]bool{}, MapSliceToMap[int, int, bool]([]int(nil), func(v int) (int, bool) { return v, v > 0 })) 43 | assert.Equal(t, map[int]bool{}, MapSliceToMap([]int{}, func(v int) (int, bool) { return v, v > 0 })) 44 | 45 | // []int -> map[int]string 46 | assert.Equal(t, map[int]string{1: "2", 2: "4", 3: "6"}, MapSliceToMap([]int{1, 2, 3}, 47 | func(v int) (int, string) { return v, strconv.Itoa(v * 2) })) 48 | } 49 | 50 | func Test_MapSliceToMapEx(t *testing.T) { 51 | // Nil/Empty maps 52 | r1, e := MapSliceToMapEx[int, int, bool]([]int(nil), func(v int) (int, bool, error) { return v, v > 0, nil }) 53 | assert.Nil(t, e) 54 | assert.Equal(t, map[int]bool{}, r1) 55 | r2, e := MapSliceToMapEx([]int{}, func(v int) (int, bool, error) { return v, v > 0, nil }) 56 | assert.Nil(t, e) 57 | assert.Equal(t, map[int]bool{}, r2) 58 | 59 | // []string -> []int 60 | r3, e := MapSliceToMapEx([]string{"1", "2", "3"}, func(v string) (string, int, error) { 61 | u, e := ParseInt[int](v) 62 | return v, u, e 63 | }) 64 | assert.Nil(t, e) 65 | assert.Equal(t, map[string]int{"1": 1, "2": 2, "3": 3}, r3) 66 | 67 | // []string -> []int with error 68 | _, e = MapSliceToMapEx([]string{"1", "x", "3"}, func(v string) (string, int, error) { 69 | u, e := ParseInt[int](v) 70 | return v, u, e 71 | }) 72 | assert.ErrorIs(t, e, strconv.ErrSyntax) 73 | } 74 | 75 | func Test_MapSliceToMapKeys(t *testing.T) { 76 | // Nil/Empty maps 77 | assert.Equal(t, map[int]struct{}{}, MapSliceToMapKeys[int]([]int(nil), struct{}{})) 78 | assert.Equal(t, map[int]int{}, MapSliceToMapKeys([]int{}, 0)) 79 | 80 | // []int -> map[int]string 81 | assert.Equal(t, map[int]string{1: "x", 2: "x", 3: "x"}, MapSliceToMapKeys([]int{1, 2, 3, 2}, "x")) 82 | } 83 | -------------------------------------------------------------------------------- /slice_uniq.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | // IsUnique checks a slice for uniqueness 4 | func IsUnique[T comparable, S ~[]T](s S) bool { 5 | length := len(s) 6 | if length <= 1 { 7 | return true 8 | } 9 | seen := make(map[T]struct{}, length) 10 | for i := 0; i < length; i++ { 11 | v := s[i] 12 | if _, ok := seen[v]; ok { 13 | return false 14 | } 15 | seen[v] = struct{}{} 16 | } 17 | return true 18 | } 19 | 20 | // IsUniqueBy checks a slice for uniqueness using key function 21 | func IsUniqueBy[T any, U comparable, S ~[]T](s S, keyFunc func(t T) U) bool { 22 | length := len(s) 23 | if length <= 1 { 24 | return true 25 | } 26 | seen := make(map[U]struct{}, length) 27 | for i := 0; i < length; i++ { 28 | v := keyFunc(s[i]) 29 | if _, ok := seen[v]; ok { 30 | return false 31 | } 32 | seen[v] = struct{}{} 33 | } 34 | return true 35 | } 36 | 37 | // Deprecated: use IsUniqueBy instead 38 | func IsUniquePred[T any, U comparable, S ~[]T](s S, keyFunc func(t T) U) bool { 39 | return IsUniqueBy(s, keyFunc) 40 | } 41 | 42 | // FindUniques finds all elements that are unique in the given slice. 43 | func FindUniques[T comparable, S ~[]T](s S) S { 44 | return FindUniquesBy(s, func(t T) T { return t }) 45 | } 46 | 47 | // FindUniquesBy finds all elements that are unique in the given slice. 48 | func FindUniquesBy[T any, U comparable, S ~[]T](s S, keyFunc func(T) U) S { 49 | length := len(s) 50 | if length <= 1 { 51 | return append(S{}, s...) 52 | } 53 | 54 | seen := make(map[U]int, length) // Map to store elements and their first indexes 55 | dupFlags := make([]bool, length) // Array to store flags of duplication of elements 56 | uniqCount := length 57 | 58 | for i := range s { 59 | k := keyFunc(s[i]) 60 | if firstIndex, ok := seen[k]; ok { 61 | dupFlags[firstIndex] = true 62 | dupFlags[i] = true 63 | uniqCount-- 64 | continue 65 | } 66 | seen[k] = i 67 | } 68 | 69 | if uniqCount == length { 70 | return append(S{}, s...) 71 | } 72 | result := make(S, 0, uniqCount) 73 | for i := 0; i < length; i++ { 74 | if !dupFlags[i] { 75 | result = append(result, s[i]) 76 | } 77 | } 78 | return result 79 | } 80 | 81 | // FindDuplicates finds all elements that are duplicated in the given slice. 82 | func FindDuplicates[T comparable, S ~[]T](s S) S { 83 | return FindDuplicatesBy(s, func(t T) T { return t }) 84 | } 85 | 86 | // FindDuplicatesBy finds all elements that are duplicated in the given slice. 87 | func FindDuplicatesBy[T any, U comparable, S ~[]T](s S, keyFunc func(T) U) S { 88 | length := len(s) 89 | if length <= 1 { 90 | return S{} 91 | } 92 | 93 | seen := make(map[U]int, length) // Map to store elements and their first indexes 94 | dupFlags := make([]bool, length) // Array to store flags of duplication of elements 95 | dupCount := 0 96 | 97 | for i := range s { 98 | k := keyFunc(s[i]) 99 | if firstIndex, ok := seen[k]; ok { 100 | dupFlags[firstIndex] = true 101 | dupCount++ 102 | continue 103 | } 104 | seen[k] = i 105 | } 106 | 107 | if dupCount == 0 { 108 | return S{} 109 | } 110 | result := make(S, 0, dupCount) 111 | for i := 0; i < length; i++ { 112 | if dupFlags[i] { 113 | result = append(result, s[i]) 114 | } 115 | } 116 | return result 117 | } 118 | 119 | // ToSet calculates unique values of a slice 120 | func ToSet[T comparable, S ~[]T](s S) S { 121 | length := len(s) 122 | if length <= 1 { 123 | return append(S{}, s...) 124 | } 125 | 126 | seen := make(map[T]struct{}, length) 127 | result := make(S, 0, length) 128 | 129 | for i := 0; i < length; i++ { 130 | v := s[i] 131 | if _, ok := seen[v]; ok { 132 | continue 133 | } 134 | seen[v] = struct{}{} 135 | result = append(result, v) 136 | } 137 | 138 | return result 139 | } 140 | 141 | // ToSetBy calculates unique values of a slice with custom key function 142 | func ToSetBy[T any, K comparable, S ~[]T](s S, keyFunc func(t T) K) S { 143 | length := len(s) 144 | if length <= 1 { 145 | return append(S{}, s...) 146 | } 147 | 148 | seen := make(map[K]struct{}, length) 149 | result := make(S, 0, length) 150 | 151 | for i := 0; i < length; i++ { 152 | v := s[i] 153 | k := keyFunc(v) 154 | if _, ok := seen[k]; ok { 155 | continue 156 | } 157 | seen[k] = struct{}{} 158 | result = append(result, v) 159 | } 160 | 161 | return result 162 | } 163 | 164 | // Deprecated: use ToSetBy instead 165 | func ToSetPred[T any, K comparable, S ~[]T](s S, keyFunc func(t T) K) S { 166 | return ToSetBy(s, keyFunc) 167 | } 168 | 169 | // ToSetByReverse calculates unique values of a slice with custom key function. 170 | // Unlike ToSetBy, this function iterates over the slice from the end. 171 | func ToSetByReverse[T any, K comparable, S ~[]T](s S, keyFunc func(t T) K) S { 172 | length := len(s) 173 | if length <= 1 { 174 | return append(S{}, s...) 175 | } 176 | 177 | seen := make(map[K]struct{}, length) 178 | result := make(S, 0, length) 179 | 180 | for i := length - 1; i >= 0; i-- { 181 | v := s[i] 182 | k := keyFunc(v) 183 | if _, ok := seen[k]; ok { 184 | continue 185 | } 186 | seen[k] = struct{}{} 187 | result = append(result, v) 188 | } 189 | 190 | return result 191 | } 192 | 193 | // Deprecated: use ToSetByReverse instead 194 | func ToSetPredReverse[T any, K comparable, S ~[]T](s S, keyFunc func(t T) K) S { 195 | return ToSetByReverse(s, keyFunc) 196 | } 197 | -------------------------------------------------------------------------------- /slice_uniq_test.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func Test_IsUnique(t *testing.T) { 10 | assert.True(t, IsUnique[int, []int](nil)) 11 | assert.True(t, IsUnique([]int{})) 12 | assert.True(t, IsUnique([]string{"one"})) 13 | assert.True(t, IsUnique([]string{"one", "two", "One", "Two"})) 14 | assert.True(t, IsUnique([]float32{1.1, 2.2, 3.3, 1.11})) 15 | 16 | assert.False(t, IsUnique([]int{1, 2, 3, 1, 2})) 17 | assert.False(t, IsUnique([]string{"one", "two", "one"})) 18 | assert.False(t, IsUnique([]float32{1.1, 2.2, 1.100})) 19 | 20 | type st struct { 21 | I int 22 | S string 23 | } 24 | assert.True(t, IsUnique([]st{{1, "one"}, {2, "two"}, {3, "three"}})) 25 | assert.True(t, IsUnique([]st{{1, "one"}, {1, "One"}})) 26 | assert.False(t, IsUnique([]st{{1, "one"}, {2, "two"}, {1, "one"}})) 27 | } 28 | 29 | // nolint: forcetypeassert 30 | func Test_IsUniqueBy(t *testing.T) { 31 | assert.True(t, IsUniqueBy[int, int, []int](nil, nil)) 32 | assert.True(t, IsUniqueBy([]int{}, func(v int) int { return v })) 33 | assert.True(t, IsUniqueBy([]any{"one"}, 34 | func(v any) string { return v.(string) })) 35 | assert.True(t, IsUniqueBy([]any{"one", "two", "One", "Two"}, 36 | func(v any) string { return v.(string) })) 37 | assert.True(t, IsUniqueBy([]any{1.1, 2.2, 3.3, 1.11}, 38 | func(v any) float64 { return v.(float64) })) 39 | 40 | assert.False(t, IsUniqueBy([]any{1, 2, 3, 1, 2}, 41 | func(v any) int { return v.(int) })) 42 | assert.False(t, IsUniqueBy([]any{"one", "two", "one"}, 43 | func(v any) string { return v.(string) })) 44 | assert.False(t, IsUniqueBy([]any{1.1, 2.2, 1.100}, 45 | func(v any) float64 { return v.(float64) })) 46 | 47 | type st struct { 48 | I int 49 | S string 50 | } 51 | assert.True(t, IsUniqueBy([]st{{1, "one"}, {2, "two"}, {3, "three"}}, func(v st) int { return v.I })) 52 | assert.False(t, IsUniqueBy([]st{{1, "one"}, {1, "One"}}, func(v st) int { return v.I })) 53 | assert.False(t, IsUniqueBy([]st{{1, "one"}, {2, "two"}, {1, "one"}}, func(v st) int { return v.I })) 54 | } 55 | 56 | // nolint: forcetypeassert 57 | func Test_IsUniquePred_Deprecated(t *testing.T) { 58 | assert.True(t, IsUniquePred([]any{1.1, 2.2, 3.3, 1.11}, 59 | func(v any) float64 { return v.(float64) })) 60 | assert.False(t, IsUniquePred([]any{1, 2, 3, 1, 2}, 61 | func(v any) int { return v.(int) })) 62 | } 63 | 64 | func Test_FindUniques(t *testing.T) { 65 | assert.Equal(t, []int{}, FindUniques([]int{})) 66 | assert.Equal(t, []int{0}, FindUniques([]int{0})) 67 | assert.Equal(t, []int{0, 1, 2, 3}, FindUniques([]int{0, 1, 2, 3})) 68 | assert.Equal(t, []int{0, 3}, FindUniques([]int{0, 1, 2, 3, 2, 1, 1})) 69 | assert.Equal(t, []int{}, FindUniques([]int{0, 1, 2, 3, 2, 1, 0, 0, 3})) 70 | } 71 | 72 | //nolint:forcetypeassert 73 | func Test_FindUniquesBy(t *testing.T) { 74 | assert.Equal(t, []any{}, FindUniquesBy([]any{}, func(any) int { return 0 })) 75 | assert.Equal(t, []any{0}, FindUniquesBy([]any{0}, func(v any) int { return v.(int) })) 76 | assert.Equal(t, []any{0, 1, 2}, FindUniquesBy([]any{0, 1, 2}, func(v any) int { return v.(int) })) 77 | assert.Equal(t, []any{0, 3}, FindUniquesBy([]any{0, 1, 2, 3, 2, 1, 1}, func(v any) int { return v.(int) })) 78 | assert.Equal(t, []any{}, FindUniquesBy([]any{0, 1, 2, 3, 2, 1, 0, 0, 3}, func(v any) int { return v.(int) })) 79 | } 80 | 81 | func Test_FindDuplicates(t *testing.T) { 82 | assert.Equal(t, []int{}, FindDuplicates([]int{})) 83 | assert.Equal(t, []int{}, FindDuplicates([]int{0})) 84 | assert.Equal(t, []int{}, FindDuplicates([]int{0, 1, 2, 3})) 85 | assert.Equal(t, []int{1, 2}, FindDuplicates([]int{0, 1, 2, 3, 2, 1, 1})) 86 | assert.Equal(t, []int{0, 1, 2, 3}, FindDuplicates([]int{0, 1, 2, 3, 2, 1, 0, 0, 3})) 87 | } 88 | 89 | //nolint:forcetypeassert 90 | func Test_FindDuplicatesBy(t *testing.T) { 91 | assert.Equal(t, []any{}, FindDuplicatesBy([]any{}, func(any) int { return 0 })) 92 | assert.Equal(t, []any{}, FindDuplicatesBy([]any{0}, func(v any) int { return v.(int) })) 93 | assert.Equal(t, []any{}, FindDuplicatesBy([]any{0, 1, 2, 3}, func(v any) int { return v.(int) })) 94 | assert.Equal(t, []any{1, 2}, FindDuplicatesBy([]any{0, 1, 2, 3, 2, 1, 1}, func(v any) int { return v.(int) })) 95 | assert.Equal(t, []any{0, 1, 2, 3}, 96 | FindDuplicatesBy([]any{0, 1, 2, 3, 2, 1, 0, 0, 3}, func(v any) int { return v.(int) })) 97 | } 98 | 99 | func Test_ToSet(t *testing.T) { 100 | assert.Equal(t, []int{}, ToSet([]int{})) 101 | assert.Equal(t, []string{"one"}, ToSet([]string{"one"})) 102 | assert.Equal(t, []string{"one", "two", "Two"}, ToSet([]string{"one", "two", "one", "Two"})) 103 | assert.Equal(t, []int{1, 2, 3}, ToSet([]int{1, 2, 3, 1, 2})) 104 | assert.Equal(t, []float32{float32(1.1), float32(2.2), float32(3.3), float32(1.11)}, 105 | ToSet([]float32{1.1, 1.1, 2.2, 3.3, 1.11})) 106 | 107 | type st struct { 108 | I int 109 | S string 110 | } 111 | assert.Equal(t, []st{{1, "one"}, {2, "two"}}, 112 | ToSet([]st{{1, "one"}, {2, "two"}, {1, "one"}})) 113 | } 114 | 115 | // nolint: forcetypeassert 116 | func Test_ToSetBy(t *testing.T) { 117 | // Comparable types 118 | assert.Equal(t, []int{}, 119 | ToSetBy([]int{}, func(t int) int { return t })) 120 | assert.Equal(t, []string{"one"}, 121 | ToSetBy([]string{"one"}, func(t string) string { return t })) 122 | assert.Equal(t, []string{"one", "two", "Two"}, 123 | ToSetBy([]string{"one", "two", "one", "Two"}, func(t string) string { return t })) 124 | assert.Equal(t, []int{1, 2, 3}, 125 | ToSetBy([]int{1, 2, 3, 1, 2}, func(t int) int { return t })) 126 | assert.Equal(t, []float32{float32(1.1), float32(2.2), float32(3.3), float32(1.11)}, 127 | ToSetBy([]float32{1.1, 1.1, 2.2, 3.3, 1.11}, func(t float32) float32 { return t })) 128 | 129 | // Incomparable types 130 | assert.Equal(t, []any{}, 131 | ToSetBy([]any{}, func(t any) int { return t.(int) })) 132 | assert.Equal(t, []any{"one"}, 133 | ToSetBy([]any{"one"}, func(t any) string { return t.(string) })) 134 | assert.Equal(t, []any{"one", "two", "Two"}, 135 | ToSetBy([]any{"one", "two", "one", "Two"}, func(t any) string { return t.(string) })) 136 | assert.Equal(t, []any{1, 2, 3}, 137 | ToSetBy([]any{1, 2, 3, 1, 2}, func(t any) int { return t.(int) })) 138 | assert.Equal(t, []any{1.1, 2.2, 3.3, 1.11}, 139 | ToSetBy([]any{1.1, 1.1, 2.2, 3.3, 1.11}, func(t any) float64 { return t.(float64) })) 140 | } 141 | 142 | // nolint: forcetypeassert 143 | func Test_ToSetPred_Deprecated(t *testing.T) { 144 | // Comparable types 145 | assert.Equal(t, []int{1, 2, 3}, 146 | ToSetPred([]int{1, 2, 3, 1, 2}, func(t int) int { return t })) 147 | assert.Equal(t, []float32{float32(1.1), float32(2.2), float32(3.3), float32(1.11)}, 148 | ToSetPred([]float32{1.1, 1.1, 2.2, 3.3, 1.11}, func(t float32) float32 { return t })) 149 | 150 | // Incomparable types 151 | assert.Equal(t, []any{1, 2, 3}, 152 | ToSetPred([]any{1, 2, 3, 1, 2}, func(t any) int { return t.(int) })) 153 | assert.Equal(t, []any{1.1, 2.2, 3.3, 1.11}, 154 | ToSetPred([]any{1.1, 1.1, 2.2, 3.3, 1.11}, func(t any) float64 { return t.(float64) })) 155 | } 156 | 157 | // nolint: forcetypeassert 158 | func Test_ToSetByReverse(t *testing.T) { 159 | // Comparable types 160 | assert.Equal(t, []int{}, 161 | ToSetByReverse([]int{}, func(t int) int { return t })) 162 | assert.Equal(t, []string{"one"}, 163 | ToSetByReverse([]string{"one"}, func(t string) string { return t })) 164 | assert.Equal(t, []string{"Two", "one", "two"}, 165 | ToSetByReverse([]string{"one", "two", "one", "Two"}, func(t string) string { return t })) 166 | assert.Equal(t, []int{2, 1, 3}, 167 | ToSetByReverse([]int{1, 2, 3, 1, 2}, func(t int) int { return t })) 168 | assert.Equal(t, []float32{float32(1.1), float32(2.2), float32(3.3), float32(1.11)}, 169 | ToSetByReverse([]float32{1.11, 3.3, 2.2, 1.1}, func(t float32) float32 { return t })) 170 | 171 | // Incomparable types 172 | assert.Equal(t, []any{}, 173 | ToSetByReverse([]any{}, func(t any) int { return t.(int) })) 174 | assert.Equal(t, []any{"one"}, 175 | ToSetByReverse([]any{"one"}, func(t any) string { return t.(string) })) 176 | assert.Equal(t, []any{"Two", "one", "two"}, 177 | ToSetByReverse([]any{"one", "two", "one", "Two"}, func(t any) string { return t.(string) })) 178 | assert.Equal(t, []any{2, 1, 3}, 179 | ToSetByReverse([]any{1, 2, 3, 1, 2}, func(t any) int { return t.(int) })) 180 | assert.Equal(t, []any{1.11, 3.3, 2.2, 1.1}, 181 | ToSetByReverse([]any{1.1, 1.1, 2.2, 3.3, 1.11}, func(t any) float64 { return t.(float64) })) 182 | } 183 | 184 | // nolint: forcetypeassert 185 | func Test_ToSetPredReverse_Deprecated(t *testing.T) { 186 | // Comparable types 187 | assert.Equal(t, []int{2, 1, 3}, 188 | ToSetPredReverse([]int{1, 2, 3, 1, 2}, func(t int) int { return t })) 189 | assert.Equal(t, []float32{float32(1.1), float32(2.2), float32(3.3), float32(1.11)}, 190 | ToSetPredReverse([]float32{1.11, 3.3, 2.2, 1.1}, func(t float32) float32 { return t })) 191 | 192 | // Incomparable types 193 | assert.Equal(t, []any{2, 1, 3}, 194 | ToSetPredReverse([]any{1, 2, 3, 1, 2}, func(t any) int { return t.(int) })) 195 | assert.Equal(t, []any{1.11, 3.3, 2.2, 1.1}, 196 | ToSetPredReverse([]any{1.1, 1.1, 2.2, 3.3, 1.11}, func(t any) float64 { return t.(float64) })) 197 | } 198 | -------------------------------------------------------------------------------- /string.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "strings" 7 | "unicode/utf8" 8 | ) 9 | 10 | var ( 11 | StrLowerAlpha = []rune("abcdefghijklmnopqrstuvwxyz") 12 | StrUpperAlpha = []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ") 13 | StrAlpha = Concat(StrLowerAlpha, StrUpperAlpha) 14 | StrDigits = []rune("0123456789") 15 | StrAlphaNumeric = Concat(StrAlpha, StrDigits) 16 | StrSpecialChars = []rune("~!@#$%^&*()-_+`'\";:,.<>/?{[}]\\|") 17 | StrAllChars = Concat(StrAlpha, StrDigits, StrSpecialChars) 18 | StrDefaultChars = StrAlphaNumeric 19 | ) 20 | 21 | // RandString generates a random string 22 | func RandString(n int) string { 23 | return RandStringEx(n, StrDefaultChars) 24 | } 25 | 26 | // RandStringEx generates a random string 27 | func RandStringEx[S ~[]rune](n int, allowedChars S) string { 28 | b := make([]rune, n) 29 | numChars := len(allowedChars) 30 | for i := range b { 31 | b[i] = allowedChars[rand.Intn(numChars)] // nolint: gosec 32 | } 33 | return string(b) 34 | } 35 | 36 | // RuneLength alias of utf8.RuneCountInString 37 | var RuneLength = utf8.RuneCountInString 38 | 39 | // StringJoin join elements from a slice of any type. 40 | // This function calls fmt.Sprintf("%v", elem) to format every element. 41 | func StringJoin[T any, S ~[]T](s S, sep string) string { 42 | return StringJoinEx(s, sep, "%v") 43 | } 44 | 45 | // StringJoinEx join elements from a slice of any type with custom format string 46 | func StringJoinEx[T any, S ~[]T](s S, sep, format string) string { 47 | return StringJoinBy(s, sep, func(v T) string { 48 | return stringFormat(format, v) 49 | }) 50 | } 51 | 52 | // StringJoinBy join elements from a slice of any type with custom format function 53 | func StringJoinBy[T any, S ~[]T](s S, sep string, fmtFunc func(v T) string) string { 54 | switch len(s) { 55 | case 0: 56 | return "" 57 | case 1: 58 | return fmtFunc(s[0]) 59 | } 60 | 61 | var sb strings.Builder 62 | for i := range s { 63 | if i > 0 { 64 | sb.WriteString(sep) 65 | } 66 | sb.WriteString(fmtFunc(s[i])) 67 | } 68 | return sb.String() 69 | } 70 | 71 | // Deprecated: use StringJoinBy instead 72 | func StringJoinPred[T any, S ~[]T](s S, sep string, fmtFunc func(v T) string) string { 73 | return StringJoinBy(s, sep, fmtFunc) 74 | } 75 | 76 | func stringFormat(format string, v any) string { 77 | if v == nil { 78 | return "null" 79 | } 80 | if stringer, ok := v.(fmt.Stringer); ok { 81 | return stringer.String() 82 | } 83 | return fmt.Sprintf(format, v) 84 | } 85 | 86 | // StringLexJoinEx lexical joins a list of items of any type. The input format will be 87 | // used with fmt.Sprintf() to render slice items as string. 88 | // 89 | // For example: 90 | // 91 | // StringLexJoinEx(["grape", "apple", "orange"], ", ", " and ", "%v") -> grape, apple and orange 92 | // StringLexJoinEx(["grape", "apple", "orange"], ", ", " or ", "%v") -> grape, apple or orange 93 | func StringLexJoinEx[T any, S ~[]T](s S, sep, lastSep, format string) string { 94 | length := len(s) 95 | if length == 0 { 96 | return "" 97 | } 98 | if length == 1 { 99 | return stringFormat(format, s[0]) 100 | } 101 | var sb strings.Builder 102 | for i := 0; i < length-1; i++ { 103 | if i > 0 { 104 | sb.WriteString(sep) 105 | } 106 | sb.WriteString(stringFormat(format, s[i])) 107 | } 108 | sb.WriteString(lastSep) 109 | sb.WriteString(stringFormat(format, s[length-1])) 110 | 111 | return sb.String() 112 | } 113 | 114 | // StringLexJoin lexical joins a list of items of any type. 115 | func StringLexJoin[T any, S ~[]T](s S, sep, lastSep string) string { 116 | return StringLexJoinEx(s, sep, lastSep, "%v") 117 | } 118 | 119 | // LinesTrimLeft trim leading characters for every line in the given string. 120 | // Deprecated 121 | func LinesTrimLeft(s string, cutset string) string { 122 | if s == "" || cutset == "" { 123 | return s 124 | } 125 | ret := make([]byte, 0, len(s)) 126 | s = strings.TrimLeft(s, cutset) // trim the first line 127 | for i := 0; i < len(s); i++ { 128 | ch := s[i] 129 | ret = append(ret, ch) 130 | if ch == '\n' || ch == '\r' { 131 | s = strings.TrimLeft(s[i+1:], cutset) 132 | i = -1 133 | } 134 | } 135 | return string(ret) 136 | } 137 | 138 | // LinesTrimLeftSpace trim leading spaces for every line in the given string. 139 | // Deprecated 140 | func LinesTrimLeftSpace(s string) string { 141 | // See unicode.IsSpace for what are considered spaces 142 | return LinesTrimLeft(s, string([]rune{' ', '\t', '\v', '\f', 0x85, 0xA0})) 143 | } 144 | 145 | // LinesTrimRight trim trailing characters for every line in the given string. 146 | // Deprecated 147 | func LinesTrimRight(s string, cutset string) string { 148 | if s == "" || cutset == "" { 149 | return s 150 | } 151 | 152 | ret := make([]byte, len(s)) 153 | j := len(ret) - 1 154 | 155 | s = strings.TrimRight(s, cutset) // trim the last line 156 | for i := len(s) - 1; i >= 0; i-- { 157 | ch := s[i] 158 | ret[j] = ch 159 | j-- 160 | if ch == '\n' || ch == '\r' { 161 | s = strings.TrimRight(s[:i], cutset) 162 | i = len(s) 163 | } 164 | } 165 | return string(ret[j+1:]) 166 | } 167 | 168 | // LinesTrimRightSpace trim trailing characters for every line in the given string. 169 | // Deprecated 170 | func LinesTrimRightSpace(s string) string { 171 | // See unicode.IsSpace for what are considered spaces 172 | return LinesTrimRight(s, string([]rune{' ', '\t', '\v', '\f', 0x85, 0xA0})) 173 | } 174 | 175 | // LinesTrim trim leading and trailing characters for every line in the given string. 176 | // Deprecated 177 | func LinesTrim(s string, cutset string) string { 178 | return LinesTrimLeft(LinesTrimRight(s, cutset), cutset) 179 | } 180 | 181 | // LinesTrimSpace trim leading and trailing spaces for every line in the given string. 182 | // Deprecated 183 | func LinesTrimSpace(s string) string { 184 | return LinesTrim(s, string([]rune{' ', '\t', '\v', '\f', 0x85, 0xA0})) 185 | } 186 | 187 | // Deprecated 188 | var MultilineString = LinesTrimLeftSpace 189 | 190 | // StringWrap wraps a string with the given token 191 | func StringWrap(s string, token string) string { 192 | return token + s + token 193 | } 194 | 195 | // StringUnwrap unwraps a string wrapped with the given token 196 | func StringUnwrap(s string, token string) string { 197 | return strings.TrimSuffix(strings.TrimPrefix(s, token), token) 198 | } 199 | 200 | // StringWrapLR wraps a string with the given tokens for the left and right side 201 | func StringWrapLR(s string, tokenLeft, tokenRight string) string { 202 | return tokenLeft + s + tokenRight 203 | } 204 | 205 | // StringUnwrapLR unwraps a string wrapped with the given tokens 206 | func StringUnwrapLR(s string, tokenLeft, tokenRight string) string { 207 | return strings.TrimSuffix(strings.TrimPrefix(s, tokenLeft), tokenRight) 208 | } 209 | -------------------------------------------------------------------------------- /string_test.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "testing" 7 | "time" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func Test_RandString(t *testing.T) { 13 | // Empty string 14 | assert.Equal(t, "", RandString(0)) 15 | 16 | s := RandString(12) 17 | assert.Equal(t, 12, len(s)) 18 | for _, ch := range s { 19 | assert.True(t, strings.ContainsRune(string(StrDefaultChars), ch)) 20 | } 21 | } 22 | 23 | func Test_RandStringEx(t *testing.T) { 24 | // Empty string 25 | assert.Equal(t, "", RandStringEx(0, StrLowerAlpha)) 26 | 27 | // Only digits 28 | s := RandStringEx(10, StrDigits) 29 | assert.Equal(t, 10, len(s)) 30 | for _, ch := range s { 31 | assert.True(t, strings.ContainsRune(string(StrDigits), ch)) 32 | } 33 | 34 | // Only alphabet 35 | s = RandStringEx(12, StrLowerAlpha) 36 | assert.Equal(t, 12, len(s)) 37 | for _, ch := range s { 38 | assert.True(t, strings.ContainsRune(string(StrLowerAlpha), ch)) 39 | } 40 | } 41 | 42 | func Test_StringJoin(t *testing.T) { 43 | assert.Equal(t, "", StringJoin[int]([]int(nil), ",")) 44 | assert.Equal(t, "1", StringJoin[int]([]int{1}, ",")) 45 | assert.Equal(t, "1,2,3", StringJoin[int64]([]int64{1, 2, 3}, ",")) 46 | // Slice has nil element 47 | assert.Equal(t, "1,null,3", StringJoin[any]([]any{1, nil, "3"}, ",")) 48 | // Slice of fmt.Stringer elements 49 | assert.Equal(t, "2020-01-01 00:00:00 +0000 UTC, 2020-12-01 00:00:00 +0000 UTC", StringJoin[any]([]any{ 50 | time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC), 51 | time.Date(2020, time.November, 31, 0, 0, 0, 0, time.UTC), 52 | }, ", ")) 53 | } 54 | 55 | func Test_StringJoinBy(t *testing.T) { 56 | assert.Equal(t, "", StringJoinBy[int]([]int(nil), ",", func(v int) string { 57 | return fmt.Sprintf("%d", v) 58 | })) 59 | assert.Equal(t, "1", StringJoinBy[int]([]int{1}, ",", func(v int) string { 60 | return fmt.Sprintf("%d", v) 61 | })) 62 | assert.Equal(t, "1,2,3", StringJoinBy[int64]([]int64{1, 2, 3}, ",", func(v int64) string { 63 | return fmt.Sprintf("%d", v) 64 | })) 65 | // Slice has nil element 66 | assert.Equal(t, "1,nil,3", StringJoinBy[any]([]any{1, nil, "3"}, ",", func(v any) string { 67 | if v == nil { 68 | return "nil" 69 | } 70 | return fmt.Sprintf("%v", v) 71 | })) 72 | } 73 | 74 | func Test_StringJoinPred_Deprecated(t *testing.T) { 75 | assert.Equal(t, "1", StringJoinPred[int]([]int{1}, ",", func(v int) string { 76 | return fmt.Sprintf("%d", v) 77 | })) 78 | assert.Equal(t, "1,2,3", StringJoinPred[int64]([]int64{1, 2, 3}, ",", func(v int64) string { 79 | return fmt.Sprintf("%d", v) 80 | })) 81 | } 82 | 83 | func Test_StringLexJoin(t *testing.T) { 84 | assert.Equal(t, "", StringLexJoin([]int{}, ", ", " and ")) 85 | assert.Equal(t, "123", StringLexJoin([]int32{123}, ", ", " and ")) 86 | assert.Equal(t, "1 or 2", StringLexJoin([]int64{1, 2}, ", ", " or ")) 87 | assert.Equal(t, "1, null and 3", StringLexJoin([]any{1, nil, "3"}, ", ", " and ")) 88 | assert.Equal(t, "1, null, true and finally 3", 89 | StringLexJoin([]any{1, nil, true, "3"}, ", ", " and finally ")) 90 | 91 | assert.Equal(t, "0xfe or 0xff", StringLexJoinEx([]int64{254, 255}, ", ", " or ", "%#x")) 92 | } 93 | 94 | func Test_LinesTrimLeft(t *testing.T) { 95 | assert.Equal(t, "", LinesTrimLeftSpace("")) 96 | assert.Equal(t, "line-1\nline-2", LinesTrimLeftSpace(`line-1 97 | line-2`)) 98 | assert.Equal(t, "\nline-1\nline-2", LinesTrimLeftSpace(` 99 | line-1 100 | line-2`)) 101 | assert.Equal(t, "\nline-1\nline-2\n", LinesTrimLeftSpace(` 102 | line-1 103 | line-2 104 | `)) 105 | assert.Equal(t, "\nx line-1\nline-2", LinesTrimLeftSpace(` 106 | x line-1 107 | line-2`)) 108 | // Unicode 109 | assert.Equal(t, "\nâ line-1\nline-2 â", LinesTrimLeftSpace(` 110 | â line-1 111 | line-2 â`)) 112 | // Extra func test 113 | assert.Equal(t, "\nê b â line-1\nê line-2 â ê ", LinesTrimLeft(` 114 | a ê b â line-1 115 | b aê line-2 â ê `, "a b\t")) // ascii cutset 116 | assert.Equal(t, "\nâ line-1\nline-2 â ê", LinesTrimLeft(` 117 | ê â line-1 118 | ê line-2 â ê`, "a b\tê")) // unicode cutset 119 | } 120 | 121 | func Test_LinesTrimRight(t *testing.T) { 122 | assert.Equal(t, "", LinesTrimRightSpace("")) 123 | assert.Equal(t, "line-1\nline-2", LinesTrimRightSpace(`line-1 124 | line-2`)) 125 | assert.Equal(t, "\nline-1\nline-2\n", LinesTrimRightSpace(` 126 | line-1 127 | line-2 128 | `)) 129 | assert.Equal(t, "\nline-1\nline-2\n", LinesTrimRightSpace(` 130 | line-1 131 | line-2 132 | `)) 133 | assert.Equal(t, "\nx line-1 x\nline-2", LinesTrimRightSpace(` 134 | x line-1 x 135 | line-2`)) 136 | // Unicode 137 | assert.Equal(t, "\nâ line-1 â\nline-2", LinesTrimRightSpace(` 138 | â line-1 â 139 | line-2`)) 140 | // Extra func test 141 | assert.Equal(t, "\nâ line-1 â ê\nline-2 ê", LinesTrimRight(` 142 | â line-1 â ê 143 | line-2 ê `, "a b\t")) // ascii cutset 144 | assert.Equal(t, "\n â line-1 â\nline-2", LinesTrimRight(` 145 | â line-1 â ê 146 | line-2 ê `, "a b\tê")) // unicode cutset 147 | } 148 | 149 | func Test_LinesTrim(t *testing.T) { 150 | assert.Equal(t, "", LinesTrimSpace("")) 151 | assert.Equal(t, "line-1\nline-2", LinesTrimSpace(`line-1 152 | line-2`)) 153 | assert.Equal(t, "\nline-1\nline-2\n", LinesTrimSpace(` 154 | line-1 155 | line-2 156 | `)) 157 | assert.Equal(t, "\nline-1\nline-2\n", LinesTrimSpace(` 158 | line-1 159 | line-2 160 | `)) 161 | assert.Equal(t, "\nx line-1 x\nline-2", LinesTrimSpace(` 162 | x line-1 x 163 | line-2 `)) 164 | // Unicode 165 | assert.Equal(t, "\nâ line-1 â\nline-2", LinesTrimSpace(` 166 | â line-1 â 167 | line-2 `)) 168 | // Extra func test 169 | assert.Equal(t, "\nâ line-1 â\nline-2", LinesTrim(` 170 | â line-1 â ê 171 | line-2 ê `, "a b\tê")) 172 | } 173 | 174 | func Test_StringWrap(t *testing.T) { 175 | assert.Equal(t, "abc", StringWrap("abc", "")) 176 | assert.Equal(t, "'abc'", StringWrap("abc", "'")) 177 | assert.Equal(t, "'''abc'''", StringWrap("abc", "'''")) 178 | 179 | assert.Equal(t, "abc", StringWrapLR("abc", "", "")) 180 | assert.Equal(t, "'abc\"", StringWrapLR("abc", "'", "\"")) 181 | assert.Equal(t, "[abc]", StringWrapLR("abc", "[", "]")) 182 | } 183 | 184 | func Test_StringUnwrap(t *testing.T) { 185 | assert.Equal(t, "abc", StringUnwrap("abc", "")) 186 | assert.Equal(t, "abc", StringUnwrap("'abc'", "'")) 187 | assert.Equal(t, "abc", StringUnwrap("'''abc'''", "'''")) 188 | assert.Equal(t, " 'abc", StringUnwrap(" 'abc'", "'")) 189 | 190 | assert.Equal(t, "abc", StringUnwrapLR("abc", "", "")) 191 | assert.Equal(t, "abc", StringUnwrapLR("'abc\"", "'", "\"")) 192 | assert.Equal(t, "abc", StringUnwrapLR("[abc]", "[", "]")) 193 | assert.Equal(t, " [abc", StringUnwrapLR(" [abc]", "[", "]")) 194 | } 195 | -------------------------------------------------------------------------------- /struct.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | import ( 4 | rflutil "github.com/tiendc/go-rflutil" 5 | ) 6 | 7 | var ( 8 | StructToMap = rflutil.StructToMap 9 | ParseTag = rflutil.ParseTag 10 | ParseTagOf = rflutil.ParseTagOf 11 | ParseTagsOf = rflutil.ParseTagsOf 12 | ) 13 | -------------------------------------------------------------------------------- /time.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | import "time" 4 | 5 | // MinTime finds the minimum time in the list. 6 | // NOTE: if zero time is in the list, the result will be zero. 7 | func MinTime(t1 time.Time, s ...time.Time) time.Time { 8 | min := t1 9 | for i := range s { 10 | if s[i].Before(min) { 11 | min = s[i] 12 | } 13 | } 14 | return min 15 | } 16 | 17 | // MaxTime finds the maximum time in the list 18 | func MaxTime(t1 time.Time, s ...time.Time) time.Time { 19 | max := t1 20 | for i := range s { 21 | if s[i].After(max) { 22 | max = s[i] 23 | } 24 | } 25 | return max 26 | } 27 | 28 | // MinMaxTime gets the minimum and maximum time values in the list 29 | func MinMaxTime(t1 time.Time, s ...time.Time) (time.Time, time.Time) { 30 | minTime := t1 31 | maxTime := t1 32 | for i := range s { 33 | if s[i].Before(minTime) { 34 | minTime = s[i] 35 | } else if s[i].After(maxTime) { 36 | maxTime = s[i] 37 | } 38 | } 39 | return minTime, maxTime 40 | } 41 | 42 | // ExecDuration measures time duration of running a function 43 | func ExecDuration(fn func()) time.Duration { 44 | start := time.Now() 45 | fn() 46 | return time.Since(start) 47 | } 48 | 49 | // ExecDuration1 measures time duration of running a function 50 | func ExecDuration1[T any](fn func() T) (T, time.Duration) { 51 | start := time.Now() 52 | val := fn() 53 | return val, time.Since(start) 54 | } 55 | 56 | // ExecDuration2 measures time duration of running a function 57 | func ExecDuration2[T1, T2 any](fn func() (T1, T2)) (T1, T2, time.Duration) { 58 | start := time.Now() 59 | val1, val2 := fn() 60 | return val1, val2, time.Since(start) 61 | } 62 | 63 | // ExecDuration3 measures time duration of running a function 64 | func ExecDuration3[T1, T2, T3 any](fn func() (T1, T2, T3)) (T1, T2, T3, time.Duration) { 65 | start := time.Now() 66 | val1, val2, val3 := fn() 67 | return val1, val2, val3, time.Since(start) 68 | } 69 | 70 | // ExecDelay is an alias of time.AfterFunc 71 | var ExecDelay = time.AfterFunc 72 | -------------------------------------------------------------------------------- /time_test.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func Test_MinTime(t *testing.T) { 11 | t0 := time.Time{} 12 | t1 := time.Now() 13 | t2 := t1.Add(time.Second) 14 | t3 := t1.Add(-time.Minute) 15 | 16 | assert.Equal(t, t0, MinTime(t0, t1, t2, t3)) 17 | assert.Equal(t, t3, MinTime(t1, t2, t3)) 18 | } 19 | 20 | func Test_MaxTime(t *testing.T) { 21 | t0 := time.Time{} 22 | t1 := time.Now() 23 | t2 := t1.Add(time.Second) 24 | t3 := t1.Add(-time.Minute) 25 | 26 | assert.Equal(t, t2, MaxTime(t0, t1, t2, t3)) 27 | assert.Equal(t, t2, MaxTime(t1, t2, t3)) 28 | } 29 | 30 | func Test_MinMaxTime(t *testing.T) { 31 | t0 := time.Time{} 32 | t1 := time.Now() 33 | t2 := t1.Add(time.Second) 34 | t3 := t1.Add(-time.Minute) 35 | 36 | m1, m2 := MinMaxTime(t0, t1, t2, t3) 37 | assert.Equal(t, t0, m1) 38 | assert.Equal(t, t2, m2) 39 | 40 | m1, m2 = MinMaxTime(t1, t2, t3) 41 | assert.Equal(t, t3, m1) 42 | assert.Equal(t, t2, m2) 43 | } 44 | 45 | func Test_ExecDuration(t *testing.T) { 46 | val := 0 47 | assert.True(t, ExecDuration(func() { val = 1 }) > 0 && val == 1) 48 | val, dur := ExecDuration1(func() int { return 10 }) 49 | assert.True(t, dur >= 0 && val == 10) 50 | val, err, dur := ExecDuration2(func() (int, error) { return 100, nil }) 51 | assert.True(t, dur >= 0 && val == 100 && err == nil) 52 | val, val2, err, dur := ExecDuration3(func() (int, string, error) { return 123, "abc", nil }) 53 | assert.True(t, dur >= 0 && val == 123 && val2 == "abc" && err == nil) 54 | } 55 | -------------------------------------------------------------------------------- /tuples.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | type Tuple2[T1, T2 any] struct { 4 | Elem1 T1 5 | Elem2 T2 6 | } 7 | 8 | // Unpack unpacks elements of tuple, panic on nil pointer 9 | func (tuple2 *Tuple2[T1, T2]) Unpack() (T1, T2) { 10 | return tuple2.Elem1, tuple2.Elem2 11 | } 12 | 13 | type Tuple3[T1, T2, T3 any] struct { 14 | Elem1 T1 15 | Elem2 T2 16 | Elem3 T3 17 | } 18 | 19 | // Unpack unpacks elements of tuple, panic on nil pointer 20 | func (tuple3 *Tuple3[T1, T2, T3]) Unpack() (T1, T2, T3) { 21 | return tuple3.Elem1, tuple3.Elem2, tuple3.Elem3 22 | } 23 | 24 | type Tuple4[T1, T2, T3, T4 any] struct { 25 | Elem1 T1 26 | Elem2 T2 27 | Elem3 T3 28 | Elem4 T4 29 | } 30 | 31 | // Unpack unpacks elements of tuple, panic on nil pointer 32 | func (tuple4 *Tuple4[T1, T2, T3, T4]) Unpack() (T1, T2, T3, T4) { 33 | return tuple4.Elem1, tuple4.Elem2, tuple4.Elem3, tuple4.Elem4 34 | } 35 | 36 | type Tuple5[T1, T2, T3, T4, T5 any] struct { 37 | Elem1 T1 38 | Elem2 T2 39 | Elem3 T3 40 | Elem4 T4 41 | Elem5 T5 42 | } 43 | 44 | // Unpack unpacks elements of tuple, panic on nil pointer 45 | func (tuple5 *Tuple5[T1, T2, T3, T4, T5]) Unpack() (T1, T2, T3, T4, T5) { 46 | return tuple5.Elem1, tuple5.Elem2, tuple5.Elem3, tuple5.Elem4, tuple5.Elem5 47 | } 48 | -------------------------------------------------------------------------------- /tuples_test.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func Test_Tuple2_Unpack(t *testing.T) { 10 | t1 := Tuple2[int, string]{} 11 | elem1, elem2 := t1.Unpack() 12 | assert.Equal(t, 0, elem1) 13 | assert.Equal(t, "", elem2) 14 | 15 | t2 := &Tuple2[int, string]{Elem1: 1, Elem2: "a"} 16 | elem1, elem2 = t2.Unpack() 17 | assert.Equal(t, 1, elem1) 18 | assert.Equal(t, "a", elem2) 19 | } 20 | 21 | func Test_Tuple3_Unpack(t *testing.T) { 22 | t1 := Tuple3[int, string, *int]{} 23 | elem1, elem2, elem3 := t1.Unpack() 24 | assert.Equal(t, 0, elem1) 25 | assert.Equal(t, "", elem2) 26 | assert.Nil(t, elem3) 27 | 28 | t2 := &Tuple3[int, string, *int]{Elem1: 1, Elem2: "a", Elem3: New(2)} 29 | elem1, elem2, elem3 = t2.Unpack() 30 | assert.Equal(t, 1, elem1) 31 | assert.Equal(t, "a", elem2) 32 | assert.Equal(t, 2, *elem3) 33 | } 34 | 35 | func Test_Tuple4_Unpack(t *testing.T) { 36 | t1 := Tuple4[int, string, *int, bool]{} 37 | elem1, elem2, elem3, elem4 := t1.Unpack() 38 | assert.Equal(t, 0, elem1) 39 | assert.Equal(t, "", elem2) 40 | assert.Nil(t, elem3) 41 | assert.False(t, elem4) 42 | 43 | t2 := &Tuple4[int, string, *int, bool]{Elem1: 1, Elem2: "a", Elem3: New(2), Elem4: true} 44 | elem1, elem2, elem3, elem4 = t2.Unpack() 45 | assert.Equal(t, 1, elem1) 46 | assert.Equal(t, "a", elem2) 47 | assert.Equal(t, 2, *elem3) 48 | assert.True(t, elem4) 49 | } 50 | 51 | func Test_Tuple5_Unpack(t *testing.T) { 52 | t1 := Tuple5[int, string, *int, bool, any]{} 53 | elem1, elem2, elem3, elem4, elem5 := t1.Unpack() 54 | assert.Equal(t, 0, elem1) 55 | assert.Equal(t, "", elem2) 56 | assert.Nil(t, elem3) 57 | assert.False(t, elem4) 58 | assert.Equal(t, nil, elem5) 59 | 60 | t2 := &Tuple5[int, string, *int, bool, any]{Elem1: 1, Elem2: "a", Elem3: New(2), Elem4: true, Elem5: 123} 61 | elem1, elem2, elem3, elem4, elem5 = t2.Unpack() 62 | assert.Equal(t, 1, elem1) 63 | assert.Equal(t, "a", elem2) 64 | assert.Equal(t, 2, *elem3) 65 | assert.True(t, elem4) 66 | assert.Equal(t, 123, elem5) 67 | } 68 | -------------------------------------------------------------------------------- /types.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | type Int interface { 4 | int | int8 | int16 | int32 | int64 5 | } 6 | 7 | type IntExt interface { 8 | ~int | ~int8 | ~int16 | ~int32 | ~int64 9 | } 10 | 11 | type IntPtr interface { 12 | *int | *int8 | *int16 | *int32 | *int64 13 | } 14 | 15 | type UInt interface { 16 | uint | uint8 | uint16 | uint32 | uint64 | uintptr 17 | } 18 | 19 | type UIntExt interface { 20 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr 21 | } 22 | 23 | type UIntPtr interface { 24 | *uint | *uint8 | *uint16 | *uint32 | *uint64 | *uintptr 25 | } 26 | 27 | type Float interface { 28 | float32 | float64 29 | } 30 | 31 | type FloatExt interface { 32 | ~float32 | ~float64 33 | } 34 | 35 | type FloatPtr interface { 36 | *float32 | *float64 37 | } 38 | 39 | type Complex interface { 40 | complex64 | complex128 41 | } 42 | 43 | type ComplexExt interface { 44 | ~complex64 | ~complex128 45 | } 46 | 47 | type ComplexPtr interface { 48 | *complex64 | *complex128 49 | } 50 | 51 | type Number interface { 52 | Int | UInt | Float 53 | } 54 | 55 | type NumberExt interface { 56 | IntExt | UIntExt | FloatExt 57 | } 58 | 59 | type NumberPtr interface { 60 | IntPtr | UIntPtr | FloatPtr 61 | } 62 | 63 | type String interface { 64 | string 65 | } 66 | 67 | type StringExt interface { 68 | ~string 69 | } 70 | 71 | type StringPtr interface { 72 | *string 73 | } 74 | 75 | // Deprecated: Use IntExt 76 | type IntEx IntExt 77 | 78 | // Deprecated: Use UIntExt 79 | type UIntEx UIntExt 80 | 81 | // Deprecated: Use FloatExt 82 | type FloatEx FloatExt 83 | 84 | // Deprecated: Use NumberExt 85 | type NumberEx NumberExt 86 | 87 | // Deprecated: Use StringExt 88 | type StringEx StringExt 89 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | import "reflect" 4 | 5 | // If returns the 2nd arg if the condition is true, 3rd arg otherwise. 6 | // This is similar to C-language ternary operation (cond ? val1 : val2). 7 | // Deprecated: this function may cause unexpected behavior upon misuses. 8 | // 9 | // For example: gofn.If(len(slice) > 0, slice[0], defaultVal) will crash if slice is empty 10 | func If[C bool, T any](cond C, v1 T, v2 T) T { 11 | if cond { 12 | return v1 13 | } 14 | return v2 15 | } 16 | 17 | // FirstNonEmpty returns the first non-empty value in the given arguments if found, otherwise 18 | // returns the zero value. This function use reflection. 19 | // 20 | // Non-empty value must be not: 21 | // - zero value (0, "", nil, false) 22 | // - empty slice, array, map, channel 23 | // - pointer points to zero value 24 | func FirstNonEmpty[T any](args ...T) (val T) { 25 | for i := range args { 26 | if isNonEmptyValue(reflect.ValueOf(args[i])) { 27 | return args[i] 28 | } 29 | } 30 | return 31 | } 32 | 33 | func isNonEmptyValue(v reflect.Value) bool { 34 | if !v.IsValid() || v.IsZero() { 35 | return false 36 | } 37 | k := v.Kind() 38 | if k == reflect.Pointer || k == reflect.Interface { 39 | return isNonEmptyValue(v.Elem()) 40 | } 41 | if k == reflect.Slice || k == reflect.Array || k == reflect.Map || k == reflect.Chan { 42 | return v.Len() > 0 43 | } 44 | return true 45 | } 46 | 47 | // Deprecated: use FirstNonEmpty instead 48 | func FirstTrue[T any](args ...T) T { 49 | return FirstNonEmpty(args...) 50 | } 51 | 52 | // Coalesce returns the first non-zero value if found, otherwise returns zero. 53 | func Coalesce[T comparable](args ...T) (result T) { 54 | for _, v := range args { 55 | if v != result { 56 | return v 57 | } 58 | } 59 | return result 60 | } 61 | 62 | // Deprecated: use Coalesce instead 63 | func Or[T comparable](args ...T) (result T) { 64 | return Coalesce(args...) 65 | } 66 | 67 | func Must1(e error) { 68 | if e != nil { 69 | panic(e) 70 | } 71 | } 72 | 73 | func Must2[T any](v T, e error) T { 74 | if e != nil { 75 | panic(e) 76 | } 77 | return v 78 | } 79 | 80 | // Must is the same as Must2 81 | func Must[T any](v T, e error) T { 82 | if e != nil { 83 | panic(e) 84 | } 85 | return v 86 | } 87 | 88 | func Must3[T1, T2 any](v1 T1, v2 T2, e error) (T1, T2) { 89 | if e != nil { 90 | panic(e) 91 | } 92 | return v1, v2 93 | } 94 | 95 | func Must4[T1, T2, T3 any](v1 T1, v2 T2, v3 T3, e error) (T1, T2, T3) { 96 | if e != nil { 97 | panic(e) 98 | } 99 | return v1, v2, v3 100 | } 101 | 102 | func Must5[T1, T2, T3, T4 any](v1 T1, v2 T2, v3 T3, v4 T4, e error) (T1, T2, T3, T4) { 103 | if e != nil { 104 | panic(e) 105 | } 106 | return v1, v2, v3, v4 107 | } 108 | 109 | func Must6[T1, T2, T3, T4, T5 any](v1 T1, v2 T2, v3 T3, v4 T4, v5 T5, e error) (T1, T2, T3, T4, T5) { 110 | if e != nil { 111 | panic(e) 112 | } 113 | return v1, v2, v3, v4, v5 114 | } 115 | 116 | // ToPtr returns pointer to the address of the input 117 | func ToPtr[T any](t T) *T { 118 | return &t 119 | } 120 | 121 | // Deprecated: Use ToPtr instead 122 | func New[T any](t T) *T { 123 | return &t 124 | } 125 | 126 | // Head returns the first argument 127 | func Head[T any](t T, s ...any) T { 128 | return t 129 | } 130 | 131 | // Tail returns the last argument 132 | func Tail[T any](t any, s ...any) (T, bool) { 133 | v := t 134 | if len(s) > 0 { 135 | v = s[len(s)-1] 136 | } 137 | ret, ok := v.(T) 138 | return ret, ok 139 | } 140 | -------------------------------------------------------------------------------- /util_test.go: -------------------------------------------------------------------------------- 1 | package gofn 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func Test_If(t *testing.T) { 12 | x, y := 1, 2 13 | assert.Equal(t, 1, If(x < y, 1, 2)) 14 | assert.Equal(t, "b", If(x > y, "a", "b")) 15 | } 16 | 17 | func Test_FirstNonEmpty(t *testing.T) { 18 | assert.Equal(t, -1, FirstNonEmpty(0, 0, -1, 2, 3)) 19 | assert.Equal(t, "a", FirstNonEmpty("", "", "a", "b")) 20 | assert.Equal(t, " ", FirstNonEmpty("", "", " ", "b")) 21 | assert.Equal(t, []int{1}, FirstNonEmpty([]int{}, []int{}, nil, []int{1}, []int{2, 3})) 22 | assert.Equal(t, map[int]int{1: 1}, FirstNonEmpty(map[int]int{}, nil, map[int]int{1: 1}, map[int]int{2: 2})) 23 | assert.Nil(t, FirstNonEmpty[*int](nil, nil, nil)) 24 | int1, int2 := 1, 2 25 | assert.Equal(t, &int2, FirstNonEmpty[*int](nil, nil, &int2, &int1)) 26 | 27 | type Str string 28 | type A struct { 29 | I int 30 | S string 31 | SS []int 32 | } 33 | type B struct { 34 | I int 35 | A A 36 | } 37 | ch := make(chan int) 38 | dt := time.Time{} 39 | cp := complex(0, 0) 40 | var iface any 41 | assert.Equal(t, []int{1}, FirstNonEmpty[any](false, "", 0, 0.0, cp, Str(""), A{}, B{}, struct{}{}, 42 | nil, ch, dt, iface, &[]int{}, []string{}, map[int]int{}, []int{1}, "x")) 43 | } 44 | 45 | func Test_FirstTrue_Deprecated(t *testing.T) { 46 | assert.Equal(t, -1, FirstNonEmpty(0, 0, -1, 2, 3)) 47 | assert.Equal(t, "a", FirstNonEmpty("", "", "a", "b")) 48 | assert.Equal(t, " ", FirstNonEmpty("", "", " ", "b")) 49 | assert.Equal(t, []int{1}, FirstNonEmpty([]int{}, []int{}, nil, []int{1}, []int{2, 3})) 50 | assert.Equal(t, map[int]int{1: 1}, FirstNonEmpty(map[int]int{}, nil, map[int]int{1: 1}, map[int]int{2: 2})) 51 | assert.Nil(t, FirstNonEmpty[*int](nil, nil, nil)) 52 | } 53 | 54 | func Test_Coalesce(t *testing.T) { 55 | // Primitive types 56 | assert.Equal(t, false, Coalesce(false, false)) 57 | assert.Equal(t, true, Coalesce(false, true, false)) 58 | assert.Equal(t, 1, Coalesce(1)) 59 | assert.Equal(t, 1, Coalesce(1, 0, 2)) 60 | assert.Equal(t, -1.5, Coalesce[float64](0, -1.5)) 61 | assert.Equal(t, float32(0), Coalesce[float32](0, 0.0, 0)) 62 | assert.Equal(t, int64(-2), Coalesce[int64](0, 0, -2, 0)) 63 | assert.Equal(t, byte(1), Coalesce[byte](1, 0, 2, 0)) 64 | assert.Equal(t, "", Coalesce("", "")) 65 | assert.Equal(t, "f", Coalesce("", "f", "")) 66 | 67 | // Pointer to primitive types 68 | assert.Equal(t, (*int)(nil), Or[*int](nil, nil)) 69 | f1, f2 := float32(0), float32(1) 70 | assert.Equal(t, &f1, Or(nil, &f1, &f2, nil)) 71 | s1, s2 := "", "1" 72 | assert.Equal(t, &s1, Or(nil, &s1, &s2, nil)) 73 | 74 | // Derived type 75 | type X string 76 | assert.Equal(t, X("f"), Or[X]("", "f", "g")) 77 | } 78 | 79 | func Test_Or_Deprecated(t *testing.T) { 80 | assert.Equal(t, false, Or(false, false)) 81 | assert.Equal(t, true, Or(false, true, false)) 82 | assert.Equal(t, 1, Or(1)) 83 | assert.Equal(t, 1, Or(1, 0, 2)) 84 | assert.Equal(t, -1.5, Or[float64](0, -1.5)) 85 | assert.Equal(t, float32(0), Or[float32](0, 0.0, 0)) 86 | assert.Equal(t, int64(-2), Or[int64](0, 0, -2, 0)) 87 | assert.Equal(t, byte(1), Or[byte](1, 0, 2, 0)) 88 | assert.Equal(t, "", Or("", "")) 89 | assert.Equal(t, "f", Or("", "f", "")) 90 | } 91 | 92 | // nolint: goerr113, forcetypeassert 93 | func Test_Must1(t *testing.T) { 94 | Must1(func() error { return nil }()) 95 | 96 | // Panic case: error 97 | defer func() { 98 | e := recover() 99 | assert.True(t, e != nil && e.(error).Error() == "error 1") 100 | }() 101 | Must1(func() error { return errors.New("error 1") }()) 102 | } 103 | 104 | // nolint: goerr113, forcetypeassert 105 | func Test_Must2(t *testing.T) { 106 | assert.Equal(t, 1, Must2(func() (int, error) { return 1, nil }())) 107 | assert.Equal(t, "a", Must2(func() (string, error) { return "a", nil }())) 108 | 109 | // Panic case: error 110 | defer func() { 111 | e := recover() 112 | assert.True(t, e != nil && e.(error).Error() == "error 2") 113 | }() 114 | assert.Equal(t, 0, Must2(func() (int, error) { return 0, errors.New("error 2") }())) 115 | } 116 | 117 | // nolint: goerr113, forcetypeassert 118 | func Test_Must(t *testing.T) { 119 | assert.Equal(t, 1, Must(func() (int, error) { return 1, nil }())) 120 | assert.Equal(t, "a", Must(func() (string, error) { return "a", nil }())) 121 | } 122 | 123 | // nolint: goerr113, forcetypeassert 124 | func Test_Must3(t *testing.T) { 125 | v1, v2 := Must3(func() (int, bool, error) { return 1, true, nil }()) 126 | assert.True(t, v1 == 1 && v2 == true) 127 | 128 | // Panic case: error 129 | defer func() { 130 | e := recover() 131 | assert.True(t, e != nil && e.(error).Error() == "error 3") 132 | }() 133 | _, _ = Must3(func() (int, bool, error) { return 0, true, errors.New("error 3") }()) 134 | } 135 | 136 | // nolint: goerr113, forcetypeassert, dogsled 137 | func Test_Must4(t *testing.T) { 138 | v1, v2, v3 := Must4(func() (int, bool, string, error) { return 1, true, "x", nil }()) 139 | assert.True(t, v1 == 1 && v2 == true && v3 == "x") 140 | 141 | // Panic case: error 142 | defer func() { 143 | e := recover() 144 | assert.True(t, e != nil && e.(error).Error() == "error 4") 145 | }() 146 | _, _, _ = Must4(func() (int, bool, string, error) { return 0, true, "", errors.New("error 4") }()) 147 | } 148 | 149 | // nolint: goerr113, forcetypeassert, dogsled 150 | func Test_Must5(t *testing.T) { 151 | v1, v2, v3, v4 := Must5(func() (int, bool, string, float32, error) { return 1, true, "x", 2.1, nil }()) 152 | assert.True(t, v1 == 1 && v2 == true && v3 == "x" && v4 == 2.1) 153 | 154 | // Panic case: error 155 | defer func() { 156 | e := recover() 157 | assert.True(t, e != nil && e.(error).Error() == "error 5") 158 | }() 159 | _, _, _, _ = Must5(func() (int, bool, string, float32, error) { return 0, true, "", 2.1, errors.New("error 5") }()) 160 | } 161 | 162 | // nolint: goerr113, forcetypeassert, dogsled 163 | func Test_Must6(t *testing.T) { 164 | v1, v2, v3, v4, v5 := Must6(func() (int, bool, string, float32, int64, error) { return 1, true, "x", 2.1, 12, nil }()) 165 | assert.True(t, v1 == 1 && v2 == true && v3 == "x" && v4 == 2.1 && v5 == 12) 166 | 167 | // Panic case: error 168 | defer func() { 169 | e := recover() 170 | assert.True(t, e != nil && e.(error).Error() == "error 6") 171 | }() 172 | _, _, _, _, _ = Must6(func() (int, bool, string, float32, int64, error) { 173 | return 0, true, "", 2.1, 12, errors.New("error 6") 174 | }()) 175 | } 176 | 177 | func Test_ToPtr(t *testing.T) { 178 | assert.Equal(t, 3, *ToPtr(3)) 179 | assert.Equal(t, "abc", *ToPtr("abc")) 180 | } 181 | 182 | func Test_New_Deprecated(t *testing.T) { 183 | assert.Equal(t, 3, *New(3)) 184 | assert.Equal(t, "abc", *New("abc")) 185 | } 186 | 187 | func Test_Head(t *testing.T) { 188 | assert.Equal(t, 1, Head(1)) 189 | assert.Equal(t, 1, Head(1, 2.0, "3", 1)) 190 | } 191 | 192 | func Test_Tail(t *testing.T) { 193 | t1, _ := Tail[int](-1) 194 | assert.Equal(t, -1, t1) 195 | t2, _ := Tail[string](1, 2.0, "3", "-1") 196 | assert.Equal(t, "-1", t2) 197 | } 198 | --------------------------------------------------------------------------------