chore: bump reva to latest main

This commit is contained in:
Ralf Haferkamp
2026-04-08 11:45:37 +02:00
committed by Ralf Haferkamp
parent 4c86d2a289
commit b8c4f581fb
139 changed files with 6620 additions and 3045 deletions
+1 -1
View File
@@ -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:
+7
View File
@@ -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:
+618 -17
View File
@@ -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
----
<h3 align="center">💖 Support This Project</h3>
<p align="center">
Im going all-in on open-source for the coming months.
<br>
Help sustain development: Become an <a href="http://github.com/sponsors/samber">individual sponsor</a> or join as a <a href="mailto:hey@samuel-berthe.fr">corporate sponsor</a>.
</p>
<div align="center">
<sup><b>💖 Sponsored by:</b></sup>
<br>
<a href="https://www.dbos.dev/?utm_campaign=gh-smbr">
<div>
<img width="200" alt="dbos" src="https://github.com/user-attachments/assets/d583cb62-7735-4d3c-beb7-e6ef1a5faf49" />
</div>
<div>
DBOS - Durable workflow orchestration library for Go
</div>
</a>
</div>
----
@@ -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.
+13 -41
View File
@@ -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.
+8 -8
View File
@@ -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() {
+4 -4
View File
@@ -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
+11 -11
View File
@@ -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
}
+346 -57
View File
@@ -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
+125 -43
View File
@@ -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{}{}
for i := range list1 {
seen[list1[i]] = struct{}{}
func Intersect[T comparable, Slice ~[]T](lists ...Slice) Slice {
if len(lists) == 0 {
return Slice{}
}
for i := range list2 {
if _, ok := seen[list2[i]]; ok {
result = append(result, list2[i])
last := lists[len(lists)-1]
seen := make(map[T]bool, len(last))
for _, item := range last {
seen[item] = false
}
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 {
+167 -8
View File
@@ -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
}
+65 -24
View File
@@ -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.
+10 -10
View File
@@ -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)
}
}
+2 -6
View File
@@ -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.
+444 -92
View File
@@ -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,35 +770,110 @@ 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 Slice{}
}
// do not change the input
indexes = append(make([]int, 0, len(indexes)), indexes...)
for i, index := range indexes {
if index < 0 {
indexes[i] += initialSize
}
}
sort.Ints(indexes)
prev := -1
indexes = mutable.Filter(indexes, func(index int) bool {
ok := index != prev && // uniq
uint(index) < uint(initialSize) // in range
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")
}
if n == 0 {
return make(Slice, 0)
}
for i := range indexes {
if indexes[i] < 0 {
indexes[i] = initialSize + indexes[i]
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
}
}
}
indexes = Uniq(indexes)
sort.Ints(indexes)
result := make(Slice, 0, initialSize)
result = append(result, collection...)
for i := range indexes {
if indexes[i]-i < 0 || indexes[i]-i >= initialSize-i {
continue
}
result = append(result[:indexes[i]-i], result[indexes[i]-i+1:]...)
}
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 HasSuffix(collection, separator) {
return collection[:len(collection)-len(separator)], 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
}
}
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
}
+101 -36
View File
@@ -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))
}
// Remove null bytes from result
return T(strings.ReplaceAll(string(str), "\x00", ""))
}
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 offset >= size {
return Empty[T]()
}
// If couldn't skip enough runes - string is shorter than offset
if offset != 0 {
return ""
}
if length > uint(size)-uint(offset) {
length = uint(size - offset)
}
// If remaining string is shorter than or equal to length - return it entirely
if uint(len(str)) <= length {
return str
}
return T(strings.ReplaceAll(string(rs[offset:offset+int(length)]), "\x00", ""))
// 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
// 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)
}
+24 -24
View File
@@ -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)
}
+860 -244
View File
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -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]
}
}
+9
View File
@@ -12,6 +12,15 @@ bench:
watch-bench:
reflex -t 50ms -s -- sh -c 'go test -benchmem -count 3 -bench ./...'
fuzz:
go test -fuzz=FuzzAttrsToMap -fuzztime=10s ./...
go test -fuzz=FuzzValueToString -fuzztime=10s ./...
go test -fuzz=FuzzAnyValueToString -fuzztime=10s ./...
go test -fuzz=FuzzFindAttribute -fuzztime=10s ./...
go test -fuzz=FuzzRemoveEmptyAttrs -fuzztime=10s ./...
go test -fuzz=FuzzUniqAttrs -fuzztime=10s ./...
go test -fuzz=FuzzAttrsToString -fuzztime=10s ./...
coverage:
go test -v -coverprofile=cover.out -covermode=atomic ./...
go tool cover -html=cover.out -o cover.html
+1 -1
View File
@@ -63,7 +63,7 @@ func AttrsToMap(attrs ...slog.Attr) map[string]any {
}
func RecordToAttrsMap(r slog.Record) map[string]any {
attrs := make([]slog.Attr, r.NumAttrs())
attrs := make([]slog.Attr, 0, r.NumAttrs())
r.Attrs(func(attr slog.Attr) bool {
attrs = append(attrs, attr)
return true
+5 -3
View File
@@ -18,10 +18,12 @@ func FindAttrByGroupAndKey(attrs []slog.Attr, groups []string, key string) (slog
}
for i := range attrs {
if attrs[i].Key == key && attrs[i].Value.Kind() == slog.KindGroup {
return FindAttrByGroupAndKey(attrs[i].Value.Group(), groups[1:], key)
if attrs[i].Key == groups[0] && attrs[i].Value.Kind() == slog.KindGroup {
attr, found := FindAttrByGroupAndKey(attrs[i].Value.Group(), groups[1:], key)
if found {
return attr, true
}
}
}
return slog.Attr{}, false
}