├── .codecov.yml ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_reqeust.yml └── workflows │ ├── license.yml │ └── test.yml ├── .gitignore ├── .licenserc.yaml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── README.zh-CN.md ├── collection ├── doc.go ├── set │ ├── set.go │ ├── set_example_test.go │ └── set_test.go ├── skipmap │ ├── flag.go │ ├── gen.go │ ├── gen.sh │ ├── gen_func.go │ ├── gen_ordered.go │ ├── gen_ordereddesc.go │ ├── json.go │ ├── oparray.go │ ├── oparray_test.go │ ├── skipmap.go │ ├── skipmap.tpl │ ├── skipmap_bench_test.go │ ├── skipmap_example_test.go │ ├── skipmap_test.go │ └── util.go ├── skipset │ ├── flag.go │ ├── flag_test.go │ ├── gen.go │ ├── gen.sh │ ├── gen_func.go │ ├── gen_ordered.go │ ├── gen_ordereddesc.go │ ├── oparray.go │ ├── oparray_test.go │ ├── skipset.go │ ├── skipset.tpl │ ├── skipset_bench_test.go │ ├── skipset_example_test.go │ ├── skipset_test.go │ └── util.go └── tuple │ ├── tuple.go │ ├── tuple_example_test.go │ └── tuple_test.go ├── gcond ├── gcond.go ├── gcond_bench_test.go ├── gcond_example_test.go └── gcond_test.go ├── gconv ├── gconv.go ├── gconv_example_test.go └── gconv_test.go ├── gfunc ├── README.md ├── gfunc.go ├── gfunc_example_test.go └── gfunc_test.go ├── gmap ├── gmap.go ├── gmap_example_test.go ├── gmap_go120_test.go └── gmap_test.go ├── go.mod ├── go.sum ├── goption ├── goption.go ├── goption_example_test.go └── goption_test.go ├── gptr ├── gptr.go ├── gptr_example_test.go └── gptr_test.go ├── gresult ├── gresult.go ├── gresult_example_test.go └── gresult_test.go ├── gslice ├── gslice.go ├── gslice_bench_test.go ├── gslice_example_test.go └── gslice_test.go ├── gson ├── gson.go ├── gson_codec.go ├── gson_codec_test.go ├── gson_example_test.go └── gson_test.go ├── gstd └── gsync │ ├── map.go │ ├── map_example_test.go │ ├── map_go120.go │ ├── map_go120_test.go │ ├── map_go123.go │ ├── map_go123_test.go │ ├── map_test.go │ ├── once.go │ ├── once_example_test.go │ ├── once_test.go │ ├── pool.go │ ├── pool_example_test.go │ └── pool_test.go ├── gvalue ├── gvalue.go ├── gvalue_example_test.go └── gvalue_test.go └── internal ├── assert └── assert.go ├── constraints ├── constraints.go ├── ordered_go118.go └── ordered_go121.go ├── conv ├── bytesconv_go118.go └── bytesconv_go121.go ├── fastrand ├── fastrand.go ├── fastrand_test.go ├── runtime_go118.go ├── runtime_go119.go └── runtime_go122.go ├── heapsort └── sort.go ├── iter ├── README.md ├── helper.go ├── helper_test.go ├── iter.go ├── iter_test.go ├── operations.go ├── operations_bench_test.go ├── operations_test.go ├── sinks.go ├── sinks_test.go ├── sources.go └── sources_test.go ├── jsonbuilder ├── array.go ├── array_test.go ├── dict.go └── dict_test.go ├── rtassert └── rtassert.go └── stream ├── README.md ├── bool.go ├── bool_gen.go ├── bool_test.go ├── comparable.go ├── comparable_gen.go ├── comparable_test.go ├── gen.go ├── gen.sh ├── kv.go ├── kv_gen.go ├── kv_test.go ├── number.go ├── number_gen.go ├── orderable.go ├── orderable_gen.go ├── orderable_test.go ├── orderablekv.go ├── orderablekv_gen.go ├── orderablekv_test.go ├── stream.go ├── stream_test.go ├── string.go ├── string_gen.go └── string_test.go /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | target: auto 6 | threshold: 0.5% 7 | patch: 8 | default: 9 | target: 90% 10 | threshold: 0% 11 | 12 | ignore: 13 | - "internal" 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report. 3 | title: "[Bug]: " 4 | labels: ["bug"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for taking the time to fill out this bug report! 10 | 11 | - type: input 12 | attributes: 13 | label: Operating System 14 | description: What operating system are you using? 15 | placeholder: "Example: Debian GNU/Linux 9 (stretch)" 16 | validations: 17 | required: true 18 | - type: input 19 | attributes: 20 | label: Go Version 21 | description: What version of golang are you using? 22 | placeholder: "Example: go1.24.2 darwin/arm64" 23 | validations: 24 | required: true 25 | - type: input 26 | attributes: 27 | label: Package Version 28 | description: What version of gg are you using? 29 | placeholder: "Example: main/v1.0.0" 30 | validations: 31 | required: true 32 | 33 | - type: textarea 34 | attributes: 35 | label: Reproduction Steps 36 | description: How do you trigger this bug? Please walk us through it step by step. 37 | placeholder: | 38 | 1. 39 | 2. 40 | 3. 41 | ... 42 | validations: 43 | required: true 44 | - type: textarea 45 | attributes: 46 | label: Expected Behavior 47 | description: What did you expect to happen? 48 | validations: 49 | required: true 50 | - type: textarea 51 | attributes: 52 | label: Actual Behavior 53 | description: What actually happened? 54 | validations: 55 | required: true 56 | - type: textarea 57 | attributes: 58 | label: Screenshots 59 | description: If applicable, add screenshots to help explain your problem. 60 | - type: textarea 61 | attributes: 62 | label: Additional Context 63 | description: Add any other context about the problem here. 64 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Security Bug Report 4 | url: https://github.com/bytedance/gg/blob/develop/CONTRIBUTING.md#3-security-bugs 5 | about: Please do not report the safe disclosure of bugs to public issues. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_reqeust.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: File a feature request 3 | labels: ["enhancement"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for taking the time to fill out this feature request! 9 | 10 | - type: textarea 11 | attributes: 12 | label: Summary 13 | description: Please give a one-paragraph explanation of the feature. 14 | validations: 15 | required: true 16 | 17 | - type: textarea 18 | attributes: 19 | label: Motivation 20 | description: Why do you want to see this feature? 21 | validations: 22 | required: true 23 | 24 | - type: textarea 25 | attributes: 26 | label: Explanation 27 | description: | 28 | Please explain the proposal as if it was already included in the project and you 29 | were teaching it to another programmer. That generally means: 30 | 31 | - Introducing new named concepts. 32 | - Explaining the feature largely in terms of examples. 33 | - If applicable, provide sample error messages, deprecation warnings, or 34 | migration guidance. 35 | 36 | *If this is a small feature, you may omit this section.* 37 | -------------------------------------------------------------------------------- /.github/workflows/license.yml: -------------------------------------------------------------------------------- 1 | name: LicenseEye 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | license-header-check: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: Check License Header 16 | uses: apache/skywalking-eyes/header@v0.7.0 -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Go Test and Benchmark 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | go-version: [1.18.x, 1.19.x, 1.20.x, 1.21.x, 1.22.x, 1.23.x, 1.24.x] 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Set up Go ${{ matrix.go-version }} 20 | uses: actions/setup-go@v5 21 | with: 22 | go-version: ${{ matrix.go-version }} 23 | check-latest: true 24 | 25 | - name: Cache Go modules 26 | uses: actions/cache@v4 27 | with: 28 | path: | 29 | ~/go/pkg/mod 30 | ~/.cache/go-build 31 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 32 | restore-keys: | 33 | ${{ runner.os }}-go- 34 | 35 | - name: Install dependencies 36 | run: go mod tidy 37 | 38 | - name: Run unit tests 39 | run: go test -v -race -coverprofile=coverage.txt -covermode=atomic ./... 40 | 41 | - name: Run benchmarks 42 | if: matrix.go-version == '1.24.x' 43 | run: go test -v -bench=. -benchmem ./... 44 | 45 | - name: Upload coverage report 46 | uses: codecov/codecov-action@v5 47 | with: 48 | fail_ci_if_error: false 49 | files: coverage.txt 50 | flags: go-${{ matrix.go-version }},unittests 51 | name: go-${{ matrix.go-version }} 52 | token: ${{ secrets.CODECOV_TOKEN }} 53 | verbose: true 54 | -------------------------------------------------------------------------------- /.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 | coverage.txt 14 | 15 | # Dependency directories (remove the comment below to include it) 16 | # vendor/ 17 | 18 | # IDE or Editor's config 19 | .DS_Store 20 | .idea/ 21 | 22 | # Go 23 | go.work 24 | go.work.sum 25 | *.pprof 26 | testdata 27 | -------------------------------------------------------------------------------- /.licenserc.yaml: -------------------------------------------------------------------------------- 1 | header: 2 | license: 3 | spdx-id: Apache-2.0 4 | copyright-owner: Bytedance Ltd. 5 | 6 | paths: 7 | - '**/*.go' 8 | - '**/*.s' 9 | 10 | paths-ignore: 11 | - 'internal/constraints/constraints.go' 12 | 13 | comment: on-failure -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | ## Your First Pull Request 4 | We use GitHub for our codebase. You can start by reading [How To Pull Request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests). 5 | 6 | ## Branch Organization 7 | We use [git-flow](https://nvie.com/posts/a-successful-git-branching-model/) as our branch organization, as known as [FDD](https://en.wikipedia.org/wiki/Feature-driven_development) 8 | 9 | ## Bugs 10 | ### 1. How to Find Known Issues 11 | We are using [GitHub Issues](https://github.com/bytedance/gg/issues) for our public bugs. We keep a close eye on this and try to make it clear when we have an internal fix in progress. Before filing a new task, try to make sure your problem doesn’t already exist. 12 | 13 | ### 2. Reporting New Issues 14 | Providing a reduced test code is a recommended way for reporting issues. Then can place in: 15 | - Just in issues 16 | - [Golang Playground](https://play.golang.org/) 17 | 18 | ### 3. Security Bugs 19 | Please do not report the safe disclosure of bugs to public issues. Contact us by [Support Email](mailto:kouzhigang@bytedance.com) 20 | 21 | ## How to Get in Touch 22 | - [Email](mailto:kouzhigang@bytedance.com) 23 | 24 | ## Submit a Pull Request 25 | Before you submit your Pull Request (PR), consider the following guidelines: 26 | 1. Search [GitHub](https://github.com/bytedance/gg/pulls) for an open or closed PR that relates to your submission. You don't want to duplicate existing efforts. 27 | 2. Be sure that an issue describes the problem you're fixing, or documents the design for the feature you'd like to add. Discussing the design upfront helps to ensure that we're ready to accept your work. 28 | 3. [Fork](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo) the bytedance/gg repo. 29 | 4. In your forked repository, make your changes in a new git branch: 30 | ``` 31 | git checkout -b bugfix/security_bug 32 | ``` 33 | 5. Create your patch, including appropriate test cases. 34 | 6. Follow our [Style Guides](#code-style-guides). 35 | 7. Commit your changes using a descriptive commit message that follows [AngularJS Git Commit Message Conventions](https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit). 36 | Adherence to these conventions is necessary because release notes are automatically generated from these messages. 37 | 8. Push your branch to GitHub: 38 | ``` 39 | git push origin bugfix/security_bug 40 | ``` 41 | 9. In GitHub, send a pull request to `gg:main` 42 | 43 | Note: you must use one of `optimize/feature/bugfix/doc/ci/test/refactor` following a slash(`/`) as the branch prefix. 44 | 45 | Your pr title and commit message should follow https://www.conventionalcommits.org/. 46 | 47 | ## Contribution Prerequisites 48 | - Our development environment keeps up with [Go Official](https://golang.org/project/). 49 | - You need to fully check with lint tools before submit your pull request. [gofmt](https://golang.org/pkg/cmd/gofmt/) and [golangci-lint](https://github.com/golangci/golangci-lint). 50 | - You are familiar with [GitHub](https://github.com). 51 | - Maybe you need familiar with [Actions](https://github.com/features/actions)(our default workflow tool). 52 | 53 | ## Code Style Guides 54 | See [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments). 55 | 56 | Good resources: 57 | - [Effective Go](https://golang.org/doc/effective_go) 58 | - [PingCAP Style Guide](https://pingcap.github.io/style-guide/general.html) 59 | - [Uber Go Style Guide](https://github.com/uber-go/guide/blob/master/style.md) 60 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default: test 2 | 3 | .PHONY: test 4 | test: 5 | go test -v -race -coverprofile=cover.out -covermode=atomic ./... 6 | go tool cover -html=cover.out 7 | 8 | .PHONY: bench 9 | bench: 10 | go test ./... -run=NOTEST -bench . -benchmem 11 | 12 | .PHONY: gen 13 | gen: 14 | cd ./internal/stream && ./gen.sh 15 | cd ./collection/skipmap && ./gen.sh 16 | cd ./collection/skipset && ./gen.sh 17 | 18 | .PHONY: license 19 | license: 20 | license-eye -c .licenserc.yaml header fix -------------------------------------------------------------------------------- /collection/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package collection provides implementations of generics data structures. 16 | package collection 17 | -------------------------------------------------------------------------------- /collection/set/set_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package set 16 | 17 | import ( 18 | "fmt" 19 | ) 20 | 21 | func Example() { 22 | s := New(10, 10, 12, 15) 23 | fmt.Println(s.Len()) // 3 24 | fmt.Println(s.Add(10)) // false 25 | fmt.Println(s.Add(11)) // true 26 | fmt.Println(s.Remove(11) && s.Remove(12)) // true 27 | fmt.Println(s) // set[10 15] 28 | 29 | fmt.Println(s.ContainsAny(10, 15)) // true 30 | fmt.Println(s.ContainsAny(11, 12)) // false 31 | fmt.Println(s.ContainsAny()) // false 32 | fmt.Println(s.ContainsAll(10, 15)) // true 33 | fmt.Println(s.ContainsAll(10, 11)) // false 34 | fmt.Println(s.ContainsAll()) // true 35 | 36 | fmt.Println(len(s.ToSlice())) // 2 37 | 38 | // Output: 39 | // 3 40 | // false 41 | // true 42 | // true 43 | // set[10 15] 44 | // true 45 | // false 46 | // false 47 | // true 48 | // false 49 | // true 50 | // 2 51 | } 52 | -------------------------------------------------------------------------------- /collection/skipmap/flag.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package skipmap 16 | 17 | import "sync/atomic" 18 | 19 | const ( 20 | fullyLinked = 1 << iota 21 | marked 22 | ) 23 | 24 | // concurrent-safe bitflag. 25 | type bitflag struct { 26 | data uint32 27 | } 28 | 29 | func (f *bitflag) SetTrue(flags uint32) { 30 | for { 31 | old := atomic.LoadUint32(&f.data) 32 | if old&flags != flags { 33 | // Flag is 0, need set it to 1. 34 | n := old | flags 35 | if atomic.CompareAndSwapUint32(&f.data, old, n) { 36 | return 37 | } 38 | continue 39 | } 40 | return 41 | } 42 | } 43 | 44 | func (f *bitflag) SetFalse(flags uint32) { 45 | for { 46 | old := atomic.LoadUint32(&f.data) 47 | check := old & flags 48 | if check != 0 { 49 | // Flag is 1, need set it to 0. 50 | n := old ^ check 51 | if atomic.CompareAndSwapUint32(&f.data, old, n) { 52 | return 53 | } 54 | continue 55 | } 56 | return 57 | } 58 | } 59 | 60 | func (f *bitflag) Get(flag uint32) bool { 61 | return (atomic.LoadUint32(&f.data) & flag) != 0 62 | } 63 | 64 | func (f *bitflag) MGet(check, expect uint32) bool { 65 | return (atomic.LoadUint32(&f.data) & check) == expect 66 | } 67 | -------------------------------------------------------------------------------- /collection/skipmap/gen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build ignore 16 | // +build ignore 17 | 18 | package main 19 | 20 | import ( 21 | "bytes" 22 | _ "embed" 23 | "fmt" 24 | "go/format" 25 | "log" 26 | "os" 27 | "text/template" 28 | ) 29 | 30 | // Inspired by sort/gen_sort_variants.go 31 | type Variant struct { 32 | // Package is the package name. 33 | Package string 34 | 35 | // Name is the variant name: should be unique among variants. 36 | Name string 37 | 38 | // Path is the file path into which the generator will emit the code for this 39 | // variant. 40 | Path string 41 | 42 | // Imports is the imports needed for this package. 43 | Imports string 44 | 45 | StructPrefix string 46 | StructPrefixLow string 47 | StructSuffix string 48 | ExtraFields string 49 | 50 | // Basic key and value type. 51 | KeyType string 52 | ValueType string 53 | 54 | // Basic type argument. 55 | TypeArgument string 56 | 57 | // TypeParam is the optional type parameter for the function. 58 | TypeParam string // e.g. [T any] 59 | 60 | // Funcs is a map of functions used from within the template. The following 61 | // functions are expected to exist: 62 | Funcs template.FuncMap 63 | } 64 | 65 | type TypeReplacement struct { 66 | Type string 67 | Desc string 68 | } 69 | 70 | func main() { 71 | // For New. 72 | base := &Variant{ 73 | Package: "skipmap", 74 | Name: "ordered", 75 | Path: "gen_ordered.go", 76 | Imports: "\"sync\"\n\"sync/atomic\"\n\"unsafe\"\n\n\"github.com/bytedance/gg/internal/constraints\"\n", 77 | KeyType: "keyT", 78 | ValueType: "valueT", 79 | TypeArgument: "[keyT, valueT]", 80 | TypeParam: "[keyT constraints.Ordered, valueT any]", 81 | StructPrefix: "Ordered", 82 | StructPrefixLow: "ordered", 83 | StructSuffix: "", 84 | Funcs: template.FuncMap{ 85 | "Less": func(i, j string) string { 86 | return fmt.Sprintf("(%s < %s)", i, j) 87 | }, 88 | "Equal": func(i, j string) string { 89 | return fmt.Sprintf("%s == %s", i, j) 90 | }, 91 | }, 92 | } 93 | generate(base) 94 | base.Name += "Desc" 95 | base.StructSuffix += "Desc" 96 | base.Path = "gen_ordereddesc.go" 97 | base.Funcs = template.FuncMap{ 98 | "Less": func(i, j string) string { 99 | return fmt.Sprintf("(%s > %s)", i, j) 100 | }, 101 | "Equal": func(i, j string) string { 102 | return fmt.Sprintf("%s == %s", i, j) 103 | }, 104 | } 105 | generate(base) 106 | 107 | // For NewFunc. 108 | basefunc := &Variant{ 109 | Package: "skipmap", 110 | Name: "func", 111 | Path: "gen_func.go", 112 | Imports: "\"sync\"\n\"sync/atomic\"\n\"unsafe\"\n", 113 | KeyType: "keyT", 114 | ValueType: "valueT", 115 | TypeArgument: "[keyT, valueT]", 116 | TypeParam: "[keyT any, valueT any]", 117 | ExtraFields: "\nless func(a,b keyT)bool\n", 118 | StructPrefix: "Func", 119 | StructPrefixLow: "func", 120 | StructSuffix: "", 121 | Funcs: template.FuncMap{ 122 | "Less": func(i, j string) string { 123 | return fmt.Sprintf("s.less(%s,%s)", i, j) 124 | }, 125 | "Equal": func(i, j string) string { 126 | return fmt.Sprintf("!s.less(%s,%s)", j, i) 127 | }, 128 | }, 129 | } 130 | generate(basefunc) 131 | } 132 | 133 | // generate generates the code for variant `v` into a file named by `v.Path`. 134 | func generate(v *Variant) { 135 | // Parse templateCode anew for each variant because Parse requires Funcs to be 136 | // registered, and it helps type-check the funcs. 137 | tmpl, err := template.New("gen").Funcs(v.Funcs).Parse(templateCode) 138 | if err != nil { 139 | log.Fatal("template Parse:", err) 140 | } 141 | 142 | var out bytes.Buffer 143 | err = tmpl.Execute(&out, v) 144 | if err != nil { 145 | log.Fatal("template Execute:", err) 146 | } 147 | 148 | os.WriteFile(v.Path, out.Bytes(), 0644) 149 | 150 | formatted, err := format.Source(out.Bytes()) 151 | if err != nil { 152 | println(string(out.Bytes())) 153 | log.Fatal("format:", err) 154 | } 155 | 156 | if err := os.WriteFile(v.Path, formatted, 0644); err != nil { 157 | log.Fatal("WriteFile:", err) 158 | } 159 | } 160 | 161 | //go:embed skipmap.tpl 162 | var templateCode string 163 | -------------------------------------------------------------------------------- /collection/skipmap/gen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | go run gen.go 4 | -------------------------------------------------------------------------------- /collection/skipmap/json.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package skipmap 16 | 17 | import ( 18 | "encoding" 19 | "encoding/json" 20 | "fmt" 21 | "reflect" 22 | "strconv" 23 | 24 | "github.com/bytedance/gg/internal/jsonbuilder" 25 | ) 26 | 27 | // MarshalJSON returns s as the JSON encoding of s. 28 | func (s *OrderedMap[keyT, valueT]) MarshalJSON() ([]byte, error) { 29 | if s == nil { 30 | return []byte("null"), nil 31 | } 32 | 33 | enc := jsonbuilder.NewDict() 34 | var err error 35 | s.Range(func(key keyT, value valueT) bool { 36 | err = enc.Store(key, value) 37 | return err == nil 38 | }) 39 | if err != nil { 40 | return nil, err 41 | } 42 | return enc.Build() 43 | } 44 | 45 | // UnmarshalJSON sets *s to a copy of data. 46 | func (s *OrderedMap[keyT, valueT]) UnmarshalJSON(data []byte) error { 47 | m := make(map[keyT]valueT) 48 | if err := json.Unmarshal(data, &m); err != nil { 49 | return err 50 | } 51 | for k, v := range m { 52 | s.Store(k, v) 53 | } 54 | return nil 55 | } 56 | 57 | // MarshalJSON returns s as the JSON encoding of s. 58 | func (s *OrderedMapDesc[keyT, valueT]) MarshalJSON() ([]byte, error) { 59 | if s == nil { 60 | return []byte("null"), nil 61 | } 62 | 63 | enc := jsonbuilder.NewDict() 64 | var err error 65 | s.Range(func(key keyT, value valueT) bool { 66 | err = enc.Store(key, value) 67 | return err == nil 68 | }) 69 | if err != nil { 70 | return nil, err 71 | } 72 | return enc.Build() 73 | } 74 | 75 | // UnmarshalJSON sets *s to a copy of data. 76 | func (s *OrderedMapDesc[keyT, valueT]) UnmarshalJSON(data []byte) error { 77 | m := make(map[keyT]valueT) 78 | if err := json.Unmarshal(data, &m); err != nil { 79 | return err 80 | } 81 | for k, v := range m { 82 | s.Store(k, v) 83 | } 84 | return nil 85 | } 86 | 87 | // MarshalJSON returns s as the JSON encoding of s. 88 | func (s *FuncMap[keyT, valueT]) MarshalJSON() ([]byte, error) { 89 | if s == nil { 90 | return []byte("null"), nil 91 | } 92 | 93 | enc := jsonbuilder.NewDict() 94 | var err error 95 | s.Range(func(key keyT, value valueT) bool { 96 | err = enc.Store(key, value) 97 | return err == nil 98 | }) 99 | if err != nil { 100 | return nil, err 101 | } 102 | return enc.Build() 103 | } 104 | 105 | // UnmarshalJSON sets *s to a copy of data. 106 | func (s *FuncMap[keyT, valueT]) UnmarshalJSON(data []byte) error { 107 | var ( 108 | m = make(map[string]valueT) 109 | zk keyT 110 | unmarshalKey func(string) (keyT, error) 111 | ) 112 | 113 | if err := json.Unmarshal(data, &m); err != nil { 114 | return err 115 | } 116 | 117 | // See also: [encoding/json.(*decodeState).object] 118 | if _, ok := any(&zk).(encoding.TextUnmarshaler); ok { 119 | unmarshalKey = func(s string) (keyT, error) { 120 | var key keyT 121 | // TODO: Unsafe conv 122 | err := any(&key).(encoding.TextUnmarshaler).UnmarshalText([]byte(s)) 123 | return key, err 124 | } 125 | } else { 126 | rk := reflect.ValueOf(zk) 127 | kt := rk.Type() 128 | switch rk.Kind() { 129 | case reflect.String: 130 | unmarshalKey = func(s string) (keyT, error) { 131 | return reflect.ValueOf(s).Convert(kt).Interface().(keyT), nil 132 | } 133 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 134 | unmarshalKey = func(s string) (keyT, error) { 135 | n, err := strconv.ParseInt(s, 10, 64) 136 | if err != nil { 137 | return zk, err 138 | } 139 | if rk.OverflowInt(n) { 140 | return zk, fmt.Errorf("%s overflow type %T", s, zk) 141 | } 142 | return reflect.ValueOf(n).Convert(kt).Interface().(keyT), nil 143 | } 144 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 145 | unmarshalKey = func(s string) (keyT, error) { 146 | n, err := strconv.ParseUint(s, 10, 64) 147 | if err != nil { 148 | return zk, err 149 | } 150 | if rk.OverflowUint(n) { 151 | return zk, fmt.Errorf("%s overflows type %T", s, zk) 152 | } 153 | return reflect.ValueOf(n).Convert(kt).Interface().(keyT), nil 154 | } 155 | default: 156 | return fmt.Errorf("unexpected key type: %T", zk) 157 | } 158 | } 159 | 160 | for ks, v := range m { 161 | k, err := unmarshalKey(ks) 162 | if err != nil { 163 | return err 164 | } 165 | s.Store(k, v) 166 | } 167 | 168 | return nil 169 | } 170 | -------------------------------------------------------------------------------- /collection/skipmap/oparray.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package skipmap 16 | 17 | import ( 18 | "sync/atomic" 19 | "unsafe" 20 | ) 21 | 22 | const ( 23 | op1 = 4 24 | op2 = maxLevel - op1 25 | ) 26 | 27 | type optionalArray struct { 28 | base [op1]unsafe.Pointer 29 | extra *([op2]unsafe.Pointer) 30 | } 31 | 32 | func (a *optionalArray) load(i int) unsafe.Pointer { 33 | if i < op1 { 34 | return a.base[i] 35 | } 36 | return a.extra[i-op1] 37 | } 38 | 39 | func (a *optionalArray) store(i int, p unsafe.Pointer) { 40 | if i < op1 { 41 | a.base[i] = p 42 | return 43 | } 44 | a.extra[i-op1] = p 45 | } 46 | 47 | func (a *optionalArray) atomicLoad(i int) unsafe.Pointer { 48 | if i < op1 { 49 | return atomic.LoadPointer(&a.base[i]) 50 | } 51 | return atomic.LoadPointer(&a.extra[i-op1]) 52 | } 53 | 54 | func (a *optionalArray) atomicStore(i int, p unsafe.Pointer) { 55 | if i < op1 { 56 | atomic.StorePointer(&a.base[i], p) 57 | return 58 | } 59 | atomic.StorePointer(&a.extra[i-op1], p) 60 | } 61 | -------------------------------------------------------------------------------- /collection/skipmap/oparray_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package skipmap 16 | 17 | import ( 18 | "testing" 19 | "unsafe" 20 | 21 | "github.com/bytedance/gg/internal/fastrand" 22 | ) 23 | 24 | type dummy struct { 25 | data optionalArray 26 | } 27 | 28 | func TestOpArray(t *testing.T) { 29 | n := new(dummy) 30 | n.data.extra = new([op2]unsafe.Pointer) 31 | 32 | var array [maxLevel]unsafe.Pointer 33 | for i := 0; i < maxLevel; i++ { 34 | value := unsafe.Pointer(&dummy{}) 35 | array[i] = value 36 | n.data.store(i, value) 37 | } 38 | 39 | for i := 0; i < maxLevel; i++ { 40 | if array[i] != n.data.load(i) || array[i] != n.data.atomicLoad(i) { 41 | t.Fatal(i, array[i], n.data.load(i)) 42 | } 43 | } 44 | 45 | for i := 0; i < 1000; i++ { 46 | r := int(fastrand.Uint32n(maxLevel)) 47 | value := unsafe.Pointer(&dummy{}) 48 | if i%100 == 0 { 49 | value = nil 50 | } 51 | array[r] = value 52 | if fastrand.Uint32n(2) == 0 { 53 | n.data.store(r, value) 54 | } else { 55 | n.data.atomicStore(r, value) 56 | } 57 | } 58 | 59 | for i := 0; i < maxLevel; i++ { 60 | if array[i] != n.data.load(i) || array[i] != n.data.atomicLoad(i) { 61 | t.Fatal(i, array[i], n.data.load(i)) 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /collection/skipmap/skipmap.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package skipmap is a high-performance, scalable, concurrent-safe map based on skip-list. 16 | // In the typical pattern(100000 operations, 90%LOAD 9%STORE 1%DELETE, 8C16T), the skipmap 17 | // up to 10x faster than the built-in sync.Map. 18 | // 19 | //go:generate go run gen.go 20 | package skipmap 21 | 22 | import "github.com/bytedance/gg/internal/constraints" 23 | 24 | // NewFunc returns an empty skipmap in ascending order. 25 | // 26 | // Note that the less function requires a strict weak ordering, 27 | // see https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings, 28 | // or undefined behavior will happen. 29 | func NewFunc[keyT any, valueT any](less func(a, b keyT) bool) *FuncMap[keyT, valueT] { 30 | var ( 31 | t1 keyT 32 | t2 valueT 33 | ) 34 | h := newFuncNode(t1, t2, maxLevel) 35 | h.flags.SetTrue(fullyLinked) 36 | return &FuncMap[keyT, valueT]{ 37 | header: h, 38 | highestLevel: defaultHighestLevel, 39 | less: less, 40 | } 41 | } 42 | 43 | // New returns an empty skipmap in ascending order. 44 | func New[keyT constraints.Ordered, valueT any]() *OrderedMap[keyT, valueT] { 45 | var ( 46 | t1 keyT 47 | t2 valueT 48 | ) 49 | h := newOrderedNode(t1, t2, maxLevel) 50 | h.flags.SetTrue(fullyLinked) 51 | return &OrderedMap[keyT, valueT]{ 52 | header: h, 53 | highestLevel: defaultHighestLevel, 54 | } 55 | } 56 | 57 | // NewDesc returns an empty skipmap in descending order. 58 | func NewDesc[keyT constraints.Ordered, valueT any]() *OrderedMapDesc[keyT, valueT] { 59 | var ( 60 | t1 keyT 61 | t2 valueT 62 | ) 63 | h := newOrderedNodeDesc(t1, t2, maxLevel) 64 | h.flags.SetTrue(fullyLinked) 65 | return &OrderedMapDesc[keyT, valueT]{ 66 | header: h, 67 | highestLevel: defaultHighestLevel, 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /collection/skipmap/skipmap_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package skipmap 16 | 17 | import ( 18 | "fmt" 19 | "strconv" 20 | "sync" 21 | 22 | "github.com/bytedance/gg/gson" 23 | ) 24 | 25 | func Example() { 26 | s := New[string, int]() 27 | s.Store("a", 0) 28 | s.Store("a", 1) 29 | s.Store("b", 2) 30 | s.Store("c", 3) 31 | fmt.Println(s.Len()) // 3 32 | 33 | fmt.Println(s.Load("a")) // 1 true 34 | fmt.Println(s.LoadAndDelete("a")) // 1 true 35 | fmt.Println(s.LoadOrStore("a", 11)) // 11 false 36 | 37 | fmt.Println(gson.ToString(s.ToMap())) // {"a":11, "b":2, "c": 3} 38 | 39 | s.Delete("a") 40 | s.Delete("b") 41 | s.Delete("c") 42 | var wg sync.WaitGroup 43 | wg.Add(1000) 44 | for i := 0; i < 1000; i++ { 45 | i := i 46 | go func() { 47 | defer wg.Done() 48 | s.Store(strconv.Itoa(i), i) 49 | }() 50 | } 51 | wg.Wait() 52 | fmt.Println(s.Len()) // 1000 53 | 54 | // Output: 55 | // 3 56 | // 1 true 57 | // 1 true 58 | // 11 false 59 | // {"a":11,"b":2,"c":3} 60 | // 1000 61 | } 62 | -------------------------------------------------------------------------------- /collection/skipmap/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package skipmap 16 | 17 | import ( 18 | "github.com/bytedance/gg/internal/fastrand" 19 | ) 20 | 21 | const ( 22 | maxLevel = 16 23 | p = 0.25 24 | defaultHighestLevel = 3 25 | ) 26 | 27 | func randomLevel() int { 28 | level := 1 29 | for fastrand.Uint32n(1/p) == 0 { 30 | level++ 31 | } 32 | if level > maxLevel { 33 | return maxLevel 34 | } 35 | return level 36 | } 37 | -------------------------------------------------------------------------------- /collection/skipset/flag.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package skipset 16 | 17 | import "sync/atomic" 18 | 19 | const ( 20 | fullyLinked = 1 << iota 21 | marked 22 | ) 23 | 24 | type bitflag struct { 25 | data uint32 26 | } 27 | 28 | func (f *bitflag) SetTrue(flags uint32) { 29 | for { 30 | old := atomic.LoadUint32(&f.data) 31 | if old&flags != flags { 32 | // Flag is 0, need set it to 1. 33 | n := old | flags 34 | if atomic.CompareAndSwapUint32(&f.data, old, n) { 35 | return 36 | } 37 | continue 38 | } 39 | return 40 | } 41 | } 42 | 43 | func (f *bitflag) SetFalse(flags uint32) { 44 | for { 45 | old := atomic.LoadUint32(&f.data) 46 | check := old & flags 47 | if check != 0 { 48 | // Flag is 1, need set it to 0. 49 | n := old ^ check 50 | if atomic.CompareAndSwapUint32(&f.data, old, n) { 51 | return 52 | } 53 | continue 54 | } 55 | return 56 | } 57 | } 58 | 59 | func (f *bitflag) Get(flag uint32) bool { 60 | return (atomic.LoadUint32(&f.data) & flag) != 0 61 | } 62 | 63 | func (f *bitflag) MGet(check, expect uint32) bool { 64 | return (atomic.LoadUint32(&f.data) & check) == expect 65 | } 66 | -------------------------------------------------------------------------------- /collection/skipset/flag_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package skipset 16 | 17 | import ( 18 | "testing" 19 | ) 20 | 21 | func TestFlag(t *testing.T) { 22 | // Correctness. 23 | const ( 24 | f0 = 1 << iota 25 | f1 26 | f2 27 | f3 28 | f4 29 | f5 30 | f6 31 | f7 32 | ) 33 | x := &bitflag{} 34 | 35 | x.SetTrue(f1 | f3) 36 | if x.Get(f0) || !x.Get(f1) || x.Get(f2) || !x.Get(f3) || !x.MGet(f0|f1|f2|f3, f1|f3) { 37 | t.Fatal("invalid") 38 | } 39 | x.SetTrue(f1) 40 | x.SetTrue(f1 | f3) 41 | if x.data != f1+f3 { 42 | t.Fatal("invalid") 43 | } 44 | 45 | x.SetFalse(f1 | f2) 46 | if x.Get(f0) || x.Get(f1) || x.Get(f2) || !x.Get(f3) || !x.MGet(f0|f1|f2|f3, f3) { 47 | t.Fatal("invalid") 48 | } 49 | x.SetFalse(f1 | f2) 50 | if x.data != f3 { 51 | t.Fatal("invalid") 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /collection/skipset/gen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build ignore 16 | // +build ignore 17 | 18 | package main 19 | 20 | import ( 21 | "bytes" 22 | _ "embed" 23 | "fmt" 24 | "go/format" 25 | "log" 26 | "os" 27 | "text/template" 28 | ) 29 | 30 | // Inspired by sort/gen_sort_variants.go 31 | type Variant struct { 32 | // Package is the package name. 33 | Package string 34 | 35 | // Name is the variant name: should be unique among variants. 36 | Name string 37 | 38 | // Path is the file path into which the generator will emit the code for this 39 | // variant. 40 | Path string 41 | 42 | // Imports is the imports needed for this package. 43 | Imports string 44 | 45 | StructPrefix string 46 | StructPrefixLow string 47 | StructSuffix string 48 | ExtraFields string 49 | 50 | // Basic type. T or "". 51 | Type string 52 | 53 | // Basic type argument. [T] or "". 54 | TypeArgument string 55 | 56 | // TypeParam is the optional type parameter for the function. 57 | TypeParam string // e.g. [T any] 58 | 59 | // Funcs is a map of functions used from within the template. The following 60 | // functions are expected to exist: 61 | Funcs template.FuncMap 62 | } 63 | 64 | type TypeReplacement struct { 65 | Type string 66 | Desc string 67 | } 68 | 69 | func main() { 70 | // For New. 71 | base := &Variant{ 72 | Package: "skipset", 73 | Name: "ordered", 74 | Path: "gen_ordered.go", 75 | Imports: "\"sync\"\n\"sync/atomic\"\n\"unsafe\"\n\n\"github.com/bytedance/gg/internal/constraints\"\n", 76 | Type: "T", 77 | TypeArgument: "[T]", 78 | TypeParam: "[T constraints.Ordered]", 79 | StructPrefix: "Ordered", 80 | StructPrefixLow: "ordered", 81 | StructSuffix: "", 82 | Funcs: template.FuncMap{ 83 | "Less": func(i, j string) string { 84 | return fmt.Sprintf("(%s < %s)", i, j) 85 | }, 86 | "Equal": func(i, j string) string { 87 | return fmt.Sprintf("%s == %s", i, j) 88 | }, 89 | }, 90 | } 91 | generate(base) 92 | base.Name += "Desc" 93 | base.StructSuffix += "Desc" 94 | base.Path = "gen_ordereddesc.go" 95 | base.Funcs = template.FuncMap{ 96 | "Less": func(i, j string) string { 97 | return fmt.Sprintf("(%s > %s)", i, j) 98 | }, 99 | "Equal": func(i, j string) string { 100 | return fmt.Sprintf("%s == %s", i, j) 101 | }, 102 | } 103 | generate(base) 104 | 105 | // For NewFunc. 106 | basefunc := &Variant{ 107 | Package: "skipset", 108 | Name: "func", 109 | Path: "gen_func.go", 110 | Imports: "\"sync\"\n\"sync/atomic\"\n\"unsafe\"\n", 111 | Type: "T", 112 | TypeArgument: "[T]", 113 | TypeParam: "[T any]", 114 | ExtraFields: "\nless func(a,b T)bool\n", 115 | StructPrefix: "Func", 116 | StructPrefixLow: "func", 117 | StructSuffix: "", 118 | Funcs: template.FuncMap{ 119 | "Less": func(i, j string) string { 120 | return fmt.Sprintf("s.less(%s,%s)", i, j) 121 | }, 122 | "Equal": func(i, j string) string { 123 | return fmt.Sprintf("!s.less(%s,%s)", j, i) 124 | }, 125 | }, 126 | } 127 | generate(basefunc) 128 | } 129 | 130 | // generate generates the code for variant `v` into a file named by `v.Path`. 131 | func generate(v *Variant) { 132 | // Parse templateCode anew for each variant because Parse requires Funcs to be 133 | // registered, and it helps type-check the funcs. 134 | tmpl, err := template.New("gen").Funcs(v.Funcs).Parse(templateCode) 135 | if err != nil { 136 | log.Fatal("template Parse:", err) 137 | } 138 | 139 | var out bytes.Buffer 140 | err = tmpl.Execute(&out, v) 141 | if err != nil { 142 | log.Fatal("template Execute:", err) 143 | } 144 | 145 | os.WriteFile(v.Path, out.Bytes(), 0644) 146 | 147 | formatted, err := format.Source(out.Bytes()) 148 | if err != nil { 149 | log.Fatal("format:", err) 150 | } 151 | 152 | if err := os.WriteFile(v.Path, formatted, 0644); err != nil { 153 | log.Fatal("WriteFile:", err) 154 | } 155 | } 156 | 157 | //go:embed skipset.tpl 158 | var templateCode string 159 | -------------------------------------------------------------------------------- /collection/skipset/gen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | go run gen.go 4 | -------------------------------------------------------------------------------- /collection/skipset/oparray.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package skipset 16 | 17 | import ( 18 | "sync/atomic" 19 | "unsafe" 20 | ) 21 | 22 | const ( 23 | op1 = 4 24 | op2 = maxLevel - op1 25 | ) 26 | 27 | type optionalArray struct { 28 | base [op1]unsafe.Pointer 29 | extra *([op2]unsafe.Pointer) 30 | } 31 | 32 | func (a *optionalArray) load(i int) unsafe.Pointer { 33 | if i < op1 { 34 | return a.base[i] 35 | } 36 | return a.extra[i-op1] 37 | } 38 | 39 | func (a *optionalArray) store(i int, p unsafe.Pointer) { 40 | if i < op1 { 41 | a.base[i] = p 42 | return 43 | } 44 | a.extra[i-op1] = p 45 | } 46 | 47 | func (a *optionalArray) atomicLoad(i int) unsafe.Pointer { 48 | if i < op1 { 49 | return atomic.LoadPointer(&a.base[i]) 50 | } 51 | return atomic.LoadPointer(&a.extra[i-op1]) 52 | } 53 | 54 | func (a *optionalArray) atomicStore(i int, p unsafe.Pointer) { 55 | if i < op1 { 56 | atomic.StorePointer(&a.base[i], p) 57 | return 58 | } 59 | atomic.StorePointer(&a.extra[i-op1], p) 60 | } 61 | -------------------------------------------------------------------------------- /collection/skipset/oparray_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package skipset 16 | 17 | import ( 18 | "testing" 19 | "unsafe" 20 | 21 | "github.com/bytedance/gg/internal/fastrand" 22 | ) 23 | 24 | type dummy struct { 25 | data optionalArray 26 | } 27 | 28 | func TestOpArray(t *testing.T) { 29 | n := new(dummy) 30 | n.data.extra = new([op2]unsafe.Pointer) 31 | 32 | var array [maxLevel]unsafe.Pointer 33 | for i := 0; i < maxLevel; i++ { 34 | value := unsafe.Pointer(&dummy{}) 35 | array[i] = value 36 | n.data.store(i, value) 37 | } 38 | 39 | for i := 0; i < maxLevel; i++ { 40 | if array[i] != n.data.load(i) || array[i] != n.data.atomicLoad(i) { 41 | t.Fatal(i, array[i], n.data.load(i)) 42 | } 43 | } 44 | 45 | for i := 0; i < 1000; i++ { 46 | r := int(fastrand.Uint32n(maxLevel)) 47 | value := unsafe.Pointer(&dummy{}) 48 | if i%100 == 0 { 49 | value = nil 50 | } 51 | array[r] = value 52 | if fastrand.Uint32n(2) == 0 { 53 | n.data.store(r, value) 54 | } else { 55 | n.data.atomicStore(r, value) 56 | } 57 | } 58 | 59 | for i := 0; i < maxLevel; i++ { 60 | if array[i] != n.data.load(i) || array[i] != n.data.atomicLoad(i) { 61 | t.Fatal(i, array[i], n.data.load(i)) 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /collection/skipset/skipset.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package skipset is a high-performance, scalable, concurrent-safe set based on skip-list. 16 | // In the typical pattern(100000 operations, 90%CONTAINS 9%Add 1%Remove, 8C16T), the skipset 17 | // up to 15x faster than the built-in sync.Map. 18 | // 19 | //go:generate go run gen.go 20 | package skipset 21 | 22 | import "github.com/bytedance/gg/internal/constraints" 23 | 24 | // New returns an empty skip set in ascending order. 25 | func New[T constraints.Ordered]() *OrderedSet[T] { 26 | var t T 27 | h := newOrderedNode(t, maxLevel) 28 | h.flags.SetTrue(fullyLinked) 29 | return &OrderedSet[T]{ 30 | header: h, 31 | highestLevel: defaultHighestLevel, 32 | } 33 | } 34 | 35 | // NewDesc returns an empty skip set in descending order. 36 | func NewDesc[T constraints.Ordered]() *OrderedSetDesc[T] { 37 | var t T 38 | h := newOrderedNodeDesc(t, maxLevel) 39 | h.flags.SetTrue(fullyLinked) 40 | return &OrderedSetDesc[T]{ 41 | header: h, 42 | highestLevel: defaultHighestLevel, 43 | } 44 | } 45 | 46 | // NewFunc returns an empty skip set in ascending order. 47 | // 48 | // Note that the less function requires a strict weak ordering, 49 | // see https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings, 50 | // or undefined behavior will happen. 51 | func NewFunc[T any](less func(a, b T) bool) *FuncSet[T] { 52 | var t T 53 | h := newFuncNode(t, maxLevel) 54 | h.flags.SetTrue(fullyLinked) 55 | return &FuncSet[T]{ 56 | header: h, 57 | highestLevel: defaultHighestLevel, 58 | less: less, 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /collection/skipset/skipset_bench_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package skipset 16 | 17 | import ( 18 | "math" 19 | "sync" 20 | "testing" 21 | 22 | "github.com/bytedance/gg/internal/fastrand" 23 | ) 24 | 25 | const ( 26 | initsize = 1 << 10 // for `contains` `1Remove9Add90Contains` `1Range9Remove90Add900Contains` 27 | randN = math.MaxUint32 28 | ) 29 | 30 | func BenchmarkInt64(b *testing.B) { 31 | all := []benchTask[int64]{{ 32 | name: "skipset", New: func() anyskipset[int64] { 33 | return New[int64]() 34 | }}} 35 | all = append(all, benchTask[int64]{ 36 | name: "skipset(func)", New: func() anyskipset[int64] { 37 | return NewFunc(func(a, b int64) bool { 38 | return a < b 39 | }) 40 | }}) 41 | all = append(all, benchTask[int64]{ 42 | name: "sync.Map", New: func() anyskipset[int64] { 43 | return new(anySyncMap[int64]) 44 | }}) 45 | rng := fastrand.Int63 46 | benchAdd(b, rng, all) 47 | bench30Add70Contains(b, rng, all) 48 | bench1Remove9Add90Contains(b, rng, all) 49 | bench1Range9Remove90Add900Contains(b, rng, all) 50 | } 51 | 52 | func benchAdd[T any](b *testing.B, rng func() T, benchTasks []benchTask[T]) { 53 | for _, v := range benchTasks { 54 | b.Run("Add/"+v.name, func(b *testing.B) { 55 | s := v.New() 56 | b.ResetTimer() 57 | b.RunParallel(func(pb *testing.PB) { 58 | for pb.Next() { 59 | s.Add(rng()) 60 | } 61 | }) 62 | }) 63 | } 64 | } 65 | 66 | func bench30Add70Contains[T any](b *testing.B, rng func() T, benchTasks []benchTask[T]) { 67 | for _, v := range benchTasks { 68 | b.Run("30Add70Contains/"+v.name, func(b *testing.B) { 69 | s := v.New() 70 | b.ResetTimer() 71 | b.RunParallel(func(pb *testing.PB) { 72 | for pb.Next() { 73 | u := fastrand.Uint32n(10) 74 | if u < 3 { 75 | s.Add(rng()) 76 | } else { 77 | s.Contains(rng()) 78 | } 79 | } 80 | }) 81 | }) 82 | } 83 | } 84 | 85 | func bench1Remove9Add90Contains[T any](b *testing.B, rng func() T, benchTasks []benchTask[T]) { 86 | for _, v := range benchTasks { 87 | b.Run("1Remove9Add90Contains/"+v.name, func(b *testing.B) { 88 | s := v.New() 89 | b.ResetTimer() 90 | b.RunParallel(func(pb *testing.PB) { 91 | for pb.Next() { 92 | u := fastrand.Uint32n(100) 93 | if u < 9 { 94 | s.Add(rng()) 95 | } else if u == 10 { 96 | s.Remove(rng()) 97 | } else { 98 | s.Contains(rng()) 99 | } 100 | } 101 | }) 102 | }) 103 | } 104 | } 105 | 106 | func bench1Range9Remove90Add900Contains[T any](b *testing.B, rng func() T, benchTasks []benchTask[T]) { 107 | for _, v := range benchTasks { 108 | b.Run("1Range9Remove90Add900Contains/"+v.name, func(b *testing.B) { 109 | s := v.New() 110 | b.ResetTimer() 111 | b.RunParallel(func(pb *testing.PB) { 112 | for pb.Next() { 113 | u := fastrand.Uint32n(1000) 114 | if u == 0 { 115 | s.Range(func(score T) bool { 116 | return true 117 | }) 118 | } else if u > 10 && u < 20 { 119 | s.Remove(rng()) 120 | } else if u >= 100 && u < 190 { 121 | s.Add(rng()) 122 | } else { 123 | s.Contains(rng()) 124 | } 125 | } 126 | }) 127 | }) 128 | } 129 | } 130 | 131 | type benchTask[T any] struct { 132 | name string 133 | New func() anyskipset[T] 134 | } 135 | 136 | type anySyncMap[T any] struct { 137 | data sync.Map 138 | } 139 | 140 | func (m *anySyncMap[T]) Add(x T) bool { 141 | m.data.Store(x, struct{}{}) 142 | return true 143 | } 144 | 145 | func (m *anySyncMap[T]) Contains(x T) bool { 146 | _, ok := m.data.Load(x) 147 | return ok 148 | } 149 | 150 | func (m *anySyncMap[T]) Remove(x T) bool { 151 | m.data.Delete(x) 152 | return true 153 | } 154 | 155 | func (m *anySyncMap[T]) Range(f func(value T) bool) { 156 | m.data.Range(func(key, _ any) bool { 157 | return !f(key.(T)) 158 | }) 159 | } 160 | 161 | func (m *anySyncMap[T]) Len() int { 162 | var i int 163 | m.data.Range(func(_, _ any) bool { 164 | i++ 165 | return true 166 | }) 167 | return i 168 | } 169 | 170 | func (m *anySyncMap[T]) ToSlice() []T { 171 | s := make([]T, 0, m.Len()) 172 | m.data.Range(func(key, _ any) bool { 173 | s = append(s, key.(T)) 174 | return true 175 | }) 176 | return s 177 | } 178 | -------------------------------------------------------------------------------- /collection/skipset/skipset_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package skipset 16 | 17 | import ( 18 | "fmt" 19 | "sync" 20 | ) 21 | 22 | func Example() { 23 | s := New[int]() 24 | fmt.Println(s.Add(10)) // true 25 | fmt.Println(s.Add(10)) // false 26 | fmt.Println(s.Add(11)) // true 27 | fmt.Println(s.Add(12)) // true 28 | fmt.Println(s.Len()) // 3 29 | 30 | fmt.Println(s.Contains(10)) // true 31 | fmt.Println(s.Remove(10)) // true 32 | fmt.Println(s.Contains(10)) // false 33 | 34 | fmt.Println(s.ToSlice()) // [11, 12] 35 | 36 | var wg sync.WaitGroup 37 | wg.Add(1000) 38 | for i := 0; i < 1000; i++ { 39 | i := i 40 | go func() { 41 | defer wg.Done() 42 | s.Add(i) 43 | }() 44 | } 45 | wg.Wait() 46 | fmt.Println(s.Len()) // 1000 47 | 48 | // Output: 49 | // true 50 | // false 51 | // true 52 | // true 53 | // 3 54 | // true 55 | // true 56 | // false 57 | // [11 12] 58 | // 1000 59 | } 60 | -------------------------------------------------------------------------------- /collection/skipset/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package skipset 16 | 17 | import ( 18 | "github.com/bytedance/gg/internal/fastrand" 19 | ) 20 | 21 | const ( 22 | maxLevel = 16 23 | p = 0.25 24 | defaultHighestLevel = 3 25 | ) 26 | 27 | func randomLevel() int { 28 | level := 1 29 | for fastrand.Uint32n(1/p) == 0 { 30 | level++ 31 | } 32 | if level > maxLevel { 33 | return maxLevel 34 | } 35 | return level 36 | } 37 | -------------------------------------------------------------------------------- /collection/tuple/tuple_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package tuple 16 | 17 | import "fmt" 18 | 19 | func Example() { 20 | addr := Make2("localhost", 8080) 21 | fmt.Printf("%s:%d\n", addr.First, addr.Second) // localhost:8080 22 | 23 | s := Zip2([]string{"red", "green", "blue"}, []int{14, 15, 16}) 24 | for _, v := range s { 25 | fmt.Printf("%s:%d\n", v.First, v.Second) 26 | } 27 | 28 | fmt.Println(s.Unzip()) // ["red", "green", "blue"] [14, 15, 16] 29 | 30 | // Output: 31 | // localhost:8080 32 | // red:14 33 | // green:15 34 | // blue:16 35 | // [red green blue] [14 15 16] 36 | } 37 | -------------------------------------------------------------------------------- /gcond/gcond_bench_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gcond 16 | 17 | import ( 18 | "testing" 19 | ) 20 | 21 | func BenchmarkIf(b *testing.B) { 22 | cond := true 23 | 24 | b.Run("Baseline", func(b *testing.B) { 25 | var v int 26 | for i := 0; i < b.N; i++ { 27 | if cond { 28 | v = 1 29 | } else { 30 | v = 2 31 | } 32 | } 33 | if v != 1 { 34 | b.FailNow() 35 | } 36 | }) 37 | 38 | b.Run("If", func(b *testing.B) { 39 | var v int 40 | for i := 0; i < b.N; i++ { 41 | v = If(cond, 1, 2) 42 | } 43 | if v != 1 { 44 | b.FailNow() 45 | } 46 | }) 47 | 48 | b.Run("IfLazy", func(b *testing.B) { 49 | var v int 50 | for i := 0; i < b.N; i++ { 51 | v = IfLazy(cond, func() int { return 1 }, func() int { return 2 }) 52 | } 53 | if v != 1 { 54 | b.FailNow() 55 | } 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /gcond/gcond_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gcond 16 | 17 | import ( 18 | "fmt" 19 | ) 20 | 21 | func Example() { 22 | fmt.Println(If(true, 1, 2)) // 1 23 | var a *struct{ A int } 24 | getA := func() int { return a.A } 25 | get1 := func() int { return 1 } 26 | fmt.Println(IfLazy(a != nil, getA, get1)) // 1 27 | fmt.Println(IfLazyL(a != nil, getA, 1)) // 1 28 | fmt.Println(IfLazyR(a == nil, 1, getA)) // 1 29 | 30 | fmt.Println(Switch[string](3). 31 | Case(1, "1"). 32 | CaseLazy(2, func() string { return "3" }). 33 | When(3, 4).Then("3/4"). 34 | When(5, 6).ThenLazy(func() string { return "5/6" }). 35 | Default("other")) // 3/4 36 | 37 | // Output: 38 | // 1 39 | // 1 40 | // 1 41 | // 1 42 | // 3/4 43 | } 44 | -------------------------------------------------------------------------------- /gcond/gcond_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gcond 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/bytedance/gg/internal/assert" 21 | ) 22 | 23 | func TestIf(t *testing.T) { 24 | assert.Equal(t, 1, If(true, 1, 2)) 25 | assert.Equal(t, 2, If(false, 1, 2)) 26 | assert.Equal(t, "2", If(false, "1", "2")) 27 | assert.Equal(t, "1", If(true, "1", "2")) 28 | 29 | assert.Panic(t, func() { 30 | var tt *testing.T 31 | _ = If(tt != nil, tt.Name(), "") 32 | }) 33 | assert.Panic(t, func() { 34 | var tt *testing.T 35 | _ = If(tt == nil, "", tt.Name()) 36 | }) 37 | } 38 | 39 | func lazy[T any](v T) Lazy[T] { 40 | return func() T { 41 | return v 42 | } 43 | } 44 | 45 | func TestIfLazy(t *testing.T) { 46 | assert.Equal(t, 1, IfLazy(true, lazy(1), lazy(2))) 47 | assert.Equal(t, 2, IfLazy(false, lazy(1), lazy(2))) 48 | assert.Equal(t, "1", IfLazy(true, lazy("1"), lazy("2"))) 49 | assert.Equal(t, "2", IfLazy(false, lazy("1"), lazy("2"))) 50 | 51 | assert.NotPanic(t, func() { 52 | var tt *testing.T 53 | assert.Equal(t, "", IfLazy(tt != nil, func() string { return tt.Name() }, lazy(""))) 54 | assert.Equal(t, "", IfLazy(tt == nil, lazy(""), func() string { return tt.Name() })) 55 | }) 56 | } 57 | 58 | func TestIfLazyL(t *testing.T) { 59 | assert.Equal(t, 1, IfLazyL(true, lazy(1), 2)) 60 | assert.Equal(t, 2, IfLazyL(false, lazy(1), 2)) 61 | assert.Equal(t, "1", IfLazyL(true, lazy("1"), "2")) 62 | assert.Equal(t, "2", IfLazyL(false, lazy("1"), "2")) 63 | 64 | assert.NotPanic(t, func() { 65 | var tt *testing.T 66 | assert.Equal(t, "", IfLazyL(tt != nil, func() string { return tt.Name() }, "")) 67 | }) 68 | } 69 | 70 | func TestIfLazyR(t *testing.T) { 71 | assert.Equal(t, 1, IfLazyR(true, 1, lazy(2))) 72 | assert.Equal(t, 2, IfLazyR(false, 1, lazy(2))) 73 | assert.Equal(t, "1", IfLazyR(true, "1", lazy("2"))) 74 | assert.Equal(t, "2", IfLazyR(false, "1", lazy("2"))) 75 | 76 | assert.NotPanic(t, func() { 77 | var tt *testing.T 78 | assert.Equal(t, "", IfLazyR(tt == nil, "", func() string { return tt.Name() })) 79 | }) 80 | } 81 | 82 | func TestSwitch(t *testing.T) { 83 | v1 := Switch[string](1). 84 | Case(1, "1"). 85 | Case(2, "2"). 86 | CaseLazy(3, func() string { return "3" }). 87 | CaseLazy(4, func() string { return "4" }). 88 | Default("5") 89 | assert.Equal(t, v1, "1") 90 | 91 | v2 := Switch[string](3). 92 | Case(1, "1"). 93 | Case(2, "2"). 94 | CaseLazy(3, func() string { return "3" }). 95 | CaseLazy(4, func() string { return "4" }). 96 | Default("5") 97 | assert.Equal(t, v2, "3") 98 | 99 | v3 := Switch[string](10). 100 | Case(1, "1"). 101 | Case(2, "2"). 102 | CaseLazy(3, func() string { return "3" }). 103 | CaseLazy(4, func() string { return "4" }). 104 | Default("5") 105 | assert.Equal(t, v3, "5") 106 | } 107 | 108 | func TestSwitchWhen(t *testing.T) { 109 | v1 := Switch[string](1). 110 | When(1, 2).Then("1"). 111 | When(3, 4).ThenLazy(func() string { return "3" }). 112 | DefaultLazy(func() string { 113 | return "5" 114 | }) 115 | assert.Equal(t, v1, "1") 116 | 117 | v2 := Switch[string](4). 118 | When(1, 2).Then("1"). 119 | When(3, 4).ThenLazy(func() string { return "3" }). 120 | DefaultLazy(func() string { 121 | return "5" 122 | }) 123 | assert.Equal(t, v2, "3") 124 | 125 | v3 := Switch[string](10). 126 | When(1, 2).Then("1"). 127 | When(3, 4).ThenLazy(func() string { return "3" }). 128 | DefaultLazy(func() string { 129 | return "5" 130 | }) 131 | assert.Equal(t, v3, "5") 132 | } 133 | -------------------------------------------------------------------------------- /gconv/gconv_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gconv 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/bytedance/gg/gptr" 21 | ) 22 | 23 | func Example() { 24 | fmt.Println(To[string](1)) // "1" 25 | fmt.Println(To[int]("1")) // 1 26 | fmt.Println(To[int]("x")) // 0 27 | fmt.Println(To[bool]("true")) // true 28 | fmt.Println(To[bool]("x")) // false 29 | fmt.Println(To[int](gptr.Of(gptr.Of(gptr.Of("1"))))) // 1 30 | type myInt int 31 | type myString string 32 | fmt.Println(To[myInt](myString("1"))) // 1 33 | fmt.Println(To[myString](myInt(1))) // "1" 34 | 35 | fmt.Println(ToE[int]("x")) // 0 strconv.ParseInt: parsing "x": invalid syntax 36 | fmt.Println(ToE[int]("1.10")) // 0 strconv.ParseInt: parsing "1.1": invalid syntax 37 | fmt.Println(ToE[int]("1.00")) // 1 nil 38 | fmt.Println(ToE[float64](".1")) // 0.1 nil 39 | 40 | // Output: 41 | // 1 42 | // 1 43 | // 0 44 | // true 45 | // false 46 | // 1 47 | // 1 48 | // 1 49 | // 0 strconv.ParseInt: parsing "x": invalid syntax 50 | // 0 strconv.ParseInt: parsing "1.1": invalid syntax 51 | // 1 52 | // 0.1 53 | } 54 | -------------------------------------------------------------------------------- /gfunc/README.md: -------------------------------------------------------------------------------- 1 | # gfunc - Operations of Functions 2 | 3 | Package *gfunc* provides operations of functions. 4 | 5 | * **Import Path** 6 | 7 | `import "github.com/bytedance/gg/gfunc"` 8 | 9 | Package *gfunc* implements [Partial Application](https://en.wikipedia.org/wiki/Partial_application) of functions. 10 | 11 | Partial Application refers to the process of fixing a number of arguments to a function, 12 | producing another function of smaller arity. 13 | 14 | ## Quick Start 15 | 16 | 17 | 1. Import `"github.com/bytedance/gg/gfunc"`. 18 | 2. Create `FuncN` (function with N parameters, such as `Func2`) 19 | 3. Use method `Func2.Partial` or `Func2.PartialR` to bind parameters, producing a `FuncN-1` (such as `Func1`) 20 | 21 | ```go 22 | package main 23 | 24 | import ( 25 | "fmt" 26 | 27 | "github.com/bytedance/gg/gfunc" 28 | ) 29 | 30 | func main() { 31 | f := func(a, b int) int { 32 | return a + b 33 | } 34 | add := gfunc.Partial2(f) // Cast f to "partial application"-able function 35 | add1 := add.Partial(1) // Bind argument a to 1 36 | fmt.Println(add1(0)) // 1 + 0 = 1 37 | fmt.Println(add1(1)) // add1 can be reused, 1 + 1 = 2 38 | add1n2 := add1.Partial(2) // Bind argument b to 2, all arguments are fixed 39 | fmt.Println(add1n2()) // 1 + 2 = 3 40 | // Output: 41 | // 1 42 | // 2 43 | // 3 44 | } 45 | ``` 46 | 47 | This is just an example. 48 | In real world, we no need to write such `add` function for specify type, 49 | `gvalue` provides a generics `gvalue.Add` functions and 50 | various utils for writing generic code. 51 | 52 | ## Limitation 53 | 54 | ### No type inference for composite literals 55 | 56 | According to the [Type Parameters Proposal](https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#type-inference-for-composite-literals), Type inference for composite 57 | literals is not support at least in Go1.18. So we can not easily cast a 58 | function to “partial application”-able type. 59 | 60 | Fortunately, [Type inference for functions is supported](https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#type-inference), we provides 61 | `MakeN` functions for casting without specify all type parameters explicitly. 62 | 63 | ### Arity Limitation 64 | 65 | If you have a need for n-ary (where n > 10) functions, please file an issue. 66 | -------------------------------------------------------------------------------- /gfunc/gfunc_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gfunc 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/bytedance/gg/gvalue" 21 | ) 22 | 23 | func Example() { 24 | add := Partial2(gvalue.Add[int]) // Cast f to "partial application"-able function 25 | add1 := add.Partial(1) // Bind argument a to 1 26 | fmt.Println(add1(0)) // 0 + 1 = 1 27 | fmt.Println(add1(1)) // add1 can be reused, 1 + 1 = 2 28 | add1n2 := add1.PartialR(2) // Bind argument b to 2, all arguments are fixed 29 | fmt.Println(add1n2()) // 1 + 2 = 3 30 | 31 | // Output: 32 | // 1 33 | // 2 34 | // 3 35 | } 36 | -------------------------------------------------------------------------------- /gfunc/gfunc_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gfunc 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/bytedance/gg/internal/assert" 21 | ) 22 | 23 | func TestPartial1(t *testing.T) { 24 | add := Partial1(func(a int) int { 25 | return a 26 | }) 27 | assert.Equal(t, 1, add.Partial(1)()) 28 | assert.Equal(t, 1, add.PartialR(1)()) 29 | } 30 | 31 | func TestPartial2(t *testing.T) { 32 | add := Partial2(func(a, b int) int { 33 | return a + b 34 | }) 35 | assert.Equal(t, 3, add.Partial(1).Partial(2)()) 36 | assert.Equal(t, 3, add.PartialR(1).PartialR(2)()) 37 | } 38 | 39 | func TestPartial3(t *testing.T) { 40 | add := Partial3(func(a, b, c int) int { 41 | return a + b + c 42 | }) 43 | assert.Equal(t, 6, add.Partial(1)(2, 3)) 44 | assert.Equal(t, 6, add.PartialR(1)(2, 3)) 45 | } 46 | 47 | func TestPartial4(t *testing.T) { 48 | add := Partial4(func(a, b, c, d int) int { 49 | return a + b + c + d 50 | }) 51 | assert.Equal(t, 10, add.Partial(1)(2, 3, 4)) 52 | assert.Equal(t, 10, add.PartialR(1)(2, 3, 4)) 53 | } 54 | 55 | func TestPartial5(t *testing.T) { 56 | add := Partial5(func(a, b, c, d, e int) int { 57 | return a + b + c + d + e 58 | }) 59 | assert.Equal(t, 15, add.Partial(1)(2, 3, 4, 5)) 60 | assert.Equal(t, 15, add.PartialR(1)(2, 3, 4, 5)) 61 | } 62 | 63 | func TestPartial6(t *testing.T) { 64 | add := Partial6(func(a, b, c, d, e, f int) int { 65 | return a + b + c + d + e + f 66 | }) 67 | assert.Equal(t, 21, add.Partial(1)(2, 3, 4, 5, 6)) 68 | assert.Equal(t, 21, add.PartialR(1)(2, 3, 4, 5, 6)) 69 | } 70 | 71 | func TestPartial7(t *testing.T) { 72 | add := Partial7(func(a, b, c, d, e, f, g int) int { 73 | return a + b + c + d + e + f + g 74 | }) 75 | assert.Equal(t, 28, add.Partial(1)(2, 3, 4, 5, 6, 7)) 76 | assert.Equal(t, 28, add.PartialR(1)(2, 3, 4, 5, 6, 7)) 77 | } 78 | 79 | func TestPartial8(t *testing.T) { 80 | add := Partial8(func(a, b, c, d, e, f, g, h int) int { 81 | return a + b + c + d + e + f + g + h 82 | }) 83 | assert.Equal(t, 36, add.Partial(1)(2, 3, 4, 5, 6, 7, 8)) 84 | assert.Equal(t, 36, add.PartialR(1)(2, 3, 4, 5, 6, 7, 8)) 85 | } 86 | 87 | func TestPartial9(t *testing.T) { 88 | add := Partial9(func(a, b, c, d, e, f, g, h, i int) int { 89 | return a + b + c + d + e + f + g + h + i 90 | }) 91 | assert.Equal(t, 45, add.Partial(1)(2, 3, 4, 5, 6, 7, 8, 9)) 92 | assert.Equal(t, 45, add.PartialR(1)(2, 3, 4, 5, 6, 7, 8, 9)) 93 | } 94 | 95 | func TestPartial10(t *testing.T) { 96 | type myInt1 int 97 | type myInt2 int 98 | type myInt3 int 99 | type myInt4 int 100 | type myInt5 int 101 | type myInt6 int 102 | type myInt7 int 103 | type myInt8 int 104 | type myInt9 int 105 | type myInt10 int 106 | 107 | add := Partial10(func(a myInt1, b myInt2, c myInt3, d myInt4, e myInt5, f myInt6, g myInt7, h myInt8, i myInt9, j myInt10) int { 108 | return int(a) + int(b) + int(c) + int(d) + int(e) + int(f) + int(g) + int(h) + int(i) + int(j) 109 | }) 110 | assert.Equal(t, 111 | 55, 112 | add. 113 | Partial(1). 114 | Partial(2). 115 | Partial(3). 116 | Partial(4). 117 | Partial(5). 118 | Partial(6). 119 | Partial(7). 120 | Partial(8). 121 | Partial(9). 122 | Partial(10)()) 123 | assert.Equal(t, 124 | 55, 125 | add. 126 | PartialR(1). 127 | PartialR(2). 128 | PartialR(3). 129 | PartialR(4). 130 | PartialR(5). 131 | PartialR(6). 132 | PartialR(7). 133 | PartialR(8). 134 | PartialR(9). 135 | PartialR(10)()) 136 | } 137 | -------------------------------------------------------------------------------- /gmap/gmap_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gmap 16 | 17 | import ( 18 | "fmt" 19 | "strconv" 20 | 21 | "github.com/bytedance/gg/gslice" 22 | "github.com/bytedance/gg/gson" 23 | ) 24 | 25 | func Example() { 26 | // Keys / Values getter 27 | fmt.Println(Keys(map[int]int{1: 2})) // [1] 28 | fmt.Println(Values(map[int]int{1: 2})) // [2] 29 | fmt.Println(Items(map[int]int{1: 2}).Unzip()) // [1] [2] 30 | fmt.Println(OrderedKeys(map[int]int{1: 2, 2: 3, 3: 4})) // [1, 2, 3] 31 | fmt.Println(OrderedValues(map[int]int{1: 2, 2: 3, 3: 4})) // [2, 3, 4] 32 | fmt.Println(OrderedItems(map[int]int{1: 2, 2: 3, 3: 4}).Unzip()) // [1, 2, 3] [2, 3, 4] 33 | f := func(k, v int) string { return strconv.Itoa(k) + ":" + strconv.Itoa(v) } 34 | fmt.Println(ToSlice(map[int]int{1: 2}, f)) // ["1:2"] 35 | fmt.Println(ToOrderedSlice(map[int]int{1: 2, 2: 3, 3: 4}, f)) // ["1:2", "2:3", "3:4"] 36 | 37 | // High-order function 38 | fmt.Println(gson.ToString(Map(map[int]int{1: 2, 2: 3, 3: 4}, func(k int, v int) (string, string) { 39 | return strconv.Itoa(k), strconv.Itoa(k + 1) 40 | }))) // {"1":"2", "2":"3", "3":"4"} 41 | fmt.Println(gson.ToString(Filter(map[int]int{1: 2, 2: 3, 3: 4}, func(k int, v int) bool { 42 | return k+v > 3 43 | }))) // {"2":2, "3":3} 44 | 45 | // CRUD operation 46 | fmt.Println(Contains(map[int]int{1: 2, 2: 3, 3: 4}, 1)) // true 47 | fmt.Println(ContainsAny(map[int]int{1: 2, 2: 3, 3: 4}, 1, 4)) // true 48 | fmt.Println(ContainsAll(map[int]int{1: 2, 2: 3, 3: 4}, 1, 4)) // false 49 | fmt.Println(Load(map[int]int{1: 2, 2: 3, 3: 4}, 1).Value()) // 2 50 | fmt.Println(LoadAny(map[int]int{1: 2, 2: 3, 3: 4}, 1, 4).Value()) // 2 51 | fmt.Println(LoadAll(map[int]int{1: 2, 2: 3, 3: 4}, 1, 4)) // [] 52 | fmt.Println(LoadSome(map[int]int{1: 2, 2: 3, 3: 4}, 1, 4)) // [2] 53 | 54 | // Partion operation 55 | Chunk(map[int]int{1: 2, 2: 3, 3: 4, 4: 5, 5: 6}, 2) // possible output: [{1:2, 2:3}, {3:4, 4:5}, {5:6}] 56 | Divide(map[int]int{1: 2, 2: 3, 3: 4, 4: 5, 5: 6}, 2) // possible output: [{1:2, 2:3, 3:4}, {4:5, 5:6}] 57 | 58 | // Math operation 59 | fmt.Println(Max(map[int]int{1: 2, 2: 3, 3: 4}).Value()) // 4 60 | fmt.Println(Min(map[int]int{1: 2, 2: 3, 3: 4}).Value()) // 2 61 | fmt.Println(MinMax(map[int]int{1: 2, 2: 3, 3: 4}).Value().Values()) // 2 4 62 | fmt.Println(Sum(map[int]int{1: 2, 2: 3, 3: 4})) // 9 63 | 64 | // Set operation 65 | fmt.Println(gson.ToString(Union(map[int]int{1: 2, 2: 3, 3: 4}, map[int]int{3: 14, 4: 15, 5: 16}))) // {1:2, 2:3, 3:14, 4:15, 5:16} 66 | fmt.Println(gson.ToString(Intersect(map[int]int{1: 2, 2: 3, 3: 4}, map[int]int{3: 14, 4: 15, 5: 16}))) // {3:14} 67 | fmt.Println(gson.ToString(Diff(map[int]int{1: 2, 2: 3, 3: 4}, map[int]int{3: 14, 4: 15, 5: 16}))) // {1:2, 2:3} 68 | fmt.Println(gson.ToString(UnionBy(gslice.Of(map[int]int{1: 2, 2: 3, 3: 4}, map[int]int{3: 14, 4: 15, 5: 16}), DiscardNew[int, int]()))) // {1:2, 2:3, 3:4, 4:15, 5:16} 69 | fmt.Println(gson.ToString(IntersectBy(gslice.Of(map[int]int{1: 2, 2: 3, 3: 4}, map[int]int{3: 14, 4: 15, 5: 16}), DiscardNew[int, int]()))) // {3:4} 70 | 71 | // Output: 72 | // [1] 73 | // [2] 74 | // [1] [2] 75 | // [1 2 3] 76 | // [2 3 4] 77 | // [1 2 3] [2 3 4] 78 | // [1:2] 79 | // [1:2 2:3 3:4] 80 | // {"1":"2","2":"3","3":"4"} 81 | // {"2":3,"3":4} 82 | // true 83 | // true 84 | // false 85 | // 2 86 | // 2 87 | // [] 88 | // [2] 89 | // 4 90 | // 2 91 | // 2 4 92 | // 9 93 | // {"1":2,"2":3,"3":14,"4":15,"5":16} 94 | // {"3":14} 95 | // {"1":2,"2":3} 96 | // {"1":2,"2":3,"3":4,"4":15,"5":16} 97 | // {"3":4} 98 | } 99 | -------------------------------------------------------------------------------- /gmap/gmap_go120_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build go1.20 16 | // +build go1.20 17 | 18 | package gmap 19 | 20 | import ( 21 | "errors" 22 | "strconv" 23 | "testing" 24 | 25 | "github.com/bytedance/gg/gresult" 26 | "github.com/bytedance/gg/internal/assert" 27 | ) 28 | 29 | // errors.Join is introduced in 1.20+ 30 | func TestTryMap(t *testing.T) { 31 | f := func(k, v string) (int, int, error) { 32 | ki, kerr := strconv.Atoi(k) 33 | vi, verr := strconv.Atoi(v) 34 | return ki, vi, errors.Join(kerr, verr) 35 | } 36 | assert.Equal(t, 37 | gresult.OK(map[int]int{}), 38 | TryMap(map[string]string{}, f)) 39 | assert.Equal(t, 40 | gresult.OK(map[int]int{1: 1, 2: 2}), 41 | TryMap(map[string]string{"1": "1", "2": "2"}, f)) 42 | assert.Equal(t, 43 | "strconv.Atoi: parsing \"a\": invalid syntax", 44 | TryMap(map[string]string{"1": "1", "2": "a"}, f).Err().Error()) 45 | } 46 | 47 | func TestFilterMap(t *testing.T) { 48 | parseInt := func(k, v string) (int, int, bool) { 49 | ki, kerr := strconv.ParseInt(k, 10, 64) 50 | vi, verr := strconv.ParseInt(v, 10, 64) 51 | return int(ki), int(vi), errors.Join(kerr, verr) == nil 52 | } 53 | assert.Equal(t, 54 | map[int]int{1: 1, 2: 2}, 55 | FilterMap(map[string]string{"1": "1", "2": "2", "a": "3", "4": "b", "c": "c"}, parseInt)) 56 | assert.Equal(t, 57 | map[int]int{}, 58 | FilterMap(map[string]string{"a": "3", "4": "b"}, parseInt)) 59 | assert.Equal(t, 60 | map[int]int{1: 1, 2: 2}, 61 | FilterMap(map[string]string{"1": "1", "2": "2"}, parseInt)) 62 | assert.Equal(t, 63 | map[int]int{}, 64 | FilterMap(map[string]string{}, parseInt)) 65 | assert.Equal(t, 66 | map[int]int{}, 67 | FilterMap(nil, parseInt)) 68 | } 69 | 70 | func TestTryFilterMap(t *testing.T) { 71 | parseInt := func(k, v string) (int, int, error) { 72 | ki, kerr := strconv.ParseInt(k, 10, 64) 73 | vi, verr := strconv.ParseInt(v, 10, 64) 74 | return int(ki), int(vi), errors.Join(kerr, verr) 75 | } 76 | assert.Equal(t, 77 | map[int]int{1: 1, 2: 2}, 78 | TryFilterMap(map[string]string{"1": "1", "2": "2", "a": "3", "4": "b", "c": "c"}, parseInt)) 79 | assert.Equal(t, 80 | map[int]int{}, 81 | TryFilterMap(map[string]string{"a": "3", "4": "b"}, parseInt)) 82 | assert.Equal(t, 83 | map[int]int{1: 1, 2: 2}, 84 | TryFilterMap(map[string]string{"1": "1", "2": "2"}, parseInt)) 85 | assert.Equal(t, 86 | map[int]int{}, 87 | TryFilterMap(map[string]string{}, parseInt)) 88 | assert.Equal(t, 89 | map[int]int{}, 90 | TryFilterMap(nil, parseInt)) 91 | } 92 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bytedance/gg 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytedance/gg/82fceccd889aa33749b21986a58d678613bbcb3d/go.sum -------------------------------------------------------------------------------- /goption/goption_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package goption 16 | 17 | import ( 18 | "fmt" 19 | "strconv" 20 | ) 21 | 22 | func Example() { 23 | fmt.Println(Of(1, true).Value()) // 1 24 | fmt.Println(Nil[int]().IsNil()) // true 25 | fmt.Println(Nil[int]().ValueOr(10)) // 10 26 | fmt.Println(OK(1).IsOK()) // true 27 | fmt.Println(OK(1).ValueOrZero()) // 1 28 | fmt.Println(OfPtr((*int)(nil)).Ptr()) // nil 29 | fmt.Println(Map(OK(1), strconv.Itoa).Get()) // "1" true 30 | 31 | // Output: 32 | // 1 33 | // true 34 | // 10 35 | // true 36 | // 1 37 | // 38 | // 1 true 39 | } 40 | -------------------------------------------------------------------------------- /gptr/gptr_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gptr 16 | 17 | import ( 18 | "fmt" 19 | "strconv" 20 | ) 21 | 22 | func Example() { 23 | a := Of(1) 24 | fmt.Println(Indirect(a)) // 1 25 | 26 | b := OfNotZero(1) 27 | fmt.Println(IsNotNil(b)) // true 28 | fmt.Println(IndirectOr(b, 2)) // 1 29 | fmt.Println(Indirect(Map(b, strconv.Itoa))) // "1" 30 | 31 | c := OfNotZero(0) 32 | fmt.Println(c) // nil 33 | fmt.Println(IsNil(c)) // true 34 | fmt.Println(IndirectOr(c, 2)) // 2 35 | 36 | // Output: 37 | // 1 38 | // true 39 | // 1 40 | // 1 41 | // 42 | // true 43 | // 2 44 | } 45 | -------------------------------------------------------------------------------- /gptr/gptr_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gptr 16 | 17 | import ( 18 | "fmt" 19 | "strconv" 20 | "testing" 21 | 22 | "github.com/bytedance/gg/gvalue" 23 | "github.com/bytedance/gg/internal/assert" 24 | ) 25 | 26 | func TestOf(t *testing.T) { 27 | assert.Equal(t, 543, *Of(543)) 28 | assert.Equal(t, "Alice", *Of("Alice")) 29 | assert.Equal(t, "Alice", **Of(Of("Alice"))) 30 | assert.Equal(t, "Alice", ***Of(Of(Of("Alice")))) 31 | assert.False(t, IsNil(Of[*int](nil))) 32 | assert.True(t, IsNil(*Of[*int](nil))) 33 | // assert.Nil(t, *Of[*interface{}](nil)) 34 | 35 | // Test modifying pointer. 36 | { 37 | v := 1 38 | p := Of(v) 39 | assert.False(t, p == &v) 40 | *p = 2 41 | assert.Equal(t, 1, v) 42 | assert.Equal(t, 2, *p) 43 | } 44 | } 45 | 46 | func TestOfNotZero(t *testing.T) { 47 | assert.Equal(t, 543, *OfNotZero(543)) 48 | assert.Equal(t, "Alice", *OfNotZero("Alice")) 49 | 50 | // Test zero. 51 | assert.True(t, IsNil(OfNotZero(0))) 52 | assert.True(t, IsNil(OfNotZero(""))) 53 | assert.True(t, IsNil(OfNotZero[*int](nil))) 54 | } 55 | 56 | func TestOfPositive(t *testing.T) { 57 | assert.Equal(t, 543, *OfPositive(543)) 58 | assert.Equal(t, 1.23, *OfPositive(1.23)) 59 | 60 | // Test non-positive number. 61 | assert.True(t, IsNil(OfPositive(0))) 62 | assert.True(t, IsNil(OfPositive(-1))) 63 | assert.True(t, IsNil(OfPositive(-1.23))) 64 | } 65 | 66 | func TestIndirect(t *testing.T) { 67 | assert.Equal(t, 543, Indirect(Of(543))) 68 | assert.Equal(t, "Alice", Indirect(Of("Alice"))) 69 | assert.Zero(t, Indirect[int](nil)) 70 | assert.Nil(t, Indirect[interface{}](nil)) 71 | assert.Nil(t, Indirect(Of[fmt.Stringer](nil))) 72 | } 73 | 74 | func TestIndirectOr(t *testing.T) { 75 | assert.Equal(t, "Alice", IndirectOr(Of("Alice"), "Bob")) 76 | assert.Equal(t, "Bob", IndirectOr(nil, "Bob")) 77 | } 78 | 79 | func TestIsNil(t *testing.T) { 80 | assert.False(t, IsNil(Of(1))) 81 | assert.True(t, IsNil[int](nil)) 82 | } 83 | 84 | func TestEqual(t *testing.T) { 85 | ptr := Of(1) 86 | assert.True(t, Equal(ptr, ptr)) 87 | assert.True(t, Equal(Of(1), Of(1))) 88 | assert.False(t, Equal(Of(1), Of(2))) 89 | assert.False(t, Equal(Of(1), nil)) 90 | assert.False(t, Equal(nil, Of(1))) 91 | assert.True(t, Equal[string](nil, nil)) 92 | } 93 | 94 | func TestEqualTo(t *testing.T) { 95 | assert.True(t, EqualTo(Of(1), 1)) 96 | assert.False(t, EqualTo(Of(2), 1)) 97 | assert.False(t, EqualTo(nil, 0)) 98 | } 99 | 100 | func TestClone(t *testing.T) { 101 | assert.True(t, IsNil(Clone(((*int)(nil))))) 102 | 103 | v := 1 104 | assert.True(t, Clone(&v) != &v) 105 | assert.True(t, Equal(Clone(&v), &v)) 106 | 107 | src := Of(1) 108 | dst := Clone(&src) 109 | assert.Equal(t, &src, dst) 110 | assert.True(t, src == *dst) 111 | } 112 | 113 | func TestCloneBy(t *testing.T) { 114 | assert.True(t, IsNil(CloneBy(((**int)(nil)), Clone[int]))) 115 | 116 | src := Of(1) 117 | dst := CloneBy(&src, Clone[int]) 118 | assert.Equal(t, &src, dst) 119 | assert.False(t, src == *dst) 120 | } 121 | 122 | func TestMap(t *testing.T) { 123 | i := 1 124 | assert.Equal(t, Of("1"), Map(&i, strconv.Itoa)) 125 | assert.True(t, Map(nil, strconv.Itoa) == nil) 126 | 127 | assert.NotPanic(t, func() { 128 | _ = Map(nil, func(int) string { 129 | panic("Q_Q") 130 | }) 131 | }) 132 | 133 | assert.Panic(t, func() { 134 | _ = Map(&i, func(int) string { 135 | panic("Q_Q") 136 | }) 137 | }) 138 | } 139 | 140 | func Indirect_gvalueZero[T any](p *T) (v T) { 141 | if p == nil { 142 | return gvalue.Zero[T]() 143 | } 144 | return *p 145 | } 146 | 147 | func BenchmarkIndirect(b *testing.B) { 148 | type Big struct { 149 | Foo [200]string 150 | Bar int 151 | } 152 | 153 | var big *Big 154 | b.Run("Named", func(b *testing.B) { 155 | var v Big 156 | for i := 0; i <= b.N; i++ { 157 | v = Indirect(big) 158 | } 159 | _ = v 160 | }) 161 | b.Run("gvalue.Zero", func(b *testing.B) { 162 | var v Big 163 | for i := 0; i <= b.N; i++ { 164 | v = Indirect_gvalueZero(big) 165 | } 166 | _ = v 167 | }) 168 | } 169 | -------------------------------------------------------------------------------- /gresult/gresult_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gresult 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | "strconv" 21 | ) 22 | 23 | func Example() { 24 | fmt.Println(Of(strconv.Atoi("1")).Value()) // 1 25 | fmt.Println(Err[int](io.EOF).IsErr()) // true 26 | fmt.Println(Err[int](io.EOF).ValueOr(10)) // 10 27 | fmt.Println(OK(1).IsOK()) // true 28 | fmt.Println(OK(1).ValueOrZero()) // 1 29 | fmt.Println(Of(strconv.Atoi("x")).Option().Get()) // 0 false 30 | fmt.Println(Map(OK(1), strconv.Itoa).Get()) // "1" nil 31 | 32 | // Output: 33 | // 1 34 | // true 35 | // 10 36 | // true 37 | // 1 38 | // 0 false 39 | // 1 40 | } 41 | -------------------------------------------------------------------------------- /gslice/gslice_bench_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gslice 16 | 17 | import ( 18 | "math/rand" 19 | "strconv" 20 | "testing" 21 | 22 | "github.com/bytedance/gg/internal/iter" 23 | ) 24 | 25 | func BenchmarkMap10(b *testing.B) { 26 | benchmarkMapN(b, 10) 27 | } 28 | 29 | func BenchmarkMap100(b *testing.B) { 30 | benchmarkMapN(b, 100) 31 | } 32 | 33 | func BenchmarkMap1000(b *testing.B) { 34 | benchmarkMapN(b, 100) 35 | } 36 | 37 | func benchmarkMapN(b *testing.B, n int) { 38 | s := []int{} 39 | for i := 0; i < n; i++ { 40 | s = append(s, i) 41 | } 42 | 43 | b.Run("baseline", func(b *testing.B) { 44 | for i := 0; i < b.N; i++ { 45 | r := make([]string, 0, n) 46 | for _, v := range s { 47 | r = append(r, strconv.Itoa(v)) 48 | } 49 | _ = r 50 | } 51 | }) 52 | b.Run("gslice", func(b *testing.B) { 53 | for i := 0; i < b.N; i++ { 54 | _ = Map(s, strconv.Itoa) 55 | } 56 | }) 57 | b.Run("iter", func(b *testing.B) { 58 | for i := 0; i < b.N; i++ { 59 | _ = iter.ToSlice(iter.Map(strconv.Itoa, iter.StealSlice(s))) 60 | } 61 | }) 62 | b.Run("reflect", func(b *testing.B) { 63 | for i := 0; i < b.N; i++ { 64 | _ = reflectMap(s, func(i any) any { return strconv.Itoa(i.(int)) }).([]string) 65 | } 66 | }) 67 | } 68 | 69 | func BenchmarkShuffle(b *testing.B) { 70 | b.Run("gslice", func(b *testing.B) { 71 | for i := 0; i < b.N; i++ { 72 | s := iter.ToSlice(iter.Range(0, 100)) 73 | Shuffle(s) 74 | _ = s 75 | } 76 | }) 77 | b.Run("math/rand", func(b *testing.B) { 78 | for i := 0; i < b.N; i++ { 79 | s := iter.ToSlice(iter.Range(0, 100)) 80 | rand.Shuffle(len(s), func(i, j int) { 81 | s[i], s[j] = s[j], s[i] 82 | }) 83 | _ = s 84 | } 85 | }) 86 | } 87 | 88 | func BenchmarkShuffle_Parallel(b *testing.B) { 89 | b.Run("gslice", func(b *testing.B) { 90 | b.RunParallel(func(pb *testing.PB) { 91 | for pb.Next() { 92 | s := iter.ToSlice(iter.Range(0, 100)) 93 | Shuffle(s) 94 | _ = s 95 | } 96 | }) 97 | }) 98 | b.Run("math/rand", func(b *testing.B) { 99 | b.RunParallel(func(pb *testing.PB) { 100 | for pb.Next() { 101 | s := iter.ToSlice(iter.Range(0, 100)) 102 | rand.Shuffle(len(s), func(i, j int) { 103 | s[i], s[j] = s[j], s[i] 104 | }) 105 | _ = s 106 | } 107 | }) 108 | }) 109 | } 110 | -------------------------------------------------------------------------------- /gslice/gslice_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gslice 16 | 17 | import ( 18 | "fmt" 19 | "strconv" 20 | 21 | "github.com/bytedance/gg/gson" 22 | "github.com/bytedance/gg/gvalue" 23 | ) 24 | 25 | func Example() { 26 | // High-order function 27 | fmt.Println(Map([]int{1, 2, 3, 4, 5}, strconv.Itoa)) // ["1", "2", "3", "4", "5"] 28 | isEven := func(i int) bool { return i%2 == 0 } 29 | fmt.Println(Filter([]int{1, 2, 3, 4, 5}, isEven)) // [2, 4] 30 | fmt.Println(Reduce([]int{1, 2, 3, 4, 5}, gvalue.Add[int]).Value()) // 15 31 | fmt.Println(Any([]int{1, 2, 3, 4, 5}, isEven)) // true 32 | fmt.Println(All([]int{1, 2, 3, 4, 5}, isEven)) // false 33 | 34 | // CRUD operation 35 | fmt.Println(Contains([]int{1, 2, 3, 4, 5}, 2)) // true 36 | fmt.Println(ContainsAny([]int{1, 2, 3, 4, 5}, 2, 6)) // true 37 | fmt.Println(ContainsAll([]int{1, 2, 3, 4, 5}, 2, 6)) // false 38 | fmt.Println(Index([]int{1, 2, 3, 4, 5}, 3).Value()) // 2 39 | fmt.Println(Find([]int{1, 2, 3, 4, 5}, isEven).Value()) // 2 40 | fmt.Println(First([]int{1, 2, 3, 4, 5}).Value()) // 1 41 | fmt.Println(Get([]int{1, 2, 3, 4, 5}, 1).Value()) // 2 42 | fmt.Println(Get([]int{1, 2, 3, 4, 5}, -1).Value()) // 5 43 | 44 | // Partion operation 45 | fmt.Println(Range(1, 5)) // [1, 2, 3, 4] 46 | fmt.Println(RangeWithStep(5, 1, -2)) // [5, 3] 47 | fmt.Println(Take([]int{1, 2, 3, 4, 5}, 2)) // [1, 2] 48 | fmt.Println(Take([]int{1, 2, 3, 4, 5}, -2)) // [4, 5] 49 | fmt.Println(Slice([]int{1, 2, 3, 4, 5}, 1, 3)) // [2, 3] 50 | fmt.Println(Chunk([]int{1, 2, 3, 4, 5}, 2)) // [[1, 2] [3, 4] [5]] 51 | fmt.Println(Divide([]int{1, 2, 3, 4, 5}, 2)) // [[1, 2, 3] [4, 5]] 52 | fmt.Println(Concat([]int{1, 2}, []int{3, 4, 5})) // [1, 2, 3, 4, 5] 53 | fmt.Println(Flatten([][]int{{1, 2}, {3, 4, 5}})) // [1, 2, 3, 4, 5] 54 | fmt.Println(Partition([]int{1, 2, 3, 4, 5}, isEven)) // [2, 4], [1, 3, 5] 55 | 56 | // Math operation 57 | fmt.Println(Max([]int{1, 2, 3, 4, 5}).Value()) // 5 58 | fmt.Println(Min([]int{1, 2, 3, 4, 5}).Value()) // 1 59 | fmt.Println(MinMax([]int{1, 2, 3, 4, 5}).Value().Values()) // 1 5 60 | fmt.Println(Sum([]int{1, 2, 3, 4, 5})) // 15 61 | 62 | // Convert to Map 63 | fmt.Println(gson.ToString(ToMap([]int{1, 2, 3, 4, 5}, func(i int) (string, int) { return strconv.Itoa(i), i }))) // {"1":1,"2":2,"3":3,"4":4,"5":5} 64 | fmt.Println(gson.ToString(ToMapValues([]int{1, 2, 3, 4, 5}, strconv.Itoa))) // {"1":1,"2":2,"3":3,"4":4,"5":5} 65 | fmt.Println(gson.ToString(GroupBy([]int{1, 2, 3, 4, 5}, func(i int) string { 66 | if i%2 == 0 { 67 | return "even" 68 | } else { 69 | return "odd" 70 | } 71 | }))) // {"even":[2,4], "odd":[1,3,5]} 72 | 73 | // Set operation 74 | fmt.Println(Union([]int{1, 2, 3}, []int{3, 4, 5})) // [1, 2, 3, 4, 5] 75 | fmt.Println(Intersect([]int{1, 2, 3}, []int{3, 4, 5})) // [3] 76 | fmt.Println(Diff([]int{1, 2, 3}, []int{3, 4, 5})) // [1, 2] 77 | fmt.Println(Uniq([]int{1, 1, 2, 2, 3})) // [1, 2, 3] 78 | fmt.Println(Dup([]int{1, 1, 2, 2, 3})) // [1, 2] 79 | 80 | // Re-order operation 81 | s1 := []int{5, 1, 2, 3, 4} 82 | s2, s3, s4 := Clone(s1), Clone(s1), Clone(s1) 83 | Sort(s1) 84 | SortBy(s2, func(i, j int) bool { return i > j }) 85 | StableSortBy(s3, func(i, j int) bool { return i > j }) 86 | Reverse(s4) 87 | fmt.Println(s1) // [1, 2, 3, 4, 5] 88 | fmt.Println(s2) // [5, 4, 3, 2, 1] 89 | fmt.Println(s3) // [5, 4, 3, 2, 1] 90 | fmt.Println(s4) // [4, 3, 2, 1, 5] 91 | 92 | // Output: 93 | // [1 2 3 4 5] 94 | // [2 4] 95 | // 15 96 | // true 97 | // false 98 | // true 99 | // true 100 | // false 101 | // 2 102 | // 2 103 | // 1 104 | // 2 105 | // 5 106 | // [1 2 3 4] 107 | // [5 3] 108 | // [1 2] 109 | // [4 5] 110 | // [2 3] 111 | // [[1 2] [3 4] [5]] 112 | // [[1 2 3] [4 5]] 113 | // [1 2 3 4 5] 114 | // [1 2 3 4 5] 115 | // [2 4] [1 3 5] 116 | // 5 117 | // 1 118 | // 1 5 119 | // 15 120 | // {"1":1,"2":2,"3":3,"4":4,"5":5} 121 | // {"1":1,"2":2,"3":3,"4":4,"5":5} 122 | // {"even":[2,4],"odd":[1,3,5]} 123 | // [1 2 3 4 5] 124 | // [3] 125 | // [1 2] 126 | // [1 2 3] 127 | // [1 2] 128 | // [1 2 3 4 5] 129 | // [5 4 3 2 1] 130 | // [5 4 3 2 1] 131 | // [4 3 2 1 5] 132 | } 133 | -------------------------------------------------------------------------------- /gson/gson.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package gson provides operations of JSON encoding and decoding. 16 | package gson 17 | 18 | import ( 19 | "encoding/json" 20 | ) 21 | 22 | // default json std lib. 23 | type stdJSONCodec struct{} 24 | 25 | func (stdJSONCodec) Valid(data []byte) bool { 26 | return json.Valid(data) 27 | } 28 | 29 | func (stdJSONCodec) Marshal(v any) ([]byte, error) { 30 | return json.Marshal(v) 31 | } 32 | 33 | func (stdJSONCodec) MarshalIndent(v any, prefix, indent string) ([]byte, error) { 34 | return json.MarshalIndent(v, prefix, indent) 35 | } 36 | 37 | func (stdJSONCodec) Unmarshal(data []byte, out any) error { 38 | return json.Unmarshal(data, out) 39 | } 40 | 41 | var stdJSON JSONCodec = stdJSONCodec{} 42 | 43 | // Valid reports whether data is a valid JSON encoding. 44 | func Valid[V ~[]byte | ~string](data V) bool { 45 | return ValidBy(stdJSON, data) 46 | } 47 | 48 | // Marshal returns the JSON-encoded bytes of v. 49 | func Marshal[V any](v V) ([]byte, error) { 50 | return MarshalBy(stdJSON, v) 51 | } 52 | 53 | // MarshalIndent returns the JSON-encoded bytes with indent and prefix. 54 | func MarshalIndent[V any](v V, prefix, indent string) ([]byte, error) { 55 | return MarshalIndentBy(stdJSON, v, prefix, indent) 56 | } 57 | 58 | // MarshalString returns the JSON-encoded string of v. 59 | func MarshalString[V any](v V) (string, error) { 60 | return MarshalStringBy(stdJSON, v) 61 | } 62 | 63 | // ToString returns the JSON-encoded string of v and ignores error. 64 | func ToString[V any](v V) string { 65 | return ToStringBy(stdJSON, v) 66 | } 67 | 68 | // ToStringIndent returns the JSON-encoded string with indent and prefix of v and ignores error. 69 | func ToStringIndent[V any](v V, prefix, indent string) string { 70 | return ToStringIndentBy(stdJSON, v, prefix, indent) 71 | } 72 | 73 | // Unmarshal parses the JSON-encoded bytes and string and returns the result. 74 | func Unmarshal[T any, V ~[]byte | ~string](v V) (T, error) { 75 | return UnmarshalBy[T](stdJSON, v) 76 | } 77 | -------------------------------------------------------------------------------- /gson/gson_codec_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gson 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/bytedance/gg/collection/set" 21 | "github.com/bytedance/gg/internal/assert" 22 | ) 23 | 24 | var codecs = map[string]JSONCodec{ 25 | "stdlib": stdJSON, 26 | // import "github.com/bytedance/sonic" 27 | // "sonic.ConfigDefault": sonic.ConfigDefault, 28 | // "sonic.ConfigStd": sonic.ConfigStd, 29 | // "sonic.ConfigFastest": sonic.ConfigFastest, 30 | // import jsoniter "github.com/json-iterator/go" 31 | // "jsoniter.ConfigDefault": jsoniter.ConfigDefault, 32 | // "jsoniter.ConfigStd": jsoniter.ConfigCompatibleWithStandardLibrary, 33 | // "jsoniter.ConfigFastest": jsoniter.ConfigFastest, 34 | } 35 | 36 | func TestValidBy(t *testing.T) { 37 | for name, codec := range codecs { 38 | t.Run(name, func(t *testing.T) { 39 | assert.True(t, ValidBy(codec, validJSONString)) 40 | assert.True(t, ValidBy(codec, validJSONBytes)) 41 | assert.True(t, ValidBy(codec, validJSONMyString)) 42 | assert.True(t, ValidBy(codec, validJSONMyBytes)) 43 | 44 | assert.False(t, ValidBy(codec, invalidJSONString)) 45 | assert.False(t, ValidBy(codec, invalidJSONBytes)) 46 | assert.False(t, ValidBy(codec, invalidJSONMyString)) 47 | assert.False(t, ValidBy(codec, invalidJSONMyBytes)) 48 | }) 49 | } 50 | } 51 | 52 | func TestMarshalBy(t *testing.T) { 53 | expected := []byte(`{"name":"test","age":10}`) 54 | for name, codec := range codecs { 55 | t.Run(name, func(t *testing.T) { 56 | got, err := MarshalBy(codec, testcase) 57 | assert.Nil(t, err) 58 | assert.Equal(t, expected, got) 59 | }) 60 | } 61 | } 62 | 63 | func TestMarshalIndentBy(t *testing.T) { 64 | expected := []byte("{\n \"name\": \"test\",\n \"age\": 10\n}") 65 | for name, codec := range codecs { 66 | t.Run(name, func(t *testing.T) { 67 | got, err := MarshalIndentBy(codec, testcase, "", " ") 68 | assert.Nil(t, err) 69 | assert.Equal(t, expected, got) 70 | }) 71 | } 72 | } 73 | 74 | func TestMarshalToStringBy(t *testing.T) { 75 | expected := `{"name":"test","age":10}` 76 | for name, codec := range codecs { 77 | t.Run(name, func(t *testing.T) { 78 | got, err := MarshalStringBy(codec, testStruct{Name: "test", Age: 10}) 79 | assert.Nil(t, err) 80 | assert.Equal(t, expected, got) 81 | }) 82 | } 83 | } 84 | 85 | func TestToStringBy(t *testing.T) { 86 | expected := `{"name":"test","age":10}` 87 | for name, codec := range codecs { 88 | t.Run(name, func(t *testing.T) { 89 | got := ToStringBy(codec, testStruct{Name: "test", Age: 10}) 90 | assert.Equal(t, expected, got) 91 | }) 92 | } 93 | } 94 | 95 | func TestToStringIndentBy(t *testing.T) { 96 | expected := `{ 97 | "name": "test", 98 | "age": 10 99 | }` 100 | for name, codec := range codecs { 101 | t.Run(name, func(t *testing.T) { 102 | got := ToStringIndentBy(codec, testStruct{Name: "test", Age: 10}, "", " ") 103 | assert.Equal(t, expected, got) 104 | }) 105 | } 106 | } 107 | 108 | func TestUnmarshalBy(t *testing.T) { 109 | for _, codec := range codecs { 110 | { 111 | got, err := UnmarshalBy[testStruct](codec, ``) 112 | expected := testStruct{} 113 | assert.NotNil(t, err) 114 | assert.Equal(t, expected, got) 115 | } 116 | { 117 | got, err := UnmarshalBy[testStruct](codec, []byte(``)) 118 | expected := testStruct{} 119 | assert.NotNil(t, err) 120 | assert.Equal(t, expected, got) 121 | } 122 | { 123 | got, err := UnmarshalBy[testStruct](codec, `{"name":"test","age":10}`) 124 | expected := testStruct{Name: "test", Age: 10} 125 | assert.Nil(t, err) 126 | assert.Equal(t, expected, got) 127 | } 128 | { 129 | got, err := UnmarshalBy[testStruct](codec, []byte(`{"name":"test","age":10}`)) 130 | expected := testStruct{Name: "test", Age: 10} 131 | assert.Nil(t, err) 132 | assert.Equal(t, expected, got) 133 | } 134 | { 135 | got, err := UnmarshalBy[*testStruct](codec, `{"name":"test","age":10}`) 136 | expected := &testStruct{Name: "test", Age: 10} 137 | assert.Nil(t, err) 138 | assert.Equal(t, expected, got) 139 | } 140 | { 141 | got, err := UnmarshalBy[*testStruct](codec, []byte(`{"name":"test","age":10}`)) 142 | expected := &testStruct{Name: "test", Age: 10} 143 | assert.Nil(t, err) 144 | assert.Equal(t, expected, got) 145 | } 146 | { 147 | got, err := UnmarshalBy[map[string]any](codec, `{"name":"test","age":10}`) 148 | expected := map[string]any{"name": "test", "age": float64(10)} 149 | assert.Nil(t, err) 150 | assert.Equal(t, expected, got) 151 | } 152 | { 153 | got, err := UnmarshalBy[[]int32](codec, `[1,2, 3]`) 154 | expected := []int32{1, 2, 3} 155 | assert.Nil(t, err) 156 | assert.Equal(t, expected, got) 157 | } 158 | { 159 | got, err := UnmarshalBy[*set.Set[int32]](codec, `[1,2, 3]`) 160 | expected := set.New[int32](1, 2, 3) 161 | assert.Nil(t, err) 162 | assert.Equal(t, expected, got) 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /gson/gson_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gson 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/bytedance/gg/gresult" 21 | ) 22 | 23 | func Example() { 24 | type testStruct struct { 25 | Name string `json:"name"` 26 | Age int `json:"age"` 27 | } 28 | testcase := testStruct{Name: "test", Age: 10} 29 | 30 | fmt.Println(string(gresult.Of(Marshal(testcase)).Value())) // `{"name":"test","age":10}` 31 | fmt.Println(gresult.Of(MarshalString(testcase)).Value()) // `{"name":"test","age":10}` 32 | fmt.Println(ToString(testcase)) // `{"name":"test","age":10}` 33 | fmt.Println(string(gresult.Of(MarshalIndent(testcase, "", " ")).Value())) // "{\n \"name\": \"test\",\n \"age\": 10\n}" 34 | fmt.Println(ToStringIndent(testcase, "", " ")) // "{\n \"name\": \"test\",\n \"age\": 10\n}" 35 | fmt.Println(Valid(`{"name":"test","age":10}`)) // true 36 | fmt.Println(Unmarshal[testStruct](`{"name":"test","age":10}`)) // {test 10} nil 37 | 38 | // Output: 39 | // {"name":"test","age":10} 40 | // {"name":"test","age":10} 41 | // {"name":"test","age":10} 42 | // { 43 | // "name": "test", 44 | // "age": 10 45 | // } 46 | // { 47 | // "name": "test", 48 | // "age": 10 49 | // } 50 | // true 51 | // {test 10} 52 | } 53 | -------------------------------------------------------------------------------- /gson/gson_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gson 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/bytedance/gg/collection/set" 21 | "github.com/bytedance/gg/internal/assert" 22 | ) 23 | 24 | type testStruct struct { 25 | Name string `json:"name"` 26 | Age int `json:"age"` 27 | } 28 | 29 | type ( 30 | MyString string 31 | MyBytes []byte 32 | ) 33 | 34 | var ( 35 | testcase = testStruct{Name: "test", Age: 10} 36 | 37 | validJSONString = `{"name":"test", "age": 10}` 38 | validJSONBytes = []byte(validJSONString) 39 | invalidJSONString = `{"name":"test", "age": 10` 40 | invalidJSONBytes = []byte(invalidJSONString) 41 | 42 | validJSONMyString = MyString(validJSONString) 43 | validJSONMyBytes = MyBytes(validJSONString) 44 | invalidJSONMyString = MyString(invalidJSONString) 45 | invalidJSONMyBytes = MyBytes(invalidJSONString) 46 | ) 47 | 48 | func TestValid(t *testing.T) { 49 | assert.True(t, Valid(validJSONString)) 50 | assert.True(t, Valid(validJSONBytes)) 51 | assert.True(t, Valid(validJSONMyString)) 52 | assert.True(t, Valid(validJSONMyBytes)) 53 | assert.False(t, Valid(invalidJSONString)) 54 | assert.False(t, Valid(invalidJSONBytes)) 55 | assert.False(t, Valid(invalidJSONMyString)) 56 | assert.False(t, Valid(invalidJSONMyBytes)) 57 | } 58 | 59 | func TestMarshal(t *testing.T) { 60 | got, err := Marshal(testcase) 61 | expected := []byte(`{"name":"test","age":10}`) 62 | assert.Nil(t, err) 63 | assert.Equal(t, expected, got) 64 | } 65 | 66 | func TestMarshalIndent(t *testing.T) { 67 | got, err := MarshalIndent(testcase, "", " ") 68 | expected := []byte("{\n \"name\": \"test\",\n \"age\": 10\n}") 69 | assert.Nil(t, err) 70 | assert.Equal(t, expected, got) 71 | } 72 | 73 | func TestMarshalString(t *testing.T) { 74 | got, err := MarshalString(testStruct{Name: "test", Age: 10}) 75 | expected := `{"name":"test","age":10}` 76 | assert.Nil(t, err) 77 | assert.Equal(t, expected, got) 78 | } 79 | 80 | func TestToString(t *testing.T) { 81 | got := ToString(testStruct{Name: "test", Age: 10}) 82 | expected := `{"name":"test","age":10}` 83 | assert.Equal(t, expected, got) 84 | } 85 | 86 | func TestToStringIndent(t *testing.T) { 87 | got := ToStringIndent(testStruct{Name: "test", Age: 10}, "", " ") 88 | expected := `{ 89 | "name": "test", 90 | "age": 10 91 | }` 92 | assert.Equal(t, expected, got) 93 | } 94 | 95 | func TestUnmarshal(t *testing.T) { 96 | { 97 | got, err := Unmarshal[testStruct](``) 98 | expected := testStruct{} 99 | assert.NotNil(t, err) 100 | assert.Equal(t, expected, got) 101 | } 102 | { 103 | got, err := Unmarshal[testStruct]([]byte(``)) 104 | expected := testStruct{} 105 | assert.NotNil(t, err) 106 | assert.Equal(t, expected, got) 107 | } 108 | { 109 | got, err := Unmarshal[testStruct](`{"name":"test","age":10}`) 110 | expected := testStruct{Name: "test", Age: 10} 111 | assert.Nil(t, err) 112 | assert.Equal(t, expected, got) 113 | } 114 | { 115 | got, err := Unmarshal[testStruct]([]byte(`{"name":"test","age":10}`)) 116 | expected := testStruct{Name: "test", Age: 10} 117 | assert.Nil(t, err) 118 | assert.Equal(t, expected, got) 119 | } 120 | { 121 | got, err := Unmarshal[*testStruct](`{"name":"test","age":10}`) 122 | expected := &testStruct{Name: "test", Age: 10} 123 | assert.Nil(t, err) 124 | assert.Equal(t, expected, got) 125 | } 126 | { 127 | got, err := Unmarshal[*testStruct]([]byte(`{"name":"test","age":10}`)) 128 | expected := &testStruct{Name: "test", Age: 10} 129 | assert.Nil(t, err) 130 | assert.Equal(t, expected, got) 131 | } 132 | { 133 | got, err := Unmarshal[map[string]any](`{"name":"test","age":10}`) 134 | expected := map[string]any{"name": "test", "age": float64(10)} 135 | assert.Nil(t, err) 136 | assert.Equal(t, expected, got) 137 | } 138 | { 139 | got, err := Unmarshal[[]int32](`[1,2, 3]`) 140 | expected := []int32{1, 2, 3} 141 | assert.Nil(t, err) 142 | assert.Equal(t, expected, got) 143 | } 144 | { 145 | got, err := Unmarshal[*set.Set[int32]](`[1,2, 3]`) 146 | expected := set.New[int32](1, 2, 3) 147 | assert.Nil(t, err) 148 | assert.Equal(t, expected, got) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /gstd/gsync/map.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package gsync provides generics wrappers of [sync] package. 16 | // 17 | // Currently, we provide these wrappers: 18 | // - [Map] 19 | // - [Pool] 20 | // - [OnceFunc], [OnceValue], [OnceValues] 21 | package gsync 22 | 23 | import ( 24 | "sync" 25 | 26 | "github.com/bytedance/gg/goption" 27 | "github.com/bytedance/gg/gvalue" 28 | ) 29 | 30 | // Map wraps [sync.Map]. 31 | type Map[K comparable, V any] struct { 32 | m sync.Map 33 | } 34 | 35 | // Load wraps [sync.Map.Load]. 36 | func (sm *Map[K, V]) Load(key K) (V, bool) { 37 | v, ok := sm.m.Load(key) 38 | if !ok { 39 | return gvalue.Zero[V](), false 40 | } 41 | return v.(V), true 42 | } 43 | 44 | // LoadO wraps [Load], returns a goption value instead of (V, bool). 45 | func (sm *Map[K, V]) LoadO(key K) goption.O[V] { 46 | return goption.Of(sm.Load(key)) 47 | } 48 | 49 | // Store wraps [sync.Map.Store]. 50 | func (sm *Map[K, V]) Store(key K, value V) { 51 | sm.m.Store(key, value) 52 | } 53 | 54 | // LoadOrStore wraps [sync.Map.LoadOrStore]. 55 | func (sm *Map[K, V]) LoadOrStore(key K, value V) (V, bool) { 56 | v, loaded := sm.m.LoadOrStore(key, value) 57 | if loaded { 58 | return v.(V), true 59 | } 60 | return value, false 61 | } 62 | 63 | // LoadAndDelete wraps [sync.Map.LoadAndDelete]. 64 | func (sm *Map[K, V]) LoadAndDelete(key K) (V, bool) { 65 | v, loaded := sm.m.LoadAndDelete(key) 66 | if loaded { 67 | return v.(V), true 68 | } 69 | return gvalue.Zero[V](), false 70 | } 71 | 72 | // Delete wraps [sync.Map.Delete]. 73 | func (sm *Map[K, V]) Delete(key K) { 74 | sm.m.Delete(key) 75 | } 76 | 77 | // Range wraps [sync.Map.Range]. 78 | func (sm *Map[K, V]) Range(f func(K, V) bool) { 79 | sm.m.Range(func(key, value any) bool { 80 | return f(key.(K), value.(V)) 81 | }) 82 | } 83 | 84 | // ToMap collects all keys and values to a go builtin map. 85 | func (sm *Map[K, V]) ToMap() map[K]V { 86 | m := make(map[K]V) 87 | sm.Range(func(k K, v V) bool { 88 | m[k] = v 89 | return true 90 | }) 91 | return m 92 | } 93 | -------------------------------------------------------------------------------- /gstd/gsync/map_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gsync 16 | 17 | import ( 18 | "fmt" 19 | ) 20 | 21 | func ExampleMap() { 22 | sm := Map[string, int]{} 23 | sm.Store("k", 1) 24 | fmt.Println(sm.Load("k")) // 1 true 25 | fmt.Println(sm.LoadO("k").Value()) // 1 26 | sm.Store("k", 2) 27 | fmt.Println(sm.Load("k")) // 2 true 28 | fmt.Println(sm.LoadAndDelete("k")) // 2 true 29 | fmt.Println(sm.Load("k")) // 0 false 30 | fmt.Println(sm.LoadOrStore("k", 3)) // 3 false 31 | fmt.Println(sm.Load("k")) // 3 true 32 | fmt.Println(sm.ToMap()) // {"k":3} 33 | 34 | // Output: 35 | // 1 true 36 | // 1 37 | // 2 true 38 | // 2 true 39 | // 0 false 40 | // 3 false 41 | // 3 true 42 | // map[k:3] 43 | } 44 | -------------------------------------------------------------------------------- /gstd/gsync/map_go120.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build go1.20 16 | // +build go1.20 17 | 18 | package gsync 19 | 20 | import ( 21 | "github.com/bytedance/gg/gvalue" 22 | ) 23 | 24 | // Swap wraps [sync.Map.Swap]. 25 | // 26 | // 💡 NOTE: Newly added in go1.20 27 | func (sm *Map[K, V]) Swap(key K, value V) (V, bool) { 28 | previous, loaded := sm.m.Swap(key, value) 29 | if loaded { 30 | return previous.(V), true 31 | } 32 | return gvalue.Zero[V](), false 33 | } 34 | 35 | // CompareAndSwap wraps [sync.Map.CompareAndSwap]. 36 | // 37 | // 💡 NOTE: Newly added in go1.20 38 | func (sm *Map[K, V]) CompareAndSwap(key K, old, new V) bool { 39 | return sm.m.CompareAndSwap(key, old, new) 40 | } 41 | 42 | // CompareAndDelete wraps [sync.Map.CompareAndDelete]. 43 | // 44 | // 💡 NOTE: Newly added in go1.20 45 | func (sm *Map[K, V]) CompareAndDelete(key K, old V) bool { 46 | return sm.m.CompareAndDelete(key, old) 47 | } 48 | -------------------------------------------------------------------------------- /gstd/gsync/map_go120_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build go1.20 16 | // +build go1.20 17 | 18 | package gsync 19 | 20 | import ( 21 | "testing" 22 | 23 | "github.com/bytedance/gg/goption" 24 | "github.com/bytedance/gg/internal/assert" 25 | ) 26 | 27 | func TestSwap(t *testing.T) { 28 | sm := Map[string, int]{} 29 | sm.Store("k", 1) 30 | assert.Equal(t, goption.OK(1), goption.Of(sm.Swap("k", 2))) 31 | assert.Equal(t, goption.OK(2), sm.LoadO("k")) 32 | assert.Equal(t, goption.Nil[int](), goption.Of(sm.Swap("l", 3))) 33 | assert.Equal(t, goption.OK(3), sm.LoadO("l")) 34 | } 35 | 36 | func TestCompareAndSwap(t *testing.T) { 37 | sm := Map[string, int]{} 38 | sm.Store("k", 1) 39 | assert.False(t, sm.CompareAndSwap("k", 2, 3)) 40 | assert.Equal(t, goption.OK(1), sm.LoadO("k")) 41 | assert.True(t, sm.CompareAndSwap("k", 1, 3)) 42 | assert.Equal(t, goption.OK(3), sm.LoadO("k")) 43 | } 44 | 45 | func TestCompareAndDelete(t *testing.T) { 46 | sm := Map[string, int]{} 47 | sm.Store("k", 1) 48 | assert.False(t, sm.CompareAndDelete("k", 2)) 49 | assert.Equal(t, goption.OK(1), sm.LoadO("k")) 50 | assert.True(t, sm.CompareAndDelete("k", 1)) 51 | assert.False(t, sm.LoadO("k").IsOK()) 52 | } 53 | -------------------------------------------------------------------------------- /gstd/gsync/map_go123.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build go1.23 16 | // +build go1.23 17 | 18 | package gsync 19 | 20 | // Clear wraps [sync.Map.Clear]. 21 | func (sm *Map[K, V]) Clear() { 22 | sm.m.Clear() 23 | } 24 | -------------------------------------------------------------------------------- /gstd/gsync/map_go123_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build go1.23 16 | // +build go1.23 17 | 18 | package gsync 19 | 20 | import ( 21 | "testing" 22 | 23 | "github.com/bytedance/gg/goption" 24 | "github.com/bytedance/gg/internal/assert" 25 | ) 26 | 27 | func TestClear(t *testing.T) { 28 | sm := Map[string, int]{} 29 | sm.Store("k", 1) 30 | assert.Equal(t, goption.OK(1), sm.LoadO("k")) 31 | sm.Clear() 32 | assert.Equal(t, goption.Nil[int](), sm.LoadO("k")) 33 | } 34 | -------------------------------------------------------------------------------- /gstd/gsync/map_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gsync 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/bytedance/gg/goption" 21 | "github.com/bytedance/gg/internal/assert" 22 | ) 23 | 24 | func TestStoreLoad(t *testing.T) { 25 | sm := Map[string, string]{} 26 | assert.False(t, sm.LoadO("k").IsOK()) 27 | sm.Store("k", "") 28 | assert.Equal(t, goption.OK(""), sm.LoadO("k")) 29 | sm.Store("k", "v") 30 | sm.Store("k1", "v1") 31 | assert.Equal(t, goption.OK("v"), sm.LoadO("k")) 32 | assert.Equal(t, goption.OK("v1"), sm.LoadO("k1")) 33 | assert.False(t, sm.LoadO("k2").IsOK()) 34 | } 35 | 36 | func TestDelete(t *testing.T) { 37 | sm := Map[string, string]{} 38 | sm.Store("k", "v") 39 | assert.Equal(t, goption.OK("v"), sm.LoadO("k")) 40 | sm.Delete("k") 41 | assert.False(t, sm.LoadO("k").IsOK()) 42 | } 43 | 44 | func TestLoadOrStore(t *testing.T) { 45 | sm := Map[string, string]{} 46 | sm.Store("k", "v") 47 | v, ok := sm.LoadOrStore("k", "v1") 48 | assert.Equal(t, "v", v) 49 | assert.Equal(t, true, ok) 50 | v, ok = sm.LoadOrStore("k1", "v1") 51 | assert.Equal(t, "v1", v) 52 | assert.Equal(t, false, ok) 53 | assert.Equal(t, goption.OK("v1"), sm.LoadO("k1")) 54 | } 55 | 56 | func TestLoadAndDelete(t *testing.T) { 57 | sm := Map[string, string]{} 58 | sm.Store("k", "v") 59 | v, ok := sm.LoadAndDelete("k") 60 | assert.Equal(t, "v", v) 61 | assert.Equal(t, true, ok) 62 | assert.False(t, sm.LoadO("k").IsOK()) 63 | v, ok = sm.LoadAndDelete("k1") 64 | assert.Equal(t, "", v) 65 | assert.Equal(t, false, ok) 66 | } 67 | 68 | func TestRange(t *testing.T) { 69 | sm := Map[string, string]{} 70 | sm.Store("k", "v") 71 | sm.Store("k1", "v1") 72 | sm.Store("k2", "v2") 73 | str := "" 74 | count := 0 75 | f := func(k, v string) bool { 76 | str = str + k + v 77 | count++ 78 | return true 79 | } 80 | sm.Range(f) 81 | assert.Equal(t, len("kvk1v1k2v2"), len(str)) 82 | assert.Equal(t, 3, count) 83 | } 84 | 85 | func TestToMap(t *testing.T) { 86 | sm := Map[string, string]{} 87 | sm.Store("k", "v") 88 | sm.Store("k1", "v1") 89 | sm.Store("k2", "v2") 90 | assert.Equal(t, map[string]string{"k": "v", "k1": "v1", "k2": "v2"}, sm.ToMap()) 91 | } 92 | -------------------------------------------------------------------------------- /gstd/gsync/once.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gsync 16 | 17 | import "sync" 18 | 19 | // OnceFunc returns a function that invokes f only once when returned 20 | // function is firstly called. 21 | func OnceFunc(f func()) func() { 22 | var once sync.Once 23 | return func() { 24 | once.Do(func() { f() }) 25 | } 26 | } 27 | 28 | // OnceValue returns a function as value getter. 29 | // Value is returned by function f, and f is invoked only once when returned 30 | // function is firstly called. 31 | // 32 | // This function can be used to lazily initialize a value, as replacement of 33 | // the packages-level init function. For example: 34 | // 35 | // var DB *sql.DB 36 | // 37 | // func init() { 38 | // // 💡 NOTE: DB is initialized here. 39 | // DB, _ = sql.Open("mysql", "user:password@/dbname") 40 | // } 41 | // 42 | // func main() { 43 | // DB.Query(...) 44 | // } 45 | // 46 | // Can be rewritten to: 47 | // 48 | // var DB = OnceValue(func () *sql.DB { 49 | // return gresult.Of(sql.Open("mysql", "user:password@/dbname")).Value() 50 | // }) 51 | // 52 | // func main() { 53 | // // 💡 NOTE: DB is *LAZILY* initialized here. 54 | // DB().Query(...) 55 | // } 56 | // 57 | // 💡 HINT: 58 | // 59 | // - See also https://github.com/golang/go/issues/56102 60 | func OnceValue[T any](f func() T) func() T { 61 | var ( 62 | once sync.Once 63 | v T 64 | ) 65 | return func() T { 66 | once.Do(func() { v = f() }) 67 | return v 68 | } 69 | } 70 | 71 | // OnceValues returns a function as values getter. 72 | // Values are returned by function f, and f is invoked only once when returned 73 | // function is firstly called. 74 | func OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) { 75 | var ( 76 | once sync.Once 77 | v1 T1 78 | v2 T2 79 | ) 80 | return func() (T1, T2) { 81 | once.Do(func() { v1, v2 = f() }) 82 | return v1, v2 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /gstd/gsync/once_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gsync 16 | 17 | import ( 18 | "fmt" 19 | ) 20 | 21 | func Example() { 22 | onceFunc := OnceFunc(func() { fmt.Println("OnceFunc") }) 23 | onceFunc() // "OnceFunc" 24 | onceFunc() // (no output) 25 | onceFunc() // (no output) 26 | 27 | i := 1 28 | onceValue := OnceValue(func() int { i++; return i }) 29 | fmt.Println(onceValue()) // 2 30 | fmt.Println(onceValue()) // 2 31 | 32 | onceValues := OnceValues(func() (int, error) { i++; return i, nil }) 33 | fmt.Println(onceValues()) // 3 nil 34 | fmt.Println(onceValues()) // 3 nil 35 | 36 | // Output: 37 | // OnceFunc 38 | // 2 39 | // 2 40 | // 3 41 | // 3 42 | } 43 | -------------------------------------------------------------------------------- /gstd/gsync/once_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gsync 16 | 17 | import ( 18 | "sync" 19 | "testing" 20 | 21 | "github.com/bytedance/gg/internal/assert" 22 | ) 23 | 24 | func TestOnceValue(t *testing.T) { 25 | { 26 | i := 2 27 | f := func() int { 28 | i++ 29 | return i 30 | } 31 | assert.Equal(t, i, 2) 32 | once := OnceValue(f) 33 | assert.Equal(t, i, 2) 34 | assert.Equal(t, 3, once()) 35 | assert.Equal(t, 3, once()) 36 | assert.Equal(t, 3, once()) 37 | assert.Equal(t, i, 3) 38 | } 39 | 40 | { // Test concurrency. 41 | i := 2 42 | f := func() int { 43 | i++ 44 | return i 45 | } 46 | assert.Equal(t, i, 2) 47 | once := OnceValue(f) 48 | assert.Equal(t, i, 2) 49 | var wg sync.WaitGroup 50 | wg.Add(100) 51 | for j := 0; j < 100; j++ { 52 | go func() { 53 | assert.Equal(t, 3, once()) 54 | assert.Equal(t, i, 3) 55 | wg.Done() 56 | }() 57 | } 58 | wg.Wait() 59 | assert.Equal(t, i, 3) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /gstd/gsync/pool.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gsync 16 | 17 | import "sync" 18 | 19 | // Pool wraps [sync.Pool]. 20 | type Pool[T any] struct { 21 | New func() T 22 | p sync.Pool 23 | newOnce sync.Once 24 | } 25 | 26 | func (p *Pool[T]) init() { 27 | p.newOnce.Do(func() { 28 | p.p.New = func() any { 29 | return p.New() 30 | } 31 | }) 32 | } 33 | 34 | // Get wraps [sync.Pool.Get]. 35 | func (p *Pool[T]) Get() T { 36 | p.init() 37 | return p.p.Get().(T) 38 | } 39 | 40 | // Put wraps [sync.Pool.Put]. 41 | func (p *Pool[T]) Put(x T) { 42 | p.init() 43 | p.p.Put(x) 44 | } 45 | -------------------------------------------------------------------------------- /gstd/gsync/pool_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gsync 16 | 17 | import ( 18 | "fmt" 19 | ) 20 | 21 | func ExamplePool() { 22 | pool := Pool[*int]{ 23 | New: func() *int { 24 | i := 1 25 | return &i 26 | }, 27 | } 28 | a := pool.Get() 29 | fmt.Println(*a) // 1 30 | *a = 2 31 | pool.Put(a) 32 | _ = *pool.Get() // possible output: 1 or 2 33 | 34 | // Output: 35 | // 1 36 | } 37 | -------------------------------------------------------------------------------- /gstd/gsync/pool_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gsync 16 | 17 | import ( 18 | "sync" 19 | "sync/atomic" 20 | "testing" 21 | 22 | "github.com/bytedance/gg/gslice" 23 | "github.com/bytedance/gg/internal/assert" 24 | ) 25 | 26 | func TestPool(t *testing.T) { 27 | var numAlloc int64 28 | 29 | p := Pool[*int]{ 30 | New: func() *int { 31 | atomic.AddInt64(&numAlloc, 1) 32 | var i int 33 | return &i 34 | }, 35 | } 36 | 37 | var ( 38 | n = 10 39 | wg sync.WaitGroup 40 | vals = make([]*int, n) 41 | ) 42 | 43 | wg.Add(n) 44 | for i := 0; i < n; i++ { 45 | go func(i int) { 46 | defer wg.Done() 47 | vals[i] = p.Get() 48 | }(i) 49 | } 50 | wg.Wait() 51 | 52 | assert.Equal(t, n, int(numAlloc)) 53 | assert.Equal(t, n, len(vals)) 54 | assert.Equal(t, n, len(gslice.Uniq(vals))) 55 | 56 | wg.Add(n) 57 | for i := 0; i < n; i++ { 58 | go func(i int) { 59 | defer wg.Done() 60 | p.Put(vals[i]) 61 | vals[i] = nil 62 | }(i) 63 | } 64 | wg.Wait() 65 | assert.Equal(t, n, int(numAlloc)) 66 | } 67 | -------------------------------------------------------------------------------- /gvalue/gvalue_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gvalue 16 | 17 | import ( 18 | "fmt" 19 | ) 20 | 21 | func Example() { 22 | // Zero value 23 | a := Zero[int]() 24 | fmt.Println(a) // 0 25 | fmt.Println(IsZero(a)) // true 26 | b := Zero[*int]() 27 | fmt.Println(b) // nil 28 | fmt.Println(IsNil(b)) // true 29 | fmt.Println(Or(0, 1, 2)) // 1 30 | 31 | // Math operation 32 | fmt.Println(Max(1, 2, 3)) // 3 33 | fmt.Println(Min(1, 2, 3)) // 1 34 | fmt.Println(MinMax(1, 2, 3)) // 1 3 35 | fmt.Println(Clamp(5, 1, 10)) // 5 36 | fmt.Println(Add(1, 2)) // 3 37 | 38 | // Comparison 39 | fmt.Println(Equal(1, 1)) // true 40 | fmt.Println(Between(2, 1, 3)) // true 41 | 42 | // Type assertion 43 | fmt.Println(TypeAssert[int](any(1))) // 1 44 | fmt.Println(TryAssert[int](any(1))) // 1 true 45 | 46 | // Output: 47 | // 0 48 | // true 49 | // 50 | // true 51 | // 1 52 | // 3 53 | // 1 54 | // 1 3 55 | // 5 56 | // 3 57 | // true 58 | // true 59 | // 1 60 | // 1 true 61 | } 62 | -------------------------------------------------------------------------------- /internal/constraints/constraints.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package constraints defines a set of useful constraints to be used 6 | // with type parameters. 7 | package constraints 8 | 9 | // Signed is a constraint that permits any signed integer type. 10 | // If future releases of Go add new predeclared signed integer types, 11 | // this constraint will be modified to include them. 12 | type Signed interface { 13 | ~int | ~int8 | ~int16 | ~int32 | ~int64 14 | } 15 | 16 | // Unsigned is a constraint that permits any unsigned integer type. 17 | // If future releases of Go add new predeclared unsigned integer types, 18 | // this constraint will be modified to include them. 19 | type Unsigned interface { 20 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr 21 | } 22 | 23 | // Integer is a constraint that permits any integer type. 24 | // If future releases of Go add new predeclared integer types, 25 | // this constraint will be modified to include them. 26 | type Integer interface { 27 | Signed | Unsigned 28 | } 29 | 30 | // Float is a constraint that permits any floating-point type. 31 | // If future releases of Go add new predeclared floating-point types, 32 | // this constraint will be modified to include them. 33 | type Float interface { 34 | ~float32 | ~float64 35 | } 36 | 37 | // Complex is a constraint that permits any complex numeric type. 38 | // If future releases of Go add new predeclared complex numeric types, 39 | // this constraint will be modified to include them. 40 | type Complex interface { 41 | ~complex64 | ~complex128 42 | } 43 | 44 | // Number is a constraint that permits any numeric type. 45 | type Number interface { 46 | Integer | Float 47 | } 48 | -------------------------------------------------------------------------------- /internal/constraints/ordered_go118.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !go1.21 16 | // +build !go1.21 17 | 18 | package constraints 19 | 20 | // Ordered is a constraint that permits any ordered type: any type 21 | // that supports the operators < <= >= >. 22 | // If future releases of Go add new ordered types, 23 | // this constraint will be modified to include them. 24 | type Ordered interface { 25 | Number | ~string 26 | } 27 | -------------------------------------------------------------------------------- /internal/constraints/ordered_go121.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build go1.21 16 | // +build go1.21 17 | 18 | package constraints 19 | 20 | import ( 21 | "cmp" 22 | ) 23 | 24 | type Ordered = cmp.Ordered 25 | -------------------------------------------------------------------------------- /internal/conv/bytesconv_go118.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !go1.21 16 | // +build !go1.21 17 | 18 | package conv 19 | 20 | import ( 21 | "reflect" 22 | "unsafe" 23 | ) 24 | 25 | // StringToBytes converts string to []byte without a memory allocation. 26 | func StringToBytes(s string) (b []byte) { 27 | sh := (*reflect.StringHeader)(unsafe.Pointer(&s)) 28 | bh := (*reflect.SliceHeader)(unsafe.Pointer(&b)) 29 | bh.Data, bh.Len, bh.Cap = sh.Data, sh.Len, sh.Len 30 | return b 31 | } 32 | 33 | // BytesToString converts []byte to string without a memory allocation. 34 | func BytesToString(b []byte) string { 35 | return *(*string)(unsafe.Pointer(&b)) 36 | } 37 | -------------------------------------------------------------------------------- /internal/conv/bytesconv_go121.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build go1.21 16 | // +build go1.21 17 | 18 | package conv 19 | 20 | import ( 21 | "unsafe" 22 | ) 23 | 24 | // StringToBytes converts string to []byte without a memory allocation. 25 | func StringToBytes(s string) []byte { 26 | return unsafe.Slice(unsafe.StringData(s), len(s)) 27 | } 28 | 29 | // BytesToString converts []byte to string without a memory allocation. 30 | func BytesToString(b []byte) string { 31 | return unsafe.String(unsafe.SliceData(b), len(b)) 32 | } 33 | -------------------------------------------------------------------------------- /internal/fastrand/fastrand_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package fastrand 16 | 17 | import ( 18 | "testing" 19 | ) 20 | 21 | func TestAll(t *testing.T) { 22 | _ = Uint32() 23 | 24 | p := make([]byte, 1000) 25 | n, err := Read(p) 26 | if n != len(p) || err != nil || (p[0] == 0 && p[1] == 0 && p[2] == 0) { 27 | t.Fatal() 28 | } 29 | 30 | a := Perm(100) 31 | for i := range a { 32 | var find bool 33 | for _, v := range a { 34 | if v == i { 35 | find = true 36 | } 37 | } 38 | if !find { 39 | t.Fatal() 40 | } 41 | } 42 | 43 | Shuffle(len(a), func(i, j int) { 44 | a[i], a[j] = a[j], a[i] 45 | }) 46 | for i := range a { 47 | var find bool 48 | for _, v := range a { 49 | if v == i { 50 | find = true 51 | } 52 | } 53 | if !find { 54 | t.Fatal() 55 | } 56 | } 57 | 58 | Shuffle2(a) 59 | for i := range a { 60 | var find bool 61 | for _, v := range a { 62 | if v == i { 63 | find = true 64 | } 65 | } 66 | if !find { 67 | t.Fatal() 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /internal/fastrand/runtime_go118.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !go1.19 16 | // +build !go1.19 17 | 18 | package fastrand 19 | 20 | import ( 21 | _ "unsafe" 22 | ) 23 | 24 | //go:linkname runtimefastrand runtime.fastrand 25 | func runtimefastrand() uint32 26 | 27 | func runtimefastrand64() uint64 { 28 | return (uint64(runtimefastrand()) << 32) | uint64(runtimefastrand()) 29 | } 30 | 31 | func runtimefastrandu() uint { 32 | // PtrSize is the size of a pointer in bytes - unsafe.Sizeof(uintptr(0)) but as an ideal constant. 33 | // It is also the size of the machine's native word size (that is, 4 on 32-bit systems, 8 on 64-bit). 34 | const PtrSize = 4 << (^uintptr(0) >> 63) 35 | if PtrSize == 4 { 36 | return uint(runtimefastrand()) 37 | } 38 | return uint(runtimefastrand64()) 39 | } 40 | -------------------------------------------------------------------------------- /internal/fastrand/runtime_go119.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build go1.19 && !go1.22 16 | // +build go1.19,!go1.22 17 | 18 | package fastrand 19 | 20 | import ( 21 | _ "unsafe" 22 | ) 23 | 24 | //go:linkname runtimefastrand runtime.fastrand 25 | func runtimefastrand() uint32 26 | 27 | //go:linkname runtimefastrand64 runtime.fastrand64 28 | func runtimefastrand64() uint64 29 | 30 | //go:linkname runtimefastrandu runtime.fastrandu 31 | func runtimefastrandu() uint 32 | -------------------------------------------------------------------------------- /internal/fastrand/runtime_go122.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build go1.22 16 | // +build go1.22 17 | 18 | package fastrand 19 | 20 | import ( 21 | "math/rand/v2" 22 | ) 23 | 24 | func runtimefastrand() uint32 { 25 | return rand.Uint32() 26 | } 27 | 28 | func runtimefastrand64() uint64 { 29 | return rand.Uint64() 30 | } 31 | 32 | func runtimefastrandu() uint { 33 | return uint(rand.Uint64()) 34 | } 35 | -------------------------------------------------------------------------------- /internal/heapsort/sort.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package heapsort 16 | 17 | import ( 18 | "github.com/bytedance/gg/internal/constraints" 19 | ) 20 | 21 | // siftDown implements the heap property on v[lo:hi]. 22 | func siftDown[T constraints.Ordered](v []T, node int) { 23 | for { 24 | child := 2*node + 1 25 | if child >= len(v) { 26 | break 27 | } 28 | if child+1 < len(v) && v[child] < v[child+1] { 29 | child++ 30 | } 31 | if v[node] >= v[child] { 32 | return 33 | } 34 | v[node], v[child] = v[child], v[node] 35 | node = child 36 | } 37 | } 38 | 39 | func Sort[T constraints.Ordered](v []T) { 40 | // Build heap with the greatest element at the top. 41 | for i := (len(v) - 1) / 2; i >= 0; i-- { 42 | siftDown(v, i) 43 | } 44 | 45 | // Pop elements into end of v. 46 | for i := len(v) - 1; i >= 1; i-- { 47 | v[0], v[i] = v[i], v[0] 48 | siftDown(v[:i], 0) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /internal/iter/helper_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package iter 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/bytedance/gg/internal/assert" 21 | ) 22 | 23 | func TestPeekerPeek(t *testing.T) { 24 | p := ToPeeker(FromSlice([]int{})) 25 | for i := 0; i < 10; i++ { 26 | assert.Zero(t, len(p.Peek(i))) 27 | } 28 | 29 | s := []int{1, 2, 3, 4} 30 | p = ToPeeker(FromSlice(s)) 31 | for i := 0; i < 10; i++ { 32 | if i < len(s) { 33 | assert.NotZero(t, len(p.Peek(1))) 34 | assert.NotZero(t, len(p.Next(1))) 35 | } else { 36 | assert.Zero(t, len(p.Peek(1))) 37 | assert.Zero(t, len(p.Next(1))) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /internal/iter/iter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package iter provides definition of generic iterator Iter and high-order functions. 16 | // 17 | // Please refer to README.md for details. 18 | package iter 19 | 20 | const ( 21 | ALL = -1 22 | ) 23 | 24 | // Iter is a generic iterator interface, which helps us iterate various 25 | // data structures in the same way. 26 | // 27 | // Users can apply various operations ([Map], [Filter], etc.) on custom data 28 | // structures by implementing Iter for them. 29 | // See ExampleIter_impl for details. 30 | type Iter[T any] interface { 31 | // Next returns N items of iterator when it is not empty. 32 | // When the iterator is empty, nil is returned. 33 | // When n = [ALL] or n is greater than the number of remaining elements, 34 | // all remaining are returned. 35 | // 36 | // The returned slice is owned by caller. So implementer should return a 37 | // newly allocated slice if needed. 38 | // 39 | // Passing in a negative n (except [ALL]) is undefined behavior. 40 | Next(n int) []T 41 | } 42 | 43 | // emptyIter returns nil whenever its Next method is called. 44 | // It can be as a default abnormal behavior when implements Iter. 45 | // For example, in RangeWithStep, if the internal does not exist, it will return emptyIter, 46 | // so the returned Iter works normally in silence in the subsequent iterator chain. 47 | type emptyIter[T any] struct{} 48 | 49 | func (i emptyIter[T]) Next(_ int) []T { 50 | return nil 51 | } 52 | -------------------------------------------------------------------------------- /internal/iter/iter_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package iter 16 | 17 | import ( 18 | "container/list" 19 | "fmt" 20 | "strconv" 21 | 22 | "github.com/bytedance/gg/gvalue" 23 | ) 24 | 25 | type listIter[T any] struct { 26 | e *list.Element 27 | } 28 | 29 | func FromList[T any](l *list.List) Iter[T] { 30 | return &listIter[T]{l.Front()} 31 | } 32 | 33 | func (i *listIter[T]) Next(n int) []T { 34 | var next []T 35 | j := 0 36 | for i.e != nil { 37 | next = append(next, i.e.Value.(T)) 38 | i.e = i.e.Next() 39 | j++ 40 | if n != ALL && j >= n { 41 | break 42 | } 43 | } 44 | return next 45 | } 46 | 47 | // Iter for container list. 48 | func ExampleIter_impl() { 49 | l := list.New() 50 | l.PushBack(0) 51 | l.PushBack(1) 52 | l.PushBack(2) 53 | 54 | s := ToSlice( 55 | Filter(gvalue.IsNotZero[int], 56 | FromList[int](l))) 57 | fmt.Println(s) 58 | 59 | // Output: 60 | // [1 2] 61 | } 62 | 63 | // Convert an int slice to string slice. 64 | func Example() { 65 | s := ToSlice( 66 | Map(strconv.Itoa, 67 | Filter(gvalue.IsNotZero[int], 68 | FromSlice([]int{0, 1, 2, 3, 4})))) 69 | fmt.Printf("%q\n", s) 70 | 71 | // Output: 72 | // ["1" "2" "3" "4"] 73 | } 74 | -------------------------------------------------------------------------------- /internal/iter/operations_bench_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package iter 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/bytedance/gg/internal/fastrand" 21 | ) 22 | 23 | func BenchmarkUniq(b *testing.B) { 24 | const M = 1000 25 | nums := make([]int, M) 26 | verify := make(map[int]struct{}) 27 | for i := 0; i < M; i++ { 28 | nums[i] = fastrand.Intn(100) 29 | verify[nums[i]] = struct{}{} 30 | } 31 | 32 | b.Run("fast", func(b *testing.B) { 33 | for n := 0; n < b.N; n++ { 34 | l := len(ToSlice(Uniq(FromSlice(nums)))) 35 | if l != len(verify) { 36 | b.Errorf("mismatched len, expect %d, found %d", len(verify), l) 37 | } 38 | } 39 | }) 40 | 41 | b.Run("slow", func(b *testing.B) { 42 | for n := 0; n < b.N; n++ { 43 | l := len(ToSlice(Take(M, Uniq(FromSlice(nums))))) 44 | if l != len(verify) { 45 | b.Errorf("mismatched len, expect %d, found %d", len(verify), l) 46 | } 47 | } 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /internal/iter/sinks.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package iter 16 | 17 | import ( 18 | "context" 19 | 20 | "github.com/bytedance/gg/collection/tuple" 21 | ) 22 | 23 | // ToSlice collects elements of iterator to a slice. 24 | // 25 | // If the iterator is empty, empty slice []T{} is returned. 26 | func ToSlice[T any](i Iter[T]) []T { 27 | all := i.Next(ALL) 28 | if all == nil { 29 | return []T{} // Always returns a slice 30 | } else { 31 | return all 32 | } 33 | } 34 | 35 | // ToMap collects elements of iterator i to a map, 36 | // both map keys and values are produced by mapping function f. 37 | // 38 | // If the iterator is empty, empty map map[K]V{} is returned. 39 | func ToMap[K comparable, V, T any](f func(T) (K, V), i Iter[T]) map[K]V { 40 | s := i.Next(ALL) 41 | m := make(map[K]V, len(s)/2) 42 | for _, e := range s { 43 | k, v := f(e) 44 | m[k] = v 45 | } 46 | return m 47 | } 48 | 49 | // ToMapValues collects elements of iterator to values of map, 50 | // the map keys are produced by mapping function f. 51 | // 52 | // If the iterator is empty, empty map map[K]T{} is returned. 53 | func ToMapValues[K comparable, T any](f func(T) K, i Iter[T]) map[K]T { 54 | s := i.Next(ALL) 55 | m := make(map[K]T, len(s)/2) 56 | for _, e := range s { 57 | m[f(e)] = e 58 | } 59 | return m 60 | } 61 | 62 | // KVToMap collects elements of iterator to a map. 63 | // 64 | // If the iterator is empty, empty map map[K]V{} is returned. 65 | func KVToMap[K comparable, V any](i Iter[tuple.T2[K, V]]) map[K]V { 66 | return ToMap(func(v tuple.T2[K, V]) (K, V) { return v.Values() }, i) 67 | } 68 | 69 | // ToChan collects elements of iterator to a chan. 70 | // The returned channel will be closed when iterator is exhausted. 71 | func ToChan[T any](ctx context.Context, i Iter[T]) <-chan T { 72 | ch := make(chan T) 73 | go func() { 74 | for { 75 | s := i.Next(1) 76 | if len(s) == 0 { 77 | close(ch) 78 | return 79 | } 80 | select { 81 | case ch <- s[0]: 82 | case <-ctx.Done(): 83 | close(ch) 84 | return 85 | } 86 | } 87 | }() 88 | return ch 89 | } 90 | 91 | // ToBufferedChan collects elements of iterator to a buffered chan with given size. 92 | // 93 | // The returned channel will be closed when iterator is exhausted. 94 | func ToBufferedChan[T any](ctx context.Context, size int, i Iter[T]) <-chan T { 95 | ch := make(chan T, size) 96 | go func() { 97 | for { 98 | s := i.Next(size) 99 | empty := len(s) != size 100 | for _, v := range s { 101 | select { 102 | case ch <- v: 103 | case <-ctx.Done(): 104 | close(ch) 105 | return 106 | } 107 | } 108 | if empty { 109 | close(ch) 110 | return 111 | } 112 | } 113 | }() 114 | return ch 115 | } 116 | -------------------------------------------------------------------------------- /internal/iter/sinks_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package iter 16 | 17 | import ( 18 | "context" 19 | "testing" 20 | 21 | "github.com/bytedance/gg/internal/assert" 22 | ) 23 | 24 | func TestToChan(t *testing.T) { 25 | assertSliceEqual(t, 26 | []int{1, 2, 3}, 27 | func() Iter[int] { 28 | return FromChan(context.Background(), 29 | ToChan(context.Background(), 30 | FromSlice([]int{1, 2, 3}))) 31 | }) 32 | 33 | assert.NotPanic(t, func() { 34 | ctx, cancel := context.WithCancel(context.Background()) 35 | cancel() // Cancel it immediately. 36 | 37 | _ = ToSlice( 38 | FromChan(context.Background(), 39 | ToChan(ctx, 40 | Iter[int](Range(1, 100000))))) 41 | }) 42 | } 43 | 44 | func TestToBufferedChan(t *testing.T) { 45 | assertSliceEqual(t, 46 | []int{1, 2, 3}, 47 | func() Iter[int] { 48 | return FromChan(context.Background(), 49 | ToBufferedChan(context.Background(), 10, 50 | FromSlice([]int{1, 2, 3}))) 51 | }) 52 | 53 | assert.NotPanic(t, func() { 54 | ctx, cancel := context.WithCancel(context.Background()) 55 | cancel() // Cancel it immediately. 56 | 57 | _ = ToSlice( 58 | FromChan(context.Background(), 59 | ToBufferedChan(ctx, 100, 60 | Iter[int](Range(1, 100000))))) 61 | }) 62 | } 63 | -------------------------------------------------------------------------------- /internal/jsonbuilder/array.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package jsonbuilder 16 | 17 | import ( 18 | "bytes" 19 | "encoding/json" 20 | 21 | "github.com/bytedance/gg/internal/stream" 22 | ) 23 | 24 | // Array is a builder for building JSON array. 25 | type Array struct { 26 | elems [][]byte 27 | size int 28 | } 29 | 30 | func NewArray() *Array { 31 | return &Array{} 32 | } 33 | 34 | func (a *Array) Append(v any) error { 35 | bs, err := json.Marshal(v) 36 | if err != nil { 37 | return err 38 | } 39 | a.elems = append(a.elems, bs) 40 | a.size += len(bs) 41 | return nil 42 | } 43 | 44 | func (a *Array) Sort() { 45 | a.elems = stream. 46 | StealSlice(a.elems). 47 | SortBy(func(a, b []byte) bool { return bytes.Compare(a, b) == -1 }). 48 | ToSlice() 49 | } 50 | 51 | func (a *Array) Build() ([]byte, error) { 52 | if a == nil { 53 | return []byte("null"), nil 54 | } 55 | 56 | var buf bytes.Buffer 57 | 58 | size := a.size 59 | size += len(a.elems) - 1 // count of comma "," 60 | size += 2 // "[" and "]" 61 | buf.Grow(size) 62 | 63 | buf.WriteByte('[') 64 | for _, bs := range a.elems { 65 | buf.Write(bs) 66 | buf.WriteByte(',') 67 | } 68 | 69 | var out []byte 70 | if len(a.elems) == 0 { 71 | buf.WriteByte(']') 72 | out = buf.Bytes() 73 | } else { 74 | extraComma := buf.Len() - 1 75 | out = buf.Bytes() 76 | // Replace extra `,` to `]`. 77 | out[extraComma] = ']' 78 | } 79 | 80 | return out, nil 81 | } 82 | -------------------------------------------------------------------------------- /internal/jsonbuilder/array_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package jsonbuilder 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/bytedance/gg/internal/assert" 21 | ) 22 | 23 | func TestArrayBuild(t *testing.T) { 24 | { 25 | s := []int{1, 2, 3, 4, 5} 26 | a := NewArray() 27 | for _, v := range s { 28 | err := a.Append(v) 29 | assert.Nil(t, err) 30 | } 31 | bs, err := a.Build() 32 | assert.Nil(t, err) 33 | assert.Equal(t, []byte(`[1,2,3,4,5]`), bs) 34 | } 35 | 36 | { 37 | s := []int{} 38 | a := NewArray() 39 | for _, v := range s { 40 | err := a.Append(v) 41 | assert.Nil(t, err) 42 | } 43 | bs, err := a.Build() 44 | assert.Nil(t, err) 45 | assert.Equal(t, []byte(`[]`), bs) 46 | } 47 | 48 | { 49 | s := []string{"a"} 50 | a := NewArray() 51 | for _, v := range s { 52 | err := a.Append(v) 53 | assert.Nil(t, err) 54 | } 55 | bs, err := a.Build() 56 | assert.Nil(t, err) 57 | assert.Equal(t, []byte(`["a"]`), bs) 58 | } 59 | 60 | { 61 | var a *Array 62 | bs, err := a.Build() 63 | assert.Nil(t, err) 64 | assert.Equal(t, []byte(`null`), bs) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /internal/jsonbuilder/dict.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package jsonbuilder 16 | 17 | import ( 18 | "bytes" 19 | "encoding" 20 | "encoding/json" 21 | "fmt" 22 | "reflect" 23 | "strconv" 24 | 25 | "github.com/bytedance/gg/internal/stream" 26 | ) 27 | 28 | // Dict is a builder for building JSON dictionary. 29 | type Dict struct { 30 | elems [][2][]byte 31 | size int 32 | } 33 | 34 | func NewDict() *Dict { 35 | return &Dict{} 36 | } 37 | 38 | // See also: [encoding/json.(*reflectWithString).resolve] 39 | func (d *Dict) marshalKey(key any) (string, error) { 40 | if tm, ok := key.(encoding.TextMarshaler); ok { 41 | bs, err := tm.MarshalText() 42 | if err != nil { 43 | return "", err 44 | } 45 | return string(bs), nil 46 | } 47 | 48 | rk := reflect.ValueOf(key) 49 | switch rk.Kind() { 50 | case reflect.String: 51 | return rk.String(), nil 52 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 53 | return strconv.FormatInt(rk.Int(), 10), nil 54 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 55 | return strconv.FormatUint(rk.Uint(), 10), nil 56 | default: 57 | return "", fmt.Errorf("unexpected map key type: %T", key) 58 | } 59 | } 60 | 61 | func (d *Dict) Store(key, value any) error { 62 | kstr, err := d.marshalKey(key) 63 | if err != nil { 64 | return err 65 | } 66 | ks, err := json.Marshal(kstr) // json key is always quoted 67 | if err != nil { 68 | return err 69 | } 70 | vs, err := json.Marshal(value) 71 | if err != nil { 72 | return err 73 | } 74 | 75 | d.elems = append(d.elems, [2][]byte{ks, vs}) 76 | d.size += len(ks) + len(vs) 77 | 78 | return nil 79 | } 80 | 81 | func (a *Dict) Sort() { 82 | a.elems = stream. 83 | StealSlice(a.elems). 84 | SortBy(func(a, b [2][]byte) bool { return bytes.Compare(a[0], b[0]) == -1 }). 85 | ToSlice() 86 | } 87 | 88 | func (d *Dict) Build() ([]byte, error) { 89 | if d == nil { 90 | return []byte("null"), nil 91 | } 92 | 93 | var buf bytes.Buffer 94 | 95 | size := d.size 96 | size += len(d.elems) // count of colon ":" 97 | size += len(d.elems) - 1 // count of comma "," 98 | size += 2 // "{" and "}" 99 | buf.Grow(size) 100 | 101 | buf.WriteByte('{') 102 | 103 | for _, kv := range d.elems { 104 | buf.Write(kv[0]) 105 | buf.WriteByte(':') 106 | buf.Write(kv[1]) 107 | buf.WriteByte(',') 108 | } 109 | 110 | var out []byte 111 | if len(d.elems) == 0 { 112 | buf.WriteByte('}') 113 | out = buf.Bytes() 114 | } else { 115 | extraComma := buf.Len() - 1 116 | out = buf.Bytes() 117 | // Replace extra `,` to `}`. 118 | out[extraComma] = '}' 119 | } 120 | 121 | return out, nil 122 | } 123 | -------------------------------------------------------------------------------- /internal/jsonbuilder/dict_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package jsonbuilder 16 | 17 | import ( 18 | "encoding/json" 19 | "testing" 20 | 21 | "github.com/bytedance/gg/internal/assert" 22 | ) 23 | 24 | func TestDict(t *testing.T) { 25 | { 26 | s := map[int]string{1: "1", 2: "2", 3: "3", 4: "4", 5: "5"} 27 | d := NewDict() 28 | for k, v := range s { 29 | assert.Nil(t, d.Store(k, v)) 30 | } 31 | d.Sort() 32 | bs, err := d.Build() 33 | assert.Nil(t, err) 34 | assert.Equal(t, []byte(`{"1":"1","2":"2","3":"3","4":"4","5":"5"}`), bs) 35 | } 36 | 37 | { 38 | s := map[int]string{} 39 | d := NewDict() 40 | for k, v := range s { 41 | assert.Nil(t, d.Store(k, v)) 42 | } 43 | bs, err := d.Build() 44 | assert.Nil(t, err) 45 | assert.Equal(t, []byte(`{}`), bs) 46 | } 47 | 48 | { 49 | s := map[int]string{1: "1"} 50 | d := NewDict() 51 | for k, v := range s { 52 | assert.Nil(t, d.Store(k, v)) 53 | } 54 | bs, err := d.Build() 55 | assert.Nil(t, err) 56 | assert.Equal(t, []byte(`{"1":"1"}`), bs) 57 | } 58 | 59 | { 60 | d := NewDict() 61 | assert.Nil(t, d.Store("a", "b")) 62 | assert.NotNil(t, d.Store(1.4, "b")) // float key is not supported 63 | assert.Nil(t, d.Store(1, 1.2)) 64 | assert.Nil(t, d.Store("w", []int{1})) 65 | 66 | bs, err := d.Build() 67 | assert.Nil(t, err) 68 | t.Log(string(bs)) 69 | assert.Equal(t, []byte(`{"a":"b","1":1.2,"w":[1]}`), bs) 70 | } 71 | 72 | { 73 | var d *Dict 74 | bs, err := d.Build() 75 | assert.Nil(t, err) 76 | assert.Equal(t, []byte(`null`), bs) 77 | } 78 | } 79 | 80 | func TestDict2(t *testing.T) { 81 | 82 | testCases := []map[string]interface{}{ 83 | { 84 | "Name": "John Doe", 85 | "Age": float64(30), 86 | "Profession": "Software Engineer", 87 | }, 88 | { 89 | "Name": "John Doe", 90 | "Age": float64(1 / 3), 91 | "Profession": "Software Engineer", 92 | }, 93 | { 94 | "customerID": "1234", 95 | "amount": 99.99, 96 | "items": []interface{}{ 97 | map[string]interface{}{"id": float64(1), "name": "Apple", "price": 1.99}, 98 | map[string]interface{}{"id": float64(2), "name": "Banana", "price": 2.99}, 99 | }, 100 | }, 101 | { 102 | "author": "Jane Austen", 103 | "books": []interface{}{ 104 | "Pride and Prejudice", 105 | "Sense and Sensibility", 106 | "Emma", 107 | }, 108 | }, 109 | { 110 | "pi": 3.14159265359, 111 | "e": 2.71828182846, 112 | "sqrt_two": 1.41421356237, 113 | "phi": 1.61803398875, 114 | }, 115 | } 116 | 117 | for _, c := range testCases { 118 | dict := NewDict() 119 | for k, v := range c { 120 | dict.Store(k, v) 121 | } 122 | out, err := dict.Build() 123 | assert.Nil(t, err) 124 | var m map[string]interface{} 125 | assert.Nil(t, json.Unmarshal(out, &m)) 126 | t.Log(c, len(c)) 127 | t.Log(m, len(m)) 128 | assert.Equal(t, c, m) 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /internal/rtassert/rtassert.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package rtassert provides runtime assertion. 16 | package rtassert 17 | 18 | import ( 19 | "fmt" 20 | 21 | "github.com/bytedance/gg/internal/constraints" 22 | ) 23 | 24 | func MustNotNeg[T constraints.Number](n T) { 25 | if n < 0 { 26 | panic(fmt.Errorf("must not be negative: %v", n)) 27 | } 28 | } 29 | 30 | func MustLessThan[T constraints.Ordered](x, y T) { 31 | if x < y { 32 | panic(fmt.Errorf("must not be less than %v", y)) 33 | } 34 | } 35 | 36 | func ErrMustNil(err error) { 37 | if err != nil { 38 | panic(fmt.Errorf("unexpected error: %s", err)) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /internal/stream/README.md: -------------------------------------------------------------------------------- 1 | # stream - Stream Processing 2 | 3 | **WARNING**: This package is **experimental** and may change in the future. 4 | 5 | Package *stream* provides a `Stream` type and its [variants](#stream-variants). 6 | All of them are wrappers of [iter - Iterator and Operations](../../iter/README.md). 7 | With these wrappers we can call [operations](#sources-operations-and-sinks) 8 | in [method chaining](https://en.wikipedia.org/wiki/Method_chaining) style. 9 | 10 | ## Quick Start 11 | 12 | 13 | 1. Import `"github.com/bytedance/gg/internal/stream"`. 14 | 2. Use `FromSlice` to construct a stream of int slice. 15 | 3. Use `Filter` to filter the zero values. 16 | 4. Use `ToSlice` to convert filtered stream to slice, evaluation is done here. 17 | 18 | ```go 19 | package main 20 | 21 | import ( 22 | "fmt" 23 | 24 | "github.com/bytedance/gg/gvalue" 25 | "github.com/bytedance/gg/internal/stream" 26 | ) 27 | 28 | func main() { 29 | s := stream.FromSlice([]int{0, 1, 2, 3, 4}). // Construct a stream from int slice 30 | Filter(gvalue.IsNotZero[int]). // Filter zero value lazily 31 | ToSlice() // Evaluate and convert back to slice 32 | 33 | fmt.Println(s) 34 | // Output: 35 | // [1 2 3 4] 36 | } 37 | ``` 38 | 39 | ## API References 40 | 41 | ### Sources, Operations and Sinks 42 | 43 | All of sources, operations and sinks have their corresponding functions in 44 | [iter - Iterator and Operations](../../iter/README.md). For example: 45 | 46 | 47 | * `Stream.Map` is corresponding to `iter.Map` 48 | * `String.Join` is corresponding to `iter.Join` 49 | 50 | So we won’t repeat them here. 51 | 52 | ### Stream Variants 53 | 54 | `Stream` has different variants depending on the element type. 55 | Variants may have additional sources or operations or sinks. 56 | 57 | | Type | Variant | 58 | | -------------------------------------------------- | -------------------------------------------------- | 59 | | `any` | `Stream` | 60 | | `comparable` | `Comparable` | 61 | | `constraints.Ordered` | `Orderable` | 62 | | `~bool` | `Bool` | 63 | | `~string` | `String` | 64 | | `map[comparable]any` | `KV` | 65 | | `map[constraints.Ordered]any` | `OrderableKV` | 66 | ## Limitation 67 | 68 | ### Can not transform Stream from one type to another 69 | 70 | We known `iter.Map` can transform an `iter.Iter` 71 | from type `F` to `T`. But in this package, `Stream.Map` can’t do. 72 | 73 | According to the [Type Parameters Proposal](https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#No-parameterized-methods), method can not have additional 74 | type parameter. The following code is invalid for now: 75 | 76 | ```go 77 | func (s *Stream[F]) Map[T any](f func(F) T) *Stream[T] 78 | // ERROR: methods cannot have type parameters 79 | ``` 80 | 81 | This means that we can not transform a stream from one type to another by 82 | method chaining. 83 | 84 | [golang/go#49085](https://github.com/golang/go/issues/49085) discussed this matter, but there is no one given a good 85 | plan yet. 86 | 87 | ### Lacking a better way for defining type-specialized variants 88 | 89 | `fold` operation can be used for `any` type, but `join` operation is only for `~string`. 90 | We can not add a `Join` method for `Stream` because its type constraint is 91 | already `[T any]` but not `[T ~string]`. 92 | 93 | So we create a variant `String[T ~string]` which has a `Stream` embedded in, 94 | then we implement a `Join` method for it. 95 | But the parameters of the method inherited from `Stream` is still `stream.Stream[T]`, 96 | not `stream.String[T]`. For now, we rewrite these methods by code generation. 97 | -------------------------------------------------------------------------------- /internal/stream/bool.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package stream 16 | 17 | import ( 18 | "github.com/bytedance/gg/internal/iter" 19 | ) 20 | 21 | // See function [github.com/bytedance/gg/internal/iter.And]. 22 | func (s Bool[T]) And() bool { 23 | return iter.And(s.Iter) 24 | } 25 | 26 | // See function [github.com/bytedance/gg/internal/iter.Or]. 27 | func (s Bool[T]) Or() bool { 28 | return iter.Or(s.Iter) 29 | } 30 | -------------------------------------------------------------------------------- /internal/stream/bool_gen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // code generated by go run gen.go; DO NOT EDIT. 16 | 17 | package stream 18 | 19 | import ( 20 | "context" 21 | 22 | "github.com/bytedance/gg/internal/iter" 23 | ) 24 | 25 | // Bool is a ~bool variant of Comparable. 26 | type Bool[T ~bool] struct { 27 | Comparable[T] 28 | } 29 | 30 | // FromIter wraps an [github.com/bytedance/gg/internal/iter.Iter] to [Stream]. 31 | func FromBoolIter[T ~bool](i iter.Iter[T]) Bool[T] { 32 | return Bool[T]{FromComparableIter(i)} 33 | } 34 | 35 | // See function [github.com/bytedance/gg/internal/iter.FromSlice]. 36 | func FromBoolSlice[T ~bool](s []T) Bool[T] { 37 | return Bool[T]{FromComparableSlice(s)} 38 | } 39 | 40 | // See function [github.com/bytedance/gg/internal/iter.StealSlice]. 41 | func StealBoolSlice[T ~bool](s []T) Bool[T] { 42 | return Bool[T]{StealComparableSlice(s)} 43 | } 44 | 45 | // See function [github.com/bytedance/gg/internal/iter.FromMapValues]. 46 | func FromBoolMapValues[I comparable, T ~bool](m map[I]T) Bool[T] { 47 | return Bool[T]{FromComparableMapValues(m)} 48 | } 49 | 50 | // See function [github.com/bytedance/gg/internal/iter.FromChan]. 51 | func FromBoolChan[T ~bool](ctx context.Context, ch <-chan T) Bool[T] { 52 | return Bool[T]{FromComparableChan(ctx, ch)} 53 | } 54 | 55 | // See function [github.com/bytedance/gg/internal/iter.Repeat]. 56 | func RepeatBool[T ~bool](v T) Bool[T] { 57 | return Bool[T]{RepeatComparable(v)} 58 | } 59 | 60 | // See function [github.com/bytedance/gg/internal/iter.MapInplace]. 61 | func (s Bool[T]) Map(f func(T) T) Bool[T] { 62 | return Bool[T]{s.Comparable.Map(f)} 63 | } 64 | 65 | // See function [github.com/bytedance/gg/internal/iter.FlatMap]. 66 | func (s Bool[T]) FlatMap(f func(T) []T) Bool[T] { 67 | return Bool[T]{s.Comparable.FlatMap(f)} 68 | } 69 | 70 | // See function [github.com/bytedance/gg/internal/iter.Filter]. 71 | func (s Bool[T]) Filter(f func(T) bool) Bool[T] { 72 | return Bool[T]{s.Comparable.Filter(f)} 73 | } 74 | 75 | // See function [github.com/bytedance/gg/internal/iter.Reverse]. 76 | func (s Bool[T]) Reverse() Bool[T] { 77 | return Bool[T]{s.Comparable.Reverse()} 78 | } 79 | 80 | // See function [github.com/bytedance/gg/internal/iter.Take]. 81 | func (s Bool[T]) Take(n int) Bool[T] { 82 | return Bool[T]{s.Comparable.Take(n)} 83 | } 84 | 85 | // See function [github.com/bytedance/gg/internal/iter.Drop]. 86 | func (s Bool[T]) Drop(n int) Bool[T] { 87 | return Bool[T]{s.Comparable.Drop(n)} 88 | } 89 | 90 | // See function [github.com/bytedance/gg/internal/iter.Concat]. 91 | func (s Bool[T]) Concat(ss ...Bool[T]) Bool[T] { 92 | conv := func(c Bool[T]) Comparable[T] { 93 | return c.Comparable 94 | } 95 | tmp := iter.ToSlice(iter.Map(conv, iter.FromSlice(ss))) 96 | return Bool[T]{s.Comparable.Concat(tmp...)} 97 | } 98 | 99 | // See function [github.com/bytedance/gg/internal/iter.Zip]. 100 | func (s Bool[T]) Zip(f func(T, T) T, another Bool[T]) Bool[T] { 101 | return Bool[T]{s.Comparable.Zip(f, another.Comparable)} 102 | } 103 | 104 | // See function [github.com/bytedance/gg/internal/iter.Intersperse]. 105 | func (s Bool[T]) Intersperse(sep T) Bool[T] { 106 | return Bool[T]{s.Comparable.Intersperse(sep)} 107 | } 108 | 109 | // See function [github.com/bytedance/gg/internal/iter.Append]. 110 | func (s Bool[T]) Append(tail T) Bool[T] { 111 | return Bool[T]{s.Comparable.Append(tail)} 112 | } 113 | 114 | // See function [github.com/bytedance/gg/internal/iter.Prepend]. 115 | func (s Bool[T]) Prepend(head T) Bool[T] { 116 | return Bool[T]{s.Comparable.Prepend(head)} 117 | } 118 | 119 | // See function [github.com/bytedance/gg/internal/iter.TakeWhile]. 120 | func (s Bool[T]) TakeWhile(f func(T) bool) Bool[T] { 121 | return Bool[T]{s.Comparable.TakeWhile(f)} 122 | } 123 | 124 | // See function [github.com/bytedance/gg/internal/iter.DropWhile]. 125 | func (s Bool[T]) DropWhile(f func(T) bool) Bool[T] { 126 | return Bool[T]{s.Comparable.DropWhile(f)} 127 | } 128 | 129 | // See function [github.com/bytedance/gg/internal/iter.SortBy]. 130 | func (s Bool[T]) SortBy(less func(T, T) bool) Bool[T] { 131 | return Bool[T]{s.Comparable.SortBy(less)} 132 | } 133 | 134 | // See function [github.com/bytedance/gg/internal/iter.Shuffle]. 135 | func (s Bool[T]) Shuffle() Bool[T] { 136 | return Bool[T]{s.Comparable.Shuffle()} 137 | } 138 | 139 | // See function [github.com/bytedance/gg/internal/iter.FromMapKeys]. 140 | func FromBoolMapKeys[T ~bool, I any](m map[T]I) Bool[T] { 141 | return Bool[T]{FromMapKeys(m)} 142 | } 143 | -------------------------------------------------------------------------------- /internal/stream/bool_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package stream 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/bytedance/gg/internal/assert" 21 | ) 22 | 23 | func TestBool_And(t *testing.T) { 24 | assert.True(t, FromBoolSlice([]bool{true, true, true}).And()) 25 | } 26 | 27 | func TestBool_Or(t *testing.T) { 28 | assert.True(t, FromBoolSlice([]bool{false, false, true}).Or()) 29 | } 30 | -------------------------------------------------------------------------------- /internal/stream/comparable.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package stream 16 | 17 | import ( 18 | "github.com/bytedance/gg/internal/iter" 19 | ) 20 | 21 | // See function [github.com/bytedance/gg/internal/iter.FromMapKeys]. 22 | func FromMapKeys[T comparable, I any](m map[T]I) Comparable[T] { 23 | return Comparable[T]{FromIter(iter.FromMapKeys(m))} 24 | } 25 | 26 | // See function [github.com/bytedance/gg/internal/iter.Contains]. 27 | func (s Comparable[T]) Contains(v T) bool { 28 | return iter.Contains(v, s.Iter) 29 | } 30 | 31 | // See function [github.com/bytedance/gg/internal/iter.ContainsAny]. 32 | func (s Comparable[T]) ContainsAny(vs ...T) bool { 33 | return iter.ContainsAny(vs, s.Iter) 34 | } 35 | 36 | // See function [github.com/bytedance/gg/internal/iter.ContainsAll]. 37 | func (s Comparable[T]) ContainsAll(vs ...T) bool { 38 | return iter.ContainsAll(vs, s.Iter) 39 | } 40 | 41 | // See function [github.com/bytedance/gg/internal/iter.Uniq]. 42 | func (s Comparable[T]) Uniq() Comparable[T] { 43 | return FromComparableIter(iter.Uniq(s.Iter)) 44 | } 45 | 46 | // See function [github.com/bytedance/gg/internal/iter.Remove]. 47 | func (s Comparable[T]) Remove(v T) Comparable[T] { 48 | return FromComparableIter(iter.Remove(v, s.Iter)) 49 | } 50 | 51 | // See function [github.com/bytedance/gg/internal/iter.RemoveN]. 52 | func (s Comparable[T]) RemoveN(v T, n int) Comparable[T] { 53 | return FromComparableIter(iter.RemoveN(v, n, s.Iter)) 54 | } 55 | -------------------------------------------------------------------------------- /internal/stream/comparable_gen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // code generated by go run gen.go; DO NOT EDIT. 16 | 17 | package stream 18 | 19 | import ( 20 | "context" 21 | 22 | "github.com/bytedance/gg/internal/iter" 23 | ) 24 | 25 | // Comparable is a comparable variant of Stream. 26 | type Comparable[T comparable] struct { 27 | Stream[T] 28 | } 29 | 30 | // FromIter wraps an [github.com/bytedance/gg/internal/iter.Iter] to [Stream]. 31 | func FromComparableIter[T comparable](i iter.Iter[T]) Comparable[T] { 32 | return Comparable[T]{FromIter(i)} 33 | } 34 | 35 | // See function [github.com/bytedance/gg/internal/iter.FromSlice]. 36 | func FromComparableSlice[T comparable](s []T) Comparable[T] { 37 | return Comparable[T]{FromSlice(s)} 38 | } 39 | 40 | // See function [github.com/bytedance/gg/internal/iter.StealSlice]. 41 | func StealComparableSlice[T comparable](s []T) Comparable[T] { 42 | return Comparable[T]{StealSlice(s)} 43 | } 44 | 45 | // See function [github.com/bytedance/gg/internal/iter.FromMapValues]. 46 | func FromComparableMapValues[I comparable, T comparable](m map[I]T) Comparable[T] { 47 | return Comparable[T]{FromMapValues(m)} 48 | } 49 | 50 | // See function [github.com/bytedance/gg/internal/iter.FromChan]. 51 | func FromComparableChan[T comparable](ctx context.Context, ch <-chan T) Comparable[T] { 52 | return Comparable[T]{FromChan(ctx, ch)} 53 | } 54 | 55 | // See function [github.com/bytedance/gg/internal/iter.Repeat]. 56 | func RepeatComparable[T comparable](v T) Comparable[T] { 57 | return Comparable[T]{Repeat(v)} 58 | } 59 | 60 | // See function [github.com/bytedance/gg/internal/iter.MapInplace]. 61 | func (s Comparable[T]) Map(f func(T) T) Comparable[T] { 62 | return Comparable[T]{s.Stream.Map(f)} 63 | } 64 | 65 | // See function [github.com/bytedance/gg/internal/iter.FlatMap]. 66 | func (s Comparable[T]) FlatMap(f func(T) []T) Comparable[T] { 67 | return Comparable[T]{s.Stream.FlatMap(f)} 68 | } 69 | 70 | // See function [github.com/bytedance/gg/internal/iter.Filter]. 71 | func (s Comparable[T]) Filter(f func(T) bool) Comparable[T] { 72 | return Comparable[T]{s.Stream.Filter(f)} 73 | } 74 | 75 | // See function [github.com/bytedance/gg/internal/iter.Reverse]. 76 | func (s Comparable[T]) Reverse() Comparable[T] { 77 | return Comparable[T]{s.Stream.Reverse()} 78 | } 79 | 80 | // See function [github.com/bytedance/gg/internal/iter.Take]. 81 | func (s Comparable[T]) Take(n int) Comparable[T] { 82 | return Comparable[T]{s.Stream.Take(n)} 83 | } 84 | 85 | // See function [github.com/bytedance/gg/internal/iter.Drop]. 86 | func (s Comparable[T]) Drop(n int) Comparable[T] { 87 | return Comparable[T]{s.Stream.Drop(n)} 88 | } 89 | 90 | // See function [github.com/bytedance/gg/internal/iter.Concat]. 91 | func (s Comparable[T]) Concat(ss ...Comparable[T]) Comparable[T] { 92 | conv := func(c Comparable[T]) Stream[T] { 93 | return c.Stream 94 | } 95 | tmp := iter.ToSlice(iter.Map(conv, iter.FromSlice(ss))) 96 | return Comparable[T]{s.Stream.Concat(tmp...)} 97 | } 98 | 99 | // See function [github.com/bytedance/gg/internal/iter.Zip]. 100 | func (s Comparable[T]) Zip(f func(T, T) T, another Comparable[T]) Comparable[T] { 101 | return Comparable[T]{s.Stream.Zip(f, another.Stream)} 102 | } 103 | 104 | // See function [github.com/bytedance/gg/internal/iter.Intersperse]. 105 | func (s Comparable[T]) Intersperse(sep T) Comparable[T] { 106 | return Comparable[T]{s.Stream.Intersperse(sep)} 107 | } 108 | 109 | // See function [github.com/bytedance/gg/internal/iter.Append]. 110 | func (s Comparable[T]) Append(tail T) Comparable[T] { 111 | return Comparable[T]{s.Stream.Append(tail)} 112 | } 113 | 114 | // See function [github.com/bytedance/gg/internal/iter.Prepend]. 115 | func (s Comparable[T]) Prepend(head T) Comparable[T] { 116 | return Comparable[T]{s.Stream.Prepend(head)} 117 | } 118 | 119 | // See function [github.com/bytedance/gg/internal/iter.TakeWhile]. 120 | func (s Comparable[T]) TakeWhile(f func(T) bool) Comparable[T] { 121 | return Comparable[T]{s.Stream.TakeWhile(f)} 122 | } 123 | 124 | // See function [github.com/bytedance/gg/internal/iter.DropWhile]. 125 | func (s Comparable[T]) DropWhile(f func(T) bool) Comparable[T] { 126 | return Comparable[T]{s.Stream.DropWhile(f)} 127 | } 128 | 129 | // See function [github.com/bytedance/gg/internal/iter.SortBy]. 130 | func (s Comparable[T]) SortBy(less func(T, T) bool) Comparable[T] { 131 | return Comparable[T]{s.Stream.SortBy(less)} 132 | } 133 | 134 | // See function [github.com/bytedance/gg/internal/iter.UniqBy]. 135 | func (s Comparable[T]) UniqBy(f func(T) any) Comparable[T] { 136 | return Comparable[T]{s.Stream.UniqBy(f)} 137 | } 138 | 139 | // See function [github.com/bytedance/gg/internal/iter.Shuffle]. 140 | func (s Comparable[T]) Shuffle() Comparable[T] { 141 | return Comparable[T]{s.Stream.Shuffle()} 142 | } 143 | -------------------------------------------------------------------------------- /internal/stream/comparable_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package stream 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/bytedance/gg/internal/assert" 21 | ) 22 | 23 | func TestComparable_Contains(t *testing.T) { 24 | assert.True(t, FromComparableSlice([]int{1, 2, 3}).Contains(3)) 25 | assert.False(t, FromComparableSlice([]int{1, 2, 3}).Contains(4)) 26 | } 27 | 28 | func TestComparable_ContainsAny(t *testing.T) { 29 | assert.True(t, FromComparableSlice([]int{1, 2, 3}).ContainsAny(3)) 30 | assert.False(t, FromComparableSlice([]int{1, 2, 3}).ContainsAny(4)) 31 | assert.True(t, FromComparableSlice([]int{1, 2, 3}).ContainsAny(1, 2)) 32 | assert.True(t, FromComparableSlice([]int{1, 2, 3}).ContainsAny(3, 4)) 33 | } 34 | 35 | func TestComparable_ContainsAll(t *testing.T) { 36 | assert.True(t, FromComparableSlice([]int{1, 2, 3}).ContainsAll(3)) 37 | assert.False(t, FromComparableSlice([]int{1, 2, 3}).ContainsAll(4)) 38 | assert.True(t, FromComparableSlice([]int{1, 2, 3}).ContainsAll(1, 2)) 39 | assert.False(t, FromComparableSlice([]int{1, 2, 3}).ContainsAll(3, 4)) 40 | } 41 | 42 | func TestComparable_Uniq(t *testing.T) { 43 | assert.Equal(t, 44 | []int{1, 2, 3}, 45 | FromComparableSlice([]int{1, 2, 2, 3}).Uniq().ToSlice()) 46 | } 47 | 48 | func TestComparable_Remove(t *testing.T) { 49 | assert.Equal(t, 50 | []int{1, 3}, 51 | FromComparableSlice([]int{1, 2, 2, 3}).Remove(2).ToSlice()) 52 | } 53 | 54 | func TestComparable_RemoveN(t *testing.T) { 55 | assert.Equal(t, 56 | []int{1, 2, 3}, 57 | FromComparableSlice([]int{1, 2, 2, 3}).RemoveN(2, 1).ToSlice()) 58 | } 59 | -------------------------------------------------------------------------------- /internal/stream/gen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Generate Comparable[comparable] from Stream[any] 4 | go run gen.go \ 5 | -parent "Stream" \ 6 | -parent-types "T any" \ 7 | -child "Comparable" \ 8 | -child-types "T comparable" \ 9 | -ignore-paths "github.com/bytedance/gg/goption github.com/bytedance/gg/collection/tuple" 10 | 11 | # Generate Bool[~bool] from from Comparable[comparable] 12 | go run gen.go \ 13 | -parent "Comparable" \ 14 | -parent-types "T comparable" \ 15 | -child "Bool" \ 16 | -child-types "T ~bool" \ 17 | -ignore-funcs "Uniq UniqBy Remove RemoveN" # Distinct funcs are meaningless for bool type. 18 | 19 | # Generate Orderable[constraints.Ordered] from Comparable[comparable] 20 | go run gen.go \ 21 | -parent "Comparable" \ 22 | -parent-types "T comparable" \ 23 | -child "Orderable" \ 24 | -child-types "T constraints.Ordered" \ 25 | -import-paths "github.com/bytedance/gg/internal/constraints" 26 | 27 | # Generate String[~string] from Orderable[constraints.Ordered] 28 | go run gen.go \ 29 | -parent "Orderable" \ 30 | -parent-types "T constraints.Ordered" \ 31 | -child "String" \ 32 | -child-types "T ~string" \ 33 | -ignore-paths "github.com/bytedance/gg/internal/constraints github.com/bytedance/gg/goption github.com/bytedance/gg/collection/tuple" 34 | 35 | # Generate KV[comparable, any] from Stream[any] 36 | go run gen.go \ 37 | -parent "Stream" \ 38 | -parent-types "T any" \ 39 | -child "KV" \ 40 | -child-types "K comparable V any" \ 41 | -child-elem "tuple.T2[K, V]" \ 42 | -import-paths "github.com/bytedance/gg/collection/tuple" \ 43 | -ignore-paths "github.com/bytedance/gg/goption" \ 44 | -ignore-funcs "FromMapValues Repeat Map Fold Reduce Filter ForEach All Any Zip Intersperse Append Prepend Find TakeWhile DropWhile SortBy UniqBy DistinctOrderedWith" 45 | 46 | # Generate OrderableKV[constraints.Ordered, any] from KV[comparable, any] 47 | go run gen.go \ 48 | -parent "KV" \ 49 | -parent-types "K comparable V any" \ 50 | -parent-elem "tuple.T2[K, V]" \ 51 | -child "OrderableKV" \ 52 | -child-types "K constraints.Ordered V any" \ 53 | -child-elem "tuple.T2[K, V]" \ 54 | -import-paths "github.com/bytedance/gg/internal/constraints" \ 55 | -ignore-paths "github.com/bytedance/gg/goption" \ 56 | -ignore-funcs "FromMap" 57 | 58 | # Generate Number[constraints.Number] from Orderable[constraints.Ordered] 59 | go run gen.go \ 60 | -parent "Orderable" \ 61 | -parent-types "T constraints.Ordered" \ 62 | -child "Number" \ 63 | -child-types "T constraints.Number" \ 64 | -ignore-paths "github.com/bytedance/gg/goption github.com/bytedance/gg/collection/tuple" 65 | -------------------------------------------------------------------------------- /internal/stream/kv_gen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // code generated by go run gen.go; DO NOT EDIT. 16 | 17 | package stream 18 | 19 | import ( 20 | "context" 21 | "github.com/bytedance/gg/collection/tuple" 22 | "github.com/bytedance/gg/internal/iter" 23 | ) 24 | 25 | // KV is a tuple.T2[K, V] variant of Stream. 26 | type KV[K comparable, V any] struct { 27 | Stream[tuple.T2[K, V]] 28 | } 29 | 30 | // FromIter wraps an [github.com/bytedance/gg/internal/iter.Iter] to [Stream]. 31 | func FromKVIter[K comparable, V any](i iter.Iter[tuple.T2[K, V]]) KV[K, V] { 32 | return KV[K, V]{FromIter(i)} 33 | } 34 | 35 | // See function [github.com/bytedance/gg/internal/iter.FromSlice]. 36 | func FromKVSlice[K comparable, V any](s []tuple.T2[K, V]) KV[K, V] { 37 | return KV[K, V]{FromSlice(s)} 38 | } 39 | 40 | // See function [github.com/bytedance/gg/internal/iter.StealSlice]. 41 | func StealKVSlice[K comparable, V any](s []tuple.T2[K, V]) KV[K, V] { 42 | return KV[K, V]{StealSlice(s)} 43 | } 44 | 45 | // See function [github.com/bytedance/gg/internal/iter.FromChan]. 46 | func FromKVChan[K comparable, V any](ctx context.Context, ch <-chan tuple.T2[K, V]) KV[K, V] { 47 | return KV[K, V]{FromChan(ctx, ch)} 48 | } 49 | 50 | // See function [github.com/bytedance/gg/internal/iter.FlatMap]. 51 | func (s KV[K, V]) FlatMap(f func(tuple.T2[K, V]) []tuple.T2[K, V]) KV[K, V] { 52 | return KV[K, V]{s.Stream.FlatMap(f)} 53 | } 54 | 55 | // See function [github.com/bytedance/gg/internal/iter.Reverse]. 56 | func (s KV[K, V]) Reverse() KV[K, V] { 57 | return KV[K, V]{s.Stream.Reverse()} 58 | } 59 | 60 | // See function [github.com/bytedance/gg/internal/iter.Take]. 61 | func (s KV[K, V]) Take(n int) KV[K, V] { 62 | return KV[K, V]{s.Stream.Take(n)} 63 | } 64 | 65 | // See function [github.com/bytedance/gg/internal/iter.Drop]. 66 | func (s KV[K, V]) Drop(n int) KV[K, V] { 67 | return KV[K, V]{s.Stream.Drop(n)} 68 | } 69 | 70 | // See function [github.com/bytedance/gg/internal/iter.Concat]. 71 | func (s KV[K, V]) Concat(ss ...KV[K, V]) KV[K, V] { 72 | conv := func(c KV[K, V]) Stream[tuple.T2[K, V]] { 73 | return c.Stream 74 | } 75 | tmp := iter.ToSlice(iter.Map(conv, iter.FromSlice(ss))) 76 | return KV[K, V]{s.Stream.Concat(tmp...)} 77 | } 78 | 79 | // See function [github.com/bytedance/gg/internal/iter.Shuffle]. 80 | func (s KV[K, V]) Shuffle() KV[K, V] { 81 | return KV[K, V]{s.Stream.Shuffle()} 82 | } 83 | -------------------------------------------------------------------------------- /internal/stream/kv_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package stream 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/bytedance/gg/internal/assert" 21 | ) 22 | 23 | func TestKVMap(t *testing.T) { 24 | assert.Equal(t, 25 | map[string]int{"Good:Alice": 99, "Good:Bob": 87, "Bad:Zhang": 59}, 26 | FromMap(map[string]int{"Alice": 99, "Bob": 87, "Zhang": 59}). 27 | Map(func(k string, v int) (string, int) { 28 | if v > 60 { 29 | return "Good:" + k, v 30 | } else { 31 | return "Bad:" + k, v 32 | } 33 | }).ToMap()) 34 | } 35 | 36 | func TestKVMapSortBy(t *testing.T) { 37 | assert.Equal(t, 38 | []string{"Zhang", "Bob", "Alice"}, 39 | FromMap(map[string]int{"Alice": 99, "Bob": 87, "Zhang": 59}). 40 | SortBy(func(_ string, v1 int, _ string, v2 int) bool { 41 | return v1 < v2 42 | }). 43 | Keys(). 44 | ToSlice()) 45 | } 46 | 47 | func BenchmarkFromMapKeys_All(b *testing.B) { 48 | n := 10000 49 | m := make(map[int]string) 50 | for i := 0; i < n; i++ { 51 | m[i] = "foo" 52 | } 53 | b.ResetTimer() 54 | 55 | b.Run("Baseline", func(b *testing.B) { 56 | for i := 0; i < b.N; i++ { 57 | var s []int 58 | for v := range m { 59 | s = append(s, v) 60 | } 61 | if len(s) != len(m) { 62 | b.Error("Mismatched len:", len(s), len(m)) 63 | b.FailNow() 64 | } 65 | } 66 | }) 67 | 68 | b.Run("Stream", func(b *testing.B) { 69 | for i := 0; i < b.N; i++ { 70 | s := FromMapKeys(m).ToSlice() 71 | if len(s) != len(m) { 72 | b.Error("Mismatched len:", len(s), len(m)) 73 | b.FailNow() 74 | } 75 | } 76 | }) 77 | } 78 | 79 | func BenchmarkFromMapKeys_Partial(b *testing.B) { 80 | n := 10000 81 | nRead := 800 82 | m := make(map[int]string) 83 | for i := 0; i < n; i++ { 84 | m[i] = "foo" 85 | } 86 | b.ResetTimer() 87 | 88 | b.Run("Baseline", func(b *testing.B) { 89 | for i := 0; i < b.N; i++ { 90 | var s []int 91 | for v := range m { 92 | s = append(s, v) 93 | if len(s) == nRead { 94 | break 95 | } 96 | } 97 | if len(s) != nRead { 98 | b.Error("Mismatched len:", len(s), nRead) 99 | b.FailNow() 100 | } 101 | } 102 | }) 103 | 104 | b.Run("Stream", func(b *testing.B) { 105 | for i := 0; i < b.N; i++ { 106 | s := FromMapKeys(m).Take(nRead).ToSlice() 107 | if len(s) != nRead { 108 | b.Error("Mismatched len:", len(s), nRead) 109 | b.FailNow() 110 | } 111 | } 112 | }) 113 | } 114 | -------------------------------------------------------------------------------- /internal/stream/number.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package stream 16 | 17 | import ( 18 | "github.com/bytedance/gg/internal/constraints" 19 | "github.com/bytedance/gg/internal/iter" 20 | ) 21 | 22 | // See function [github.com/bytedance/gg/internal/iter.Range]. 23 | func Range[T constraints.Number](start, stop T) Number[T] { 24 | return FromNumberIter(iter.Range(start, stop)) 25 | } 26 | 27 | // See function [github.com/bytedance/gg/internal/iter.RangeWithStep]. 28 | func RangeWithStep[T constraints.Number](start, stop, step T) Number[T] { 29 | return FromNumberIter(iter.RangeWithStep(start, stop, step)) 30 | } 31 | 32 | // See function [github.com/bytedance/gg/internal/iter.Sum]. 33 | func (s Number[T]) Sum() T { 34 | return iter.Sum(s.Iter) 35 | } 36 | 37 | // See function [github.com/bytedance/gg/internal/iter.Avg]. 38 | func (s Number[T]) Avg() float64 { 39 | return iter.Avg(s.Iter) 40 | } 41 | -------------------------------------------------------------------------------- /internal/stream/orderable.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package stream 16 | 17 | import ( 18 | "github.com/bytedance/gg/collection/tuple" 19 | "github.com/bytedance/gg/goption" 20 | "github.com/bytedance/gg/internal/iter" 21 | ) 22 | 23 | // See function [github.com/bytedance/gg/internal/iter.Max]. 24 | func (s Orderable[T]) Max() goption.O[T] { 25 | return iter.Max(s.Iter) 26 | } 27 | 28 | // See function [github.com/bytedance/gg/internal/iter.Min]. 29 | func (s Orderable[T]) Min() goption.O[T] { 30 | return iter.Min(s.Iter) 31 | } 32 | 33 | // See function [github.com/bytedance/gg/internal/iter.MinMax]. 34 | func (s Orderable[T]) MinMax() goption.O[tuple.T2[T, T]] { 35 | return iter.MinMax(s.Iter) 36 | } 37 | 38 | // See function [github.com/bytedance/gg/internal/iter.Sort]. 39 | func (s Orderable[T]) Sort() Orderable[T] { 40 | return FromOrderableIter(iter.Sort(s.Iter)) 41 | } 42 | -------------------------------------------------------------------------------- /internal/stream/orderable_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package stream 16 | 17 | import ( 18 | "math/rand" 19 | "testing" 20 | 21 | "github.com/bytedance/gg/internal/assert" 22 | ) 23 | 24 | func BenchmarkOrderableMax(b *testing.B) { 25 | n := 10000 26 | var s []int 27 | for i := 0; i < n; i++ { 28 | s = append(s, rand.Int()) 29 | } 30 | b.ResetTimer() 31 | 32 | b.Run("Baseline", func(b *testing.B) { 33 | for i := 0; i <= b.N; i++ { 34 | var res int 35 | if len(s) != 0 { 36 | res = s[0] 37 | } 38 | for _, v := range s { 39 | if res < v { 40 | res = v 41 | } 42 | } 43 | _ = res 44 | } 45 | }) 46 | b.Run("Stream", func(b *testing.B) { 47 | var res int 48 | for i := 0; i <= b.N; i++ { 49 | res = StealOrderableSlice(s).Max().Value() 50 | } 51 | _ = res 52 | }) 53 | } 54 | 55 | func TestOrderable_Max(t *testing.T) { 56 | assert.Equal(t, 99, FromOrderableSlice([]int{1, 3, 4, 99}).Max().Value()) 57 | } 58 | 59 | func TestOrderable_Min(t *testing.T) { 60 | assert.Equal(t, 1, FromOrderableSlice([]int{1, 3, 4, 99}).Min().Value()) 61 | } 62 | 63 | func TestOrderable_Sort(t *testing.T) { 64 | assert.Equal(t, []int{1, 2, 3, 4}, FromOrderableSlice([]int{4, 1, 3, 2}).Sort().ToSlice()) 65 | } 66 | -------------------------------------------------------------------------------- /internal/stream/orderablekv.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package stream 16 | 17 | import ( 18 | "github.com/bytedance/gg/collection/tuple" 19 | "github.com/bytedance/gg/internal/constraints" 20 | "github.com/bytedance/gg/internal/iter" 21 | ) 22 | 23 | // See function [github.com/bytedance/gg/internal/iter.FromMap]. 24 | func FromOrderableMap[K constraints.Ordered, V any](m map[K]V) OrderableKV[K, V] { 25 | return OrderableKV[K, V]{FromMap(m)} 26 | } 27 | 28 | // See function [github.com/bytedance/gg/internal/iter.Sort]. 29 | func (s OrderableKV[K, V]) Sort() OrderableKV[K, V] { 30 | less := func(x, y tuple.T2[K, V]) bool { return x.First < y.First } 31 | return FromOrderableKVIter(iter.SortBy(less, s.Iter)) 32 | } 33 | 34 | // Keys returns stream of key. 35 | func (s OrderableKV[K, V]) Keys() Orderable[K] { 36 | return FromOrderableIter(iter.Map(func(v tuple.T2[K, V]) K { 37 | return v.First 38 | }, s.Iter)) 39 | } 40 | -------------------------------------------------------------------------------- /internal/stream/orderablekv_gen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // code generated by go run gen.go; DO NOT EDIT. 16 | 17 | package stream 18 | 19 | import ( 20 | "context" 21 | 22 | "github.com/bytedance/gg/collection/tuple" 23 | "github.com/bytedance/gg/internal/constraints" 24 | "github.com/bytedance/gg/internal/iter" 25 | ) 26 | 27 | // OrderableKV is a tuple.T2[K, V] variant of KV. 28 | type OrderableKV[K constraints.Ordered, V any] struct { 29 | KV[K, V] 30 | } 31 | 32 | // FromIter wraps an [github.com/bytedance/gg/internal/iter.Iter] to [Stream]. 33 | func FromOrderableKVIter[K constraints.Ordered, V any](i iter.Iter[tuple.T2[K, V]]) OrderableKV[K, V] { 34 | return OrderableKV[K, V]{FromKVIter(i)} 35 | } 36 | 37 | // See function [github.com/bytedance/gg/internal/iter.FromSlice]. 38 | func FromOrderableKVSlice[K constraints.Ordered, V any](s []tuple.T2[K, V]) OrderableKV[K, V] { 39 | return OrderableKV[K, V]{FromKVSlice(s)} 40 | } 41 | 42 | // See function [github.com/bytedance/gg/internal/iter.StealSlice]. 43 | func StealOrderableKVSlice[K constraints.Ordered, V any](s []tuple.T2[K, V]) OrderableKV[K, V] { 44 | return OrderableKV[K, V]{StealKVSlice(s)} 45 | } 46 | 47 | // See function [github.com/bytedance/gg/internal/iter.FromChan]. 48 | func FromOrderableKVChan[K constraints.Ordered, V any](ctx context.Context, ch <-chan tuple.T2[K, V]) OrderableKV[K, V] { 49 | return OrderableKV[K, V]{FromKVChan(ctx, ch)} 50 | } 51 | 52 | // See function [github.com/bytedance/gg/internal/iter.FlatMap]. 53 | func (s OrderableKV[K, V]) FlatMap(f func(tuple.T2[K, V]) []tuple.T2[K, V]) OrderableKV[K, V] { 54 | return OrderableKV[K, V]{s.KV.FlatMap(f)} 55 | } 56 | 57 | // See function [github.com/bytedance/gg/internal/iter.Reverse]. 58 | func (s OrderableKV[K, V]) Reverse() OrderableKV[K, V] { 59 | return OrderableKV[K, V]{s.KV.Reverse()} 60 | } 61 | 62 | // See function [github.com/bytedance/gg/internal/iter.Take]. 63 | func (s OrderableKV[K, V]) Take(n int) OrderableKV[K, V] { 64 | return OrderableKV[K, V]{s.KV.Take(n)} 65 | } 66 | 67 | // See function [github.com/bytedance/gg/internal/iter.Drop]. 68 | func (s OrderableKV[K, V]) Drop(n int) OrderableKV[K, V] { 69 | return OrderableKV[K, V]{s.KV.Drop(n)} 70 | } 71 | 72 | // See function [github.com/bytedance/gg/internal/iter.Concat]. 73 | func (s OrderableKV[K, V]) Concat(ss ...OrderableKV[K, V]) OrderableKV[K, V] { 74 | conv := func(c OrderableKV[K, V]) KV[K, V] { 75 | return c.KV 76 | } 77 | tmp := iter.ToSlice(iter.Map(conv, iter.FromSlice(ss))) 78 | return OrderableKV[K, V]{s.KV.Concat(tmp...)} 79 | } 80 | 81 | // See function [github.com/bytedance/gg/internal/iter.Shuffle]. 82 | func (s OrderableKV[K, V]) Shuffle() OrderableKV[K, V] { 83 | return OrderableKV[K, V]{s.KV.Shuffle()} 84 | } 85 | 86 | // See function [github.com/bytedance/gg/internal/iter.Repeat]. 87 | func RepeatOrderableKV[K constraints.Ordered, V any](k K, v V) OrderableKV[K, V] { 88 | return OrderableKV[K, V]{RepeatKV(k, v)} 89 | } 90 | 91 | // See function [github.com/bytedance/gg/internal/iter.MapInplace]. 92 | func (s OrderableKV[K, V]) Map(f func(k K, v V) (K, V)) OrderableKV[K, V] { 93 | return OrderableKV[K, V]{s.KV.Map(f)} 94 | } 95 | 96 | // See function [github.com/bytedance/gg/internal/iter.Filter]. 97 | func (s OrderableKV[K, V]) Filter(f func(K, V) bool) OrderableKV[K, V] { 98 | return OrderableKV[K, V]{s.KV.Filter(f)} 99 | } 100 | 101 | // See function [github.com/bytedance/gg/internal/iter.Zip]. 102 | func (s OrderableKV[K, V]) Zip(f func(K, V, K, V) (K, V), another OrderableKV[K, V]) OrderableKV[K, V] { 103 | return OrderableKV[K, V]{s.KV.Zip(f, another.KV)} 104 | } 105 | 106 | // See function [github.com/bytedance/gg/internal/iter.Intersperse]. 107 | func (s OrderableKV[K, V]) Intersperse(sepK K, sepV V) OrderableKV[K, V] { 108 | return OrderableKV[K, V]{s.KV.Intersperse(sepK, sepV)} 109 | } 110 | 111 | // See function [github.com/bytedance/gg/internal/iter.Append]. 112 | func (s OrderableKV[K, V]) Append(tailK K, tailV V) OrderableKV[K, V] { 113 | return OrderableKV[K, V]{s.KV.Append(tailK, tailV)} 114 | } 115 | 116 | // See function [github.com/bytedance/gg/internal/iter.Prepend]. 117 | func (s OrderableKV[K, V]) Prepend(tailK K, tailV V) OrderableKV[K, V] { 118 | return OrderableKV[K, V]{s.KV.Prepend(tailK, tailV)} 119 | } 120 | 121 | // See function [github.com/bytedance/gg/internal/iter.TakeWhile]. 122 | func (s OrderableKV[K, V]) TakeWhile(f func(K, V) bool) OrderableKV[K, V] { 123 | return OrderableKV[K, V]{s.KV.TakeWhile(f)} 124 | } 125 | 126 | // See function [github.com/bytedance/gg/internal/iter.DropWhile]. 127 | func (s OrderableKV[K, V]) DropWhile(f func(K, V) bool) OrderableKV[K, V] { 128 | return OrderableKV[K, V]{s.KV.DropWhile(f)} 129 | } 130 | 131 | // See function [github.com/bytedance/gg/internal/iter.SortBy]. 132 | func (s OrderableKV[K, V]) SortBy(less func(K, V, K, V) bool) OrderableKV[K, V] { 133 | return OrderableKV[K, V]{s.KV.SortBy(less)} 134 | } 135 | 136 | // See function [github.com/bytedance/gg/internal/iter.UniqBy]. 137 | func (s OrderableKV[K, V]) UniqBy(f func(K, V) any) OrderableKV[K, V] { 138 | return OrderableKV[K, V]{s.KV.UniqBy(f)} 139 | } 140 | -------------------------------------------------------------------------------- /internal/stream/orderablekv_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package stream 16 | 17 | import ( 18 | "math/rand" 19 | "sort" 20 | "strconv" 21 | "testing" 22 | "time" 23 | 24 | "github.com/bytedance/gg/internal/assert" 25 | ) 26 | 27 | func TestOrderableKVSort(t *testing.T) { 28 | assert.Equal(t, 29 | []string{"Alice", "Bob", "Zhang"}, 30 | FromOrderableMap(map[string]int{"Alice": 99, "Bob": 100, "Zhang": 59}). 31 | Sort(). 32 | Keys(). 33 | ToSlice()) 34 | assert.Equal(t, 35 | []int{99, 100, 59}, 36 | FromOrderableMap(map[string]int{"Alice": 99, "Bob": 100, "Zhang": 59}). 37 | Sort(). 38 | Values(). 39 | ToSlice()) 40 | } 41 | 42 | func BenchmarkOrderedRangeString(b *testing.B) { 43 | rnd := rand.New(rand.NewSource(time.Now().Unix())) 44 | randString := func(n int) string { 45 | b := make([]byte, n) 46 | for i := range b { 47 | b[i] = byte(rnd.Intn(256)) 48 | } 49 | return string(b) 50 | } 51 | 52 | n := 1000 53 | m := make(map[string]int) 54 | for i := 0; i < n; i++ { 55 | m[randString(32)] = i 56 | } 57 | b.ResetTimer() 58 | 59 | b.Run("Baseline", func(b *testing.B) { 60 | for i := 0; i <= b.N; i++ { 61 | var keys []string 62 | for k := range m { 63 | keys = append(keys, k) 64 | } 65 | sort.Strings(keys) 66 | for _, k := range keys { 67 | v := m[k] 68 | _ = v 69 | } 70 | } 71 | }) 72 | 73 | b.Run("SortKey", func(b *testing.B) { 74 | for i := 0; i <= b.N; i++ { 75 | for _, k := range FromOrderableMapKeys(m).Sort().ToSlice() { 76 | v := m[k] 77 | _ = v 78 | } 79 | } 80 | }) 81 | 82 | b.Run("SortKV", func(b *testing.B) { 83 | for i := 0; i <= b.N; i++ { 84 | for _, t := range FromOrderableMap(m).Sort().ToSlice() { 85 | _, _ = t.First, t.Second 86 | } 87 | } 88 | }) 89 | } 90 | 91 | func BenchmarkOrderedRangeInt(b *testing.B) { 92 | n := 1000 93 | m := make(map[int]string) 94 | for i := 0; i < n; i++ { 95 | m[i] = strconv.Itoa(i) 96 | } 97 | b.ResetTimer() 98 | 99 | b.Run("Baseline", func(b *testing.B) { 100 | for i := 0; i <= b.N; i++ { 101 | var keys []int 102 | for k := range m { 103 | keys = append(keys, k) 104 | } 105 | sort.Ints(keys) 106 | for _, k := range keys { 107 | v := m[k] 108 | _ = v 109 | } 110 | } 111 | }) 112 | 113 | b.Run("SortKey", func(b *testing.B) { 114 | for i := 0; i <= b.N; i++ { 115 | for _, k := range FromOrderableMapKeys(m).Sort().ToSlice() { 116 | v := m[k] 117 | _ = v 118 | } 119 | } 120 | }) 121 | 122 | b.Run("SortKV", func(b *testing.B) { 123 | for i := 0; i <= b.N; i++ { 124 | for _, t := range FromOrderableMap(m).Sort().ToSlice() { 125 | _, _ = t.First, t.Second 126 | } 127 | } 128 | }) 129 | } 130 | 131 | func TestOrderableKV_Keys(t *testing.T) { 132 | assert.Equal(t, []int{1, 2, 3}, 133 | FromOrderableMap(map[int]int{1: 2, 2: 4, 3: 6}). 134 | Sort(). 135 | Keys(). 136 | ToSlice()) 137 | } 138 | -------------------------------------------------------------------------------- /internal/stream/string.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package stream 16 | 17 | import ( 18 | "github.com/bytedance/gg/internal/iter" 19 | ) 20 | 21 | // See function [github.com/bytedance/gg/internal/iter.Join]. 22 | func (s String[T]) Join(sep T) T { 23 | return iter.Join(sep, s.Iter) 24 | } 25 | -------------------------------------------------------------------------------- /internal/stream/string_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Bytedance Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package stream 16 | 17 | import ( 18 | "math/rand" 19 | "strconv" 20 | "strings" 21 | "testing" 22 | 23 | "github.com/bytedance/gg/internal/assert" 24 | "github.com/bytedance/gg/internal/iter" 25 | ) 26 | 27 | func BenchmarkStringJoin(b *testing.B) { 28 | n := 10000 29 | var s []int 30 | for i := 0; i < n; i++ { 31 | s = append(s, rand.Int()) 32 | } 33 | b.ResetTimer() 34 | 35 | b.Run("Baseline", func(b *testing.B) { 36 | for i := 0; i <= b.N; i++ { 37 | var strs []string 38 | for _, v := range s { 39 | strs = append(strs, strconv.Itoa(v)) 40 | } 41 | strings.Join(strs, ", ") 42 | } 43 | }) 44 | b.Run("Stream", func(b *testing.B) { 45 | for i := 0; i <= b.N; i++ { 46 | FromStringIter( 47 | iter.Map(strconv.Itoa, iter.StealSlice(s)), 48 | ).Join(", ") 49 | } 50 | }) 51 | } 52 | 53 | func TestString_Join(t *testing.T) { 54 | assert.Equal(t, "1,2,3", FromStringSlice([]string{"1", "2", "3"}).Join(",")) 55 | } 56 | --------------------------------------------------------------------------------