diff --git a/go.mod b/go.mod index d825c9d..5cb750e 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.25.5 require ( github.com/bradleyjkemp/cupaloy v2.3.0+incompatible github.com/google/go-cmp v0.7.0 - github.com/samber/lo v1.52.0 + github.com/samber/lo v1.53.0 github.com/stretchr/testify v1.11.1 golang.org/x/crypto v0.48.0 golang.org/x/text v0.34.0 diff --git a/go.sum b/go.sum index 7a710bc..b435cc7 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw= -github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= +github.com/samber/lo v1.53.0 h1:t975lj2py4kJPQ6haz1QMgtId2gtmfktACxIXArw3HM= +github.com/samber/lo v1.53.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= diff --git a/vendor/github.com/samber/lo/.golangci.yml b/vendor/github.com/samber/lo/.golangci.yml index 2a71979..2f6c810 100644 --- a/vendor/github.com/samber/lo/.golangci.yml +++ b/vendor/github.com/samber/lo/.golangci.yml @@ -40,7 +40,7 @@ linters: # disable noisy/controversial ones which you might enable later disable: - - lll # line length β€” handled by gfmt/gofumpt + - lll # line length β€” handled by gofmt/gofumpt settings: dupl: diff --git a/vendor/github.com/samber/lo/Makefile b/vendor/github.com/samber/lo/Makefile index 5a6591d..ad9267b 100644 --- a/vendor/github.com/samber/lo/Makefile +++ b/vendor/github.com/samber/lo/Makefile @@ -1,9 +1,14 @@ +# Only build/test/lint exp/simd when Go version is >= 1.26 (requires goexperiment.simd) +GO_VERSION := $(shell go version 2>/dev/null | sed -n 's/.*go\([0-9]*\)\.\([0-9]*\).*/\1.\2/p') +GO_SIMD_SUPPORT := $(shell ver="$(GO_VERSION)"; [ -n "$$ver" ] && [ "$$(printf '%s\n1.26\n' "$$ver" | sort -V | tail -1)" = "$$ver" ] && echo yes) build: go build -v ./... + @if [ -n "$(GO_SIMD_SUPPORT)" ]; then cd ./exp/simd && GOEXPERIMENT=simd go build -v ./; fi test: go test -race ./... + @if [ -n "$(GO_SIMD_SUPPORT)" ]; then cd ./exp/simd && GOEXPERIMENT=simd go test -race ./; fi watch-test: reflex -t 50ms -s -- sh -c 'gotest -race ./...' @@ -32,9 +37,11 @@ tools: lint: golangci-lint run --timeout 60s --max-same-issues 50 ./... + @if [ -n "$(GO_SIMD_SUPPORT)" ]; then cd ./exp/simd && golangci-lint run --timeout 60s --max-same-issues 50 ./; fi # mdsf verify --debug --log-level warn docs/ lint-fix: golangci-lint run --timeout 60s --max-same-issues 50 --fix ./... + @if [ -n "$(GO_SIMD_SUPPORT)" ]; then cd ./exp/simd && golangci-lint run --timeout 60s --max-same-issues 50 --fix ./; fi # mdsf format --debug --log-level warn docs/ audit: diff --git a/vendor/github.com/samber/lo/README.md b/vendor/github.com/samber/lo/README.md index 7a4ee33..c9ad483 100644 --- a/vendor/github.com/samber/lo/README.md +++ b/vendor/github.com/samber/lo/README.md @@ -18,18 +18,28 @@ A utility library based on Go 1.18+ generics that makes it easier to work with s **See also:** +- [samber/ro](https://github.com/samber/ro): Reactive Programming for Go: declarative and composable API for event-driven applications - [samber/do](https://github.com/samber/do): A dependency injection toolkit based on Go 1.18+ Generics - [samber/mo](https://github.com/samber/mo): Monads based on Go 1.18+ Generics (Option, Result, Either...) ----- +What makes it different from **samber/ro**? +- lo: synchronous helpers across finite sequences (maps, slices...) +- ro: processing of infinite data streams for event-driven scenarios -

πŸ’– Support This Project

+---- -

- I’m going all-in on open-source for the coming months. -
- Help sustain development: Become an individual sponsor or join as a corporate sponsor. -

+
+ πŸ’– Sponsored by: +
+ +
+ dbos +
+
+ DBOS - Durable workflow orchestration library for Go +
+
+
---- @@ -37,7 +47,7 @@ A utility library based on Go 1.18+ generics that makes it easier to work with s I wanted a **short name**, similar to "Lodash", and no Go package uses this name. -![lo](img/logo-full.png) +![lo](docs/static/img/logo-full.png) ## πŸš€ Install @@ -47,7 +57,7 @@ go get github.com/samber/lo@v1 This library is v1 and follows SemVer strictly. -No breaking changes will be made to exported APIs before v2.0.0. +No breaking changes will be made to exported APIs before v2.0.0, except for experimental packages under `exp/`. This library has no dependencies outside the Go standard library. @@ -106,8 +116,11 @@ Supported helpers for slices: - [GroupBy](#groupby) - [GroupByMap](#groupbymap) - [Chunk](#chunk) +- [Window](#window) +- [Sliding](#sliding) - [PartitionBy](#partitionby) - [Flatten](#flatten) +- [Concat](#concat) - [Interleave](#interleave) - [Shuffle](#shuffle) - [Reverse](#reverse) @@ -118,6 +131,9 @@ Supported helpers for slices: - [SliceToMap / Associate](#slicetomap-alias-associate) - [FilterSliceToMap](#filterslicetomap) - [Keyify](#keyify) +- [Take](#take) +- [TakeWhile](#takewhile) +- [TakeFilter](#takefilter) - [Drop](#drop) - [DropRight](#dropright) - [DropWhile](#dropwhile) @@ -134,9 +150,10 @@ Supported helpers for slices: - [Slice](#slice) - [Replace](#replace) - [ReplaceAll](#replaceall) +- [Clone](#clone) - [Compact](#compact) - [IsSorted](#issorted) -- [IsSortedByKey](#issortedbykey) +- [IsSortedBy](#issortedby) - [Splice](#Splice) - [Cut](#Cut) - [CutPrefix](#CutPrefix) @@ -239,6 +256,7 @@ Supported intersection helpers: - [None](#none) - [NoneBy](#noneby) - [Intersect](#intersect) +- [IntersectBy](#intersectby) - [Difference](#difference) - [Union](#union) - [Without](#without) @@ -375,6 +393,17 @@ even := lo.Filter([]int{1, 2, 3, 4}, func(x int, index int) bool { // []int{2, 4} ``` +```go +// Use FilterErr when the predicate can return an error +even, err := lo.FilterErr([]int{1, 2, 3, 4}, func(x int, _ int) (bool, error) { + if x == 3 { + return false, fmt.Errorf("number 3 is not allowed") + } + return x%2 == 0, nil +}) +// []int(nil), error("number 3 is not allowed") +``` + [[play](https://go.dev/play/p/Apjg3WeSi7K)] Mutable: like `lo.Filter()`, but the slice is updated in place. @@ -407,9 +436,20 @@ lo.Map([]int64{1, 2, 3, 4}, func(x int64, index int) string { // []string{"1", "2", "3", "4"} ``` +```go +// Use MapErr when the transform function can return an error +result, err := lo.MapErr([]int{1, 2, 3, 4}, func(x int, _ int) (string, error) { + if x == 3 { + return "", fmt.Errorf("number 3 is not allowed") + } + return strconv.Itoa(x), nil +}) +// []string(nil), error("number 3 is not allowed") +``` + [[play](https://go.dev/play/p/OkPcYAhBo0D)] -Parallel processing: like `lo.Map()`, but the mapper function is called in a goroutine. Results are returned in the same order. +Parallel processing: like `lo.Map()`, but the transform function is called in a goroutine. Results are returned in the same order. ```go import lop "github.com/samber/lo/parallel" @@ -487,6 +527,17 @@ lo.FlatMap([]int64{0, 1, 2}, func(x int64, _ int) []string { // []string{"0", "0", "1", "1", "2", "2"} ``` +```go +// Use FlatMapErr when the transform function can return an error +result, err := lo.FlatMapErr([]int64{0, 1, 2, 3}, func(x int64, _ int) ([]string, error) { + if x == 2 { + return nil, fmt.Errorf("number 2 is not allowed") + } + return []string{strconv.FormatInt(x, 10), strconv.FormatInt(x, 10)}, nil +}) +// []string(nil), error("number 2 is not allowed") +``` + [[play](https://go.dev/play/p/YSoYmQTA8-U)] ### Reduce @@ -500,6 +551,17 @@ sum := lo.Reduce([]int{1, 2, 3, 4}, func(agg int, item int, _ int) int { // 10 ``` +```go +// Use ReduceErr when the accumulator function can return an error +result, err := lo.ReduceErr([]int{1, 2, 3, 4}, func(agg int, item int, _ int) (int, error) { + if item == 3 { + return 0, fmt.Errorf("number 3 is not allowed") + } + return agg + item, nil +}, 0) +// 0, error("number 3 is not allowed") +``` + [[play](https://go.dev/play/p/R4UHXZNaaUG)] ### ReduceRight @@ -513,6 +575,17 @@ result := lo.ReduceRight([][]int{{0, 1}, {2, 3}, {4, 5}}, func(agg []int, item [ // []int{4, 5, 2, 3, 0, 1} ``` +```go +// Use ReduceRightErr when the accumulator function can return an error +result, err := lo.ReduceRightErr([]int{1, 2, 3, 4}, func(agg int, item int, _ int) (int, error) { + if item == 2 { + return 0, fmt.Errorf("number 2 is not allowed") + } + return agg + item, nil +}, 0) +// 0, error("number 2 is not allowed") +``` + [[play](https://go.dev/play/p/Fq3W70l7wXF)] ### ForEach @@ -609,6 +682,17 @@ uniqValues := lo.UniqBy([]int{0, 1, 2, 3, 4, 5}, func(i int) int { // []int{0, 1, 2} ``` +```go +// Use UniqByErr when the iteratee function can return an error +result, err := lo.UniqByErr([]int{0, 1, 2, 3, 4, 5}, func(i int) (int, error) { + if i == 3 { + return 0, fmt.Errorf("number 3 is not allowed") + } + return i % 3, nil +}) +// []int(nil), error("number 3 is not allowed") +``` + [[play](https://go.dev/play/p/g42Z3QSb53u)] ### GroupBy @@ -624,6 +708,17 @@ groups := lo.GroupBy([]int{0, 1, 2, 3, 4, 5}, func(i int) int { // map[int][]int{0: []int{0, 3}, 1: []int{1, 4}, 2: []int{2, 5}} ``` +```go +// Use GroupByErr when the iteratee function can return an error +result, err := lo.GroupByErr([]int{0, 1, 2, 3, 4, 5}, func(i int) (int, error) { + if i == 3 { + return 0, fmt.Errorf("number 3 is not allowed") + } + return i % 3, nil +}) +// map[int][]int(nil), error("number 3 is not allowed") +``` + [[play](https://go.dev/play/p/XnQBd_v6brd)] Parallel processing: like `lo.GroupBy()`, but callback is called in goroutine. @@ -650,6 +745,17 @@ groups := lo.GroupByMap([]int{0, 1, 2, 3, 4, 5}, func(i int) (int, int) { // map[int][]int{0: []int{0, 6}, 1: []int{2, 8}, 2: []int{4, 10}} ``` +```go +// Use GroupByMapErr when the transform function can return an error +result, err := lo.GroupByMapErr([]int{0, 1, 2, 3, 4, 5}, func(i int) (int, int, error) { + if i == 3 { + return 0, 0, fmt.Errorf("number 3 is not allowed") + } + return i % 3, i * 2, nil +}) +// map[int][]int(nil), error("number 3 is not allowed") +``` + [[play](https://go.dev/play/p/iMeruQ3_W80)] ### Chunk @@ -672,6 +778,36 @@ lo.Chunk([]int{0}, 2) [[play](https://go.dev/play/p/kEMkFbdu85g)] +### Window + +Creates a slice of sliding windows of a given size. Each window shares size-1 elements with the previous one. This is equivalent to `Sliding(collection, size, 1)`. + +```go +lo.Window([]int{1, 2, 3, 4, 5}, 3) +// [][]int{{1, 2, 3}, {2, 3, 4}, {3, 4, 5}} + +lo.Window([]float64{20, 22, 21, 23, 24}, 3) +// [][]float64{{20, 22, 21}, {22, 21, 23}, {21, 23, 24}} +``` + +### Sliding + +Creates a slice of sliding windows of a given size with a given step. If step is equal to size, windows have no common elements (similar to Chunk). If step is less than size, windows share common elements. + +```go +// Windows with shared elements (step < size) +lo.Sliding([]int{1, 2, 3, 4, 5, 6}, 3, 1) +// [][]int{{1, 2, 3}, {2, 3, 4}, {3, 4, 5}, {4, 5, 6}} + +// Windows with no shared elements (step == size, like Chunk) +lo.Sliding([]int{1, 2, 3, 4, 5, 6}, 3, 3) +// [][]int{{1, 2, 3}, {4, 5, 6}} + +// Step > size (skipping elements) +lo.Sliding([]int{1, 2, 3, 4, 5, 6, 7, 8}, 2, 3) +// [][]int{{1, 2}, {4, 5}, {7, 8}} +``` + ### PartitionBy Returns a slice of elements split into groups. The order of grouped values is determined by the order they occur in collection. The grouping is generated from the results of running each element of collection through iteratee. @@ -690,6 +826,22 @@ partitions := lo.PartitionBy([]int{-2, -1, 0, 1, 2, 3, 4, 5}, func(x int) string // [][]int{{-2, -1}, {0, 2, 4}, {1, 3, 5}} ``` +```go +// Use PartitionByErr when the iteratee function can return an error +result, err := lo.PartitionByErr([]int{-2, -1, 0, 1, 2}, func(x int) (string, error) { + if x == 0 { + return "", fmt.Errorf("zero is not allowed") + } + if x < 0 { + return "negative", nil + } else if x%2 == 0 { + return "even", nil + } + return "odd", nil +}) +// [][]int(nil), error("zero is not allowed") +``` + [[play](https://go.dev/play/p/NfQ_nGjkgXW)] Parallel processing: like `lo.PartitionBy()`, but callback is called in goroutine. Results are returned in the same order. @@ -719,6 +871,20 @@ flat := lo.Flatten([][]int{{0, 1}, {2, 3, 4, 5}}) [[play](https://go.dev/play/p/rbp9ORaMpjw)] +### Concat + +Returns a new slice containing all the elements in collections. Concat conserves the order of the elements. + +```go +slice := lo.Concat([]int{1, 2}, []int{3, 4}) +// []int{1, 2, 3, 4} + +slice := lo.Concat(nil, []int{1, 2}, nil, []int{3, 4}, nil) +// []int{1, 2, 3, 4} + +slice := lo.Concat[int]() +// []int{} +``` ### Interleave Round-robin alternating input slices and sequentially appending value at index into result. @@ -825,6 +991,18 @@ slice := lo.RepeatBy(5, func(i int) string { [[play](https://go.dev/play/p/ozZLCtX_hNU)] +With error handling: + +```go +slice, err := lo.RepeatByErr(5, func(i int) (string, error) { + if i == 3 { + return "", fmt.Errorf("index 3 is not allowed") + } + return fmt.Sprintf("item-%d", i), nil +}) +// []string(nil), error("index 3 is not allowed") +``` + ### KeyBy Transforms a slice or a slice of structs to a map based on a pivot callback. @@ -849,6 +1027,16 @@ result := lo.KeyBy(characters, func(char Character) string { //map[a:{dir:left code:97} d:{dir:right code:100}] ``` +```go +result, err := lo.KeyByErr([]string{"a", "aa", "aaa", ""}, func(str string) (int, error) { + if str == "" { + return 0, fmt.Errorf("empty string not allowed") + } + return len(str), nil +}) +// map[int]string(nil), error("empty string not allowed") +``` + [[play](https://go.dev/play/p/mdaClUAT-zZ)] ### SliceToMap (alias: Associate) @@ -901,6 +1089,50 @@ set := lo.Keyify([]int{1, 1, 2, 3, 4}) [[play](https://go.dev/play/p/RYhhM_csqIG)] +### Take + +Takes the first n elements from a slice. + +```go +l := lo.Take([]int{0, 1, 2, 3, 4, 5}, 3) +// []int{0, 1, 2} + +l := lo.Take([]int{0, 1, 2}, 5) +// []int{0, 1, 2} +``` + +### TakeWhile + +Takes elements from the beginning while the predicate returns true. + +```go +l := lo.TakeWhile([]int{0, 1, 2, 3, 4, 5}, func(val int) bool { + return val < 3 +}) +// []int{0, 1, 2} + +l := lo.TakeWhile([]string{"a", "aa", "aaa", "aa"}, func(val string) bool { + return len(val) <= 2 +}) +// []string{"a", "aa"} +``` + +### TakeFilter + +Filters elements and takes the first n elements that match the predicate. Equivalent to calling Take(Filter(...)), but more efficient as it stops after finding n matches. + +```go +l := lo.TakeFilter([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 3, func(val int, index int) bool { + return val%2 == 0 +}) +// []int{2, 4, 6} + +l := lo.TakeFilter([]string{"a", "aa", "aaa", "aaaa"}, 2, func(val string, index int) bool { + return len(val) > 1 +}) +// []string{"aa", "aaa"} +``` + ### Drop Drops n elements from the beginning of a slice. @@ -971,6 +1203,17 @@ odd := lo.Reject([]int{1, 2, 3, 4}, func(x int, _ int) bool { // []int{1, 3} ``` +```go +// Use RejectErr when the predicate can return an error +odd, err := lo.RejectErr([]int{1, 2, 3, 4}, func(x int, _ int) (bool, error) { + if x == 3 { + return false, fmt.Errorf("number 3 is not allowed") + } + return x%2 == 0, nil +}) +// []int(nil), error("number 3 is not allowed") +``` + [[play](https://go.dev/play/p/YkLMODy1WEL)] ### RejectMap @@ -1023,6 +1266,17 @@ count := lo.CountBy([]int{1, 5, 1}, func(i int) bool { // 2 ``` +```go +// Use CountByErr when the predicate can return an error +count, err := lo.CountByErr([]int{1, 5, 1}, func(i int) (bool, error) { + if i == 5 { + return false, fmt.Errorf("5 not allowed") + } + return i < 4, nil +}) +// 0, error("5 not allowed") +``` + [[play](https://go.dev/play/p/ByQbNYQQi4X)] ### CountValues @@ -1158,6 +1412,20 @@ slice := lo.ReplaceAll(in, -1, 42) [[play](https://go.dev/play/p/a9xZFUHfYcV)] +### Clone + +Returns a shallow copy of the collection. + +```go +in := []int{1, 2, 3, 4, 5} +cloned := lo.Clone(in) +// Verify it's a different slice by checking that modifying one doesn't affect the other +in[0] = 99 +// cloned is []int{1, 2, 3, 4, 5} +``` + +[[play](https://go.dev/play/p/hgHmoOIxmuH)] + ### Compact Returns a slice of all non-zero elements. @@ -1182,12 +1450,12 @@ slice := lo.IsSorted([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}) [[play](https://go.dev/play/p/mc3qR-t4mcx)] -### IsSortedByKey +### IsSortedBy Checks if a slice is sorted by iteratee. ```go -slice := lo.IsSortedByKey([]string{"a", "bb", "ccc"}, func(s string) int { +slice := lo.IsSortedBy([]string{"a", "bb", "ccc"}, func(s string) int { return len(s) }) // true @@ -1455,6 +1723,17 @@ m := lo.PickBy(map[string]int{"foo": 1, "bar": 2, "baz": 3}, func(key string, va // map[string]int{"foo": 1, "baz": 3} ``` +```go +// Use PickByErr when the predicate can return an error +m, err := lo.PickByErr(map[string]int{"foo": 1, "bar": 2, "baz": 3}, func(key string, value int) (bool, error) { + if key == "bar" { + return false, fmt.Errorf("bar not allowed") + } + return value%2 == 1, nil +}) +// map[string]int(nil), error("bar not allowed") +``` + [[play](https://go.dev/play/p/kdg8GR_QMmf)] ### PickByKeys @@ -1490,6 +1769,17 @@ m := lo.OmitBy(map[string]int{"foo": 1, "bar": 2, "baz": 3}, func(key string, va // map[string]int{"bar": 2} ``` +```go +// Use OmitByErr when the predicate can return an error +m, err := lo.OmitByErr(map[string]int{"foo": 1, "bar": 2, "baz": 3}, func(key string, value int) (bool, error) { + if key == "bar" { + return false, fmt.Errorf("bar not allowed") + } + return value%2 == 1, nil +}) +// map[string]int(nil), error("bar not allowed") +``` + [[play](https://go.dev/play/p/EtBsR43bdsd)] ### OmitByKeys @@ -1615,6 +1905,17 @@ m2 := lo.MapKeys(map[int]int{1: 1, 2: 2, 3: 3, 4: 4}, func(_ int, v int) string // map[string]int{"1": 1, "2": 2, "3": 3, "4": 4} ``` +```go +// Use MapKeysErr when the iteratee can return an error +m2, err := lo.MapKeysErr(map[int]int{1: 1, 2: 2, 3: 3}, func(_ int, v int) (string, error) { + if v == 2 { + return "", fmt.Errorf("even number not allowed") + } + return strconv.FormatInt(int64(v), 10), nil +}) +// map[string]int(nil), error("even number not allowed") +``` + [[play](https://go.dev/play/p/9_4WPIqOetJ)] ### MapValues @@ -1630,6 +1931,18 @@ m2 := lo.MapValues(m1, func(x int64, _ int) string { // map[int]string{1: "1", 2: "2", 3: "3"} ``` +```go +// Use MapValuesErr when the iteratee can return an error +m1 := map[int]int64{1: 1, 2: 2, 3: 3} +m2, err := lo.MapValuesErr(m1, func(x int64, _ int) (string, error) { + if x == 2 { + return "", fmt.Errorf("even number not allowed") + } + return strconv.FormatInt(x, 10), nil +}) +// map[int]string(nil), error("even number not allowed") +``` + [[play](https://go.dev/play/p/T_8xAfvcf0W)] ### MapEntries @@ -1645,6 +1958,18 @@ out := lo.MapEntries(in, func(k string, v int) (int, string) { // map[int]string{1: "foo", 2: "bar"} ``` +```go +// Use MapEntriesErr when the iteratee can return an error +in := map[string]int{"foo": 1, "bar": 2, "baz": 3} +out, err := lo.MapEntriesErr(in, func(k string, v int) (int, string, error) { + if k == "bar" { + return 0, "", fmt.Errorf("bar not allowed") + } + return v, k, nil +}) +// map[int]string(nil), error("bar not allowed") +``` + [[play](https://go.dev/play/p/VuvNQzxKimT)] ### MapToSlice @@ -1660,6 +1985,18 @@ s := lo.MapToSlice(m, func(k int, v int64) string { // []string{"1_4", "2_5", "3_6"} ``` +```go +// Use MapToSliceErr when the iteratee can return an error +m := map[int]int64{1: 4, 2: 5, 3: 6} +s, err := lo.MapToSliceErr(m, func(k int, v int64) (string, error) { + if k == 2 { + return "", fmt.Errorf("key 2 not allowed") + } + return fmt.Sprintf("%d_%d", k, v), nil +}) +// []string(nil), error("key 2 not allowed") +``` + [[play](https://go.dev/play/p/ZuiCZpDt6LD)] ### FilterMapToSlice @@ -1677,6 +2014,18 @@ result := lo.FilterMapToSlice(kv, func(k int, v int64) (string, bool) { // []{"2_2", "4_4"} ``` +```go +kv := map[int]int64{1: 1, 2: 2, 3: 3, 4: 4} + +result, err := lo.FilterMapToSliceErr(kv, func(k int, v int64) (string, bool, error) { + if k == 3 { + return "", false, fmt.Errorf("key 3 not allowed") + } + return fmt.Sprintf("%d_%d", k, v), k%2 == 0, nil +}) +// []string(nil), error("key 3 not allowed") +``` + ### FilterKeys Transforms a map into a slice based on predicate returns true for specific elements. It is a mix of `lo.Filter()` and `lo.Keys()`. @@ -1690,6 +2039,17 @@ result := FilterKeys(kv, func(k int, v string) bool { // [1] ``` +```go +// Use FilterKeysErr when the predicate can return an error +result, err := lo.FilterKeysErr(map[int]string{1: "foo", 2: "bar", 3: "baz"}, func(k int, v string) (bool, error) { + if k == 3 { + return false, fmt.Errorf("key 3 not allowed") + } + return v == "foo", nil +}) +// []int(nil), error("key 3 not allowed") +``` + [[play](https://go.dev/play/p/OFlKXlPrBAe)] ### FilterValues @@ -1705,6 +2065,17 @@ result := FilterValues(kv, func(k int, v string) bool { // ["foo"] ``` +```go +// Use FilterValuesErr when the predicate can return an error +result, err := lo.FilterValuesErr(map[int]string{1: "foo", 2: "bar", 3: "baz"}, func(k int, v string) (bool, error) { + if k == 3 { + return false, fmt.Errorf("key 3 not allowed") + } + return v == "foo", nil +}) +// []string(nil), error("key 3 not allowed") +``` + [[play](https://go.dev/play/p/YVD5r_h-LX-)] ### Range / RangeFrom / RangeWithSteps @@ -1784,6 +2155,19 @@ sum := lo.SumBy(strings, func(item string) int { // 6 ``` +With error handling: + +```go +strings := []string{"foo", "bar", "baz"} +sum, err := lo.SumByErr(strings, func(item string) (int, error) { + if item == "bar" { + return 0, fmt.Errorf("invalid item: %s", item) + } + return len(item), nil +}) +// sum: 3, err: invalid item: bar +``` + ### Product Calculates the product of the values in a collection. @@ -1812,6 +2196,18 @@ product := lo.ProductBy(strings, func(item string) int { // 9 ``` +```go +// Use ProductByErr when the transform function can return an error +strings := []string{"foo", "bar", "baz"} +product, err := lo.ProductByErr(strings, func(item string) (int, error) { + if item == "bar" { + return 0, fmt.Errorf("bar is not allowed") + } + return len(item), nil +}) +// 3, error("bar is not allowed") +``` + [[play](https://go.dev/play/p/wadzrWr9Aer)] ### Mean @@ -1850,6 +2246,18 @@ mean := lo.MeanBy([]float64{}, mapper) // 0 ``` +```go +// Use MeanByErr when the transform function can return an error +list := []string{"aa", "bbb", "cccc", "ddddd"} +mean, err := lo.MeanByErr(list, func(item string) (float64, error) { + if item == "cccc" { + return 0, fmt.Errorf("cccc is not allowed") + } + return float64(len(item)), nil +}) +// 0, error("cccc is not allowed") +``` + [[play](https://go.dev/play/p/j7TsVwBOZ7P)] ### Mode @@ -1920,6 +2328,8 @@ lo.ChunkString("1", 2) // []string{"1"} ``` +Note: `lo.ChunkString` and `lo.Chunk` functions behave inconsistently for empty input: `lo.ChunkString("", n)` returns `[""]` instead of `[]`. See [#788](https://github.com/samber/lo/issues/788). + [[play](https://go.dev/play/p/__FLTuJVz54)] ### RuneLength @@ -2004,7 +2414,7 @@ str := lo.Capitalize("heLLO") ### Ellipsis -Trims and truncates a string to a specified length in `bytes` and appends an ellipsis if truncated. If the string contains non-ASCII characters (which may occupy multiple bytes in UTF-8), truncating by byte length may split a character in the middle, potentially resulting in garbled output. +Trims and truncates a string to a specified length in runes (Unicode code points) and appends an ellipsis if truncated. Multi-byte characters such as emoji or CJK ideographs are never split in the middle. ```go str := lo.Ellipsis(" Lorem Ipsum ", 5) @@ -2015,6 +2425,12 @@ str := lo.Ellipsis("Lorem Ipsum", 100) str := lo.Ellipsis("Lorem Ipsum", 3) // ... + +str := lo.Ellipsis("hello δΈ–η•Œ! δ½ ε₯½", 8) +// hello... + +str := lo.Ellipsis("🏠🐢🐱🌟", 4) +// 🏠🐢🐱🌟 ``` [[play](https://go.dev/play/p/qE93rgqe1TW)] @@ -2079,6 +2495,18 @@ items := lo.ZipBy2([]string{"a", "b"}, []int{1, 2}, func(a string, b int) string // []string{"a-1", "b-2"} ``` +With error handling: + +```go +items, err := lo.ZipByErr2([]string{"a", "b"}, []int{1, 2}, func(a string, b int) (string, error) { + if b == 2 { + return "", fmt.Errorf("number 2 is not allowed") + } + return fmt.Sprintf("%s-%d", a, b), nil +}) +// []string(nil), error("number 2 is not allowed") +``` + ### Unzip2 -> Unzip9 Unzip accepts a slice of grouped elements and creates a slice regrouping the elements to their pre-zip configuration. @@ -2103,6 +2531,18 @@ a, b := lo.UnzipBy2([]string{"hello", "john", "doe"}, func(str string) (string, // []int{5, 4, 3} ``` +```go +a, b, err := lo.UnzipByErr2([]string{"hello", "error", "world"}, func(str string) (string, int, error) { + if str == "error" { + return "", 0, fmt.Errorf("error string not allowed") + } + return str, len(str), nil +}) +// []string{} +// []int{} +// error string not allowed +``` + ### CrossJoin2 -> CrossJoin9 Combines every item from one list with every item from others. It is the cartesian product of lists received as arguments. Returns an empty list if a list is empty. @@ -2119,7 +2559,7 @@ result := lo.CrossJoin2([]string{"hello", "john", "doe"}, []int{1, 2}) ### CrossJoinBy2 -> CrossJoinBy9 -Combines every item from one list with every item from others. It is the cartesian product of lists received as arguments. The project function is used to create the output values. Returns an empty list if a list is empty. +Combines every item from one list with every item from others. It is the cartesian product of lists received as arguments. The transform function is used to create the output values. Returns an empty list if a list is empty. ```go result := lo.CrossJoinBy2([]string{"hello", "john", "doe"}, []int{1, 2}, func(a A, b B) string { @@ -2133,6 +2573,18 @@ result := lo.CrossJoinBy2([]string{"hello", "john", "doe"}, []int{1, 2}, func(a // "doe - 2" ``` +With error handling: + +```go +result, err := lo.CrossJoinByErr2([]string{"hello", "john"}, []int{1, 2}, func(a string, b int) (string, error) { + if a == "john" { + return "", fmt.Errorf("john not allowed") + } + return fmt.Sprintf("%s - %d", a, b), nil +}) +// []string(nil), error("john not allowed") +``` + ### Duration Returns the time taken to execute a function. @@ -2550,7 +3002,7 @@ b := NoneBy([]int{1, 2, 3, 4}, func(x int) bool { ### Intersect -Returns the intersection between two collections. +Returns the intersection between collections. ```go result1 := lo.Intersect([]int{0, 1, 2, 3, 4, 5}, []int{0, 2}) @@ -2561,6 +3013,31 @@ result2 := lo.Intersect([]int{0, 1, 2, 3, 4, 5}, []int{0, 6}) result3 := lo.Intersect([]int{0, 1, 2, 3, 4, 5}, []int{-1, 6}) // []int{} + +result4 := lo.Intersect([]int{0, 3, 5, 7}, []int{3, 5}, []int{0, 1, 2, 0, 3, 0}) +// []int{3} +``` + +### IntersectBy + +Returns the intersection between two collections using a custom key selector function. + +```go +transform := func(v int) string { + return strconv.Itoa(v) +} + +result1 := lo.IntersectBy(transform, []int{0, 1, 2, 3, 4, 5}, []int{0, 2}) +// []int{0, 2} + +result2 := lo.IntersectBy(transform, []int{0, 1, 2, 3, 4, 5}, []int{0, 6}) +// []int{0} + +result3 := lo.IntersectBy(transform, []int{0, 1, 2, 3, 4, 5}, []int{-1, 6}) +// []int{} + +result4 := lo.IntersectBy(transform, []int{0, 3, 5, 7}, []int{3, 5}, []int{0, 1, 2, 0, 3, 0}) +// []int{3} ``` ### Difference @@ -2608,7 +3085,7 @@ Filters a slice by excluding elements whose extracted keys match any in the excl Returns a new slice containing only the elements whose keys are not in the exclude list. ```go -type struct User { +type User struct { ID int Name string } @@ -2633,6 +3110,30 @@ filteredUsers := lo.WithoutBy(users, getID, excludedIDs...) // []User[{ID: 1, Name: "Alice"}] ``` +```go +// Use WithoutByErr when the iteratee can return an error +type struct User { + ID int + Name string +} + +users := []User{ + {ID: 1, Name: "Alice"}, + {ID: 2, Name: "Bob"}, + {ID: 3, Name: "Charlie"}, +} + +getID := func(user User) (int, error) { + if user.ID == 2 { + return 0, fmt.Errorf("Bob not allowed") + } + return user.ID, nil +} + +filteredUsers, err := lo.WithoutByErr(users, getID, 2, 3) +// []User(nil), error("Bob not allowed") +``` + ### WithoutEmpty Returns a slice excluding zero values. @@ -2751,6 +3252,25 @@ str, ok := lo.Find([]string{"foobar"}, func(i string) bool { // "", false ``` +```go +// Use FindErr when the predicate can return an error +str, err := lo.FindErr([]string{"a", "b", "c", "d"}, func(i string) (bool, error) { + if i == "c" { + return false, fmt.Errorf("c is not allowed") + } + return i == "b", nil +}) +// "b", nil + +str, err = lo.FindErr([]string{"a", "b", "c"}, func(i string) (bool, error) { + if i == "b" { + return false, fmt.Errorf("b is not allowed") + } + return i == "b", nil +}) +// "", error("b is not allowed") +``` + [[play](https://go.dev/play/p/Eo7W0lvKTky)] ### FindIndexOf @@ -2879,6 +3399,18 @@ duplicatedValues := lo.FindDuplicatesBy([]int{3, 4, 5, 6, 7}, func(i int) int { // []int{3, 4} ``` +With error handling: + +```go +duplicatedValues, err := lo.FindDuplicatesByErr([]int{3, 4, 5, 6, 7}, func(i int) (int, error) { + if i == 5 { + return 0, fmt.Errorf("number 5 is not allowed") + } + return i % 3, nil +}) +// []int(nil), error("number 5 is not allowed") +``` + ### Min Search the minimum value of a collection. @@ -2935,6 +3467,17 @@ min := lo.MinBy([]string{}, func(item string, min string) bool { // "" ``` +```go +// Use MinByErr when the comparison function can return an error +min, err := lo.MinByErr([]string{"s1", "string2", "s3"}, func(item string, min string) (bool, error) { + if item == "string2" { + return false, fmt.Errorf("string2 is not allowed") + } + return len(item) < len(min), nil +}) +// "s1", error("string2 is not allowed") +``` + ### MinIndexBy Search the minimum value of a collection using the given comparison function and the index of the minimum value. @@ -2955,6 +3498,16 @@ min, index := lo.MinIndexBy([]string{}, func(item string, min string) bool { // "", -1 ``` +```go +min, index, err := lo.MinIndexByErr([]string{"s1", "string2", "s3"}, func(item string, min string) (bool, error) { + if item == "s2" { + return false, fmt.Errorf("s2 is not allowed") + } + return len(item) < len(min), nil +}) +// "s1", 0, error("s2 is not allowed") +``` + ### Earliest Search the minimum time.Time of a collection. @@ -2983,6 +3536,17 @@ earliest := lo.EarliestBy([]foo{{time.Now()}, {}}, func(i foo) time.Time { // {bar:{2023-04-01 01:02:03 +0000 UTC}} ``` +```go +// Use EarliestByErr when the iteratee function can return an error +earliest, err := lo.EarliestByErr([]foo{{time.Now()}, {}}, func(i foo) (time.Time, error) { + if i.bar.IsZero() { + return time.Time{}, fmt.Errorf("zero time not allowed") + } + return i.bar, nil +}) +// {bar:{...}}, error("zero time not allowed") +``` + ### Max Search the maximum value of a collection. @@ -3037,6 +3601,19 @@ max := lo.MaxBy([]string{}, func(item string, max string) bool { // "" ``` +```go +// Use MaxByErr when the comparison function can return an error +max, err := lo.MaxByErr([]string{"string1", "s2", "string3"}, func(item string, max string) (bool, error) { + if item == "s2" { + return false, fmt.Errorf("s2 is not allowed") + } + return len(item) > len(max), nil +}) +// "string1", error("s2 is not allowed") +``` + +[[play](https://go.dev/play/p/JW1qu-ECwF7)] + ### MaxIndexBy Search the maximum value of a collection using the given comparison function and the index of the maximum value. @@ -3057,6 +3634,19 @@ max, index := lo.MaxIndexBy([]string{}, func(item string, max string) bool { // "", -1 ``` +```go +// Use MaxIndexByErr when the comparison function can return an error +max, index, err := lo.MaxIndexByErr([]string{"string1", "s2", "string3"}, func(item string, max string) (bool, error) { + if item == "s2" { + return false, fmt.Errorf("s2 is not allowed") + } + return len(item) > len(max), nil +}) +// "string1", 0, error("s2 is not allowed") +``` + +[[play](https://go.dev/play/p/uaUszc-c9QK)] + ### Latest Search the maximum time.Time of a collection. @@ -3085,6 +3675,17 @@ latest := lo.LatestBy([]foo{{time.Now()}, {}}, func(i foo) time.Time { // {bar:{2023-04-01 01:02:03 +0000 UTC}} ``` +```go +// Use LatestByErr when the iteratee function can return an error +result, err := lo.LatestByErr([]foo{{time.Now()}, {}}, func(i foo) (time.Time, error) { + if i.bar.IsZero() { + return time.Time{}, fmt.Errorf("zero time not allowed") + } + return i.bar, nil +}) +// foo{}, error("zero time not allowed") +``` + ### First Returns the first element of a collection and check for availability of the first element. diff --git a/vendor/github.com/samber/lo/channel.go b/vendor/github.com/samber/lo/channel.go index 9a10852..5c7dd9e 100644 --- a/vendor/github.com/samber/lo/channel.go +++ b/vendor/github.com/samber/lo/channel.go @@ -26,12 +26,7 @@ func ChannelDispatcher[T any](stream <-chan T, count, channelBufferCap int, stra var i uint64 - for { - msg, ok := <-stream - if !ok { - return - } - + for msg := range stream { destination := strategy(msg, i, roChildren) % count children[destination] <- msg @@ -107,8 +102,8 @@ func DispatchingStrategyRandom[T any](msg T, index uint64, channels []<-chan T) func DispatchingStrategyWeightedRandom[T any](weights []int) DispatchingStrategy[T] { seq := []int{} - for i := 0; i < len(weights); i++ { - for j := 0; j < weights[i]; j++ { + for i, weight := range weights { + for j := 0; j < weight; j++ { seq = append(seq, i) } } @@ -143,22 +138,22 @@ func DispatchingStrategyFirst[T any](msg T, index uint64, channels []<-chan T) i // DispatchingStrategyLeast distributes messages in the emptiest channel. // Play: https://go.dev/play/p/ypy0jrRcEe7 func DispatchingStrategyLeast[T any](msg T, index uint64, channels []<-chan T) int { - seq := Range(len(channels)) - - return MinBy(seq, func(item, mIn int) bool { - return len(channels[item]) < len(channels[mIn]) + _, i := MinIndexBy(channels, func(a, b <-chan T) bool { + return len(a) < len(b) }) + + return i } // DispatchingStrategyMost distributes messages in the fullest channel. // If the channel capacity is exceeded, the next channel will be selected and so on. // Play: https://go.dev/play/p/erHHone7rF9 func DispatchingStrategyMost[T any](msg T, index uint64, channels []<-chan T) int { - seq := Range(len(channels)) - - return MaxBy(seq, func(item, mAx int) bool { - return len(channels[item]) > len(channels[mAx]) && channelIsNotFull(channels[item]) + _, i := MaxIndexBy(channels, func(a, b <-chan T) bool { + return len(a) > len(b) && channelIsNotFull(a) }) + + return i } // SliceToChannel returns a read-only channel of collection elements. @@ -213,10 +208,9 @@ func Generator[T any](bufferSize int, generator func(yield func(T))) <-chan T { // Play: https://go.dev/play/p/gPQ-6xmcKQI func Buffer[T any](ch <-chan T, size int) (collection []T, length int, readTime time.Duration, ok bool) { buffer := make([]T, 0, size) - index := 0 now := time.Now() - for ; index < size; index++ { + for index := 0; index < size; index++ { item, ok := <-ch if !ok { return buffer, index, time.Since(now), false @@ -225,14 +219,7 @@ func Buffer[T any](ch <-chan T, size int) (collection []T, length int, readTime buffer = append(buffer, item) } - return buffer, index, time.Since(now), true -} - -// Batch creates a slice of n elements from a channel. Returns the slice and the slice length. -// -// Deprecated: Use [Buffer] instead. -func Batch[T any](ch <-chan T, size int) (collection []T, length int, readTime time.Duration, ok bool) { - return Buffer(ch, size) + return buffer, size, time.Since(now), true } // BufferWithContext creates a slice of n elements from a channel, with context. Returns the slice and the slice length. @@ -267,13 +254,6 @@ func BufferWithTimeout[T any](ch <-chan T, size int, timeout time.Duration) (col return BufferWithContext(ctx, ch, size) } -// BatchWithTimeout creates a slice of n elements from a channel, with timeout. Returns the slice and the slice length. -// -// Deprecated: Use [BufferWithTimeout] instead. -func BatchWithTimeout[T any](ch <-chan T, size int, timeout time.Duration) (collection []T, length int, readTime time.Duration, ok bool) { - return BufferWithTimeout(ch, size, timeout) -} - // FanIn collects messages from multiple input channels into a single buffered channel. // Output messages have no priority. When all upstream channels reach EOF, downstream channel closes. // Play: https://go.dev/play/p/FH8Wq-T04Jb @@ -300,14 +280,6 @@ func FanIn[T any](channelBufferCap int, upstreams ...<-chan T) <-chan T { return out } -// ChannelMerge collects messages from multiple input channels into a single buffered channel. -// Output messages have no priority. When all upstream channels reach EOF, downstream channel closes. -// -// Deprecated: Use [FanIn] instead. -func ChannelMerge[T any](channelBufferCap int, upstreams ...<-chan T) <-chan T { - return FanIn(channelBufferCap, upstreams...) -} - // FanOut broadcasts all the upstream messages to multiple downstream channels. // When upstream channel reaches EOF, downstream channels close. If any downstream // channels is full, broadcasting is paused. diff --git a/vendor/github.com/samber/lo/concurrency.go b/vendor/github.com/samber/lo/concurrency.go index 35e1f74..70871b2 100644 --- a/vendor/github.com/samber/lo/concurrency.go +++ b/vendor/github.com/samber/lo/concurrency.go @@ -10,9 +10,9 @@ type synchronize struct { locker sync.Locker } -func (s *synchronize) Do(cb func()) { +func (s *synchronize) Do(callback func()) { s.locker.Lock() - Try0(cb) + Try0(callback) s.locker.Unlock() } @@ -52,13 +52,13 @@ func Async0(f func()) <-chan struct{} { } // Async1 is an alias to Async. -// Play: https://go.dev/play/p/uo35gosuTLw +// Play: https://go.dev/play/p/RBQWtIn4PsF func Async1[A any](f func() A) <-chan A { return Async(f) } // Async2 has the same behavior as Async, but returns the 2 results as a tuple inside the channel. -// Play: https://go.dev/play/p/7W7mKQi0AhA +// Play: https://go.dev/play/p/5SzzDjssXOH func Async2[A, B any](f func() (A, B)) <-chan Tuple2[A, B] { ch := make(chan Tuple2[A, B], 1) go func() { @@ -68,7 +68,7 @@ func Async2[A, B any](f func() (A, B)) <-chan Tuple2[A, B] { } // Async3 has the same behavior as Async, but returns the 3 results as a tuple inside the channel. -// Play: https://go.dev/play/p/L1d6o6l6q0d +// Play: https://go.dev/play/p/cZpZsDXNmlx func Async3[A, B, C any](f func() (A, B, C)) <-chan Tuple3[A, B, C] { ch := make(chan Tuple3[A, B, C], 1) go func() { @@ -78,7 +78,7 @@ func Async3[A, B, C any](f func() (A, B, C)) <-chan Tuple3[A, B, C] { } // Async4 has the same behavior as Async, but returns the 4 results as a tuple inside the channel. -// Play: https://go.dev/play/p/1X7q6oL0TqF +// Play: https://go.dev/play/p/9X5O2VrLzkR func Async4[A, B, C, D any](f func() (A, B, C, D)) <-chan Tuple4[A, B, C, D] { ch := make(chan Tuple4[A, B, C, D], 1) go func() { @@ -88,7 +88,7 @@ func Async4[A, B, C, D any](f func() (A, B, C, D)) <-chan Tuple4[A, B, C, D] { } // Async5 has the same behavior as Async, but returns the 5 results as a tuple inside the channel. -// Play: https://go.dev/play/p/2W7q4oL1TqG +// Play: https://go.dev/play/p/MqnUJpkmopA func Async5[A, B, C, D, E any](f func() (A, B, C, D, E)) <-chan Tuple5[A, B, C, D, E] { ch := make(chan Tuple5[A, B, C, D, E], 1) go func() { @@ -98,7 +98,7 @@ func Async5[A, B, C, D, E any](f func() (A, B, C, D, E)) <-chan Tuple5[A, B, C, } // Async6 has the same behavior as Async, but returns the 6 results as a tuple inside the channel. -// Play: https://go.dev/play/p/3X8q5pM2UrH +// Play: https://go.dev/play/p/kM1X67JPdSP func Async6[A, B, C, D, E, F any](f func() (A, B, C, D, E, F)) <-chan Tuple6[A, B, C, D, E, F] { ch := make(chan Tuple6[A, B, C, D, E, F], 1) go func() { diff --git a/vendor/github.com/samber/lo/condition.go b/vendor/github.com/samber/lo/condition.go index 1eb017e..92ed36b 100644 --- a/vendor/github.com/samber/lo/condition.go +++ b/vendor/github.com/samber/lo/condition.go @@ -121,9 +121,9 @@ func (s *switchCase[T, R]) Case(val T, result R) *switchCase[T, R] { // CaseF. // Play: https://go.dev/play/p/TGbKUMAeRUd -func (s *switchCase[T, R]) CaseF(val T, cb func() R) *switchCase[T, R] { +func (s *switchCase[T, R]) CaseF(val T, callback func() R) *switchCase[T, R] { if !s.done && s.predicate == val { - s.result = cb() + s.result = callback() s.done = true } @@ -142,9 +142,9 @@ func (s *switchCase[T, R]) Default(result R) R { // DefaultF. // Play: https://go.dev/play/p/TGbKUMAeRUd -func (s *switchCase[T, R]) DefaultF(cb func() R) R { +func (s *switchCase[T, R]) DefaultF(callback func() R) R { if !s.done { - s.result = cb() + s.result = callback() } return s.result diff --git a/vendor/github.com/samber/lo/errors.go b/vendor/github.com/samber/lo/errors.go index 135230a..8313edd 100644 --- a/vendor/github.com/samber/lo/errors.go +++ b/vendor/github.com/samber/lo/errors.go @@ -30,8 +30,8 @@ func messageFromMsgAndArgs(msgAndArgs ...any) string { return "" } -// must panics if err is error or false. -func must(err any, messageArgs ...any) { +// MustChecker panics if err is error or false. +var MustChecker = func(err any, messageArgs ...any) { if err == nil { return } @@ -63,14 +63,14 @@ func must(err any, messageArgs ...any) { // and panics if err is error or false. // Play: https://go.dev/play/p/fOqtX5HudtN func Must[T any](val T, err any, messageArgs ...any) T { - must(err, messageArgs...) + MustChecker(err, messageArgs...) return val } // Must0 has the same behavior as Must, but callback returns no variable. // Play: https://go.dev/play/p/TMoWrRp3DyC func Must0(err any, messageArgs ...any) { - must(err, messageArgs...) + MustChecker(err, messageArgs...) } // Must1 is an alias to Must. @@ -82,35 +82,35 @@ func Must1[T any](val T, err any, messageArgs ...any) T { // Must2 has the same behavior as Must, but callback returns 2 variables. // Play: https://go.dev/play/p/TMoWrRp3DyC func Must2[T1, T2 any](val1 T1, val2 T2, err any, messageArgs ...any) (T1, T2) { - must(err, messageArgs...) + MustChecker(err, messageArgs...) return val1, val2 } // Must3 has the same behavior as Must, but callback returns 3 variables. // Play: https://go.dev/play/p/TMoWrRp3DyC func Must3[T1, T2, T3 any](val1 T1, val2 T2, val3 T3, err any, messageArgs ...any) (T1, T2, T3) { - must(err, messageArgs...) + MustChecker(err, messageArgs...) return val1, val2, val3 } // Must4 has the same behavior as Must, but callback returns 4 variables. // Play: https://go.dev/play/p/TMoWrRp3DyC func Must4[T1, T2, T3, T4 any](val1 T1, val2 T2, val3 T3, val4 T4, err any, messageArgs ...any) (T1, T2, T3, T4) { - must(err, messageArgs...) + MustChecker(err, messageArgs...) return val1, val2, val3, val4 } // Must5 has the same behavior as Must, but callback returns 5 variables. // Play: https://go.dev/play/p/TMoWrRp3DyC func Must5[T1, T2, T3, T4, T5 any](val1 T1, val2 T2, val3 T3, val4 T4, val5 T5, err any, messageArgs ...any) (T1, T2, T3, T4, T5) { - must(err, messageArgs...) + MustChecker(err, messageArgs...) return val1, val2, val3, val4, val5 } // Must6 has the same behavior as Must, but callback returns 6 variables. // Play: https://go.dev/play/p/TMoWrRp3DyC func Must6[T1, T2, T3, T4, T5, T6 any](val1 T1, val2 T2, val3 T3, val4 T4, val5 T5, val6 T6, err any, messageArgs ...any) (T1, T2, T3, T4, T5, T6) { - must(err, messageArgs...) + MustChecker(err, messageArgs...) return val1, val2, val3, val4, val5, val6 } @@ -356,7 +356,7 @@ func ErrorsAs[T error](err error) (T, bool) { // Assert does nothing when the condition is true, otherwise it panics with an optional message. // Play: https://go.dev/play/p/Xv8LLKBMNwI -func Assert(condition bool, message ...string) { +var Assert = func(condition bool, message ...string) { if condition { return } @@ -370,7 +370,7 @@ func Assert(condition bool, message ...string) { // Assertf does nothing when the condition is true, otherwise it panics with a formatted message. // Play: https://go.dev/play/p/TVPEmVcyrdY -func Assertf(condition bool, format string, args ...any) { +var Assertf = func(condition bool, format string, args ...any) { if condition { return } diff --git a/vendor/github.com/samber/lo/find.go b/vendor/github.com/samber/lo/find.go index 534b4d7..f5be852 100644 --- a/vendor/github.com/samber/lo/find.go +++ b/vendor/github.com/samber/lo/find.go @@ -1,7 +1,6 @@ package lo import ( - "fmt" "time" "github.com/samber/lo/internal/constraints" @@ -81,6 +80,26 @@ func Find[T any](collection []T, predicate func(item T) bool) (T, bool) { return result, false } +// FindErr searches for an element in a slice based on a predicate that can return an error. +// Returns the element and nil error if the element is found. +// Returns zero value and nil error if the element is not found. +// If the predicate returns an error, iteration stops immediately and returns zero value and the error. +func FindErr[T any](collection []T, predicate func(item T) (bool, error)) (T, error) { + for i := range collection { + matches, err := predicate(collection[i]) + if err != nil { + var result T + return result, err + } + if matches { + return collection[i], nil + } + } + + var result T + return result, nil +} + // FindIndexOf searches for an element in a slice based on a predicate and returns the index and true. // Returns -1 and false if the element is not found. // Play: https://go.dev/play/p/XWSEM4Ic_t0 @@ -152,16 +171,20 @@ func FindKeyBy[K comparable, V any](object map[K]V, predicate func(key K, value func FindUniques[T comparable, Slice ~[]T](collection Slice) Slice { isDupl := make(map[T]bool, len(collection)) + duplicates := 0 + for i := range collection { - duplicated, ok := isDupl[collection[i]] - if !ok { - isDupl[collection[i]] = false - } else if !duplicated { - isDupl[collection[i]] = true + duplicated, seen := isDupl[collection[i]] + if !duplicated { + isDupl[collection[i]] = seen + + if seen { + duplicates++ + } } } - result := make(Slice, 0, len(collection)-len(isDupl)) + result := make(Slice, 0, len(isDupl)-duplicates) for i := range collection { if duplicated := isDupl[collection[i]]; !duplicated { @@ -178,18 +201,22 @@ func FindUniques[T comparable, Slice ~[]T](collection Slice) Slice { func FindUniquesBy[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) U) Slice { isDupl := make(map[U]bool, len(collection)) + duplicates := 0 + for i := range collection { key := iteratee(collection[i]) - duplicated, ok := isDupl[key] - if !ok { - isDupl[key] = false - } else if !duplicated { - isDupl[key] = true + duplicated, seen := isDupl[key] + if !duplicated { + isDupl[key] = seen + + if seen { + duplicates++ + } } } - result := make(Slice, 0, len(collection)-len(isDupl)) + result := make(Slice, 0, len(isDupl)-duplicates) for i := range collection { key := iteratee(collection[i]) @@ -207,16 +234,20 @@ func FindUniquesBy[T any, U comparable, Slice ~[]T](collection Slice, iteratee f func FindDuplicates[T comparable, Slice ~[]T](collection Slice) Slice { isDupl := make(map[T]bool, len(collection)) + duplicates := 0 + for i := range collection { - duplicated, ok := isDupl[collection[i]] - if !ok { - isDupl[collection[i]] = false - } else if !duplicated { - isDupl[collection[i]] = true + duplicated, seen := isDupl[collection[i]] + if !duplicated { + isDupl[collection[i]] = seen + + if seen { + duplicates++ + } } } - result := make(Slice, 0, len(collection)-len(isDupl)) + result := make(Slice, 0, duplicates) for i := range collection { if duplicated := isDupl[collection[i]]; duplicated { @@ -234,18 +265,22 @@ func FindDuplicates[T comparable, Slice ~[]T](collection Slice) Slice { func FindDuplicatesBy[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) U) Slice { isDupl := make(map[U]bool, len(collection)) + duplicates := 0 + for i := range collection { key := iteratee(collection[i]) - duplicated, ok := isDupl[key] - if !ok { - isDupl[key] = false - } else if !duplicated { - isDupl[key] = true + duplicated, seen := isDupl[key] + if !duplicated { + isDupl[key] = seen + + if seen { + duplicates++ + } } } - result := make(Slice, 0, len(collection)-len(isDupl)) + result := make(Slice, 0, duplicates) for i := range collection { key := iteratee(collection[i]) @@ -259,6 +294,52 @@ func FindDuplicatesBy[T any, U comparable, Slice ~[]T](collection Slice, iterate return result } +// FindDuplicatesByErr returns a slice with the first occurrence of each duplicated element in the collection. +// The order of result values is determined by the order they occur in the slice. It accepts `iteratee` which is +// invoked for each element in the slice to generate the criterion by which uniqueness is computed. +// If the iteratee returns an error, iteration stops immediately and the error is returned with a nil slice. +func FindDuplicatesByErr[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) (U, error)) (Slice, error) { + isDupl := make(map[U]bool, len(collection)) + + duplicates := 0 + + // First pass: identify duplicates + for i := range collection { + key, err := iteratee(collection[i]) + if err != nil { + var result Slice + return result, err + } + + duplicated, seen := isDupl[key] + if !duplicated { + isDupl[key] = seen + + if seen { + duplicates++ + } + } + } + + result := make(Slice, 0, duplicates) + + // Second pass: collect first occurrences of duplicates + for i := range collection { + key, err := iteratee(collection[i]) + if err != nil { + var result Slice + return result, err + } + + if duplicated := isDupl[key]; duplicated { + result = append(result, collection[i]) + isDupl[key] = false + } + } + + return result, nil +} + // Min search the minimum value of a collection. // Returns zero value when the collection is empty. // Play: https://go.dev/play/p/r6e-Z8JozS8 @@ -311,7 +392,7 @@ func MinIndex[T constraints.Ordered](collection []T) (T, int) { // MinBy search the minimum value of a collection using the given comparison function. // If several values of the collection are equal to the smallest value, returns the first such value. // Returns zero value when the collection is empty. -func MinBy[T any](collection []T, comparison func(a, b T) bool) T { +func MinBy[T any](collection []T, less func(a, b T) bool) T { var mIn T if len(collection) == 0 { @@ -323,7 +404,7 @@ func MinBy[T any](collection []T, comparison func(a, b T) bool) T { for i := 1; i < len(collection); i++ { item := collection[i] - if comparison(item, mIn) { + if less(item, mIn) { mIn = item } } @@ -331,10 +412,39 @@ func MinBy[T any](collection []T, comparison func(a, b T) bool) T { return mIn } +// MinByErr search the minimum value of a collection using the given comparison function. +// If several values of the collection are equal to the smallest value, returns the first such value. +// Returns zero value and nil error when the collection is empty. +// If the comparison function returns an error, iteration stops and the error is returned. +func MinByErr[T any](collection []T, less func(a, b T) (bool, error)) (T, error) { + var mIn T + + if len(collection) == 0 { + return mIn, nil + } + + mIn = collection[0] + + for i := 1; i < len(collection); i++ { + item := collection[i] + + isLess, err := less(item, mIn) + if err != nil { + var zero T + return zero, err + } + if isLess { + mIn = item + } + } + + return mIn, nil +} + // MinIndexBy search the minimum value of a collection using the given comparison function and the index of the minimum value. // If several values of the collection are equal to the smallest value, returns the first such value. // Returns (zero value, -1) when the collection is empty. -func MinIndexBy[T any](collection []T, comparison func(a, b T) bool) (T, int) { +func MinIndexBy[T any](collection []T, less func(a, b T) bool) (T, int) { var ( mIn T index int @@ -349,7 +459,7 @@ func MinIndexBy[T any](collection []T, comparison func(a, b T) bool) (T, int) { for i := 1; i < len(collection); i++ { item := collection[i] - if comparison(item, mIn) { + if less(item, mIn) { mIn = item index = i } @@ -358,6 +468,40 @@ func MinIndexBy[T any](collection []T, comparison func(a, b T) bool) (T, int) { return mIn, index } +// MinIndexByErr search the minimum value of a collection using the given comparison function and the index of the minimum value. +// If several values of the collection are equal to the smallest value, returns the first such value. +// Returns (zero value, -1) when the collection is empty. +// Comparison function can return an error to stop iteration immediately. +func MinIndexByErr[T any](collection []T, less func(a, b T) (bool, error)) (T, int, error) { + var ( + mIn T + index int + ) + + if len(collection) == 0 { + return mIn, -1, nil + } + + mIn = collection[0] + + for i := 1; i < len(collection); i++ { + item := collection[i] + + isLess, err := less(item, mIn) + if err != nil { + var zero T + return zero, -1, err + } + + if isLess { + mIn = item + index = i + } + } + + return mIn, index, nil +} + // Earliest search the minimum time.Time of a collection. // Returns zero value when the collection is empty. func Earliest(times ...time.Time) time.Time { @@ -404,6 +548,37 @@ func EarliestBy[T any](collection []T, iteratee func(item T) time.Time) T { return earliest } +// EarliestByErr search the minimum time.Time of a collection using the given iteratee function. +// Returns zero value and nil error when the collection is empty. +// If the iteratee returns an error, iteration stops and the error is returned. +func EarliestByErr[T any](collection []T, iteratee func(item T) (time.Time, error)) (T, error) { + var earliest T + + if len(collection) == 0 { + return earliest, nil + } + + earliestTime, err := iteratee(collection[0]) + if err != nil { + return earliest, err + } + earliest = collection[0] + + for i := 1; i < len(collection); i++ { + itemTime, err := iteratee(collection[i]) + if err != nil { + return earliest, err + } + + if itemTime.Before(earliestTime) { + earliest = collection[i] + earliestTime = itemTime + } + } + + return earliest, nil +} + // Max searches the maximum value of a collection. // Returns zero value when the collection is empty. // Play: https://go.dev/play/p/r6e-Z8JozS8 @@ -456,7 +631,12 @@ func MaxIndex[T constraints.Ordered](collection []T) (T, int) { // MaxBy search the maximum value of a collection using the given comparison function. // If several values of the collection are equal to the greatest value, returns the first such value. // Returns zero value when the collection is empty. -func MaxBy[T any](collection []T, comparison func(a, b T) bool) T { +// +// Note: the comparison function is inconsistent with most languages, since we use the opposite of the usual convention. +// See https://github.com/samber/lo/issues/129 +// +// Play: https://go.dev/play/p/JW1qu-ECwF7 +func MaxBy[T any](collection []T, greater func(a, b T) bool) T { var mAx T if len(collection) == 0 { @@ -468,7 +648,7 @@ func MaxBy[T any](collection []T, comparison func(a, b T) bool) T { for i := 1; i < len(collection); i++ { item := collection[i] - if comparison(item, mAx) { + if greater(item, mAx) { mAx = item } } @@ -476,10 +656,46 @@ func MaxBy[T any](collection []T, comparison func(a, b T) bool) T { return mAx } +// MaxByErr search the maximum value of a collection using the given comparison function. +// If several values of the collection are equal to the greatest value, returns the first such value. +// Returns zero value and nil error when the collection is empty. +// If the comparison function returns an error, iteration stops and the error is returned. +// +// Note: the comparison function is inconsistent with most languages, since we use the opposite of the usual convention. +// See https://github.com/samber/lo/issues/129 +func MaxByErr[T any](collection []T, greater func(a, b T) (bool, error)) (T, error) { + var mAx T + + if len(collection) == 0 { + return mAx, nil + } + + mAx = collection[0] + + for i := 1; i < len(collection); i++ { + item := collection[i] + + isGreater, err := greater(item, mAx) + if err != nil { + return mAx, err + } + if isGreater { + mAx = item + } + } + + return mAx, nil +} + // MaxIndexBy search the maximum value of a collection using the given comparison function and the index of the maximum value. // If several values of the collection are equal to the greatest value, returns the first such value. // Returns (zero value, -1) when the collection is empty. -func MaxIndexBy[T any](collection []T, comparison func(a, b T) bool) (T, int) { +// +// Note: the comparison function is inconsistent with most languages, since we use the opposite of the usual convention. +// See https://github.com/samber/lo/issues/129 +// +// Play: https://go.dev/play/p/uaUszc-c9QK +func MaxIndexBy[T any](collection []T, greater func(a, b T) bool) (T, int) { var ( mAx T index int @@ -494,7 +710,7 @@ func MaxIndexBy[T any](collection []T, comparison func(a, b T) bool) (T, int) { for i := 1; i < len(collection); i++ { item := collection[i] - if comparison(item, mAx) { + if greater(item, mAx) { mAx = item index = i } @@ -503,6 +719,42 @@ func MaxIndexBy[T any](collection []T, comparison func(a, b T) bool) (T, int) { return mAx, index } +// MaxIndexByErr search the maximum value of a collection using the given comparison function and the index of the maximum value. +// If several values of the collection are equal to the greatest value, returns the first such value. +// Returns (zero value, -1, nil) when the collection is empty. +// If the comparison function returns an error, iteration stops and the error is returned. +// +// Note: the comparison function is inconsistent with most languages, since we use the opposite of the usual convention. +// See https://github.com/samber/lo/issues/129 +func MaxIndexByErr[T any](collection []T, greater func(a, b T) (bool, error)) (T, int, error) { + var ( + mAx T + index int + ) + + if len(collection) == 0 { + return mAx, -1, nil + } + + mAx = collection[0] + + for i := 1; i < len(collection); i++ { + item := collection[i] + + isGreater, err := greater(item, mAx) + if err != nil { + var zero T + return zero, -1, err + } + if isGreater { + mAx = item + index = i + } + } + + return mAx, index, nil +} + // Latest search the maximum time.Time of a collection. // Returns zero value when the collection is empty. func Latest(times ...time.Time) time.Time { @@ -549,6 +801,37 @@ func LatestBy[T any](collection []T, iteratee func(item T) time.Time) T { return latest } +// LatestByErr search the maximum time.Time of a collection using the given iteratee function. +// Returns zero value and nil error when the collection is empty. +// If the iteratee returns an error, iteration stops and the error is returned. +func LatestByErr[T any](collection []T, iteratee func(item T) (time.Time, error)) (T, error) { + var latest T + + if len(collection) == 0 { + return latest, nil + } + + latestTime, err := iteratee(collection[0]) + if err != nil { + return latest, err + } + latest = collection[0] + + for i := 1; i < len(collection); i++ { + itemTime, err := iteratee(collection[i]) + if err != nil { + return latest, err + } + + if itemTime.After(latestTime) { + latest = collection[i] + latestTime = itemTime + } + } + + return latest, nil +} + // First returns the first element of a collection and check for availability of the first element. // Play: https://go.dev/play/p/ul45Z0y2EFO func First[T any](collection []T) (T, bool) { @@ -615,17 +898,22 @@ func LastOr[T any](collection []T, fallback T) T { // from the end is returned. An error is returned when nth is out of slice bounds. // Play: https://go.dev/play/p/sHoh88KWt6B func Nth[T any, N constraints.Integer](collection []T, nth N) (T, error) { + value, ok := sliceNth(collection, nth) + + return value, Validate(ok, "nth: %d out of slice bounds", nth) +} + +func sliceNth[T any, N constraints.Integer](collection []T, nth N) (T, bool) { n := int(nth) l := len(collection) if n >= l || -n > l { - var t T - return t, fmt.Errorf("nth: %d out of slice bounds", n) + return Empty[T](), false } if n >= 0 { - return collection[n], nil + return collection[n], true } - return collection[l+n], nil + return collection[l+n], true } // NthOr returns the element at index `nth` of collection. @@ -633,8 +921,8 @@ func Nth[T any, N constraints.Integer](collection []T, nth N) (T, error) { // If `nth` is out of slice bounds, it returns the fallback value instead of an error. // Play: https://go.dev/play/p/sHoh88KWt6B func NthOr[T any, N constraints.Integer](collection []T, nth N, fallback T) T { - value, err := Nth(collection, nth) - if err != nil { + value, ok := sliceNth(collection, nth) + if !ok { return fallback } return value @@ -645,11 +933,7 @@ func NthOr[T any, N constraints.Integer](collection []T, nth N, fallback T) T { // If `nth` is out of slice bounds, it returns the zero value (empty value) for that type. // Play: https://go.dev/play/p/sHoh88KWt6B func NthOrEmpty[T any, N constraints.Integer](collection []T, nth N) T { - value, err := Nth(collection, nth) - if err != nil { - var zeroValue T - return zeroValue - } + value, _ := sliceNth(collection, nth) return value } @@ -660,8 +944,7 @@ type randomIntGenerator func(n int) int // Sample returns a random item from collection. // Play: https://go.dev/play/p/vCcSJbh5s6l func Sample[T any](collection []T) T { - result := SampleBy(collection, xrand.IntN) - return result + return SampleBy(collection, xrand.IntN) } // SampleBy returns a random item from collection, using randomIntGenerator as the random index generator. @@ -677,29 +960,35 @@ func SampleBy[T any](collection []T, randomIntGenerator randomIntGenerator) T { // Samples returns N random unique items from collection. // Play: https://go.dev/play/p/vCcSJbh5s6l func Samples[T any, Slice ~[]T](collection Slice, count int) Slice { - results := SamplesBy(collection, count, xrand.IntN) - return results + return SamplesBy(collection, count, xrand.IntN) } // SamplesBy returns N random unique items from collection, using randomIntGenerator as the random index generator. // Play: https://go.dev/play/p/HDmKmMgq0XN func SamplesBy[T any, Slice ~[]T](collection Slice, count int, randomIntGenerator randomIntGenerator) Slice { + if count <= 0 { + return Slice{} + } + size := len(collection) - cOpy := append(Slice{}, collection...) + if size < count { + count = size + } - results := Slice{} + indexes := Range(size) + results := make(Slice, count) - for i := 0; i < size && i < count; i++ { - copyLength := size - i + for i := range results { + n := len(indexes) - index := randomIntGenerator(size - i) - results = append(results, cOpy[index]) + index := randomIntGenerator(n) + results[i] = collection[indexes[index]] - // Removes element. + // Removes index. // It is faster to swap with last element and remove it. - cOpy[index] = cOpy[copyLength-1] - cOpy = cOpy[:copyLength-1] + indexes[index] = indexes[n-1] + indexes = indexes[:n-1] } return results diff --git a/vendor/github.com/samber/lo/intersect.go b/vendor/github.com/samber/lo/intersect.go index 6c79f99..469cbbb 100644 --- a/vendor/github.com/samber/lo/intersect.go +++ b/vendor/github.com/samber/lo/intersect.go @@ -27,8 +27,14 @@ func ContainsBy[T any](collection []T, predicate func(item T) bool) bool { // Every returns true if all elements of a subset are contained in a collection or if the subset is empty. // Play: https://go.dev/play/p/W1EvyqY6t9j func Every[T comparable](collection, subset []T) bool { - for i := range subset { - if !Contains(collection, subset[i]) { + if len(subset) == 0 { + return true + } + + seen := Keyify(collection) + + for _, item := range subset { + if _, ok := seen[item]; !ok { return false } } @@ -52,8 +58,13 @@ func EveryBy[T any](collection []T, predicate func(item T) bool) bool { // If the subset is empty Some returns false. // Play: https://go.dev/play/p/Lj4ceFkeT9V func Some[T comparable](collection, subset []T) bool { - for i := range subset { - if Contains(collection, subset[i]) { + if len(subset) == 0 { + return false + } + + seen := Keyify(subset) + for i := range collection { + if _, ok := seen[collection[i]]; ok { return true } } @@ -77,8 +88,13 @@ func SomeBy[T any](collection []T, predicate func(item T) bool) bool { // None returns true if no element of a subset is contained in a collection or if the subset is empty. // Play: https://go.dev/play/p/fye7JsmxzPV func None[T comparable](collection, subset []T) bool { - for i := range subset { - if Contains(collection, subset[i]) { + if len(subset) == 0 { + return true + } + + seen := Keyify(subset) + for i := range collection { + if _, ok := seen[collection[i]]; ok { return false } } @@ -98,19 +114,88 @@ func NoneBy[T any](collection []T, predicate func(item T) bool) bool { return true } -// Intersect returns the intersection between two collections. +// Intersect returns the intersection between collections. // Play: https://go.dev/play/p/uuElL9X9e58 -func Intersect[T comparable, Slice ~[]T](list1, list2 Slice) Slice { - result := Slice{} - seen := map[T]struct{}{} +func Intersect[T comparable, Slice ~[]T](lists ...Slice) Slice { + if len(lists) == 0 { + return Slice{} + } - for i := range list1 { - seen[list1[i]] = struct{}{} + last := lists[len(lists)-1] + + seen := make(map[T]bool, len(last)) + + for _, item := range last { + seen[item] = false } - for i := range list2 { - if _, ok := seen[list2[i]]; ok { - result = append(result, list2[i]) + for i := len(lists) - 2; i > 0 && len(seen) != 0; i-- { + for _, item := range lists[i] { + if _, ok := seen[item]; ok { + seen[item] = true + } + } + + for k, v := range seen { + if v { + seen[k] = false + } else { + delete(seen, k) + } + } + } + + result := make(Slice, 0, len(seen)) + + for _, item := range lists[0] { + if _, ok := seen[item]; ok { + result = append(result, item) + delete(seen, item) + } + } + + return result +} + +// IntersectBy returns the intersection between two collections using a custom key selector function. +func IntersectBy[T any, K comparable, Slice ~[]T](transform func(T) K, lists ...Slice) Slice { + if len(lists) == 0 { + return Slice{} + } + + last := lists[len(lists)-1] + + seen := make(map[K]bool, len(last)) + + for _, item := range last { + k := transform(item) + seen[k] = false + } + + for i := len(lists) - 2; i > 0 && len(seen) != 0; i-- { + for _, item := range lists[i] { + k := transform(item) + if _, ok := seen[k]; ok { + seen[k] = true + } + } + + for k, v := range seen { + if v { + seen[k] = false + } else { + delete(seen, k) + } + } + } + + result := make(Slice, 0, len(seen)) + + for _, item := range lists[0] { + k := transform(item) + if _, ok := seen[k]; ok { + result = append(result, item) + delete(seen, k) } } @@ -125,16 +210,8 @@ func Difference[T comparable, Slice ~[]T](list1, list2 Slice) (Slice, Slice) { left := Slice{} right := Slice{} - seenLeft := map[T]struct{}{} - seenRight := map[T]struct{}{} - - for i := range list1 { - seenLeft[list1[i]] = struct{}{} - } - - for i := range list2 { - seenRight[list2[i]] = struct{}{} - } + seenLeft := Keyify(list1) + seenRight := Keyify(list2) for i := range list1 { if _, ok := seenRight[list1[i]]; !ok { @@ -179,10 +256,7 @@ func Union[T comparable, Slice ~[]T](lists ...Slice) Slice { // Without returns a slice excluding all given values. // Play: https://go.dev/play/p/5j30Ux8TaD0 func Without[T comparable, Slice ~[]T](collection Slice, exclude ...T) Slice { - excludeMap := make(map[T]struct{}, len(exclude)) - for i := range exclude { - excludeMap[exclude[i]] = struct{}{} - } + excludeMap := Keyify(exclude) result := make(Slice, 0, len(collection)) for i := range collection { @@ -197,10 +271,7 @@ func Without[T comparable, Slice ~[]T](collection Slice, exclude ...T) Slice { // Returns a new slice containing only the elements whose keys are not in the exclude list. // Play: https://go.dev/play/p/VgWJOF01NbJ func WithoutBy[T any, K comparable, Slice ~[]T](collection Slice, iteratee func(item T) K, exclude ...K) Slice { - excludeMap := make(map[K]struct{}, len(exclude)) - for _, e := range exclude { - excludeMap[e] = struct{}{} - } + excludeMap := Keyify(exclude) result := make(Slice, 0, len(collection)) for _, item := range collection { @@ -211,6 +282,24 @@ func WithoutBy[T any, K comparable, Slice ~[]T](collection Slice, iteratee func( return result } +// WithoutByErr filters a slice by excluding elements whose extracted keys match any in the exclude list. +// It returns the first error returned by the iteratee. +func WithoutByErr[T any, K comparable, Slice ~[]T](collection Slice, iteratee func(item T) (K, error), exclude ...K) (Slice, error) { + excludeMap := Keyify(exclude) + + result := make(Slice, 0, len(collection)) + for _, item := range collection { + key, err := iteratee(item) + if err != nil { + return nil, err + } + if _, ok := excludeMap[key]; !ok { + result = append(result, item) + } + } + return result, nil +} + // WithoutEmpty returns a slice excluding zero values. // // Deprecated: Use lo.Compact instead. @@ -220,15 +309,8 @@ func WithoutEmpty[T comparable, Slice ~[]T](collection Slice) Slice { // WithoutNth returns a slice excluding the nth value. // Play: https://go.dev/play/p/5g3F9R2H1xL -func WithoutNth[T comparable, Slice ~[]T](collection Slice, nths ...int) Slice { - length := len(collection) - - toRemove := make(map[int]struct{}, len(nths)) - for i := range nths { - if nths[i] >= 0 && nths[i] <= length-1 { - toRemove[nths[i]] = struct{}{} - } - } +func WithoutNth[T any, Slice ~[]T](collection Slice, nths ...int) Slice { + toRemove := Keyify(nths) result := make(Slice, 0, len(collection)) for i := range collection { diff --git a/vendor/github.com/samber/lo/map.go b/vendor/github.com/samber/lo/map.go index cd0e9d7..e5c90e6 100644 --- a/vendor/github.com/samber/lo/map.go +++ b/vendor/github.com/samber/lo/map.go @@ -112,6 +112,22 @@ func PickBy[K comparable, V any, Map ~map[K]V](in Map, predicate func(key K, val return r } +// PickByErr returns same map type filtered by given predicate. +// It returns the first error returned by the predicate. +func PickByErr[K comparable, V any, Map ~map[K]V](in Map, predicate func(key K, value V) (bool, error)) (Map, error) { + r := Map{} + for k, v := range in { + ok, err := predicate(k, v) + if err != nil { + return nil, err + } + if ok { + r[k] = v + } + } + return r, nil +} + // PickByKeys returns same map type filtered by given keys. // Play: https://go.dev/play/p/R1imbuci9qU func PickByKeys[K comparable, V any, Map ~map[K]V](in Map, keys []K) Map { @@ -128,8 +144,10 @@ func PickByKeys[K comparable, V any, Map ~map[K]V](in Map, keys []K) Map { // Play: https://go.dev/play/p/1zdzSvbfsJc func PickByValues[K, V comparable, Map ~map[K]V](in Map, values []V) Map { r := Map{} + + seen := Keyify(values) for k, v := range in { - if Contains(values, v) { + if _, ok := seen[v]; ok { r[k] = v } } @@ -148,6 +166,22 @@ func OmitBy[K comparable, V any, Map ~map[K]V](in Map, predicate func(key K, val return r } +// OmitByErr returns same map type filtered by given predicate. +// It returns the first error returned by the predicate. +func OmitByErr[K comparable, V any, Map ~map[K]V](in Map, predicate func(key K, value V) (bool, error)) (Map, error) { + r := Map{} + for k, v := range in { + ok, err := predicate(k, v) + if err != nil { + return nil, err + } + if !ok { + r[k] = v + } + } + return r, nil +} + // OmitByKeys returns same map type filtered by given keys. // Play: https://go.dev/play/p/t1QjCrs-ysk func OmitByKeys[K comparable, V any, Map ~map[K]V](in Map, keys []K) Map { @@ -165,11 +199,14 @@ func OmitByKeys[K comparable, V any, Map ~map[K]V](in Map, keys []K) Map { // Play: https://go.dev/play/p/9UYZi-hrs8j func OmitByValues[K, V comparable, Map ~map[K]V](in Map, values []V) Map { r := Map{} + + seen := Keyify(values) for k, v := range in { - if !Contains(values, v) { + if _, ok := seen[v]; !ok { r[k] = v } } + return r } @@ -259,12 +296,7 @@ func ChunkEntries[K comparable, V any](m map[K]V, size int) []map[K]V { return []map[K]V{} } - chunksNum := count / size - if count%size != 0 { - chunksNum++ - } - - result := make([]map[K]V, 0, chunksNum) + result := make([]map[K]V, 0, ((count-1)/size)+1) for k, v := range m { if len(result) == 0 || len(result[len(result)-1]) == size { @@ -289,6 +321,22 @@ func MapKeys[K comparable, V any, R comparable](in map[K]V, iteratee func(value return result } +// MapKeysErr manipulates map keys and transforms it to a map of another type. +// It returns the first error returned by the iteratee. +func MapKeysErr[K comparable, V any, R comparable](in map[K]V, iteratee func(value V, key K) (R, error)) (map[R]V, error) { + result := make(map[R]V, len(in)) + + for k, v := range in { + r, err := iteratee(v, k) + if err != nil { + return nil, err + } + result[r] = v + } + + return result, nil +} + // MapValues manipulates map values and transforms it to a map of another type. // Play: https://go.dev/play/p/T_8xAfvcf0W func MapValues[K comparable, V, R any](in map[K]V, iteratee func(value V, key K) R) map[K]R { @@ -301,6 +349,22 @@ func MapValues[K comparable, V, R any](in map[K]V, iteratee func(value V, key K) return result } +// MapValuesErr manipulates map values and transforms it to a map of another type. +// It returns the first error returned by the iteratee. +func MapValuesErr[K comparable, V, R any](in map[K]V, iteratee func(value V, key K) (R, error)) (map[K]R, error) { + result := make(map[K]R, len(in)) + + for k, v := range in { + r, err := iteratee(v, k) + if err != nil { + return nil, err + } + result[k] = r + } + + return result, nil +} + // MapEntries manipulates map entries and transforms it to a map of another type. // Play: https://go.dev/play/p/VuvNQzxKimT func MapEntries[K1 comparable, V1 any, K2 comparable, V2 any](in map[K1]V1, iteratee func(key K1, value V1) (K2, V2)) map[K2]V2 { @@ -314,6 +378,22 @@ func MapEntries[K1 comparable, V1 any, K2 comparable, V2 any](in map[K1]V1, iter return result } +// MapEntriesErr manipulates map entries and transforms it to a map of another type. +// It returns the first error returned by the iteratee. +func MapEntriesErr[K1 comparable, V1 any, K2 comparable, V2 any](in map[K1]V1, iteratee func(key K1, value V1) (K2, V2, error)) (map[K2]V2, error) { + result := make(map[K2]V2, len(in)) + + for k1 := range in { + k2, v2, err := iteratee(k1, in[k1]) + if err != nil { + return nil, err + } + result[k2] = v2 + } + + return result, nil +} + // MapToSlice transforms a map into a slice based on specified iteratee. // Play: https://go.dev/play/p/ZuiCZpDt6LD func MapToSlice[K comparable, V, R any](in map[K]V, iteratee func(key K, value V) R) []R { @@ -326,6 +406,22 @@ func MapToSlice[K comparable, V, R any](in map[K]V, iteratee func(key K, value V return result } +// MapToSliceErr transforms a map into a slice based on specified iteratee. +// It returns the first error returned by the iteratee. +func MapToSliceErr[K comparable, V, R any](in map[K]V, iteratee func(key K, value V) (R, error)) ([]R, error) { + result := make([]R, 0, len(in)) + + for k, v := range in { + r, err := iteratee(k, v) + if err != nil { + return nil, err + } + result = append(result, r) + } + + return result, nil +} + // FilterMapToSlice transforms a map into a slice based on specified iteratee. // The iteratee returns a value and a boolean. If the boolean is true, the value is added to the result slice. // If the boolean is false, the value is not added to the result slice. @@ -343,6 +439,27 @@ func FilterMapToSlice[K comparable, V, R any](in map[K]V, iteratee func(key K, v return result } +// FilterMapToSliceErr transforms a map into a slice based on specified iteratee. +// The iteratee returns a value, a boolean, and an error. If the boolean is true, the value is added to the result slice. +// If the boolean is false, the value is not added to the result slice. +// If an error is returned, iteration stops immediately and returns the error. +// The order of the keys in the input map is not specified and the order of the keys in the output slice is not guaranteed. +func FilterMapToSliceErr[K comparable, V, R any](in map[K]V, iteratee func(key K, value V) (R, bool, error)) ([]R, error) { + result := make([]R, 0, len(in)) + + for k, v := range in { + r, ok, err := iteratee(k, v) + if err != nil { + return nil, err + } + if ok { + result = append(result, r) + } + } + + return result, nil +} + // FilterKeys transforms a map into a slice based on predicate returns true for specific elements. // It is a mix of lo.Filter() and lo.Keys(). // Play: https://go.dev/play/p/OFlKXlPrBAe @@ -372,3 +489,45 @@ func FilterValues[K comparable, V any](in map[K]V, predicate func(key K, value V return result } + +// FilterKeysErr transforms a map into a slice of keys based on predicate that can return an error. +// It is a mix of lo.Filter() and lo.Keys() with error handling. +// If the predicate returns true, the key is added to the result slice. +// If the predicate returns an error, iteration stops immediately and returns the error. +// The order of the keys in the input map is not specified. +func FilterKeysErr[K comparable, V any](in map[K]V, predicate func(key K, value V) (bool, error)) ([]K, error) { + result := make([]K, 0) + + for k, v := range in { + ok, err := predicate(k, v) + if err != nil { + return nil, err + } + if ok { + result = append(result, k) + } + } + + return result, nil +} + +// FilterValuesErr transforms a map into a slice of values based on predicate that can return an error. +// It is a mix of lo.Filter() and lo.Values() with error handling. +// If the predicate returns true, the value is added to the result slice. +// If the predicate returns an error, iteration stops immediately and returns the error. +// The order of the keys in the input map is not specified. +func FilterValuesErr[K comparable, V any](in map[K]V, predicate func(key K, value V) (bool, error)) ([]V, error) { + result := make([]V, 0) + + for k, v := range in { + ok, err := predicate(k, v) + if err != nil { + return nil, err + } + if ok { + result = append(result, v) + } + } + + return result, nil +} diff --git a/vendor/github.com/samber/lo/math.go b/vendor/github.com/samber/lo/math.go index 9342b59..f08f227 100644 --- a/vendor/github.com/samber/lo/math.go +++ b/vendor/github.com/samber/lo/math.go @@ -1,15 +1,17 @@ package lo import ( + "math" + "github.com/samber/lo/internal/constraints" ) // Range creates a slice of numbers (positive and/or negative) with given length. // Play: https://go.dev/play/p/0r6VimXAi9H func Range(elementNum int) []int { - length := If(elementNum < 0, -elementNum).Else(elementNum) + step := Ternary(elementNum < 0, -1, 1) + length := elementNum * step result := make([]int, length) - step := If(elementNum < 0, -1).Else(1) for i, j := 0, 0; i < length; i, j = i+1, j+step { result[i] = j } @@ -19,9 +21,9 @@ func Range(elementNum int) []int { // RangeFrom creates a slice of numbers from start with specified length. // Play: https://go.dev/play/p/0r6VimXAi9H func RangeFrom[T constraints.Integer | constraints.Float](start T, elementNum int) []T { - length := If(elementNum < 0, -elementNum).Else(elementNum) + step := Ternary(elementNum < 0, -1, 1) + length := elementNum * step result := make([]T, length) - step := If(elementNum < 0, -1).Else(1) for i, j := 0, start; i < length; i, j = i+1, j+T(step) { result[i] = j } @@ -32,22 +34,32 @@ func RangeFrom[T constraints.Integer | constraints.Float](start T, elementNum in // step set to zero will return an empty slice. // Play: https://go.dev/play/p/0r6VimXAi9H func RangeWithSteps[T constraints.Integer | constraints.Float](start, end, step T) []T { - result := []T{} if start == end || step == 0 { - return result + return []T{} + } + + capacity := func(count, delta T) int { + // Use math.Ceil instead of (count-1)/delta+1 because integer division + // fails for floats (e.g., 5.5/2.5=2.2 β†’ ceil=3, not 2). + return int(math.Ceil(float64(count) / float64(delta))) } + if start < end { if step < 0 { - return result + return []T{} } + + result := make([]T, 0, capacity(end-start, step)) for i := start; i < end; i += step { result = append(result, i) } return result } if step > 0 { - return result + return []T{} } + + result := make([]T, 0, capacity(start-end, -step)) for i := start; i > end; i += step { result = append(result, i) } @@ -85,17 +97,24 @@ func SumBy[T any, R constraints.Float | constraints.Integer | constraints.Comple return sum } +// SumByErr summarizes the values in a collection using the given return value from the iteration function. +// If the iteratee returns an error, iteration stops and the error is returned. +// If collection is empty 0 and nil error are returned. +func SumByErr[T any, R constraints.Float | constraints.Integer | constraints.Complex](collection []T, iteratee func(item T) (R, error)) (R, error) { + var sum R + for i := range collection { + v, err := iteratee(collection[i]) + if err != nil { + return sum, err + } + sum += v + } + return sum, nil +} + // Product gets the product of the values in a collection. If collection is empty 1 is returned. // Play: https://go.dev/play/p/2_kjM_smtAH func Product[T constraints.Float | constraints.Integer | constraints.Complex](collection []T) T { - if collection == nil { - return 1 - } - - if len(collection) == 0 { - return 1 - } - var product T = 1 for i := range collection { product *= collection[i] @@ -106,14 +125,6 @@ func Product[T constraints.Float | constraints.Integer | constraints.Complex](co // ProductBy summarizes the values in a collection using the given return value from the iteration function. If collection is empty 1 is returned. // Play: https://go.dev/play/p/wadzrWr9Aer func ProductBy[T any, R constraints.Float | constraints.Integer | constraints.Complex](collection []T, iteratee func(item T) R) R { - if collection == nil { - return 1 - } - - if len(collection) == 0 { - return 1 - } - var product R = 1 for i := range collection { product *= iteratee(collection[i]) @@ -121,6 +132,21 @@ func ProductBy[T any, R constraints.Float | constraints.Integer | constraints.Co return product } +// ProductByErr summarizes the values in a collection using the given return value from the iteration function. +// If the iteratee returns an error, iteration stops and the error is returned. +// If collection is empty 1 and nil error are returned. +func ProductByErr[T any, R constraints.Float | constraints.Integer | constraints.Complex](collection []T, iteratee func(item T) (R, error)) (R, error) { + var product R = 1 + for i := range collection { + v, err := iteratee(collection[i]) + if err != nil { + return product, err + } + product *= v + } + return product, nil +} + // Mean calculates the mean of a collection of numbers. // Play: https://go.dev/play/p/tPURSuteUsP func Mean[T constraints.Float | constraints.Integer](collection []T) T { @@ -143,6 +169,21 @@ func MeanBy[T any, R constraints.Float | constraints.Integer](collection []T, it return sum / length } +// MeanByErr calculates the mean of a collection of numbers using the given return value from the iteration function. +// If the iteratee returns an error, iteration stops and the error is returned. +// If collection is empty 0 and nil error are returned. +func MeanByErr[T any, R constraints.Float | constraints.Integer](collection []T, iteratee func(item T) (R, error)) (R, error) { + length := R(len(collection)) + if length == 0 { + return 0, nil + } + sum, err := SumByErr(collection, iteratee) + if err != nil { + return 0, err + } + return sum / length, nil +} + // Mode returns the mode (most frequent value) of a collection. // If multiple values have the same highest frequency, then multiple values are returned. // If the collection is empty, then the zero value of T is returned. diff --git a/vendor/github.com/samber/lo/mutable/slice.go b/vendor/github.com/samber/lo/mutable/slice.go index f3412c1..4b8e916 100644 --- a/vendor/github.com/samber/lo/mutable/slice.go +++ b/vendor/github.com/samber/lo/mutable/slice.go @@ -10,9 +10,9 @@ import "github.com/samber/lo/internal/xrand" // Play: https://go.dev/play/p/0jY3Z0B7O_5 func Filter[T any, Slice ~[]T](collection Slice, predicate func(item T) bool) Slice { j := 0 - for _, item := range collection { - if predicate(item) { - collection[j] = item + for i := range collection { + if predicate(collection[i]) { + collection[j] = collection[i] j++ } } @@ -26,9 +26,9 @@ func Filter[T any, Slice ~[]T](collection Slice, predicate func(item T) bool) Sl // Note that the order of elements in the original slice is preserved in the output. func FilterI[T any, Slice ~[]T](collection Slice, predicate func(item T, index int) bool) Slice { j := 0 - for i, item := range collection { - if predicate(item, i) { - collection[j] = item + for i := range collection { + if predicate(collection[i], i) { + collection[j] = collection[i] j++ } } @@ -38,17 +38,17 @@ func FilterI[T any, Slice ~[]T](collection Slice, predicate func(item T, index i // Map is a generic function that modifies the input slice in-place to contain the result of applying the provided // function to each element of the slice. The function returns the modified slice, which has the same length as the original. // Play: https://go.dev/play/p/0jY3Z0B7O_5 -func Map[T any, Slice ~[]T](collection Slice, fn func(item T) T) { +func Map[T any, Slice ~[]T](collection Slice, transform func(item T) T) { for i := range collection { - collection[i] = fn(collection[i]) + collection[i] = transform(collection[i]) } } // MapI is a generic function that modifies the input slice in-place to contain the result of applying the provided // function to each element of the slice. The function returns the modified slice, which has the same length as the original. -func MapI[T any, Slice ~[]T](collection Slice, fn func(item T, index int) T) { +func MapI[T any, Slice ~[]T](collection Slice, transform func(item T, index int) T) { for i := range collection { - collection[i] = fn(collection[i], i) + collection[i] = transform(collection[i], i) } } diff --git a/vendor/github.com/samber/lo/retry.go b/vendor/github.com/samber/lo/retry.go index e8b98c4..a4779f6 100644 --- a/vendor/github.com/samber/lo/retry.go +++ b/vendor/github.com/samber/lo/retry.go @@ -315,10 +315,6 @@ func (th *throttleBy[T]) throttledFunc(key T) { th.mu.Lock() defer th.mu.Unlock() - if _, ok := th.count[key]; !ok { - th.count[key] = 0 - } - if th.count[key] < th.countLimit { th.count[key]++ @@ -361,7 +357,7 @@ func NewThrottleWithCount(interval time.Duration, count int, f ...func()) (throt } }) - throttleFn, reset := NewThrottleByWithCount[struct{}](interval, count, callbacks...) + throttleFn, reset := NewThrottleByWithCount(interval, count, callbacks...) return func() { throttleFn(struct{}{}) }, reset @@ -371,7 +367,7 @@ func NewThrottleWithCount(interval time.Duration, count int, f ...func()) (throt // This returns 2 functions, First one is throttled function and Second one is a function to reset interval. // Play: https://go.dev/play/p/0Wv6oX7dHdC func NewThrottleBy[T comparable](interval time.Duration, f ...func(key T)) (throttle func(key T), reset func()) { - return NewThrottleByWithCount[T](interval, 1, f...) + return NewThrottleByWithCount(interval, 1, f...) } // NewThrottleByWithCount is NewThrottleBy with count limit, throttled function will be invoked count times in every interval. diff --git a/vendor/github.com/samber/lo/slice.go b/vendor/github.com/samber/lo/slice.go index d35d276..a53da54 100644 --- a/vendor/github.com/samber/lo/slice.go +++ b/vendor/github.com/samber/lo/slice.go @@ -21,32 +21,66 @@ func Filter[T any, Slice ~[]T](collection Slice, predicate func(item T, index in return result } +// FilterErr iterates over elements of collection, returning a slice of all elements predicate returns true for. +// If the predicate returns an error, iteration stops immediately and returns the error. +// Play: https://go.dev/play/p/Apjg3WeSi7K +func FilterErr[T any, Slice ~[]T](collection Slice, predicate func(item T, index int) (bool, error)) (Slice, error) { + result := make(Slice, 0, len(collection)) + + for i := range collection { + ok, err := predicate(collection[i], i) + if err != nil { + return nil, err + } + if ok { + result = append(result, collection[i]) + } + } + + return result, nil +} + // Map manipulates a slice and transforms it to a slice of another type. // Play: https://go.dev/play/p/OkPcYAhBo0D -func Map[T, R any](collection []T, iteratee func(item T, index int) R) []R { +func Map[T, R any](collection []T, transform func(item T, index int) R) []R { result := make([]R, len(collection)) for i := range collection { - result[i] = iteratee(collection[i], i) + result[i] = transform(collection[i], i) } return result } +// MapErr manipulates a slice and transforms it to a slice of another type. +// It returns the first error returned by the transform function. +func MapErr[T, R any](collection []T, transform func(item T, index int) (R, error)) ([]R, error) { + result := make([]R, len(collection)) + + for i := range collection { + r, err := transform(collection[i], i) + if err != nil { + return nil, err + } + result[i] = r + } + + return result, nil +} + // UniqMap manipulates a slice and transforms it to a slice of another type with unique values. // Play: https://go.dev/play/p/fygzLBhvUdB -func UniqMap[T any, R comparable](collection []T, iteratee func(item T, index int) R) []R { - result := make([]R, 0, len(collection)) +func UniqMap[T any, R comparable](collection []T, transform func(item T, index int) R) []R { seen := make(map[R]struct{}, len(collection)) for i := range collection { - r := iteratee(collection[i], i) + r := transform(collection[i], i) if _, ok := seen[r]; !ok { - result = append(result, r) seen[r] = struct{}{} } } - return result + + return Keys(seen) } // FilterMap returns a slice obtained after both filtering and mapping using the given callback function. @@ -71,16 +105,34 @@ func FilterMap[T, R any](collection []T, callback func(item T, index int) (R, bo // The transform function can either return a slice or a `nil`, and in the `nil` case // no value is added to the final slice. // Play: https://go.dev/play/p/pFCF5WVB225 -func FlatMap[T, R any](collection []T, iteratee func(item T, index int) []R) []R { +func FlatMap[T, R any](collection []T, transform func(item T, index int) []R) []R { result := make([]R, 0, len(collection)) for i := range collection { - result = append(result, iteratee(collection[i], i)...) + result = append(result, transform(collection[i], i)...) } return result } +// FlatMapErr manipulates a slice and transforms and flattens it to a slice of another type. +// The transform function can either return a slice or a `nil`, and in the `nil` case +// no value is added to the final slice. +// It returns the first error returned by the transform function. +func FlatMapErr[T, R any](collection []T, transform func(item T, index int) ([]R, error)) ([]R, error) { + result := make([]R, 0, len(collection)) + + for i := range collection { + r, err := transform(collection[i], i) + if err != nil { + return nil, err + } + result = append(result, r...) + } + + return result, nil +} + // Reduce reduces collection to a value which is the accumulated result of running each element in collection // through accumulator, where each successive invocation is supplied the return value of the previous. // Play: https://go.dev/play/p/CgHYNUpOd1I @@ -92,6 +144,22 @@ func Reduce[T, R any](collection []T, accumulator func(agg R, item T, index int) return initial } +// ReduceErr reduces collection to a value which is the accumulated result of running each element in collection +// through accumulator, where each successive invocation is supplied the return value of the previous. +// It returns the first error returned by the accumulator function. +func ReduceErr[T, R any](collection []T, accumulator func(agg R, item T, index int) (R, error), initial R) (R, error) { + for i := range collection { + result, err := accumulator(initial, collection[i], i) + if err != nil { + var zero R + return zero, err + } + initial = result + } + + return initial, nil +} + // ReduceRight is like Reduce except that it iterates over elements of collection from right to left. // Play: https://go.dev/play/p/Fq3W70l7wXF func ReduceRight[T, R any](collection []T, accumulator func(agg R, item T, index int) R, initial R) R { @@ -102,20 +170,35 @@ func ReduceRight[T, R any](collection []T, accumulator func(agg R, item T, index return initial } -// ForEach iterates over elements of collection and invokes iteratee for each element. +// ReduceRightErr is like ReduceRight except that the accumulator function can return an error. +// It returns the first error returned by the accumulator function. +func ReduceRightErr[T, R any](collection []T, accumulator func(agg R, item T, index int) (R, error), initial R) (R, error) { + for i := len(collection) - 1; i >= 0; i-- { + result, err := accumulator(initial, collection[i], i) + if err != nil { + var zero R + return zero, err + } + initial = result + } + + return initial, nil +} + +// ForEach iterates over elements of collection and invokes callback for each element. // Play: https://go.dev/play/p/oofyiUPRf8t -func ForEach[T any](collection []T, iteratee func(item T, index int)) { +func ForEach[T any](collection []T, callback func(item T, index int)) { for i := range collection { - iteratee(collection[i], i) + callback(collection[i], i) } } -// ForEachWhile iterates over elements of collection and invokes iteratee for each element +// ForEachWhile iterates over elements of collection and invokes predicate for each element // collection return value decide to continue or break, like do while(). // Play: https://go.dev/play/p/QnLGt35tnow -func ForEachWhile[T any](collection []T, iteratee func(item T, index int) bool) { +func ForEachWhile[T any](collection []T, predicate func(item T, index int) bool) { for i := range collection { - if !iteratee(collection[i], i) { + if !predicate(collection[i], i) { break } } @@ -175,6 +258,31 @@ func UniqBy[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(ite return result } +// UniqByErr returns a duplicate-free version of a slice, in which only the first occurrence of each element is kept. +// The order of result values is determined by the order they occur in the slice. It accepts `iteratee` which is +// invoked for each element in the slice to generate the criterion by which uniqueness is computed. +// It returns the first error returned by the iteratee function. +func UniqByErr[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) (U, error)) (Slice, error) { + result := make(Slice, 0, len(collection)) + seen := make(map[U]struct{}, len(collection)) + + for i := range collection { + key, err := iteratee(collection[i]) + if err != nil { + return nil, err + } + + if _, ok := seen[key]; ok { + continue + } + + seen[key] = struct{}{} + result = append(result, collection[i]) + } + + return result, nil +} + // GroupBy returns an object composed of keys generated from the results of running each element of collection through iteratee. // Play: https://go.dev/play/p/XnQBd_v6brd func GroupBy[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) U) map[U]Slice { @@ -189,13 +297,30 @@ func GroupBy[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(it return result } -// GroupByMap returns an object composed of keys generated from the results of running each element of collection through iteratee. +// GroupByErr returns an object composed of keys generated from the results of running each element of collection through iteratee. +// It returns the first error returned by the iteratee function. +func GroupByErr[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) (U, error)) (map[U]Slice, error) { + result := map[U]Slice{} + + for i := range collection { + key, err := iteratee(collection[i]) + if err != nil { + return nil, err + } + + result[key] = append(result[key], collection[i]) + } + + return result, nil +} + +// GroupByMap returns an object composed of keys generated from the results of running each element of collection through transform. // Play: https://go.dev/play/p/iMeruQ3_W80 -func GroupByMap[T any, K comparable, V any](collection []T, iteratee func(item T) (K, V)) map[K][]V { +func GroupByMap[T any, K comparable, V any](collection []T, transform func(item T) (K, V)) map[K][]V { result := map[K][]V{} for i := range collection { - k, v := iteratee(collection[i]) + k, v := transform(collection[i]) result[k] = append(result[k], v) } @@ -203,6 +328,23 @@ func GroupByMap[T any, K comparable, V any](collection []T, iteratee func(item T return result } +// GroupByMapErr returns an object composed of keys generated from the results of running each element of collection through transform. +// It returns the first error returned by the transform function. +func GroupByMapErr[T any, K comparable, V any](collection []T, transform func(item T) (K, V, error)) (map[K][]V, error) { + result := map[K][]V{} + + for i := range collection { + k, v, err := transform(collection[i]) + if err != nil { + return nil, err + } + + result[k] = append(result[k], v) + } + + return result, nil +} + // Chunk returns a slice of elements split into groups of length size. If the slice can't be split evenly, // the final chunk will be the remaining elements. // Play: https://go.dev/play/p/kEMkFbdu85g @@ -245,13 +387,12 @@ func PartitionBy[T any, K comparable, Slice ~[]T](collection Slice, iteratee fun key := iteratee(collection[i]) resultIndex, ok := seen[key] - if !ok { - resultIndex = len(result) - seen[key] = resultIndex - result = append(result, Slice{}) + if ok { + result[resultIndex] = append(result[resultIndex], collection[i]) + } else { + seen[key] = len(result) + result = append(result, Slice{collection[i]}) } - - result[resultIndex] = append(result[resultIndex], collection[i]) } return result @@ -261,7 +402,34 @@ func PartitionBy[T any, K comparable, Slice ~[]T](collection Slice, iteratee fun // return Values[K, []T](groups) } +// PartitionByErr partitions a slice into groups determined by a key computed from each element. +// The order of the partitions is determined by the order they occur in collection. The grouping +// is generated from the results of running each element of collection through iteratee. +// It returns the first error returned by the iteratee function. +func PartitionByErr[T any, K comparable, Slice ~[]T](collection Slice, iteratee func(item T) (K, error)) ([]Slice, error) { + result := []Slice{} + seen := map[K]int{} + + for i := range collection { + key, err := iteratee(collection[i]) + if err != nil { + return nil, err + } + + resultIndex, ok := seen[key] + if ok { + result[resultIndex] = append(result[resultIndex], collection[i]) + } else { + seen[key] = len(result) + result = append(result, Slice{collection[i]}) + } + } + + return result, nil +} + // Flatten returns a slice a single level deep. +// See also: Concat // Play: https://go.dev/play/p/rbp9ORaMpjw func Flatten[T any, Slice ~[]T](collection []Slice) Slice { totalLen := 0 @@ -277,6 +445,50 @@ func Flatten[T any, Slice ~[]T](collection []Slice) Slice { return result } +// Concat returns a new slice containing all the elements in collections. Concat conserves the order of the elements. +// See also: Flatten, Union. +func Concat[T any, Slice ~[]T](collections ...Slice) Slice { + return Flatten(collections) +} + +// Window creates a slice of sliding windows of a given size. +// Each window overlaps with the previous one by size-1 elements. +// This is equivalent to Sliding(collection, size, 1). +func Window[T any, Slice ~[]T](collection Slice, size int) []Slice { + if size <= 0 { + panic("lo.Window: size must be greater than 0") + } + return Sliding(collection, size, 1) +} + +// Sliding creates a slice of sliding windows of a given size with a given step. +// If step is equal to size, windows don't overlap (similar to Chunk). +// If step is less than size, windows overlap. +func Sliding[T any, Slice ~[]T](collection Slice, size, step int) []Slice { + if size <= 0 { + panic("lo.Sliding: size must be greater than 0") + } + + if step <= 0 { + panic("lo.Sliding: step must be greater than 0") + } + + n := len(collection) - size + if n < 0 { + return []Slice{} + } + + result := make([]Slice, 0, n/step+1) + + for i := 0; i <= n; i += step { + window := make(Slice, size) + copy(window, collection[i:i+size]) + result = append(result, window) + } + + return result +} + // Interleave round-robin alternating input slices and sequentially appending value at index into result. // Play: https://go.dev/play/p/-RJkTLQEDVt func Interleave[T any, Slice ~[]T](collections ...Slice) Slice { @@ -359,16 +571,32 @@ func Repeat[T Clonable[T]](count int, initial T) []T { // RepeatBy builds a slice with values returned by N calls of callback. // Play: https://go.dev/play/p/ozZLCtX_hNU -func RepeatBy[T any](count int, predicate func(index int) T) []T { +func RepeatBy[T any](count int, callback func(index int) T) []T { result := make([]T, 0, count) for i := 0; i < count; i++ { - result = append(result, predicate(i)) + result = append(result, callback(i)) } return result } +// RepeatByErr builds a slice with values returned by N calls of callback. +// It returns the first error returned by the callback function. +func RepeatByErr[T any](count int, callback func(index int) (T, error)) ([]T, error) { + result := make([]T, 0, count) + + for i := 0; i < count; i++ { + r, err := callback(i) + if err != nil { + return nil, err + } + result = append(result, r) + } + + return result, nil +} + // KeyBy transforms a slice or a slice of structs to a map based on a pivot callback. // Play: https://go.dev/play/p/ccUiUL_Lnel func KeyBy[K comparable, V any](collection []V, iteratee func(item V) K) map[K]V { @@ -382,6 +610,23 @@ func KeyBy[K comparable, V any](collection []V, iteratee func(item V) K) map[K]V return result } +// KeyByErr transforms a slice or a slice of structs to a map based on a pivot callback to compute keys. +// Iteratee can return an error to stop iteration immediately. +// Play: https://go.dev/play/p/ccUiUL_Lnel +func KeyByErr[K comparable, V any](collection []V, iteratee func(item V) (K, error)) (map[K]V, error) { + result := make(map[K]V, len(collection)) + + for i := range collection { + k, err := iteratee(collection[i]) + if err != nil { + return nil, err + } + result[k] = collection[i] + } + + return result, nil +} + // Associate returns a map containing key-value pairs provided by transform function applied to elements of the given slice. // If any of two pairs have the same key the last one gets added to the map. // The order of keys in returned map is not specified and is not guaranteed to be the same from the original slice. @@ -525,33 +770,108 @@ func DropRightWhile[T any, Slice ~[]T](collection Slice, predicate func(item T) return append(result, collection[:i+1]...) } +// Take takes the first n elements from a slice. +func Take[T any, Slice ~[]T](collection Slice, n int) Slice { + if n < 0 { + panic("lo.Take: n must not be negative") + } + + if n == 0 { + return make(Slice, 0) + } + + size := len(collection) + if size == 0 { + return make(Slice, 0) + } + + if n >= size { + result := make(Slice, size) + copy(result, collection) + return result + } + + result := make(Slice, n) + copy(result, collection) + return result +} + +// TakeWhile takes elements from the beginning of a slice while the predicate returns true. +func TakeWhile[T any, Slice ~[]T](collection Slice, predicate func(item T) bool) Slice { + i := 0 + for ; i < len(collection); i++ { + if !predicate(collection[i]) { + break + } + } + + result := make(Slice, i) + copy(result, collection[:i]) + return result +} + // DropByIndex drops elements from a slice by the index. // A negative index will drop elements from the end of the slice. // Play: https://go.dev/play/p/bPIH4npZRxS func DropByIndex[T any, Slice ~[]T](collection Slice, indexes ...int) Slice { initialSize := len(collection) if initialSize == 0 { - return make(Slice, 0) + return Slice{} } - for i := range indexes { - if indexes[i] < 0 { - indexes[i] = initialSize + indexes[i] + // do not change the input + indexes = append(make([]int, 0, len(indexes)), indexes...) + + for i, index := range indexes { + if index < 0 { + indexes[i] += initialSize } } - indexes = Uniq(indexes) sort.Ints(indexes) - result := make(Slice, 0, initialSize) - result = append(result, collection...) + prev := -1 + indexes = mutable.Filter(indexes, func(index int) bool { + ok := index != prev && // uniq + uint(index) < uint(initialSize) // in range - for i := range indexes { - if indexes[i]-i < 0 || indexes[i]-i >= initialSize-i { - continue - } + prev = index + return ok + }) + + result := make(Slice, 0, initialSize-len(indexes)) + + i := 0 + for _, index := range indexes { + result = append(result, collection[i:index]...) + i = index + 1 + } + + return append(result, collection[i:]...) +} + +// TakeFilter filters elements and takes the first n elements that match the predicate. +// Equivalent to calling Take(Filter(...)), but more efficient as it stops after finding n matches. +func TakeFilter[T any, Slice ~[]T](collection Slice, n int, predicate func(item T, index int) bool) Slice { + if n < 0 { + panic("lo.TakeFilter: n must not be negative") + } - result = append(result[:indexes[i]-i], result[indexes[i]-i+1:]...) + if n == 0 { + return make(Slice, 0) + } + + result := make(Slice, 0, n) + count := 0 + + for i := range collection { + if predicate(collection[i], i) { + result = append(result, collection[i]) + count++ + if count >= n { + break + } + } } return result @@ -571,6 +891,25 @@ func Reject[T any, Slice ~[]T](collection Slice, predicate func(item T, index in return result } +// RejectErr is the opposite of FilterErr, this method returns the elements of collection that predicate does not return true for. +// If the predicate returns an error, iteration stops immediately and returns the error. +// Play: https://go.dev/play/p/pFCF5WVB225 +func RejectErr[T any, Slice ~[]T](collection Slice, predicate func(item T, index int) (bool, error)) (Slice, error) { + result := Slice{} + + for i := range collection { + match, err := predicate(collection[i], i) + if err != nil { + return nil, err + } + if !match { + result = append(result, collection[i]) + } + } + + return result, nil +} + // RejectMap is the opposite of FilterMap, this method returns a slice obtained after both filtering and mapping using the given callback function. // The callback function should return two values: // - the result of the mapping operation and @@ -635,6 +974,24 @@ func CountBy[T any](collection []T, predicate func(item T) bool) int { return count } +// CountByErr counts the number of elements in the collection for which predicate is true. +// It returns the first error returned by the predicate. +func CountByErr[T any](collection []T, predicate func(item T) (bool, error)) (int, error) { + var count int + + for i := range collection { + ok, err := predicate(collection[i]) + if err != nil { + return 0, err + } + if ok { + count++ + } + } + + return count, nil +} + // CountValues counts the number of each element in the collection. // Play: https://go.dev/play/p/-p-PyLT4dfy func CountValues[T comparable](collection []T) map[T]int { @@ -647,14 +1004,14 @@ func CountValues[T comparable](collection []T) map[T]int { return result } -// CountValuesBy counts the number of each element returned from mapper function. +// CountValuesBy counts the number of each element returned from transform function. // Is equivalent to chaining lo.Map and lo.CountValues. // Play: https://go.dev/play/p/2U0dG1SnOmS -func CountValuesBy[T any, U comparable](collection []T, mapper func(item T) U) map[U]int { +func CountValuesBy[T any, U comparable](collection []T, transform func(item T) U) map[U]int { result := make(map[U]int) for i := range collection { - result[mapper(collection[i])]++ + result[transform(collection[i])]++ } return result @@ -683,27 +1040,24 @@ func Subset[T any, Slice ~[]T](collection Slice, offset int, length uint) Slice return collection[offset : offset+int(length)] } -// Slice returns a copy of a slice from `start` up to, but not including `end`. Like `slice[start:end]`, but does not panic on overflow. +// Slice returns a slice from `start` up to, but not including `end`. Like `slice[start:end]`, but does not panic on overflow. // Play: https://go.dev/play/p/8XWYhfMMA1h func Slice[T any, Slice ~[]T](collection Slice, start, end int) Slice { - size := len(collection) - if start >= end { return Slice{} } - if start > size { - start = size - } + size := len(collection) if start < 0 { start = 0 + } else if start > size { + start = size } - if end > size { - end = size - } if end < 0 { end = 0 + } else if end > size { + end = size } return collection[start:end] @@ -731,6 +1085,20 @@ func ReplaceAll[T comparable, Slice ~[]T](collection Slice, old, nEw T) Slice { return Replace(collection, old, nEw, -1) } +// Clone returns a shallow copy of the collection. +func Clone[T any, Slice ~[]T](collection Slice) Slice { + // backporting from slices.Clone in Go 1.21 + // when we drop support for Go 1.20, this can be replaced with: return slices.Clone(collection) + + // Preserve nilness in case it matters. + if collection == nil { + return nil + } + // Avoid s[:0:0] as it leads to unwanted liveness when cloning a + // zero-length slice of a large array; see https://go.dev/issue/68488. + return append(Slice{}, collection...) +} + // Compact returns a slice of all non-zero elements. // Play: https://go.dev/play/p/tXiy-iK6PAc func Compact[T comparable, Slice ~[]T](collection Slice) Slice { @@ -759,8 +1127,8 @@ func IsSorted[T constraints.Ordered](collection []T) bool { return true } -// IsSortedByKey checks if a slice is sorted by iteratee. -func IsSortedByKey[T any, K constraints.Ordered](collection []T, iteratee func(item T) K) bool { +// IsSortedBy checks if a slice is sorted by iteratee. +func IsSortedBy[T any, K constraints.Ordered](collection []T, iteratee func(item T) K) bool { size := len(collection) for i := 0; i < size-1; i++ { @@ -772,6 +1140,13 @@ func IsSortedByKey[T any, K constraints.Ordered](collection []T, iteratee func(i return true } +// IsSortedByKey checks if a slice is sorted by iteratee. +// +// Deprecated: Use lo.IsSortedBy instead. +func IsSortedByKey[T any, K constraints.Ordered](collection []T, iteratee func(item T) K) bool { + return IsSortedBy(collection, iteratee) +} + // Splice inserts multiple elements at index i. A negative index counts back // from the end of the slice. The helper is protected against overflow errors. // Play: https://go.dev/play/p/G5_GhkeSUBA @@ -828,20 +1203,10 @@ func Cut[T comparable, Slice ~[]T](collection, separator Slice) (before, after S // If prefix is the empty []T, CutPrefix returns collection, true. // Play: https://go.dev/play/p/7Plak4a1ICl func CutPrefix[T comparable, Slice ~[]T](collection, separator Slice) (after Slice, found bool) { - if len(separator) == 0 { - return collection, true + if HasPrefix(collection, separator) { + return collection[len(separator):], true } - if len(separator) > len(collection) { - return collection, false - } - - for i := range separator { - if collection[i] != separator[i] { - return collection, false - } - } - - return collection[len(separator):], true + return collection, false } // CutSuffix returns collection without the provided ending suffix []T and reports @@ -849,21 +1214,10 @@ func CutPrefix[T comparable, Slice ~[]T](collection, separator Slice) (after Sli // If suffix is the empty []T, CutSuffix returns collection, true. // Play: https://go.dev/play/p/7FKfBFvPTaT func CutSuffix[T comparable, Slice ~[]T](collection, separator Slice) (before Slice, found bool) { - if len(separator) == 0 { - return collection, true - } - if len(separator) > len(collection) { - return collection, false - } - - start := len(collection) - len(separator) - for i := range separator { - if collection[start+i] != separator[i] { - return collection, false - } + if HasSuffix(collection, separator) { + return collection[:len(collection)-len(separator)], true } - - return collection[:start], true + return collection, false } // Trim removes all the leading and trailing cutset from the collection. @@ -911,12 +1265,11 @@ func TrimPrefix[T comparable, Slice ~[]T](collection, prefix Slice) Slice { return collection } - for { - if !HasPrefix(collection, prefix) { - return collection - } + for HasPrefix(collection, prefix) { collection = collection[len(prefix):] } + + return collection } // TrimRight removes all the trailing cutset from the collection. @@ -937,10 +1290,9 @@ func TrimSuffix[T comparable, Slice ~[]T](collection, suffix Slice) Slice { return collection } - for { - if !HasSuffix(collection, suffix) { - return collection - } + for HasSuffix(collection, suffix) { collection = collection[:len(collection)-len(suffix)] } + + return collection } diff --git a/vendor/github.com/samber/lo/string.go b/vendor/github.com/samber/lo/string.go index 9590114..9b0fc6e 100644 --- a/vendor/github.com/samber/lo/string.go +++ b/vendor/github.com/samber/lo/string.go @@ -7,10 +7,10 @@ import ( "unicode" "unicode/utf8" - "github.com/samber/lo/internal/xrand" - "golang.org/x/text/cases" "golang.org/x/text/language" + + "github.com/samber/lo/internal/xrand" ) var ( @@ -41,7 +41,7 @@ func RandomString(size int, charset []rune) string { } // see https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-go - sb := strings.Builder{} + var sb strings.Builder sb.Grow(size) if len(charset) == 1 { @@ -100,42 +100,107 @@ func nearestPowerOfTwo(capacity int) int { return n + 1 } -// Substring return part of a string. +// Substring extracts a substring from a string with Unicode character (rune) awareness. +// offset - starting position of the substring (can be positive, negative, or zero) +// length - number of characters to extract +// With positive offset, counting starts from the beginning of the string +// With negative offset, counting starts from the end of the string // Play: https://go.dev/play/p/TQlxQi82Lu1 func Substring[T ~string](str T, offset int, length uint) T { - rs := []rune(str) - size := len(rs) + str = substring(str, offset, length) - if offset < 0 { - offset = size + offset - if offset < 0 { - offset = 0 - } + // Validate UTF-8 and fix invalid sequences + if !utf8.ValidString(string(str)) { + // Convert to []rune to replicate behavior with duplicated οΏ½ + str = T([]rune(str)) } - if offset >= size { - return Empty[T]() - } + // Remove null bytes from result + return T(strings.ReplaceAll(string(str), "\x00", "")) +} - if length > uint(size)-uint(offset) { - length = uint(size - offset) - } +func substring[T ~string](str T, offset int, length uint) T { + switch { + // Empty length or offset beyond string bounds - return empty string + case length == 0, offset >= len(str): + return "" + + // Positive offset - count from the beginning + case offset > 0: + // Skip offset runes from the start + for i, r := range str { + if offset--; offset == 0 { + str = str[i+utf8.RuneLen(r):] + break + } + } + + // If couldn't skip enough runes - string is shorter than offset + if offset != 0 { + return "" + } + + // If remaining string is shorter than or equal to length - return it entirely + if uint(len(str)) <= length { + return str + } + + // Otherwise proceed to trimming by length + fallthrough + + // Zero offset or offset less than minus string length - start from beginning + case offset < -len(str), offset == 0: + // Count length runes from the start + for i := range str { + if length == 0 { + return str[:i] + } + length-- + } + + return str - return T(strings.ReplaceAll(string(rs[offset:offset+int(length)]), "\x00", "")) + // Negative offset - count from the end of string + default: // -len(str) < offset < 0 + // Helper function to move backward through runes + backwardPos := func(end int, count uint) (start int) { + for { + _, i := utf8.DecodeLastRuneInString(string(str[:end])) + end -= i + + if count--; count == 0 || end == 0 { + return end + } + } + } + + offset := uint(-offset) + + // If offset is less than or equal to length - take from position to end + if offset <= length { + start := backwardPos(len(str), offset) + return str[start:] + } + + // Otherwise calculate start and end positions + end := backwardPos(len(str), offset-length) + start := backwardPos(end, length) + + return str[start:end] + } } // ChunkString returns a slice of strings split into groups of length size. If the string can't be split evenly, // the final chunk will be the remaining characters. // Play: https://go.dev/play/p/__FLTuJVz54 +// +// Note: lo.ChunkString and lo.Chunk functions behave inconsistently for empty input: lo.ChunkString("", n) returns [""] instead of []. +// See https://github.com/samber/lo/issues/788 func ChunkString[T ~string](str T, size int) []T { if size <= 0 { panic("lo.ChunkString: size must be greater than 0") } - if len(str) == 0 { - return []T{""} - } - if size >= len(str) { return []T{str} } @@ -212,6 +277,7 @@ func Words(str string) []string { // example: Int8Value => Int 8Value => Int 8 Value str = splitNumberLetterReg.ReplaceAllString(str, "$1 $2") var result strings.Builder + result.Grow(len(str)) for _, r := range str { if unicode.IsLetter(r) || unicode.IsDigit(r) { result.WriteRune(r) @@ -228,26 +294,25 @@ func Capitalize(str string) string { return cases.Title(language.English).String(str) } -// Ellipsis trims and truncates a string to a specified length **in bytes** and appends an ellipsis -// if truncated. If the string contains non-ASCII characters (which may occupy multiple bytes in UTF-8), -// truncating by byte length may split a character in the middle, potentially resulting in garbled output. +// Ellipsis trims and truncates a string to a specified length in runes and appends an ellipsis +// if truncated. The length parameter counts Unicode code points (runes), not bytes, so multi-byte +// characters such as emoji or CJK ideographs are never split in the middle. // Play: https://go.dev/play/p/qE93rgqe1TW func Ellipsis(str string, length int) string { str = strings.TrimSpace(str) - if len(str) > length { - if len(str) < 3 || length < 3 { - return "..." + const ellipsis = "..." + + cutPosition := 0 + for i := range str { + if length == len(ellipsis) { + cutPosition = i + } + + if length--; length < 0 { + return strings.TrimSpace(str[:cutPosition]) + ellipsis } - return strings.TrimSpace(str[0:length-3]) + "..." } return str } - -// Elipse trims and truncates a string to a specified length and appends an ellipsis if truncated. -// -// Deprecated: Use Ellipsis instead. -func Elipse(str string, length int) string { - return Ellipsis(str, length) -} diff --git a/vendor/github.com/samber/lo/time.go b/vendor/github.com/samber/lo/time.go index f295adb..a2b1c7e 100644 --- a/vendor/github.com/samber/lo/time.go +++ b/vendor/github.com/samber/lo/time.go @@ -6,94 +6,94 @@ import ( // Duration returns the time taken to execute a function. // Play: https://go.dev/play/p/HQfbBbAXaFP -func Duration(cb func()) time.Duration { - return Duration0(cb) +func Duration(callback func()) time.Duration { + return Duration0(callback) } // Duration0 returns the time taken to execute a function. // Play: https://go.dev/play/p/HQfbBbAXaFP -func Duration0(cb func()) time.Duration { +func Duration0(callback func()) time.Duration { start := time.Now() - cb() + callback() return time.Since(start) } // Duration1 returns the time taken to execute a function. // Play: https://go.dev/play/p/HQfbBbAXaFP -func Duration1[A any](cb func() A) (A, time.Duration) { +func Duration1[A any](callback func() A) (A, time.Duration) { start := time.Now() - a := cb() + a := callback() return a, time.Since(start) } // Duration2 returns the time taken to execute a function. // Play: https://go.dev/play/p/HQfbBbAXaFP -func Duration2[A, B any](cb func() (A, B)) (A, B, time.Duration) { +func Duration2[A, B any](callback func() (A, B)) (A, B, time.Duration) { start := time.Now() - a, b := cb() + a, b := callback() return a, b, time.Since(start) } // Duration3 returns the time taken to execute a function. // Play: https://go.dev/play/p/xr863iwkAxQ -func Duration3[A, B, C any](cb func() (A, B, C)) (A, B, C, time.Duration) { +func Duration3[A, B, C any](callback func() (A, B, C)) (A, B, C, time.Duration) { start := time.Now() - a, b, c := cb() + a, b, c := callback() return a, b, c, time.Since(start) } // Duration4 returns the time taken to execute a function. // Play: https://go.dev/play/p/xr863iwkAxQ -func Duration4[A, B, C, D any](cb func() (A, B, C, D)) (A, B, C, D, time.Duration) { +func Duration4[A, B, C, D any](callback func() (A, B, C, D)) (A, B, C, D, time.Duration) { start := time.Now() - a, b, c, d := cb() + a, b, c, d := callback() return a, b, c, d, time.Since(start) } // Duration5 returns the time taken to execute a function. // Play: https://go.dev/play/p/xr863iwkAxQ -func Duration5[A, B, C, D, E any](cb func() (A, B, C, D, E)) (A, B, C, D, E, time.Duration) { +func Duration5[A, B, C, D, E any](callback func() (A, B, C, D, E)) (A, B, C, D, E, time.Duration) { start := time.Now() - a, b, c, d, e := cb() + a, b, c, d, e := callback() return a, b, c, d, e, time.Since(start) } // Duration6 returns the time taken to execute a function. // Play: https://go.dev/play/p/mR4bTQKO-Tf -func Duration6[A, B, C, D, E, F any](cb func() (A, B, C, D, E, F)) (A, B, C, D, E, F, time.Duration) { +func Duration6[A, B, C, D, E, F any](callback func() (A, B, C, D, E, F)) (A, B, C, D, E, F, time.Duration) { start := time.Now() - a, b, c, d, e, f := cb() + a, b, c, d, e, f := callback() return a, b, c, d, e, f, time.Since(start) } // Duration7 returns the time taken to execute a function. // Play: https://go.dev/play/p/jgIAcBWWInS -func Duration7[A, B, C, D, E, F, G any](cb func() (A, B, C, D, E, F, G)) (A, B, C, D, E, F, G, time.Duration) { +func Duration7[A, B, C, D, E, F, G any](callback func() (A, B, C, D, E, F, G)) (A, B, C, D, E, F, G, time.Duration) { start := time.Now() - a, b, c, d, e, f, g := cb() + a, b, c, d, e, f, g := callback() return a, b, c, d, e, f, g, time.Since(start) } // Duration8 returns the time taken to execute a function. // Play: https://go.dev/play/p/T8kxpG1c5Na -func Duration8[A, B, C, D, E, F, G, H any](cb func() (A, B, C, D, E, F, G, H)) (A, B, C, D, E, F, G, H, time.Duration) { +func Duration8[A, B, C, D, E, F, G, H any](callback func() (A, B, C, D, E, F, G, H)) (A, B, C, D, E, F, G, H, time.Duration) { start := time.Now() - a, b, c, d, e, f, g, h := cb() + a, b, c, d, e, f, g, h := callback() return a, b, c, d, e, f, g, h, time.Since(start) } // Duration9 returns the time taken to execute a function. // Play: https://go.dev/play/p/bg9ix2VrZ0j -func Duration9[A, B, C, D, E, F, G, H, I any](cb func() (A, B, C, D, E, F, G, H, I)) (A, B, C, D, E, F, G, H, I, time.Duration) { +func Duration9[A, B, C, D, E, F, G, H, I any](callback func() (A, B, C, D, E, F, G, H, I)) (A, B, C, D, E, F, G, H, I, time.Duration) { start := time.Now() - a, b, c, d, e, f, g, h, i := cb() + a, b, c, d, e, f, g, h, i := callback() return a, b, c, d, e, f, g, h, i, time.Since(start) } // Duration10 returns the time taken to execute a function. // Play: https://go.dev/play/p/Y3n7oJXqJbk -func Duration10[A, B, C, D, E, F, G, H, I, J any](cb func() (A, B, C, D, E, F, G, H, I, J)) (A, B, C, D, E, F, G, H, I, J, time.Duration) { +func Duration10[A, B, C, D, E, F, G, H, I, J any](callback func() (A, B, C, D, E, F, G, H, I, J)) (A, B, C, D, E, F, G, H, I, J, time.Duration) { start := time.Now() - a, b, c, d, e, f, g, h, i, j := cb() + a, b, c, d, e, f, g, h, i, j := callback() return a, b, c, d, e, f, g, h, i, j, time.Since(start) } diff --git a/vendor/github.com/samber/lo/tuples.go b/vendor/github.com/samber/lo/tuples.go index d9805b2..1911f8d 100644 --- a/vendor/github.com/samber/lo/tuples.go +++ b/vendor/github.com/samber/lo/tuples.go @@ -101,18 +101,13 @@ func Unpack9[A, B, C, D, E, F, G, H, I any](tuple Tuple9[A, B, C, D, E, F, G, H, // When collections are different sizes, the Tuple attributes are filled with zero value. // Play: https://go.dev/play/p/jujaA6GaJTp func Zip2[A, B any](a []A, b []B) []Tuple2[A, B] { - size := Max([]int{len(a), len(b)}) + size := uint(Max([]int{len(a), len(b)})) - result := make([]Tuple2[A, B], 0, size) + result := make([]Tuple2[A, B], size) - for index := 0; index < size; index++ { - _a, _ := Nth(a, index) - _b, _ := Nth(b, index) - - result = append(result, Tuple2[A, B]{ - A: _a, - B: _b, - }) + for index := uint(0); index < size; index++ { + result[index].A = NthOrEmpty(a, index) + result[index].B = NthOrEmpty(b, index) } return result @@ -123,20 +118,14 @@ func Zip2[A, B any](a []A, b []B) []Tuple2[A, B] { // When collections are different sizes, the Tuple attributes are filled with zero value. // Play: https://go.dev/play/p/jujaA6GaJTp func Zip3[A, B, C any](a []A, b []B, c []C) []Tuple3[A, B, C] { - size := Max([]int{len(a), len(b), len(c)}) - - result := make([]Tuple3[A, B, C], 0, size) + size := uint(Max([]int{len(a), len(b), len(c)})) - for index := 0; index < size; index++ { - _a, _ := Nth(a, index) - _b, _ := Nth(b, index) - _c, _ := Nth(c, index) + result := make([]Tuple3[A, B, C], size) - result = append(result, Tuple3[A, B, C]{ - A: _a, - B: _b, - C: _c, - }) + for index := uint(0); index < size; index++ { + result[index].A = NthOrEmpty(a, index) + result[index].B = NthOrEmpty(b, index) + result[index].C = NthOrEmpty(c, index) } return result @@ -147,22 +136,15 @@ func Zip3[A, B, C any](a []A, b []B, c []C) []Tuple3[A, B, C] { // When collections are different sizes, the Tuple attributes are filled with zero value. // Play: https://go.dev/play/p/jujaA6GaJTp func Zip4[A, B, C, D any](a []A, b []B, c []C, d []D) []Tuple4[A, B, C, D] { - size := Max([]int{len(a), len(b), len(c), len(d)}) + size := uint(Max([]int{len(a), len(b), len(c), len(d)})) - result := make([]Tuple4[A, B, C, D], 0, size) + result := make([]Tuple4[A, B, C, D], size) - for index := 0; index < size; index++ { - _a, _ := Nth(a, index) - _b, _ := Nth(b, index) - _c, _ := Nth(c, index) - _d, _ := Nth(d, index) - - result = append(result, Tuple4[A, B, C, D]{ - A: _a, - B: _b, - C: _c, - D: _d, - }) + for index := uint(0); index < size; index++ { + result[index].A = NthOrEmpty(a, index) + result[index].B = NthOrEmpty(b, index) + result[index].C = NthOrEmpty(c, index) + result[index].D = NthOrEmpty(d, index) } return result @@ -173,24 +155,16 @@ func Zip4[A, B, C, D any](a []A, b []B, c []C, d []D) []Tuple4[A, B, C, D] { // When collections are different sizes, the Tuple attributes are filled with zero value. // Play: https://go.dev/play/p/jujaA6GaJTp func Zip5[A, B, C, D, E any](a []A, b []B, c []C, d []D, e []E) []Tuple5[A, B, C, D, E] { - size := Max([]int{len(a), len(b), len(c), len(d), len(e)}) - - result := make([]Tuple5[A, B, C, D, E], 0, size) + size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e)})) - for index := 0; index < size; index++ { - _a, _ := Nth(a, index) - _b, _ := Nth(b, index) - _c, _ := Nth(c, index) - _d, _ := Nth(d, index) - _e, _ := Nth(e, index) + result := make([]Tuple5[A, B, C, D, E], size) - result = append(result, Tuple5[A, B, C, D, E]{ - A: _a, - B: _b, - C: _c, - D: _d, - E: _e, - }) + for index := uint(0); index < size; index++ { + result[index].A = NthOrEmpty(a, index) + result[index].B = NthOrEmpty(b, index) + result[index].C = NthOrEmpty(c, index) + result[index].D = NthOrEmpty(d, index) + result[index].E = NthOrEmpty(e, index) } return result @@ -201,26 +175,17 @@ func Zip5[A, B, C, D, E any](a []A, b []B, c []C, d []D, e []E) []Tuple5[A, B, C // When collections are different sizes, the Tuple attributes are filled with zero value. // Play: https://go.dev/play/p/jujaA6GaJTp func Zip6[A, B, C, D, E, F any](a []A, b []B, c []C, d []D, e []E, f []F) []Tuple6[A, B, C, D, E, F] { - size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f)}) + size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e), len(f)})) - result := make([]Tuple6[A, B, C, D, E, F], 0, size) + result := make([]Tuple6[A, B, C, D, E, F], size) - for index := 0; index < size; index++ { - _a, _ := Nth(a, index) - _b, _ := Nth(b, index) - _c, _ := Nth(c, index) - _d, _ := Nth(d, index) - _e, _ := Nth(e, index) - _f, _ := Nth(f, index) - - result = append(result, Tuple6[A, B, C, D, E, F]{ - A: _a, - B: _b, - C: _c, - D: _d, - E: _e, - F: _f, - }) + for index := uint(0); index < size; index++ { + result[index].A = NthOrEmpty(a, index) + result[index].B = NthOrEmpty(b, index) + result[index].C = NthOrEmpty(c, index) + result[index].D = NthOrEmpty(d, index) + result[index].E = NthOrEmpty(e, index) + result[index].F = NthOrEmpty(f, index) } return result @@ -231,28 +196,18 @@ func Zip6[A, B, C, D, E, F any](a []A, b []B, c []C, d []D, e []E, f []F) []Tupl // When collections are different sizes, the Tuple attributes are filled with zero value. // Play: https://go.dev/play/p/jujaA6GaJTp func Zip7[A, B, C, D, E, F, G any](a []A, b []B, c []C, d []D, e []E, f []F, g []G) []Tuple7[A, B, C, D, E, F, G] { - size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g)}) - - result := make([]Tuple7[A, B, C, D, E, F, G], 0, size) - - for index := 0; index < size; index++ { - _a, _ := Nth(a, index) - _b, _ := Nth(b, index) - _c, _ := Nth(c, index) - _d, _ := Nth(d, index) - _e, _ := Nth(e, index) - _f, _ := Nth(f, index) - _g, _ := Nth(g, index) - - result = append(result, Tuple7[A, B, C, D, E, F, G]{ - A: _a, - B: _b, - C: _c, - D: _d, - E: _e, - F: _f, - G: _g, - }) + size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g)})) + + result := make([]Tuple7[A, B, C, D, E, F, G], size) + + for index := uint(0); index < size; index++ { + result[index].A = NthOrEmpty(a, index) + result[index].B = NthOrEmpty(b, index) + result[index].C = NthOrEmpty(c, index) + result[index].D = NthOrEmpty(d, index) + result[index].E = NthOrEmpty(e, index) + result[index].F = NthOrEmpty(f, index) + result[index].G = NthOrEmpty(g, index) } return result @@ -263,30 +218,19 @@ func Zip7[A, B, C, D, E, F, G any](a []A, b []B, c []C, d []D, e []E, f []F, g [ // When collections are different sizes, the Tuple attributes are filled with zero value. // Play: https://go.dev/play/p/jujaA6GaJTp func Zip8[A, B, C, D, E, F, G, H any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, h []H) []Tuple8[A, B, C, D, E, F, G, H] { - size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h)}) - - result := make([]Tuple8[A, B, C, D, E, F, G, H], 0, size) - - for index := 0; index < size; index++ { - _a, _ := Nth(a, index) - _b, _ := Nth(b, index) - _c, _ := Nth(c, index) - _d, _ := Nth(d, index) - _e, _ := Nth(e, index) - _f, _ := Nth(f, index) - _g, _ := Nth(g, index) - _h, _ := Nth(h, index) - - result = append(result, Tuple8[A, B, C, D, E, F, G, H]{ - A: _a, - B: _b, - C: _c, - D: _d, - E: _e, - F: _f, - G: _g, - H: _h, - }) + size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h)})) + + result := make([]Tuple8[A, B, C, D, E, F, G, H], size) + + for index := uint(0); index < size; index++ { + result[index].A = NthOrEmpty(a, index) + result[index].B = NthOrEmpty(b, index) + result[index].C = NthOrEmpty(c, index) + result[index].D = NthOrEmpty(d, index) + result[index].E = NthOrEmpty(e, index) + result[index].F = NthOrEmpty(f, index) + result[index].G = NthOrEmpty(g, index) + result[index].H = NthOrEmpty(h, index) } return result @@ -297,32 +241,20 @@ func Zip8[A, B, C, D, E, F, G, H any](a []A, b []B, c []C, d []D, e []E, f []F, // When collections are different sizes, the Tuple attributes are filled with zero value. // Play: https://go.dev/play/p/jujaA6GaJTp func Zip9[A, B, C, D, E, F, G, H, I any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, h []H, i []I) []Tuple9[A, B, C, D, E, F, G, H, I] { - size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h), len(i)}) - - result := make([]Tuple9[A, B, C, D, E, F, G, H, I], 0, size) - - for index := 0; index < size; index++ { - _a, _ := Nth(a, index) - _b, _ := Nth(b, index) - _c, _ := Nth(c, index) - _d, _ := Nth(d, index) - _e, _ := Nth(e, index) - _f, _ := Nth(f, index) - _g, _ := Nth(g, index) - _h, _ := Nth(h, index) - _i, _ := Nth(i, index) - - result = append(result, Tuple9[A, B, C, D, E, F, G, H, I]{ - A: _a, - B: _b, - C: _c, - D: _d, - E: _e, - F: _f, - G: _g, - H: _h, - I: _i, - }) + size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h), len(i)})) + + result := make([]Tuple9[A, B, C, D, E, F, G, H, I], size) + + for index := uint(0); index < size; index++ { + result[index].A = NthOrEmpty(a, index) + result[index].B = NthOrEmpty(b, index) + result[index].C = NthOrEmpty(c, index) + result[index].D = NthOrEmpty(d, index) + result[index].E = NthOrEmpty(e, index) + result[index].F = NthOrEmpty(f, index) + result[index].G = NthOrEmpty(g, index) + result[index].H = NthOrEmpty(h, index) + result[index].I = NthOrEmpty(i, index) } return result @@ -333,15 +265,15 @@ func Zip9[A, B, C, D, E, F, G, H, I any](a []A, b []B, c []C, d []D, e []E, f [] // When collections are different sizes, the Tuple attributes are filled with zero value. // Play: https://go.dev/play/p/wlHur6yO8rR func ZipBy2[A, B, Out any](a []A, b []B, iteratee func(a A, b B) Out) []Out { - size := Max([]int{len(a), len(b)}) - - result := make([]Out, 0, size) + size := uint(Max([]int{len(a), len(b)})) - for index := 0; index < size; index++ { - _a, _ := Nth(a, index) - _b, _ := Nth(b, index) + result := make([]Out, size) - result = append(result, iteratee(_a, _b)) + for index := uint(0); index < size; index++ { + result[index] = iteratee( + NthOrEmpty(a, index), + NthOrEmpty(b, index), + ) } return result @@ -352,16 +284,16 @@ func ZipBy2[A, B, Out any](a []A, b []B, iteratee func(a A, b B) Out) []Out { // When collections are different sizes, the Tuple attributes are filled with zero value. // Play: https://go.dev/play/p/j9maveOnSQX func ZipBy3[A, B, C, Out any](a []A, b []B, c []C, iteratee func(a A, b B, c C) Out) []Out { - size := Max([]int{len(a), len(b), len(c)}) + size := uint(Max([]int{len(a), len(b), len(c)})) - result := make([]Out, 0, size) - - for index := 0; index < size; index++ { - _a, _ := Nth(a, index) - _b, _ := Nth(b, index) - _c, _ := Nth(c, index) + result := make([]Out, size) - result = append(result, iteratee(_a, _b, _c)) + for index := uint(0); index < size; index++ { + result[index] = iteratee( + NthOrEmpty(a, index), + NthOrEmpty(b, index), + NthOrEmpty(c, index), + ) } return result @@ -372,17 +304,17 @@ func ZipBy3[A, B, C, Out any](a []A, b []B, c []C, iteratee func(a A, b B, c C) // When collections are different sizes, the Tuple attributes are filled with zero value. // Play: https://go.dev/play/p/Y1eF2Ke0Ayz func ZipBy4[A, B, C, D, Out any](a []A, b []B, c []C, d []D, iteratee func(a A, b B, c C, d D) Out) []Out { - size := Max([]int{len(a), len(b), len(c), len(d)}) - - result := make([]Out, 0, size) + size := uint(Max([]int{len(a), len(b), len(c), len(d)})) - for index := 0; index < size; index++ { - _a, _ := Nth(a, index) - _b, _ := Nth(b, index) - _c, _ := Nth(c, index) - _d, _ := Nth(d, index) + result := make([]Out, size) - result = append(result, iteratee(_a, _b, _c, _d)) + for index := uint(0); index < size; index++ { + result[index] = iteratee( + NthOrEmpty(a, index), + NthOrEmpty(b, index), + NthOrEmpty(c, index), + NthOrEmpty(d, index), + ) } return result @@ -393,18 +325,18 @@ func ZipBy4[A, B, C, D, Out any](a []A, b []B, c []C, d []D, iteratee func(a A, // When collections are different sizes, the Tuple attributes are filled with zero value. // Play: https://go.dev/play/p/SLynyalh5Oa func ZipBy5[A, B, C, D, E, Out any](a []A, b []B, c []C, d []D, e []E, iteratee func(a A, b B, c C, d D, e E) Out) []Out { - size := Max([]int{len(a), len(b), len(c), len(d), len(e)}) - - result := make([]Out, 0, size) - - for index := 0; index < size; index++ { - _a, _ := Nth(a, index) - _b, _ := Nth(b, index) - _c, _ := Nth(c, index) - _d, _ := Nth(d, index) - _e, _ := Nth(e, index) - - result = append(result, iteratee(_a, _b, _c, _d, _e)) + size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e)})) + + result := make([]Out, size) + + for index := uint(0); index < size; index++ { + result[index] = iteratee( + NthOrEmpty(a, index), + NthOrEmpty(b, index), + NthOrEmpty(c, index), + NthOrEmpty(d, index), + NthOrEmpty(e, index), + ) } return result @@ -415,19 +347,19 @@ func ZipBy5[A, B, C, D, E, Out any](a []A, b []B, c []C, d []D, e []E, iteratee // When collections are different sizes, the Tuple attributes are filled with zero value. // Play: https://go.dev/play/p/IK6KVgw9e-S func ZipBy6[A, B, C, D, E, F, Out any](a []A, b []B, c []C, d []D, e []E, f []F, iteratee func(a A, b B, c C, d D, e E, f F) Out) []Out { - size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f)}) - - result := make([]Out, 0, size) - - for index := 0; index < size; index++ { - _a, _ := Nth(a, index) - _b, _ := Nth(b, index) - _c, _ := Nth(c, index) - _d, _ := Nth(d, index) - _e, _ := Nth(e, index) - _f, _ := Nth(f, index) - - result = append(result, iteratee(_a, _b, _c, _d, _e, _f)) + size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e), len(f)})) + + result := make([]Out, size) + + for index := uint(0); index < size; index++ { + result[index] = iteratee( + NthOrEmpty(a, index), + NthOrEmpty(b, index), + NthOrEmpty(c, index), + NthOrEmpty(d, index), + NthOrEmpty(e, index), + NthOrEmpty(f, index), + ) } return result @@ -438,20 +370,20 @@ func ZipBy6[A, B, C, D, E, F, Out any](a []A, b []B, c []C, d []D, e []E, f []F, // When collections are different sizes, the Tuple attributes are filled with zero value. // Play: https://go.dev/play/p/4uW6a2vXh8w func ZipBy7[A, B, C, D, E, F, G, Out any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, iteratee func(a A, b B, c C, d D, e E, f F, g G) Out) []Out { - size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g)}) - - result := make([]Out, 0, size) - - for index := 0; index < size; index++ { - _a, _ := Nth(a, index) - _b, _ := Nth(b, index) - _c, _ := Nth(c, index) - _d, _ := Nth(d, index) - _e, _ := Nth(e, index) - _f, _ := Nth(f, index) - _g, _ := Nth(g, index) - - result = append(result, iteratee(_a, _b, _c, _d, _e, _f, _g)) + size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g)})) + + result := make([]Out, size) + + for index := uint(0); index < size; index++ { + result[index] = iteratee( + NthOrEmpty(a, index), + NthOrEmpty(b, index), + NthOrEmpty(c, index), + NthOrEmpty(d, index), + NthOrEmpty(e, index), + NthOrEmpty(f, index), + NthOrEmpty(g, index), + ) } return result @@ -462,21 +394,21 @@ func ZipBy7[A, B, C, D, E, F, G, Out any](a []A, b []B, c []C, d []D, e []E, f [ // When collections are different sizes, the Tuple attributes are filled with zero value. // Play: https://go.dev/play/p/tk8xW7XzY4v func ZipBy8[A, B, C, D, E, F, G, H, Out any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, h []H, iteratee func(a A, b B, c C, d D, e E, f F, g G, h H) Out) []Out { - size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h)}) - - result := make([]Out, 0, size) - - for index := 0; index < size; index++ { - _a, _ := Nth(a, index) - _b, _ := Nth(b, index) - _c, _ := Nth(c, index) - _d, _ := Nth(d, index) - _e, _ := Nth(e, index) - _f, _ := Nth(f, index) - _g, _ := Nth(g, index) - _h, _ := Nth(h, index) - - result = append(result, iteratee(_a, _b, _c, _d, _e, _f, _g, _h)) + size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h)})) + + result := make([]Out, size) + + for index := uint(0); index < size; index++ { + result[index] = iteratee( + NthOrEmpty(a, index), + NthOrEmpty(b, index), + NthOrEmpty(c, index), + NthOrEmpty(d, index), + NthOrEmpty(e, index), + NthOrEmpty(f, index), + NthOrEmpty(g, index), + NthOrEmpty(h, index), + ) } return result @@ -487,25 +419,229 @@ func ZipBy8[A, B, C, D, E, F, G, H, Out any](a []A, b []B, c []C, d []D, e []E, // When collections are different sizes, the Tuple attributes are filled with zero value. // Play: https://go.dev/play/p/VGqjDmQ9YqX func ZipBy9[A, B, C, D, E, F, G, H, I, Out any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, h []H, i []I, iteratee func(a A, b B, c C, d D, e E, f F, g G, h H, i I) Out) []Out { - size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h), len(i)}) + size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h), len(i)})) + + result := make([]Out, size) + + for index := uint(0); index < size; index++ { + result[index] = iteratee( + NthOrEmpty(a, index), + NthOrEmpty(b, index), + NthOrEmpty(c, index), + NthOrEmpty(d, index), + NthOrEmpty(e, index), + NthOrEmpty(f, index), + NthOrEmpty(g, index), + NthOrEmpty(h, index), + NthOrEmpty(i, index), + ) + } - result := make([]Out, 0, size) + return result +} + +// ZipByErr2 creates a slice of transformed elements, the first of which contains the first elements +// of the given slices, the second of which contains the second elements of the given slices, and so on. +// When collections are different sizes, the Tuple attributes are filled with zero value. +// It returns the first error returned by the iteratee. +func ZipByErr2[A, B, Out any](a []A, b []B, iteratee func(a A, b B) (Out, error)) ([]Out, error) { + size := uint(Max([]int{len(a), len(b)})) + result := make([]Out, size) + + for index := uint(0); index < size; index++ { + r, err := iteratee( + NthOrEmpty(a, index), + NthOrEmpty(b, index), + ) + if err != nil { + return nil, err + } + result[index] = r + } - for index := 0; index < size; index++ { - _a, _ := Nth(a, index) - _b, _ := Nth(b, index) - _c, _ := Nth(c, index) - _d, _ := Nth(d, index) - _e, _ := Nth(e, index) - _f, _ := Nth(f, index) - _g, _ := Nth(g, index) - _h, _ := Nth(h, index) - _i, _ := Nth(i, index) + return result, nil +} - result = append(result, iteratee(_a, _b, _c, _d, _e, _f, _g, _h, _i)) +// ZipByErr3 creates a slice of transformed elements, the first of which contains the first elements +// of the given slices, the second of which contains the second elements of the given slices, and so on. +// When collections are different sizes, the Tuple attributes are filled with zero value. +// It returns the first error returned by the iteratee. +func ZipByErr3[A, B, C, Out any](a []A, b []B, c []C, iteratee func(a A, b B, c C) (Out, error)) ([]Out, error) { + size := uint(Max([]int{len(a), len(b), len(c)})) + result := make([]Out, size) + + for index := uint(0); index < size; index++ { + r, err := iteratee( + NthOrEmpty(a, index), + NthOrEmpty(b, index), + NthOrEmpty(c, index), + ) + if err != nil { + return nil, err + } + result[index] = r } - return result + return result, nil +} + +// ZipByErr4 creates a slice of transformed elements, the first of which contains the first elements +// of the given slices, the second of which contains the second elements of the given slices, and so on. +// When collections are different sizes, the Tuple attributes are filled with zero value. +// It returns the first error returned by the iteratee. +func ZipByErr4[A, B, C, D, Out any](a []A, b []B, c []C, d []D, iteratee func(a A, b B, c C, d D) (Out, error)) ([]Out, error) { + size := uint(Max([]int{len(a), len(b), len(c), len(d)})) + result := make([]Out, size) + + for index := uint(0); index < size; index++ { + r, err := iteratee( + NthOrEmpty(a, index), + NthOrEmpty(b, index), + NthOrEmpty(c, index), + NthOrEmpty(d, index), + ) + if err != nil { + return nil, err + } + result[index] = r + } + + return result, nil +} + +// ZipByErr5 creates a slice of transformed elements, the first of which contains the first elements +// of the given slices, the second of which contains the second elements of the given slices, and so on. +// When collections are different sizes, the Tuple attributes are filled with zero value. +// It returns the first error returned by the iteratee. +func ZipByErr5[A, B, C, D, E, Out any](a []A, b []B, c []C, d []D, e []E, iteratee func(a A, b B, c C, d D, e E) (Out, error)) ([]Out, error) { + size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e)})) + result := make([]Out, size) + + for index := uint(0); index < size; index++ { + r, err := iteratee( + NthOrEmpty(a, index), + NthOrEmpty(b, index), + NthOrEmpty(c, index), + NthOrEmpty(d, index), + NthOrEmpty(e, index), + ) + if err != nil { + return nil, err + } + result[index] = r + } + + return result, nil +} + +// ZipByErr6 creates a slice of transformed elements, the first of which contains the first elements +// of the given slices, the second of which contains the second elements of the given slices, and so on. +// When collections are different sizes, the Tuple attributes are filled with zero value. +// It returns the first error returned by the iteratee. +func ZipByErr6[A, B, C, D, E, F, Out any](a []A, b []B, c []C, d []D, e []E, f []F, iteratee func(a A, b B, c C, d D, e E, f F) (Out, error)) ([]Out, error) { + size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e), len(f)})) + result := make([]Out, size) + + for index := uint(0); index < size; index++ { + r, err := iteratee( + NthOrEmpty(a, index), + NthOrEmpty(b, index), + NthOrEmpty(c, index), + NthOrEmpty(d, index), + NthOrEmpty(e, index), + NthOrEmpty(f, index), + ) + if err != nil { + return nil, err + } + result[index] = r + } + + return result, nil +} + +// ZipByErr7 creates a slice of transformed elements, the first of which contains the first elements +// of the given slices, the second of which contains the second elements of the given slices, and so on. +// When collections are different sizes, the Tuple attributes are filled with zero value. +// It returns the first error returned by the iteratee. +func ZipByErr7[A, B, C, D, E, F, G, Out any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, iteratee func(a A, b B, c C, d D, e E, f F, g G) (Out, error)) ([]Out, error) { + size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g)})) + result := make([]Out, size) + + for index := uint(0); index < size; index++ { + r, err := iteratee( + NthOrEmpty(a, index), + NthOrEmpty(b, index), + NthOrEmpty(c, index), + NthOrEmpty(d, index), + NthOrEmpty(e, index), + NthOrEmpty(f, index), + NthOrEmpty(g, index), + ) + if err != nil { + return nil, err + } + result[index] = r + } + + return result, nil +} + +// ZipByErr8 creates a slice of transformed elements, the first of which contains the first elements +// of the given slices, the second of which contains the second elements of the given slices, and so on. +// When collections are different sizes, the Tuple attributes are filled with zero value. +// It returns the first error returned by the iteratee. +func ZipByErr8[A, B, C, D, E, F, G, H, Out any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, h []H, iteratee func(a A, b B, c C, d D, e E, f F, g G, h H) (Out, error)) ([]Out, error) { + size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h)})) + result := make([]Out, size) + + for index := uint(0); index < size; index++ { + r, err := iteratee( + NthOrEmpty(a, index), + NthOrEmpty(b, index), + NthOrEmpty(c, index), + NthOrEmpty(d, index), + NthOrEmpty(e, index), + NthOrEmpty(f, index), + NthOrEmpty(g, index), + NthOrEmpty(h, index), + ) + if err != nil { + return nil, err + } + result[index] = r + } + + return result, nil +} + +// ZipByErr9 creates a slice of transformed elements, the first of which contains the first elements +// of the given slices, the second of which contains the second elements of the given slices, and so on. +// When collections are different sizes, the Tuple attributes are filled with zero value. +// It returns the first error returned by the iteratee. +func ZipByErr9[A, B, C, D, E, F, G, H, I, Out any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, h []H, i []I, iteratee func(a A, b B, c C, d D, e E, f F, g G, h H, i I) (Out, error)) ([]Out, error) { + size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h), len(i)})) + result := make([]Out, size) + + for index := uint(0); index < size; index++ { + r, err := iteratee( + NthOrEmpty(a, index), + NthOrEmpty(b, index), + NthOrEmpty(c, index), + NthOrEmpty(d, index), + NthOrEmpty(e, index), + NthOrEmpty(f, index), + NthOrEmpty(g, index), + NthOrEmpty(h, index), + NthOrEmpty(i, index), + ) + if err != nil { + return nil, err + } + result[index] = r + } + + return result, nil } // Unzip2 accepts a slice of grouped elements and creates a slice regrouping the elements @@ -884,6 +1020,222 @@ func UnzipBy9[In, A, B, C, D, E, F, G, H, I any](items []In, iteratee func(In) ( return r1, r2, r3, r4, r5, r6, r7, r8, r9 } +// UnzipByErr2 iterates over a collection and creates a slice regrouping the elements +// to their pre-zip configuration. +// It returns the first error returned by the iteratee. +func UnzipByErr2[In, A, B any](items []In, iteratee func(In) (a A, b B, err error)) ([]A, []B, error) { + size := len(items) + r1 := make([]A, 0, size) + r2 := make([]B, 0, size) + + for i := range items { + a, b, err := iteratee(items[i]) + if err != nil { + return nil, nil, err + } + r1 = append(r1, a) + r2 = append(r2, b) + } + + return r1, r2, nil +} + +// UnzipByErr3 iterates over a collection and creates a slice regrouping the elements +// to their pre-zip configuration. +// It returns the first error returned by the iteratee. +func UnzipByErr3[In, A, B, C any](items []In, iteratee func(In) (a A, b B, c C, err error)) ([]A, []B, []C, error) { + size := len(items) + r1 := make([]A, 0, size) + r2 := make([]B, 0, size) + r3 := make([]C, 0, size) + + for i := range items { + a, b, c, err := iteratee(items[i]) + if err != nil { + return nil, nil, nil, err + } + r1 = append(r1, a) + r2 = append(r2, b) + r3 = append(r3, c) + } + + return r1, r2, r3, nil +} + +// UnzipByErr4 iterates over a collection and creates a slice regrouping the elements +// to their pre-zip configuration. +// It returns the first error returned by the iteratee. +func UnzipByErr4[In, A, B, C, D any](items []In, iteratee func(In) (a A, b B, c C, d D, err error)) ([]A, []B, []C, []D, error) { + size := len(items) + r1 := make([]A, 0, size) + r2 := make([]B, 0, size) + r3 := make([]C, 0, size) + r4 := make([]D, 0, size) + + for i := range items { + a, b, c, d, err := iteratee(items[i]) + if err != nil { + return nil, nil, nil, nil, err + } + r1 = append(r1, a) + r2 = append(r2, b) + r3 = append(r3, c) + r4 = append(r4, d) + } + + return r1, r2, r3, r4, nil +} + +// UnzipByErr5 iterates over a collection and creates a slice regrouping the elements +// to their pre-zip configuration. +// It returns the first error returned by the iteratee. +func UnzipByErr5[In, A, B, C, D, E any](items []In, iteratee func(In) (a A, b B, c C, d D, e E, err error)) ([]A, []B, []C, []D, []E, error) { + size := len(items) + r1 := make([]A, 0, size) + r2 := make([]B, 0, size) + r3 := make([]C, 0, size) + r4 := make([]D, 0, size) + r5 := make([]E, 0, size) + + for i := range items { + a, b, c, d, e, err := iteratee(items[i]) + if err != nil { + return nil, nil, nil, nil, nil, err + } + r1 = append(r1, a) + r2 = append(r2, b) + r3 = append(r3, c) + r4 = append(r4, d) + r5 = append(r5, e) + } + + return r1, r2, r3, r4, r5, nil +} + +// UnzipByErr6 iterates over a collection and creates a slice regrouping the elements +// to their pre-zip configuration. +// It returns the first error returned by the iteratee. +func UnzipByErr6[In, A, B, C, D, E, F any](items []In, iteratee func(In) (a A, b B, c C, d D, e E, f F, err error)) ([]A, []B, []C, []D, []E, []F, error) { + size := len(items) + r1 := make([]A, 0, size) + r2 := make([]B, 0, size) + r3 := make([]C, 0, size) + r4 := make([]D, 0, size) + r5 := make([]E, 0, size) + r6 := make([]F, 0, size) + + for i := range items { + a, b, c, d, e, f, err := iteratee(items[i]) + if err != nil { + return nil, nil, nil, nil, nil, nil, err + } + r1 = append(r1, a) + r2 = append(r2, b) + r3 = append(r3, c) + r4 = append(r4, d) + r5 = append(r5, e) + r6 = append(r6, f) + } + + return r1, r2, r3, r4, r5, r6, nil +} + +// UnzipByErr7 iterates over a collection and creates a slice regrouping the elements +// to their pre-zip configuration. +// It returns the first error returned by the iteratee. +func UnzipByErr7[In, A, B, C, D, E, F, G any](items []In, iteratee func(In) (a A, b B, c C, d D, e E, f F, g G, err error)) ([]A, []B, []C, []D, []E, []F, []G, error) { + size := len(items) + r1 := make([]A, 0, size) + r2 := make([]B, 0, size) + r3 := make([]C, 0, size) + r4 := make([]D, 0, size) + r5 := make([]E, 0, size) + r6 := make([]F, 0, size) + r7 := make([]G, 0, size) + + for i := range items { + a, b, c, d, e, f, g, err := iteratee(items[i]) + if err != nil { + return nil, nil, nil, nil, nil, nil, nil, err + } + r1 = append(r1, a) + r2 = append(r2, b) + r3 = append(r3, c) + r4 = append(r4, d) + r5 = append(r5, e) + r6 = append(r6, f) + r7 = append(r7, g) + } + + return r1, r2, r3, r4, r5, r6, r7, nil +} + +// UnzipByErr8 iterates over a collection and creates a slice regrouping the elements +// to their pre-zip configuration. +// It returns the first error returned by the iteratee. +func UnzipByErr8[In, A, B, C, D, E, F, G, H any](items []In, iteratee func(In) (a A, b B, c C, d D, e E, f F, g G, h H, err error)) ([]A, []B, []C, []D, []E, []F, []G, []H, error) { + size := len(items) + r1 := make([]A, 0, size) + r2 := make([]B, 0, size) + r3 := make([]C, 0, size) + r4 := make([]D, 0, size) + r5 := make([]E, 0, size) + r6 := make([]F, 0, size) + r7 := make([]G, 0, size) + r8 := make([]H, 0, size) + + for i := range items { + a, b, c, d, e, f, g, h, err := iteratee(items[i]) + if err != nil { + return nil, nil, nil, nil, nil, nil, nil, nil, err + } + r1 = append(r1, a) + r2 = append(r2, b) + r3 = append(r3, c) + r4 = append(r4, d) + r5 = append(r5, e) + r6 = append(r6, f) + r7 = append(r7, g) + r8 = append(r8, h) + } + + return r1, r2, r3, r4, r5, r6, r7, r8, nil +} + +// UnzipByErr9 iterates over a collection and creates a slice regrouping the elements +// to their pre-zip configuration. +// It returns the first error returned by the iteratee. +func UnzipByErr9[In, A, B, C, D, E, F, G, H, I any](items []In, iteratee func(In) (a A, b B, c C, d D, e E, f F, g G, h H, i I, err error)) ([]A, []B, []C, []D, []E, []F, []G, []H, []I, error) { + size := len(items) + r1 := make([]A, 0, size) + r2 := make([]B, 0, size) + r3 := make([]C, 0, size) + r4 := make([]D, 0, size) + r5 := make([]E, 0, size) + r6 := make([]F, 0, size) + r7 := make([]G, 0, size) + r8 := make([]H, 0, size) + r9 := make([]I, 0, size) + + for i := range items { + a, b, c, d, e, f, g, h, i, err := iteratee(items[i]) + if err != nil { + return nil, nil, nil, nil, nil, nil, nil, nil, nil, err + } + r1 = append(r1, a) + r2 = append(r2, b) + r3 = append(r3, c) + r4 = append(r4, d) + r5 = append(r5, e) + r6 = append(r6, f) + r7 = append(r7, g) + r8 = append(r8, h) + r9 = append(r9, i) + } + + return r1, r2, r3, r4, r5, r6, r7, r8, r9, nil +} + // CrossJoin2 combines every item from one list with every item from others. // It is the cartesian product of lists received as arguments. // Returns an empty list if a list is empty. @@ -949,11 +1301,11 @@ func CrossJoin9[A, B, C, D, E, F, G, H, I any](listA []A, listB []B, listC []C, } // CrossJoinBy2 combines every item from one list with every item from others. -// It is the cartesian product of lists received as arguments. The project function +// It is the cartesian product of lists received as arguments. The transform function // is used to create the output values. // Returns an empty list if a list is empty. // Play: https://go.dev/play/p/8Y7btpvuA-C -func CrossJoinBy2[A, B, Out any](listA []A, listB []B, project func(a A, b B) Out) []Out { +func CrossJoinBy2[A, B, Out any](listA []A, listB []B, transform func(a A, b B) Out) []Out { size := len(listA) * len(listB) if size == 0 { return []Out{} @@ -963,7 +1315,7 @@ func CrossJoinBy2[A, B, Out any](listA []A, listB []B, project func(a A, b B) Ou for _, a := range listA { for _, b := range listB { - result = append(result, project(a, b)) + result = append(result, transform(a, b)) } } @@ -971,11 +1323,11 @@ func CrossJoinBy2[A, B, Out any](listA []A, listB []B, project func(a A, b B) Ou } // CrossJoinBy3 combines every item from one list with every item from others. -// It is the cartesian product of lists received as arguments. The project function +// It is the cartesian product of lists received as arguments. The transform function // is used to create the output values. // Returns an empty list if a list is empty. // Play: https://go.dev/play/p/3z4y5x6w7v8 -func CrossJoinBy3[A, B, C, Out any](listA []A, listB []B, listC []C, project func(a A, b B, c C) Out) []Out { +func CrossJoinBy3[A, B, C, Out any](listA []A, listB []B, listC []C, transform func(a A, b B, c C) Out) []Out { size := len(listA) * len(listB) * len(listC) if size == 0 { return []Out{} @@ -986,7 +1338,7 @@ func CrossJoinBy3[A, B, C, Out any](listA []A, listB []B, listC []C, project fun for _, a := range listA { for _, b := range listB { for _, c := range listC { - result = append(result, project(a, b, c)) + result = append(result, transform(a, b, c)) } } } @@ -995,11 +1347,11 @@ func CrossJoinBy3[A, B, C, Out any](listA []A, listB []B, listC []C, project fun } // CrossJoinBy4 combines every item from one list with every item from others. -// It is the cartesian product of lists received as arguments. The project function +// It is the cartesian product of lists received as arguments. The transform function // is used to create the output values. // Returns an empty list if a list is empty. // Play: https://go.dev/play/p/8b9c0d1e2f3 -func CrossJoinBy4[A, B, C, D, Out any](listA []A, listB []B, listC []C, listD []D, project func(a A, b B, c C, d D) Out) []Out { +func CrossJoinBy4[A, B, C, D, Out any](listA []A, listB []B, listC []C, listD []D, transform func(a A, b B, c C, d D) Out) []Out { size := len(listA) * len(listB) * len(listC) * len(listD) if size == 0 { return []Out{} @@ -1011,7 +1363,7 @@ func CrossJoinBy4[A, B, C, D, Out any](listA []A, listB []B, listC []C, listD [] for _, b := range listB { for _, c := range listC { for _, d := range listD { - result = append(result, project(a, b, c, d)) + result = append(result, transform(a, b, c, d)) } } } @@ -1021,11 +1373,11 @@ func CrossJoinBy4[A, B, C, D, Out any](listA []A, listB []B, listC []C, listD [] } // CrossJoinBy5 combines every item from one list with every item from others. -// It is the cartesian product of lists received as arguments. The project function +// It is the cartesian product of lists received as arguments. The transform function // is used to create the output values. // Returns an empty list if a list is empty. // Play: https://go.dev/play/p/4g5h6i7j8k9 -func CrossJoinBy5[A, B, C, D, E, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, project func(a A, b B, c C, d D, e E) Out) []Out { +func CrossJoinBy5[A, B, C, D, E, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, transform func(a A, b B, c C, d D, e E) Out) []Out { size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE) if size == 0 { return []Out{} @@ -1038,7 +1390,7 @@ func CrossJoinBy5[A, B, C, D, E, Out any](listA []A, listB []B, listC []C, listD for _, c := range listC { for _, d := range listD { for _, e := range listE { - result = append(result, project(a, b, c, d, e)) + result = append(result, transform(a, b, c, d, e)) } } } @@ -1049,11 +1401,11 @@ func CrossJoinBy5[A, B, C, D, E, Out any](listA []A, listB []B, listC []C, listD } // CrossJoinBy6 combines every item from one list with every item from others. -// It is the cartesian product of lists received as arguments. The project function +// It is the cartesian product of lists received as arguments. The transform function // is used to create the output values. // Returns an empty list if a list is empty. // Play: https://go.dev/play/p/1l2m3n4o5p6 -func CrossJoinBy6[A, B, C, D, E, F, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, project func(a A, b B, c C, d D, e E, f F) Out) []Out { +func CrossJoinBy6[A, B, C, D, E, F, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, transform func(a A, b B, c C, d D, e E, f F) Out) []Out { size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE) * len(listF) if size == 0 { return []Out{} @@ -1067,7 +1419,7 @@ func CrossJoinBy6[A, B, C, D, E, F, Out any](listA []A, listB []B, listC []C, li for _, d := range listD { for _, e := range listE { for _, f := range listF { - result = append(result, project(a, b, c, d, e, f)) + result = append(result, transform(a, b, c, d, e, f)) } } } @@ -1079,11 +1431,11 @@ func CrossJoinBy6[A, B, C, D, E, F, Out any](listA []A, listB []B, listC []C, li } // CrossJoinBy7 combines every item from one list with every item from others. -// It is the cartesian product of lists received as arguments. The project function +// It is the cartesian product of lists received as arguments. The transform function // is used to create the output values. // Returns an empty list if a list is empty. // Play: https://go.dev/play/p/7q8r9s0t1u2 -func CrossJoinBy7[A, B, C, D, E, F, G, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, project func(a A, b B, c C, d D, e E, f F, g G) Out) []Out { +func CrossJoinBy7[A, B, C, D, E, F, G, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, transform func(a A, b B, c C, d D, e E, f F, g G) Out) []Out { size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE) * len(listF) * len(listG) if size == 0 { return []Out{} @@ -1098,7 +1450,7 @@ func CrossJoinBy7[A, B, C, D, E, F, G, Out any](listA []A, listB []B, listC []C, for _, e := range listE { for _, f := range listF { for _, g := range listG { - result = append(result, project(a, b, c, d, e, f, g)) + result = append(result, transform(a, b, c, d, e, f, g)) } } } @@ -1111,11 +1463,11 @@ func CrossJoinBy7[A, B, C, D, E, F, G, Out any](listA []A, listB []B, listC []C, } // CrossJoinBy8 combines every item from one list with every item from others. -// It is the cartesian product of lists received as arguments. The project function +// It is the cartesian product of lists received as arguments. The transform function // is used to create the output values. // Returns an empty list if a list is empty. // Play: https://go.dev/play/p/3v4w5x6y7z8 -func CrossJoinBy8[A, B, C, D, E, F, G, H, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, listH []H, project func(a A, b B, c C, d D, e E, f F, g G, h H) Out) []Out { +func CrossJoinBy8[A, B, C, D, E, F, G, H, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, listH []H, transform func(a A, b B, c C, d D, e E, f F, g G, h H) Out) []Out { size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE) * len(listF) * len(listG) * len(listH) if size == 0 { return []Out{} @@ -1131,7 +1483,7 @@ func CrossJoinBy8[A, B, C, D, E, F, G, H, Out any](listA []A, listB []B, listC [ for _, f := range listF { for _, g := range listG { for _, h := range listH { - result = append(result, project(a, b, c, d, e, f, g, h)) + result = append(result, transform(a, b, c, d, e, f, g, h)) } } } @@ -1145,11 +1497,11 @@ func CrossJoinBy8[A, B, C, D, E, F, G, H, Out any](listA []A, listB []B, listC [ } // CrossJoinBy9 combines every item from one list with every item from others. -// It is the cartesian product of lists received as arguments. The project function +// It is the cartesian product of lists received as arguments. The transform function // is used to create the output values. // Returns an empty list if a list is empty. // Play: https://go.dev/play/p/9a0b1c2d3e4 -func CrossJoinBy9[A, B, C, D, E, F, G, H, I, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, listH []H, listI []I, project func(a A, b B, c C, d D, e E, f F, g G, h H, i I) Out) []Out { +func CrossJoinBy9[A, B, C, D, E, F, G, H, I, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, listH []H, listI []I, transform func(a A, b B, c C, d D, e E, f F, g G, h H, i I) Out) []Out { size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE) * len(listF) * len(listG) * len(listH) * len(listI) if size == 0 { return []Out{} @@ -1166,7 +1518,7 @@ func CrossJoinBy9[A, B, C, D, E, F, G, H, I, Out any](listA []A, listB []B, list for _, g := range listG { for _, h := range listH { for _, i := range listI { - result = append(result, project(a, b, c, d, e, f, g, h, i)) + result = append(result, transform(a, b, c, d, e, f, g, h, i)) } } } @@ -1179,3 +1531,267 @@ func CrossJoinBy9[A, B, C, D, E, F, G, H, I, Out any](listA []A, listB []B, list return result } + +// CrossJoinByErr2 combines every item from one list with every item from others. +// It is the cartesian product of lists received as arguments. The transform function +// is used to create the output values. +// Returns an empty list if a list is empty. +// It returns the first error returned by the transform function. +func CrossJoinByErr2[A, B, Out any](listA []A, listB []B, transform func(a A, b B) (Out, error)) ([]Out, error) { + size := len(listA) * len(listB) + if size == 0 { + return []Out{}, nil + } + + result := make([]Out, 0, size) + + for _, a := range listA { + for _, b := range listB { + r, err := transform(a, b) + if err != nil { + return nil, err + } + result = append(result, r) + } + } + + return result, nil +} + +// CrossJoinByErr3 combines every item from one list with every item from others. +// It is the cartesian product of lists received as arguments. The transform function +// is used to create the output values. +// Returns an empty list if a list is empty. +// It returns the first error returned by the transform function. +func CrossJoinByErr3[A, B, C, Out any](listA []A, listB []B, listC []C, transform func(a A, b B, c C) (Out, error)) ([]Out, error) { + size := len(listA) * len(listB) * len(listC) + if size == 0 { + return []Out{}, nil + } + + result := make([]Out, 0, size) + + for _, a := range listA { + for _, b := range listB { + for _, c := range listC { + r, err := transform(a, b, c) + if err != nil { + return nil, err + } + result = append(result, r) + } + } + } + + return result, nil +} + +// CrossJoinByErr4 combines every item from one list with every item from others. +// It is the cartesian product of lists received as arguments. The transform function +// is used to create the output values. +// Returns an empty list if a list is empty. +// It returns the first error returned by the transform function. +func CrossJoinByErr4[A, B, C, D, Out any](listA []A, listB []B, listC []C, listD []D, transform func(a A, b B, c C, d D) (Out, error)) ([]Out, error) { + size := len(listA) * len(listB) * len(listC) * len(listD) + if size == 0 { + return []Out{}, nil + } + + result := make([]Out, 0, size) + + for _, a := range listA { + for _, b := range listB { + for _, c := range listC { + for _, d := range listD { + r, err := transform(a, b, c, d) + if err != nil { + return nil, err + } + result = append(result, r) + } + } + } + } + + return result, nil +} + +// CrossJoinByErr5 combines every item from one list with every item from others. +// It is the cartesian product of lists received as arguments. The transform function +// is used to create the output values. +// Returns an empty list if a list is empty. +// It returns the first error returned by the transform function. +func CrossJoinByErr5[A, B, C, D, E, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, transform func(a A, b B, c C, d D, e E) (Out, error)) ([]Out, error) { + size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE) + if size == 0 { + return []Out{}, nil + } + + result := make([]Out, 0, size) + + for _, a := range listA { + for _, b := range listB { + for _, c := range listC { + for _, d := range listD { + for _, e := range listE { + r, err := transform(a, b, c, d, e) + if err != nil { + return nil, err + } + result = append(result, r) + } + } + } + } + } + + return result, nil +} + +// CrossJoinByErr6 combines every item from one list with every item from others. +// It is the cartesian product of lists received as arguments. The transform function +// is used to create the output values. +// Returns an empty list if a list is empty. +// It returns the first error returned by the transform function. +func CrossJoinByErr6[A, B, C, D, E, F, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, transform func(a A, b B, c C, d D, e E, f F) (Out, error)) ([]Out, error) { + size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE) * len(listF) + if size == 0 { + return []Out{}, nil + } + + result := make([]Out, 0, size) + + for _, a := range listA { + for _, b := range listB { + for _, c := range listC { + for _, d := range listD { + for _, e := range listE { + for _, f := range listF { + r, err := transform(a, b, c, d, e, f) + if err != nil { + return nil, err + } + result = append(result, r) + } + } + } + } + } + } + + return result, nil +} + +// CrossJoinByErr7 combines every item from one list with every item from others. +// It is the cartesian product of lists received as arguments. The transform function +// is used to create the output values. +// Returns an empty list if a list is empty. +// It returns the first error returned by the transform function. +func CrossJoinByErr7[A, B, C, D, E, F, G, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, transform func(a A, b B, c C, d D, e E, f F, g G) (Out, error)) ([]Out, error) { + size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE) * len(listF) * len(listG) + if size == 0 { + return []Out{}, nil + } + + result := make([]Out, 0, size) + + for _, a := range listA { + for _, b := range listB { + for _, c := range listC { + for _, d := range listD { + for _, e := range listE { + for _, f := range listF { + for _, g := range listG { + r, err := transform(a, b, c, d, e, f, g) + if err != nil { + return nil, err + } + result = append(result, r) + } + } + } + } + } + } + } + + return result, nil +} + +// CrossJoinByErr8 combines every item from one list with every item from others. +// It is the cartesian product of lists received as arguments. The transform function +// is used to create the output values. +// Returns an empty list if a list is empty. +// It returns the first error returned by the transform function. +func CrossJoinByErr8[A, B, C, D, E, F, G, H, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, listH []H, transform func(a A, b B, c C, d D, e E, f F, g G, h H) (Out, error)) ([]Out, error) { + size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE) * len(listF) * len(listG) * len(listH) + if size == 0 { + return []Out{}, nil + } + + result := make([]Out, 0, size) + + for _, a := range listA { + for _, b := range listB { + for _, c := range listC { + for _, d := range listD { + for _, e := range listE { + for _, f := range listF { + for _, g := range listG { + for _, h := range listH { + r, err := transform(a, b, c, d, e, f, g, h) + if err != nil { + return nil, err + } + result = append(result, r) + } + } + } + } + } + } + } + } + + return result, nil +} + +// CrossJoinByErr9 combines every item from one list with every item from others. +// It is the cartesian product of lists received as arguments. The transform function +// is used to create the output values. +// Returns an empty list if a list is empty. +// It returns the first error returned by the transform function. +func CrossJoinByErr9[A, B, C, D, E, F, G, H, I, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, listH []H, listI []I, transform func(a A, b B, c C, d D, e E, f F, g G, h H, i I) (Out, error)) ([]Out, error) { + size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE) * len(listF) * len(listG) * len(listH) * len(listI) + if size == 0 { + return []Out{}, nil + } + + result := make([]Out, 0, size) + + for _, a := range listA { + for _, b := range listB { + for _, c := range listC { + for _, d := range listD { + for _, e := range listE { + for _, f := range listF { + for _, g := range listG { + for _, h := range listH { + for _, i := range listI { + r, err := transform(a, b, c, d, e, f, g, h, i) + if err != nil { + return nil, err + } + result = append(result, r) + } + } + } + } + } + } + } + } + } + + return result, nil +} diff --git a/vendor/github.com/samber/lo/type_manipulation.go b/vendor/github.com/samber/lo/type_manipulation.go index 281f316..c2f93d7 100644 --- a/vendor/github.com/samber/lo/type_manipulation.go +++ b/vendor/github.com/samber/lo/type_manipulation.go @@ -173,7 +173,7 @@ func CoalesceOrEmpty[T comparable](v ...T) T { // Play: https://go.dev/play/p/Gyo9otyvFHH func CoalesceSlice[T any](v ...[]T) ([]T, bool) { for i := range v { - if v[i] != nil && len(v[i]) > 0 { + if len(v[i]) > 0 { return v[i], true } } @@ -184,7 +184,7 @@ func CoalesceSlice[T any](v ...[]T) ([]T, bool) { // Play: https://go.dev/play/p/Gyo9otyvFHH func CoalesceSliceOrEmpty[T any](v ...[]T) []T { for i := range v { - if v[i] != nil && len(v[i]) > 0 { + if len(v[i]) > 0 { return v[i] } } @@ -195,7 +195,7 @@ func CoalesceSliceOrEmpty[T any](v ...[]T) []T { // Play: https://go.dev/play/p/Gyo9otyvFHH func CoalesceMap[K comparable, V any](v ...map[K]V) (map[K]V, bool) { for i := range v { - if v[i] != nil && len(v[i]) > 0 { + if len(v[i]) > 0 { return v[i], true } } @@ -206,7 +206,7 @@ func CoalesceMap[K comparable, V any](v ...map[K]V) (map[K]V, bool) { // Play: https://go.dev/play/p/Gyo9otyvFHH func CoalesceMapOrEmpty[K comparable, V any](v ...map[K]V) map[K]V { for i := range v { - if v[i] != nil && len(v[i]) > 0 { + if len(v[i]) > 0 { return v[i] } } diff --git a/vendor/modules.txt b/vendor/modules.txt index 0e5d69c..ad27294 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -14,7 +14,7 @@ github.com/google/go-cmp/cmp/internal/value # github.com/pmezard/go-difflib v1.0.0 ## explicit github.com/pmezard/go-difflib/difflib -# github.com/samber/lo v1.52.0 +# github.com/samber/lo v1.53.0 ## explicit; go 1.18 github.com/samber/lo github.com/samber/lo/internal/constraints