diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 1b99f615..204bc469 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -18,3 +18,6 @@ jobs: uses: golangci/golangci-lint-action@v3 with: args: --timeout 120s --max-same-issues 50 + + - name: Bearer + uses: bearer/bearer-action@v2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5494f88c..16fefa1a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,7 +5,7 @@ on: inputs: semver: type: string - description: Semver + description: 'Semver (eg: v1.2.3)' required: true jobs: @@ -45,7 +45,7 @@ jobs: git config --global user.email "${{ github.triggering_actor}}@users.noreply.github.com" git add . - git commit -m 'bump ${{ inputs.semver }}' + git commit --allow-empty -m 'bump ${{ inputs.semver }}' git tag ${{ inputs.semver }} git push origin ${{ inputs.semver }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 794cac1b..e9050deb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,13 +10,22 @@ jobs: test: runs-on: ubuntu-latest + strategy: + matrix: + go: + - '1.18' + - '1.19' + - '1.20' + - '1.21' + - '1.22' + - '1.x' steps: - uses: actions/checkout@v2 - name: Set up Go uses: actions/setup-go@v2 with: - go-version: 1.18 + go-version: ${{ matrix.go }} stable: false - name: Build @@ -35,3 +44,4 @@ jobs: file: ./cover.out flags: unittests verbose: true + if: matrix.go == '1.18' diff --git a/CHANGELOG.md b/CHANGELOG.md index 25815f76..8b9e4e11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ @samber: I sometimes forget to update this file. Ping me on [Twitter](https://twitter.com/samuelberthe) or open an issue in case of error. We need to keep a clear changelog for easier lib upgrade. +## 1.39.0 (2023-12-01) + +Improvement: +- Adding IsNil + ## 1.38.1 (2023-03-20) Improvement: @@ -15,7 +20,7 @@ Adding: - lo.EmptyableToPtr Improvement: -- Substring: add support for non-english chars +- Substring: add support for non-English chars Fix: - Async: Fix goroutine leak diff --git a/Dockerfile b/Dockerfile index bd01bbbb..9fd99660 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ -FROM golang:1.18 +FROM golang:1.21.10 WORKDIR /go/src/github.com/samber/lo diff --git a/Makefile b/Makefile index 57bb4915..f97ded85 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,6 @@ -BIN=go - build: - ${BIN} build -v ./... + go build -v ./... test: go test -race -v ./... @@ -15,18 +13,18 @@ watch-bench: reflex -t 50ms -s -- sh -c 'go test -benchmem -count 3 -bench ./...' coverage: - ${BIN} test -v -coverprofile=cover.out -covermode=atomic . - ${BIN} tool cover -html=cover.out -o cover.html + go test -v -coverprofile=cover.out -covermode=atomic ./... + go tool cover -html=cover.out -o cover.html # tools tools: - ${BIN} install github.com/cespare/reflex@latest - ${BIN} install github.com/rakyll/gotest@latest - ${BIN} install github.com/psampaz/go-mod-outdated@latest - ${BIN} install github.com/jondot/goweight@latest - ${BIN} install github.com/golangci/golangci-lint/cmd/golangci-lint@latest - ${BIN} get -t -u golang.org/x/tools/cmd/cover - ${BIN} install github.com/sonatype-nexus-community/nancy@latest + go install github.com/cespare/reflex@latest + go install github.com/rakyll/gotest@latest + go install github.com/psampaz/go-mod-outdated@latest + go install github.com/jondot/goweight@latest + go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest + go get -t -u golang.org/x/tools/cmd/cover + go install github.com/sonatype-nexus-community/nancy@latest go mod tidy lint: @@ -35,10 +33,10 @@ lint-fix: golangci-lint run --timeout 60s --max-same-issues 50 --fix ./... audit: tools - ${BIN} list -json -m all | nancy sleuth + go list -json -m all | nancy sleuth outdated: tools - ${BIN} list -u -m -json all | go-mod-outdated -update -direct + go list -u -m -json all | go-mod-outdated -update -direct weight: tools goweight diff --git a/README.md b/README.md index c0852b50..0f09c7bc 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ In the future, 5 to 10 helpers will overlap with those coming into the Go standa **Why this name?** -I wanted a **short name**, similar to "Lodash" and no Go package currently uses this name. +I wanted a **short name**, similar to "Lodash" and no Go package uses this name. ![lo](img/logo-full.png) @@ -54,7 +54,7 @@ import ( Then use one of the helpers below: ```go -names := lo.Uniq[string]([]string{"Samuel", "John", "Samuel"}) +names := lo.Uniq([]string{"Samuel", "John", "Samuel"}) // []string{"Samuel", "John"} ``` @@ -105,6 +105,8 @@ Supported helpers for slices: - [DropWhile](#dropwhile) - [DropRightWhile](#droprightwhile) - [Reject](#reject) +- [RejectMap](#rejectmap) +- [FilterReject](#filterreject) - [Count](#count) - [CountBy](#countby) - [CountValues](#countvalues) @@ -120,6 +122,7 @@ Supported helpers for slices: Supported helpers for maps: - [Keys](#keys) +- [HasKey](#HasKey) - [ValueOr](#valueor) - [Values](#values) - [PickBy](#pickby) @@ -143,6 +146,8 @@ Supported math helpers: - [Clamp](#clamp) - [Sum](#sum) - [SumBy](#sumby) +- [Mean](#mean) +- [MeanBy](#meanby) Supported helpers for strings: @@ -150,13 +155,27 @@ Supported helpers for strings: - [Substring](#substring) - [ChunkString](#chunkstring) - [RuneLength](#runelength) +- [PascalCase](#pascalcase) +- [CamelCase](#camelcase) +- [KebabCase](#kebabcase) +- [SnakeCase](#snakecase) +- [Words](#words) +- [Capitalize](#capitalize) +- [Elipse](#elipse) Supported helpers for tuples: - [T2 -> T9](#t2---t9) - [Unpack2 -> Unpack9](#unpack2---unpack9) - [Zip2 -> Zip9](#zip2---zip9) +- [ZipBy2 -> ZipBy9](#zipby2---zipby9) - [Unzip2 -> Unzip9](#unzip2---unzip9) +- [UnzipBy2 -> UnzipBy9](#unzipby2---unzipby9) + +Supported helpers for time and duration: + +- [Duration](#duration) +- [Duration0 -> Duration10](#duration0-duration10) Supported helpers for channels: @@ -200,9 +219,16 @@ Supported search helpers: - [FindDuplicatesBy](#findduplicatesby) - [Min](#min) - [MinBy](#minby) +- [Earliest](#earliest) - [Max](#max) - [MaxBy](#maxby) +- [Latest](#latest) +- [First](#first) +- [FirstOrEmpty](#FirstOrEmpty) +- [FirstOr](#FirstOr) - [Last](#last) +- [LastOrEmpty](#LastOrEmpty) +- [LastOr](#LastOr) - [Nth](#nth) - [Sample](#sample) - [Samples](#samples) @@ -216,8 +242,9 @@ Conditional helpers: Type manipulation helpers: -- [IsNil](#IsNil) +- [IsNil](#isnil) - [ToPtr](#toptr) +- [Nil](#nil) - [EmptyableToPtr](#emptyabletoptr) - [FromPtr](#fromptr) - [FromPtrOr](#fromptror) @@ -228,6 +255,7 @@ Type manipulation helpers: - [IsEmpty](#isempty) - [IsNotEmpty](#isnotempty) - [Coalesce](#coalesce) +- [CoalesceOrEmpty](#coalesceorempty) Function helpers: @@ -245,6 +273,7 @@ Concurrency helpers: - [Synchronize](#synchronize) - [Async](#async) - [Transaction](#transaction) +- [WaitFor](#waitfor) Error handling: @@ -325,11 +354,11 @@ matching := lo.FilterMap([]string{"cpu", "gpu", "mouse", "keyboard"}, func(x str 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. ```go -lo.FlatMap([]int{0, 1, 2}, func(x int, _ int) []string { - return []string{ - strconv.FormatInt(x, 10), - strconv.FormatInt(x, 10), - } +lo.FlatMap([]int64{0, 1, 2}, func(x int64, _ int) []string { + return []string{ + strconv.FormatInt(x, 10), + strconv.FormatInt(x, 10), + } }) // []string{"0", "0", "1", "1", "2", "2"} ``` @@ -543,7 +572,7 @@ interleaved := lo.Interleave([]int{1}, []int{2, 5, 8}, []int{3, 6}, []int{4, 7, // []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} ``` -[[play](https://go.dev/play/p/DDhlwrShbwe)] +[[play](https://go.dev/play/p/-RJkTLQEDVt)] ### Shuffle @@ -730,6 +759,33 @@ odd := lo.Reject([]int{1, 2, 3, 4}, func(x int, _ int) bool { [[play](https://go.dev/play/p/YkLMODy1WEL)] +### RejectMap + +The opposite of FilterMap, this method returns a slice which 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 +- whether the result element should be included or not. + +```go +items := lo.RejectMap([]int{1, 2, 3, 4}, func(x int, _ int) (int, bool) { + return x*10, x%2 == 0 +}) +// []int{10, 30} +``` + +### FilterReject + +Mixes Filter and Reject, this method returns two slices, one for the elements of collection that predicate returns truthy for and one for the elements that predicate does not return truthy for. + +```go +kept, rejected := lo.FilterReject([]int{1, 2, 3, 4}, func(x int, _ int) bool { + return x%2 == 0 +}) +// []int{2, 4} +// []int{1, 3} +``` + ### Count Counts the number of elements in the collection that compare equal to value. @@ -894,7 +950,7 @@ Returns a slice of all non-zero elements. ```go in := []string{"", "foo", "", "bar", ""} -slice := lo.Compact[string](in) +slice := lo.Compact(in) // []string{"foo", "bar"} ``` @@ -929,18 +985,32 @@ slice := lo.IsSortedByKey([]string{"a", "bb", "ccc"}, func(s string) int { Creates an array of the map keys. ```go -keys := lo.Keys[string, int](map[string]int{"foo": 1, "bar": 2}) +keys := lo.Keys(map[string]int{"foo": 1, "bar": 2}) // []string{"foo", "bar"} ``` [[play](https://go.dev/play/p/Uu11fHASqrU)] +### HasKey + +Returns whether the given key exists. + +```go +exists := lo.HasKey(map[string]int{"foo": 1, "bar": 2}, "foo") +// true + +exists := lo.HasKey(map[string]int{"foo": 1, "bar": 2}, "baz") +// false +``` + +[[play](https://go.dev/play/p/aVwubIvECqS)] + ### Values Creates an array of the map values. ```go -values := lo.Values[string, int](map[string]int{"foo": 1, "bar": 2}) +values := lo.Values(map[string]int{"foo": 1, "bar": 2}) // []int{1, 2} ``` @@ -951,10 +1021,10 @@ values := lo.Values[string, int](map[string]int{"foo": 1, "bar": 2}) Returns the value of the given key or the fallback value if the key is not present. ```go -value := lo.ValueOr[string, int](map[string]int{"foo": 1, "bar": 2}, "foo", 42) +value := lo.ValueOr(map[string]int{"foo": 1, "bar": 2}, "foo", 42) // 1 -value := lo.ValueOr[string, int](map[string]int{"foo": 1, "bar": 2}, "baz", 42) +value := lo.ValueOr(map[string]int{"foo": 1, "bar": 2}, "baz", 42) // 42 ``` @@ -1089,7 +1159,7 @@ m2 := lo.Invert(map[string]int{"a": 1, "b": 2, "c": 1}) Merges multiple maps from left to right. ```go -mergedMaps := lo.Assign[string, int]( +mergedMaps := lo.Assign( map[string]int{"a": 1, "b": 2}, map[string]int{"b": 3, "c": 4}, ) @@ -1235,6 +1305,42 @@ sum := lo.SumBy(strings, func(item string) int { [[play](https://go.dev/play/p/Dz_a_7jN_ca)] +### Mean + +Calculates the mean of a collection of numbers. + +If collection is empty 0 is returned. + +```go +mean := lo.Mean([]int{2, 3, 4, 5}) +// 3 + +mean := lo.Mean([]float64{2, 3, 4, 5}) +// 3.5 + +mean := lo.Mean([]float64{}) +// 0 +``` + +### MeanBy + +Calculates the mean of a collection of numbers using the given return value from the iteration function. + +If collection is empty 0 is returned. + +```go +list := []string{"aa", "bbb", "cccc", "ddddd"} +mapper := func(item string) float64 { + return float64(len(item)) +} + +mean := lo.MeanBy(list, mapper) +// 3.5 + +mean := lo.MeanBy([]float64{}, mapper) +// 0 +``` + ### RandomString Returns a random string of the specified length and made of the specified charset. @@ -1297,6 +1403,85 @@ sub := len("hellô") [[play](https://go.dev/play/p/tuhgW_lWY8l)] +### PascalCase + +Converts string to pascal case. + +```go +str := lo.PascalCase("hello_world") +// HelloWorld +``` + +[[play](https://go.dev/play/p/iZkdeLP9oiB)] + +### CamelCase + +Converts string to camel case. + +```go +str := lo.CamelCase("hello_world") +// helloWorld +``` + +[[play](https://go.dev/play/p/dtyFB58MBRp)] + +### KebabCase + +Converts string to kebab case. + +```go +str := lo.KebabCase("helloWorld") +// hello-world +``` + +[[play](https://go.dev/play/p/2YTuPafwECA)] + +### SnakeCase + +Converts string to snake case. + +```go +str := lo.SnakeCase("HelloWorld") +// hello_world +``` + +[[play](https://go.dev/play/p/QVKJG9nOnDg)] + +### Words + +Splits string into an array of its words. + +```go +str := lo.Words("helloWorld") +// []string{"hello", "world"} +``` + +[[play](https://go.dev/play/p/2P4zhqqq61g)] + +### Capitalize + +Converts the first character of string to upper case and the remaining to lower case. + +```go +str := lo.Capitalize("heLLO") +// Hello +``` + +### Elipse + +Truncates a string to a specified length and appends an ellipsis if truncated. + +```go +str := lo.Elipse("Lorem Ipsum", 5) +// Lo... + +str := lo.Elipse("Lorem Ipsum", 100) +// Lorem Ipsum + +str := lo.Elipse("Lorem Ipsum", 3) +// ... +``` + ### T2 -> T9 Creates a tuple from a list of values. @@ -1326,7 +1511,7 @@ Unpack is also available as a method of TupleX. ```go tuple2 := lo.T2("a", 1) a, b := tuple2.Unpack() -// "a" 1 +// "a", 1 ``` [[play](https://go.dev/play/p/xVP_k0kJ96W)] @@ -1344,6 +1529,19 @@ tuples := lo.Zip2([]string{"a", "b"}, []int{1, 2}) [[play](https://go.dev/play/p/jujaA6GaJTp)] +### ZipBy2 -> ZipBy9 + +ZipBy creates a slice of transformed elements, the first of which contains the first elements of the given arrays, the second of which contains the second elements of the given arrays, and so on. + +When collections have different size, the Tuple attributes are filled with zero value. + +```go +items := lo.ZipBy2([]string{"a", "b"}, []int{1, 2}, func(a string, b int) string { + return fmt.Sprintf("%s-%d", a, b) +}) +// []string{"a-1", "b-2"} +``` + ### Unzip2 -> Unzip9 Unzip accepts an array of grouped elements and creates an array regrouping the elements to their pre-zip configuration. @@ -1356,6 +1554,56 @@ a, b := lo.Unzip2([]Tuple2[string, int]{{A: "a", B: 1}, {A: "b", B: 2}}) [[play](https://go.dev/play/p/ciHugugvaAW)] +### UnzipBy2 -> UnzipBy9 + +UnzipBy2 iterates over a collection and creates an array regrouping the elements to their pre-zip configuration. + +```go +a, b := lo.UnzipBy2([]string{"hello", "john", "doe"}, func(str string) (string, int) { + return str, len(str) +}) +// []string{"hello", "john", "doe"} +// []int{5, 4, 3} +``` + +### Duration + +Returns the time taken to execute a function. + +```go +duration := lo.Duration(func() { + // very long job +}) +// 3s +``` + +### Duration0 -> Duration10 + +Returns the time taken to execute a function. + +```go +duration := lo.Duration0(func() { + // very long job +}) +// 3s + +err, duration := lo.Duration1(func() error { + // very long job + return fmt.Errorf("an error") +}) +// an error +// 3s + +err, duration := lo.Duration3(func() (string, int, error) { + // very long job + return "hello", 42, nil +}) +// hello +// 42 +// nil +// 3s +``` + ### ChannelDispatcher Distributes messages from input channels into N child channels. Close events are propagated to children. @@ -1824,7 +2072,7 @@ str, index, ok := lo.FindLastIndexOf([]string{"foobar"}, func(i string) bool { ### FindOrElse -Search an element in a slice based on a predicate. It returns element and true if element was found. +Search an element in a slice based on a predicate. It returns the element if found or a given fallback value otherwise. ```go str := lo.FindOrElse([]string{"a", "b", "c", "d"}, "x", func(i string) bool { @@ -1916,7 +2164,7 @@ duplicatedValues := lo.FindDuplicatesBy([]int{3, 4, 5, 6, 7}, func(i int) int { Search the minimum value of a collection. -Returns zero value when collection is empty. +Returns zero value when the collection is empty. ```go min := lo.Min([]int{1, 2, 3}) @@ -1924,6 +2172,9 @@ min := lo.Min([]int{1, 2, 3}) min := lo.Min([]int{}) // 0 + +min := lo.Min([]time.Duration{time.Second, time.Hour}) +// 1s ``` ### MinBy @@ -1932,7 +2183,7 @@ 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 collection is empty. +Returns zero value when the collection is empty. ```go min := lo.MinBy([]string{"s1", "string2", "s3"}, func(item string, min string) bool { @@ -1946,11 +2197,22 @@ min := lo.MinBy([]string{}, func(item string, min string) bool { // "" ``` +### Earliest + +Search the minimum time.Time of a collection. + +Returns zero value when the collection is empty. + +```go +earliest := lo.Earliest(time.Now(), time.Time{}) +// 0001-01-01 00:00:00 +0000 UTC +``` + ### Max Search the maximum value of a collection. -Returns zero value when collection is empty. +Returns zero value when the collection is empty. ```go max := lo.Max([]int{1, 2, 3}) @@ -1958,6 +2220,9 @@ max := lo.Max([]int{1, 2, 3}) max := lo.Max([]int{}) // 0 + +max := lo.Max([]time.Duration{time.Second, time.Hour}) +// 1h ``` ### MaxBy @@ -1966,7 +2231,7 @@ 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 collection is empty. +Returns zero value when the collection is empty. ```go max := lo.MaxBy([]string{"string1", "s2", "string3"}, func(item string, max string) bool { @@ -1980,13 +2245,87 @@ max := lo.MaxBy([]string{}, func(item string, max string) bool { // "" ``` +### Latest + +Search the maximum time.Time of a collection. + +Returns zero value when the collection is empty. + +```go +latest := lo.Latest([]time.Time{time.Now(), time.Time{}}) +// 2023-04-01 01:02:03 +0000 UTC +``` + +### First + +Returns the first element of a collection and check for availability of the first element. + +```go +first, ok := lo.First([]int{1, 2, 3}) +// 1, true + +first, ok := lo.First([]int{}) +// 0, false +``` + +### FirstOrEmpty + +Returns the first element of a collection or zero value if empty. + +```go +first := lo.FirstOrEmpty([]int{1, 2, 3}) +// 1 + +first := lo.FirstOrEmpty([]int{}) +// 0 +``` +### FirstOr + +Returns the first element of a collection or the fallback value if empty. + +```go +first := lo.FirstOr([]int{1, 2, 3}, 245) +// 1 + +first := lo.FirstOr([]int{}, 31) +// 31 +``` + ### Last Returns the last element of a collection or error if empty. ```go -last, err := lo.Last([]int{1, 2, 3}) +last, ok := lo.Last([]int{1, 2, 3}) // 3 +// true + +last, ok := lo.Last([]int{}) +// 0 +// false +``` + +### LastOrEmpty + +Returns the first element of a collection or zero value if empty. + +```go +last := lo.LastOrEmpty([]int{1, 2, 3}) +// 3 + +last := lo.LastOrEmpty([]int{}) +// 0 +``` +### LastOr + +Returns the first element of a collection or the fallback value if empty. + +```go +last := lo.LastOr([]int{1, 2, 3}, 245) +// 3 + +last := lo.LastOr([]int{}, 31) +// 31 ``` ### Nth @@ -2048,7 +2387,7 @@ result := lo.TernaryF(false, func() string { return "a" }, func() string { retur // "b" ``` -Useful to avoid nil-pointer dereferencing in intializations, or avoid running unnecessary code +Useful to avoid nil-pointer dereferencing in initializations, or avoid running unnecessary code ```go var s *string @@ -2189,22 +2528,31 @@ ptr := lo.ToPtr("hello world") // *string{"hello world"} ``` +### Nil + +Returns a nil pointer of type. + +```go +ptr := lo.Nil[float64]() +// nil +``` + ### EmptyableToPtr Returns a pointer copy of value if it's nonzero. Otherwise, returns nil pointer. ```go -ptr := lo.EmptyableToPtr[[]int](nil) +ptr := lo.EmptyableToPtr(nil) // nil -ptr := lo.EmptyableToPtr[string]("") +ptr := lo.EmptyableToPtr("") // nil -ptr := lo.EmptyableToPtr[[]int]([]int{}) +ptr := lo.EmptyableToPtr([]int{}) // *[]int{} -ptr := lo.EmptyableToPtr[string]("hello world") +ptr := lo.EmptyableToPtr("hello world") // *string{"hello world"} ``` @@ -2217,7 +2565,7 @@ str := "hello world" value := lo.FromPtr(&str) // "hello world" -value := lo.FromPtr[string](nil) +value := lo.FromPtr(nil) // "" ``` @@ -2230,7 +2578,7 @@ str := "hello world" value := lo.FromPtrOr(&str, "empty") // "hello world" -value := lo.FromPtrOr[string](nil, "empty") +value := lo.FromPtrOr(nil, "empty") // "empty" ``` @@ -2340,10 +2688,27 @@ result, ok := lo.Coalesce("") var nilStr *string str := "foobar" -result, ok := lo.Coalesce[*string](nil, nilStr, &str) +result, ok := lo.Coalesce(nil, nilStr, &str) // &"foobar" true ``` +### CoalesceOrEmpty + +Returns the first non-empty arguments. Arguments must be comparable. + +```go +result := lo.CoalesceOrEmpty(0, 1, 2, 3) +// 1 + +result := lo.CoalesceOrEmpty("") +// "" + +var nilStr *string +str := "foobar" +result := lo.CoalesceOrEmpty(nil, nilStr, &str) +// &"foobar" +``` + ### Partial Returns new function that, when called, has its first argument set to the provided value. @@ -2593,7 +2958,7 @@ ch := lo.Async2(func() (int, string) { Implements a Saga pattern. ```go -transaction := NewTransaction[int](). +transaction := NewTransaction(). Then( func(state int) (int, error) { fmt.Println("step 1") @@ -2640,6 +3005,38 @@ _, _ = transaction.Process(-5) // rollback 1 ``` +### WaitFor + +Runs periodically until a condition is validated. + +```go +alwaysTrue := func(i int) bool { return true } +alwaysFalse := func(i int) bool { return false } +laterTrue := func(i int) bool { + return i > 5 +} + +iterations, duration, ok := lo.WaitFor(alwaysTrue, 10*time.Millisecond, time.Millisecond) +// 1 +// 0ms +// true + +iterations, duration, ok := lo.WaitFor(alwaysFalse, 10*time.Millisecond, time.Millisecond) +// 10 +// 10ms +// false + +iterations, duration, ok := lo.WaitFor(laterTrue, 10*time.Millisecond, time.Millisecond) +// 7 +// 7ms +// true + +iterations, duration, ok := lo.WaitFor(laterTrue, 10*time.Millisecond, 5*time.Millisecond) +// 2 +// 10ms +// false +``` + ### Validate Helper function that creates an error when a condition is not met. @@ -2712,7 +3109,7 @@ lo.Must0(ok, "'%s' must always contain '%s'", myString, requiredChar) list := []int{0, 1, 2} item := 5 -lo.Must0(lo.Contains[int](list, item), "'%s' must always contain '%s'", list, item) +lo.Must0(lo.Contains(list, item), "'%s' must always contain '%s'", list, item) ... ``` @@ -2720,7 +3117,7 @@ lo.Must0(lo.Contains[int](list, item), "'%s' must always contain '%s'", list, it ### Try -Calls the function and return false in case of error and on panic. +Calls the function and returns false in case of error and panic. ```go ok := lo.Try(func() error { @@ -2744,7 +3141,7 @@ ok := lo.Try(func() error { ### Try{0->6} -The same behavior than `Try`, but callback returns 2 variables. +The same behavior as `Try`, but the callback returns 2 variables. ```go ok := lo.Try2(func() (string, error) { @@ -2785,7 +3182,7 @@ str, ok := lo.TryOr(func() error { ### TryOr{0->6} -The same behavior than `TryOr`, but callback returns `X` variables. +The same behavior as `TryOr`, but the callback returns `X` variables. ```go str, nbr, ok := lo.TryOr2(func() (string, int, error) { @@ -2801,7 +3198,7 @@ str, nbr, ok := lo.TryOr2(func() (string, int, error) { ### TryWithErrorValue -The same behavior than `Try`, but also returns value passed to panic. +The same behavior as `Try`, but also returns the value passed to panic. ```go err, ok := lo.TryWithErrorValue(func() error { @@ -2815,7 +3212,7 @@ err, ok := lo.TryWithErrorValue(func() error { ### TryCatch -The same behavior than `Try`, but calls the catch function in case of error. +The same behavior as `Try`, but calls the catch function in case of error. ```go caught := false @@ -2834,7 +3231,7 @@ ok := lo.TryCatch(func() error { ### TryCatchWithErrorValue -The same behavior than `TryWithErrorValue`, but calls the catch function in case of error. +The same behavior as `TryWithErrorValue`, but calls the catch function in case of error. ```go caught := false @@ -2878,7 +3275,7 @@ if rateLimitErr, ok := lo.ErrorsAs[*RateLimitError](err); ok { ## 🛩 Benchmark -We executed a simple benchmark with the a dead-simple `lo.Map` loop: +We executed a simple benchmark with a dead-simple `lo.Map` loop: See the full implementation [here](./benchmark_test.go). @@ -2915,13 +3312,13 @@ ok github.com/samber/lo 6.657s ## 🤝 Contributing -- Ping me on twitter [@samuelberthe](https://twitter.com/samuelberthe) (DMs, mentions, whatever :)) +- Ping me on Twitter [@samuelberthe](https://twitter.com/samuelberthe) (DMs, mentions, whatever :)) - Fork the [project](https://github.com/samber/lo) - Fix [open issues](https://github.com/samber/lo/issues) or request new features Don't hesitate ;) -Helper naming: helpers must be self explanatory and respect standards (other languages, libraries...). Feel free to suggest many names in your contributions. +Helper naming: helpers must be self-explanatory and respect standards (other languages, libraries...). Feel free to suggest many names in your contributions. ### With Docker @@ -2949,10 +3346,10 @@ make watch-test Give a ⭐️ if this project helped you! -[![support us](https://c5.patreon.com/external/logo/become_a_patron_button.png)](https://www.patreon.com/samber) +[![GitHub Sponsors](https://img.shields.io/github/sponsors/samber?style=for-the-badge)](https://github.com/sponsors/samber) ## 📝 License Copyright © 2022 [Samuel Berthe](https://github.com/samber). -This project is [MIT](./LICENSE) licensed. +This project is under [MIT](./LICENSE) license. diff --git a/benchmark_test.go b/benchmark_test.go deleted file mode 100644 index 97b7389e..00000000 --- a/benchmark_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package lo - -import ( - "math/rand" - "strconv" - "testing" - "time" - - lop "github.com/samber/lo/parallel" - "github.com/thoas/go-funk" -) - -func sliceGenerator(size uint) []int64 { - r := rand.New(rand.NewSource(time.Now().Unix())) - - result := make([]int64, size) - - for i := uint(0); i < size; i++ { - result[i] = r.Int63() - } - - return result -} - -func BenchmarkMap(b *testing.B) { - arr := sliceGenerator(1000000) - - b.Run("lo.Map", func(b *testing.B) { - for n := 0; n < b.N; n++ { - _ = Map(arr, func(x int64, i int) string { - return strconv.FormatInt(x, 10) - }) - } - }) - - b.Run("lop.Map", func(b *testing.B) { - for n := 0; n < b.N; n++ { - _ = lop.Map(arr, func(x int64, i int) string { - return strconv.FormatInt(x, 10) - }) - } - }) - - b.Run("reflect", func(b *testing.B) { - for n := 0; n < b.N; n++ { - _ = funk.Map(arr, func(x int64) string { - return strconv.FormatInt(x, 10) - }) - } - }) - - b.Run("for", func(b *testing.B) { - for n := 0; n < b.N; n++ { - results := make([]string, len(arr)) - - for i, item := range arr { - result := strconv.FormatInt(item, 10) - results[i] = result - } - } - }) -} diff --git a/channel.go b/channel.go index 5dcac328..2ffdb381 100644 --- a/channel.go +++ b/channel.go @@ -86,6 +86,8 @@ func DispatchingStrategyRoundRobin[T any](msg T, index uint64, channels []<-chan // If the channel capacity is exceeded, another random channel will be selected and so on. func DispatchingStrategyRandom[T any](msg T, index uint64, channels []<-chan T) int { for { + // @TODO: Upgrade to math/rand/v2 as soon as we set the minimum Go version to 1.22. + // bearer:disable go_gosec_crypto_weak_random i := rand.Intn(len(channels)) if channelIsNotFull(channels[i]) { return i @@ -108,6 +110,8 @@ func DispatchingStrategyWeightedRandom[T any](weights []int) DispatchingStrategy return func(msg T, index uint64, channels []<-chan T) int { for { + // @TODO: Upgrade to math/rand/v2 as soon as we set the minimum Go version to 1.22. + // bearer:disable go_gosec_crypto_weak_random i := seq[rand.Intn(len(seq))] if channelIsNotFull(channels[i]) { return i @@ -156,8 +160,8 @@ func SliceToChannel[T any](bufferSize int, collection []T) <-chan T { ch := make(chan T, bufferSize) go func() { - for _, item := range collection { - ch <- item + for i := range collection { + ch <- collection[i] } close(ch) @@ -261,13 +265,13 @@ func FanIn[T any](channelBufferCap int, upstreams ...<-chan T) <-chan T { // Start an output goroutine for each input channel in upstreams. wg.Add(len(upstreams)) - for _, c := range upstreams { - go func(c <-chan T) { - for n := range c { + for i := range upstreams { + go func(index int) { + for n := range upstreams[index] { out <- n } wg.Done() - }(c) + }(i) } // Start a goroutine to close out once all the output goroutines are done. diff --git a/channel_test.go b/channel_test.go deleted file mode 100644 index 04c87164..00000000 --- a/channel_test.go +++ /dev/null @@ -1,390 +0,0 @@ -package lo - -import ( - "math/rand" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestChannelDispatcher(t *testing.T) { - t.Parallel() - testWithTimeout(t, 100*time.Millisecond) - is := assert.New(t) - - ch := make(chan int, 10) - - ch <- 0 - ch <- 1 - ch <- 2 - ch <- 3 - - is.Equal(4, len(ch)) - - children := ChannelDispatcher(ch, 5, 10, DispatchingStrategyRoundRobin[int]) - time.Sleep(10 * time.Millisecond) - - // check channels allocation - is.Equal(5, len(children)) - - is.Equal(10, cap(children[0])) - is.Equal(10, cap(children[1])) - is.Equal(10, cap(children[2])) - is.Equal(10, cap(children[3])) - is.Equal(10, cap(children[4])) - - is.Equal(1, len(children[0])) - is.Equal(1, len(children[1])) - is.Equal(1, len(children[2])) - is.Equal(1, len(children[3])) - is.Equal(0, len(children[4])) - - // check channels content - is.Equal(0, len(ch)) - - msg0, ok0 := <-children[0] - is.Equal(ok0, true) - is.Equal(msg0, 0) - - msg1, ok1 := <-children[1] - is.Equal(ok1, true) - is.Equal(msg1, 1) - - msg2, ok2 := <-children[2] - is.Equal(ok2, true) - is.Equal(msg2, 2) - - msg3, ok3 := <-children[3] - is.Equal(ok3, true) - is.Equal(msg3, 3) - - // msg4, ok4 := <-children[4] - // is.Equal(ok4, false) - // is.Equal(msg4, 0) - // is.Nil(children[4]) - - // check it is closed - close(ch) - time.Sleep(10 * time.Millisecond) - is.Panics(func() { - ch <- 42 - }) - - msg0, ok0 = <-children[0] - is.Equal(ok0, false) - is.Equal(msg0, 0) - - msg1, ok1 = <-children[1] - is.Equal(ok1, false) - is.Equal(msg1, 0) - - msg2, ok2 = <-children[2] - is.Equal(ok2, false) - is.Equal(msg2, 0) - - msg3, ok3 = <-children[3] - is.Equal(ok3, false) - is.Equal(msg3, 0) - - msg4, ok4 := <-children[4] - is.Equal(ok4, false) - is.Equal(msg4, 0) - - // unbuffered channels - children = ChannelDispatcher(ch, 5, 0, DispatchingStrategyRoundRobin[int]) - is.Equal(0, cap(children[0])) -} - -func TestDispatchingStrategyRoundRobin(t *testing.T) { - t.Parallel() - testWithTimeout(t, 10*time.Millisecond) - is := assert.New(t) - - children := createChannels[int](3, 2) - rochildren := channelsToReadOnly(children) - defer closeChannels(children) - - is.Equal(0, DispatchingStrategyRoundRobin(42, 0, rochildren)) - is.Equal(1, DispatchingStrategyRoundRobin(42, 1, rochildren)) - is.Equal(2, DispatchingStrategyRoundRobin(42, 2, rochildren)) - is.Equal(0, DispatchingStrategyRoundRobin(42, 3, rochildren)) -} - -func TestDispatchingStrategyRandom(t *testing.T) { - testWithTimeout(t, 10*time.Millisecond) - is := assert.New(t) - - // with this seed, the order of random channels are: 1 - 0 - rand.Seed(14) - - children := createChannels[int](2, 2) - rochildren := channelsToReadOnly(children) - defer closeChannels(children) - - for i := 0; i < 2; i++ { - children[1] <- i - } - - is.Equal(0, DispatchingStrategyRandom(42, 0, rochildren)) -} - -func TestDispatchingStrategyWeightedRandom(t *testing.T) { - t.Parallel() - testWithTimeout(t, 10*time.Millisecond) - is := assert.New(t) - - children := createChannels[int](2, 2) - rochildren := channelsToReadOnly(children) - defer closeChannels(children) - - dispatcher := DispatchingStrategyWeightedRandom[int]([]int{0, 42}) - - is.Equal(1, dispatcher(42, 0, rochildren)) - children[0] <- 0 - is.Equal(1, dispatcher(42, 0, rochildren)) - children[1] <- 1 - is.Equal(1, dispatcher(42, 0, rochildren)) -} - -func TestDispatchingStrategyFirst(t *testing.T) { - t.Parallel() - testWithTimeout(t, 10*time.Millisecond) - is := assert.New(t) - - children := createChannels[int](2, 2) - rochildren := channelsToReadOnly(children) - defer closeChannels(children) - - is.Equal(0, DispatchingStrategyFirst(42, 0, rochildren)) - children[0] <- 0 - is.Equal(0, DispatchingStrategyFirst(42, 0, rochildren)) - children[0] <- 1 - is.Equal(1, DispatchingStrategyFirst(42, 0, rochildren)) -} - -func TestDispatchingStrategyLeast(t *testing.T) { - t.Parallel() - testWithTimeout(t, 10*time.Millisecond) - is := assert.New(t) - - children := createChannels[int](2, 2) - rochildren := channelsToReadOnly(children) - defer closeChannels(children) - - is.Equal(0, DispatchingStrategyLeast(42, 0, rochildren)) - children[0] <- 0 - is.Equal(1, DispatchingStrategyLeast(42, 0, rochildren)) - children[1] <- 0 - is.Equal(0, DispatchingStrategyLeast(42, 0, rochildren)) - children[0] <- 1 - is.Equal(1, DispatchingStrategyLeast(42, 0, rochildren)) - children[1] <- 1 - is.Equal(0, DispatchingStrategyLeast(42, 0, rochildren)) -} - -func TestDispatchingStrategyMost(t *testing.T) { - t.Parallel() - testWithTimeout(t, 10*time.Millisecond) - is := assert.New(t) - - children := createChannels[int](2, 2) - rochildren := channelsToReadOnly(children) - defer closeChannels(children) - - is.Equal(0, DispatchingStrategyMost(42, 0, rochildren)) - children[0] <- 0 - is.Equal(0, DispatchingStrategyMost(42, 0, rochildren)) - children[1] <- 0 - is.Equal(0, DispatchingStrategyMost(42, 0, rochildren)) - children[0] <- 1 - is.Equal(0, DispatchingStrategyMost(42, 0, rochildren)) - children[1] <- 1 - is.Equal(0, DispatchingStrategyMost(42, 0, rochildren)) -} - -func TestSliceToChannel(t *testing.T) { - t.Parallel() - testWithTimeout(t, 10*time.Millisecond) - is := assert.New(t) - - ch := SliceToChannel(2, []int{1, 2, 3}) - - r1, ok1 := <-ch - r2, ok2 := <-ch - r3, ok3 := <-ch - is.True(ok1) - is.Equal(1, r1) - is.True(ok2) - is.Equal(2, r2) - is.True(ok3) - is.Equal(3, r3) - - _, ok4 := <-ch - is.False(ok4) -} - -func TestChannelToSlice(t *testing.T) { - t.Parallel() - testWithTimeout(t, 10*time.Millisecond) - is := assert.New(t) - - ch := SliceToChannel(2, []int{1, 2, 3}) - items := ChannelToSlice(ch) - - is.Equal([]int{1, 2, 3}, items) -} - -func TestGenerate(t *testing.T) { - t.Parallel() - testWithTimeout(t, 10*time.Millisecond) - is := assert.New(t) - - generator := func(yield func(int)) { - yield(0) - yield(1) - yield(2) - yield(3) - } - - i := 0 - - for v := range Generator(2, generator) { - is.Equal(i, v) - i++ - } - - is.Equal(i, 4) -} - -func TestBuffer(t *testing.T) { - t.Parallel() - testWithTimeout(t, 10*time.Millisecond) - is := assert.New(t) - - ch := SliceToChannel(2, []int{1, 2, 3}) - - items1, length1, _, ok1 := Buffer(ch, 2) - items2, length2, _, ok2 := Buffer(ch, 2) - items3, length3, _, ok3 := Buffer(ch, 2) - - is.Equal([]int{1, 2}, items1) - is.Equal(2, length1) - is.True(ok1) - is.Equal([]int{3}, items2) - is.Equal(1, length2) - is.False(ok2) - is.Equal([]int{}, items3) - is.Equal(0, length3) - is.False(ok3) -} - -func TestBufferWithTimeout(t *testing.T) { - t.Parallel() - testWithTimeout(t, 200*time.Millisecond) - is := assert.New(t) - - generator := func(yield func(int)) { - for i := 0; i < 5; i++ { - yield(i) - time.Sleep(10 * time.Millisecond) - } - } - ch := Generator(0, generator) - - items1, length1, _, ok1 := BufferWithTimeout(ch, 20, 15*time.Millisecond) - is.Equal([]int{0, 1}, items1) - is.Equal(2, length1) - is.True(ok1) - - items2, length2, _, ok2 := BufferWithTimeout(ch, 20, 2*time.Millisecond) - is.Equal([]int{}, items2) - is.Equal(0, length2) - is.True(ok2) - - items3, length3, _, ok3 := BufferWithTimeout(ch, 1, 30*time.Millisecond) - is.Equal([]int{2}, items3) - is.Equal(1, length3) - is.True(ok3) - - items4, length4, _, ok4 := BufferWithTimeout(ch, 2, 25*time.Millisecond) - is.Equal([]int{3, 4}, items4) - is.Equal(2, length4) - is.True(ok4) - - items5, length5, _, ok5 := BufferWithTimeout(ch, 3, 25*time.Millisecond) - is.Equal([]int{}, items5) - is.Equal(0, length5) - is.False(ok5) -} - -func TestFanIn(t *testing.T) { - t.Parallel() - testWithTimeout(t, 100*time.Millisecond) - is := assert.New(t) - - upstreams := createChannels[int](3, 10) - roupstreams := channelsToReadOnly(upstreams) - for i := range roupstreams { - go func(i int) { - upstreams[i] <- 1 - upstreams[i] <- 1 - close(upstreams[i]) - }(i) - } - out := FanIn(10, roupstreams...) - time.Sleep(10 * time.Millisecond) - - // check input channels - is.Equal(0, len(roupstreams[0])) - is.Equal(0, len(roupstreams[1])) - is.Equal(0, len(roupstreams[2])) - - // check channels allocation - is.Equal(6, len(out)) - is.Equal(10, cap(out)) - - // check channels content - for i := 0; i < 6; i++ { - msg0, ok0 := <-out - is.Equal(true, ok0) - is.Equal(1, msg0) - } - - // check it is closed - time.Sleep(10 * time.Millisecond) - msg0, ok0 := <-out - is.Equal(false, ok0) - is.Equal(0, msg0) -} - -func TestFanOut(t *testing.T) { - t.Parallel() - testWithTimeout(t, 100*time.Millisecond) - is := assert.New(t) - - upstream := SliceToChannel(10, []int{0, 1, 2, 3, 4, 5}) - rodownstreams := FanOut(3, 10, upstream) - - time.Sleep(10 * time.Millisecond) - - // check output channels - is.Equal(3, len(rodownstreams)) - - // check channels allocation - for i := range rodownstreams { - is.Equal(6, len(rodownstreams[i])) - is.Equal(10, cap(rodownstreams[i])) - is.Equal([]int{0, 1, 2, 3, 4, 5}, ChannelToSlice(rodownstreams[i])) - } - - // check it is closed - time.Sleep(10 * time.Millisecond) - - // check channels allocation - for i := range rodownstreams { - msg, ok := <-rodownstreams[i] - is.Equal(false, ok) - is.Equal(0, msg) - } -} diff --git a/concurrency.go b/concurrency.go index d0aca2aa..95580661 100644 --- a/concurrency.go +++ b/concurrency.go @@ -1,6 +1,9 @@ package lo -import "sync" +import ( + "sync" + "time" +) type synchronize struct { locker sync.Locker @@ -50,7 +53,7 @@ func Async1[A any](f func() A) <-chan A { } // Async2 has the same behavior as Async, but returns the 2 results as a tuple inside the channel. -func Async2[A any, B any](f func() (A, B)) <-chan Tuple2[A, B] { +func Async2[A, B any](f func() (A, B)) <-chan Tuple2[A, B] { ch := make(chan Tuple2[A, B], 1) go func() { ch <- T2(f()) @@ -59,7 +62,7 @@ func Async2[A any, 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. -func Async3[A any, B any, C any](f func() (A, B, C)) <-chan Tuple3[A, B, C] { +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() { ch <- T3(f()) @@ -68,7 +71,7 @@ func Async3[A any, B any, 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. -func Async4[A any, B any, C any, D any](f func() (A, B, C, D)) <-chan Tuple4[A, B, C, D] { +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() { ch <- T4(f()) @@ -77,7 +80,7 @@ func Async4[A any, B any, C any, D any](f func() (A, B, C, D)) <-chan Tuple4[A, } // Async5 has the same behavior as Async, but returns the 5 results as a tuple inside the channel. -func Async5[A any, B any, C any, D any, E any](f func() (A, B, C, D, E)) <-chan Tuple5[A, B, C, D, E] { +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() { ch <- T5(f()) @@ -86,10 +89,42 @@ func Async5[A any, B any, C any, D any, E any](f func() (A, B, C, D, E)) <-chan } // Async6 has the same behavior as Async, but returns the 6 results as a tuple inside the channel. -func Async6[A any, B any, C any, D any, E any, F any](f func() (A, B, C, D, E, F)) <-chan Tuple6[A, B, C, D, E, F] { +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() { ch <- T6(f()) }() return ch } + +// WaitFor runs periodically until a condition is validated. +func WaitFor(condition func(i int) bool, maxDuration time.Duration, tick time.Duration) (int, time.Duration, bool) { + if condition(0) { + return 1, 0, true + } + + start := time.Now() + + timer := time.NewTimer(maxDuration) + ticker := time.NewTicker(tick) + + defer func() { + timer.Stop() + ticker.Stop() + }() + + i := 1 + + for { + select { + case <-timer.C: + return i, time.Since(start), false + case <-ticker.C: + if condition(i) { + return i + 1, time.Since(start), true + } + + i++ + } + } +} diff --git a/concurrency_test.go b/concurrency_test.go deleted file mode 100644 index ae65efdd..00000000 --- a/concurrency_test.go +++ /dev/null @@ -1,214 +0,0 @@ -package lo - -import ( - "sync" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestSynchronize(t *testing.T) { - t.Parallel() - testWithTimeout(t, 100*time.Millisecond) - is := assert.New(t) - - // check that callbacks are not executed concurrently - { - start := time.Now() - - wg := sync.WaitGroup{} - wg.Add(10) - - s := Synchronize() - - for i := 0; i < 10; i++ { - go s.Do(func() { - time.Sleep(5 * time.Millisecond) - wg.Done() - }) - } - - wg.Wait() - - duration := time.Since(start) - - is.Greater(duration, 50*time.Millisecond) - is.Less(duration, 60*time.Millisecond) - } - - // check locker is locked - { - mu := &sync.Mutex{} - s := Synchronize(mu) - - s.Do(func() { - is.False(mu.TryLock()) - }) - is.True(mu.TryLock()) - - Try0(func() { - mu.Unlock() - }) - } - - // check we don't accept multiple arguments - { - is.PanicsWithValue("unexpected arguments", func() { - mu := &sync.Mutex{} - Synchronize(mu, mu, mu) - }) - } -} - -func TestAsync(t *testing.T) { - t.Parallel() - testWithTimeout(t, 100*time.Millisecond) - is := assert.New(t) - - sync := make(chan struct{}) - - ch := Async(func() int { - <-sync - return 10 - }) - - sync <- struct{}{} - - select { - case result := <-ch: - is.Equal(result, 10) - case <-time.After(time.Millisecond): - is.Fail("Async should not block") - } -} - -func TestAsyncX(t *testing.T) { - t.Parallel() - testWithTimeout(t, 100*time.Millisecond) - is := assert.New(t) - - { - sync := make(chan struct{}) - - ch := Async0(func() { - <-sync - }) - - sync <- struct{}{} - - select { - case <-ch: - case <-time.After(time.Millisecond): - is.Fail("Async0 should not block") - } - } - - { - sync := make(chan struct{}) - - ch := Async1(func() int { - <-sync - return 10 - }) - - sync <- struct{}{} - - select { - case result := <-ch: - is.Equal(result, 10) - case <-time.After(time.Millisecond): - is.Fail("Async1 should not block") - } - } - - { - sync := make(chan struct{}) - - ch := Async2(func() (int, string) { - <-sync - return 10, "Hello" - }) - - sync <- struct{}{} - - select { - case result := <-ch: - is.Equal(result, Tuple2[int, string]{10, "Hello"}) - case <-time.After(time.Millisecond): - is.Fail("Async2 should not block") - } - } - - { - sync := make(chan struct{}) - - ch := Async3(func() (int, string, bool) { - <-sync - return 10, "Hello", true - }) - - sync <- struct{}{} - - select { - case result := <-ch: - is.Equal(result, Tuple3[int, string, bool]{10, "Hello", true}) - case <-time.After(time.Millisecond): - is.Fail("Async3 should not block") - } - } - - { - sync := make(chan struct{}) - - ch := Async4(func() (int, string, bool, float64) { - <-sync - return 10, "Hello", true, 3.14 - }) - - sync <- struct{}{} - - select { - case result := <-ch: - is.Equal(result, Tuple4[int, string, bool, float64]{10, "Hello", true, 3.14}) - case <-time.After(time.Millisecond): - is.Fail("Async4 should not block") - } - } - - { - sync := make(chan struct{}) - - ch := Async5(func() (int, string, bool, float64, string) { - <-sync - return 10, "Hello", true, 3.14, "World" - }) - - sync <- struct{}{} - - select { - case result := <-ch: - is.Equal(result, Tuple5[int, string, bool, float64, string]{10, "Hello", true, 3.14, "World"}) - case <-time.After(time.Millisecond): - is.Fail("Async5 should not block") - } - } - - { - sync := make(chan struct{}) - - ch := Async6(func() (int, string, bool, float64, string, int) { - <-sync - return 10, "Hello", true, 3.14, "World", 100 - }) - - sync <- struct{}{} - - select { - case result := <-ch: - is.Equal(result, Tuple6[int, string, bool, float64, string, int]{10, "Hello", true, 3.14, "World", 100}) - case <-time.After(time.Millisecond): - is.Fail("Async6 should not block") - } - } -} diff --git a/condition_example_test.go b/condition_example_test.go deleted file mode 100644 index 1700967e..00000000 --- a/condition_example_test.go +++ /dev/null @@ -1,489 +0,0 @@ -package lo - -import ( - "fmt" -) - -func ExampleTernary() { - result := Ternary(true, "a", "b") - - fmt.Printf("%v", result) - // Output: a -} - -func ExampleTernaryF() { - result := TernaryF(true, func() string { return "a" }, func() string { return "b" }) - - fmt.Printf("%v", result) - // Output: a -} - -func ExampleIf() { - result1 := If(true, 1). - ElseIf(false, 2). - Else(3) - - result2 := If(false, 1). - ElseIf(true, 2). - Else(3) - - result3 := If(false, 1). - ElseIf(false, 2). - Else(3) - - result4 := IfF(true, func() int { return 1 }). - ElseIfF(false, func() int { return 2 }). - ElseF(func() int { return 3 }) - - result5 := IfF(false, func() int { return 1 }). - ElseIfF(true, func() int { return 2 }). - ElseF(func() int { return 3 }) - - result6 := IfF(false, func() int { return 1 }). - ElseIfF(false, func() int { return 2 }). - ElseF(func() int { return 3 }) - - fmt.Printf("%v\n", result1) - fmt.Printf("%v\n", result2) - fmt.Printf("%v\n", result3) - fmt.Printf("%v\n", result4) - fmt.Printf("%v\n", result5) - fmt.Printf("%v\n", result6) - // Output: - // 1 - // 2 - // 3 - // 1 - // 2 - // 3 -} - -func ExampleIfF() { - result1 := If(true, 1). - ElseIf(false, 2). - Else(3) - - result2 := If(false, 1). - ElseIf(true, 2). - Else(3) - - result3 := If(false, 1). - ElseIf(false, 2). - Else(3) - - result4 := IfF(true, func() int { return 1 }). - ElseIfF(false, func() int { return 2 }). - ElseF(func() int { return 3 }) - - result5 := IfF(false, func() int { return 1 }). - ElseIfF(true, func() int { return 2 }). - ElseF(func() int { return 3 }) - - result6 := IfF(false, func() int { return 1 }). - ElseIfF(false, func() int { return 2 }). - ElseF(func() int { return 3 }) - - fmt.Printf("%v\n", result1) - fmt.Printf("%v\n", result2) - fmt.Printf("%v\n", result3) - fmt.Printf("%v\n", result4) - fmt.Printf("%v\n", result5) - fmt.Printf("%v\n", result6) - // Output: - // 1 - // 2 - // 3 - // 1 - // 2 - // 3 -} - -func ExampleifElse_ElseIf() { - result1 := If(true, 1). - ElseIf(false, 2). - Else(3) - - result2 := If(false, 1). - ElseIf(true, 2). - Else(3) - - result3 := If(false, 1). - ElseIf(false, 2). - Else(3) - - result4 := IfF(true, func() int { return 1 }). - ElseIfF(false, func() int { return 2 }). - ElseF(func() int { return 3 }) - - result5 := IfF(false, func() int { return 1 }). - ElseIfF(true, func() int { return 2 }). - ElseF(func() int { return 3 }) - - result6 := IfF(false, func() int { return 1 }). - ElseIfF(false, func() int { return 2 }). - ElseF(func() int { return 3 }) - - fmt.Printf("%v\n", result1) - fmt.Printf("%v\n", result2) - fmt.Printf("%v\n", result3) - fmt.Printf("%v\n", result4) - fmt.Printf("%v\n", result5) - fmt.Printf("%v\n", result6) - // Output: - // 1 - // 2 - // 3 - // 1 - // 2 - // 3 -} - -func ExampleifElse_ElseIfF() { - result1 := If(true, 1). - ElseIf(false, 2). - Else(3) - - result2 := If(false, 1). - ElseIf(true, 2). - Else(3) - - result3 := If(false, 1). - ElseIf(false, 2). - Else(3) - - result4 := IfF(true, func() int { return 1 }). - ElseIfF(false, func() int { return 2 }). - ElseF(func() int { return 3 }) - - result5 := IfF(false, func() int { return 1 }). - ElseIfF(true, func() int { return 2 }). - ElseF(func() int { return 3 }) - - result6 := IfF(false, func() int { return 1 }). - ElseIfF(false, func() int { return 2 }). - ElseF(func() int { return 3 }) - - fmt.Printf("%v\n", result1) - fmt.Printf("%v\n", result2) - fmt.Printf("%v\n", result3) - fmt.Printf("%v\n", result4) - fmt.Printf("%v\n", result5) - fmt.Printf("%v\n", result6) - // Output: - // 1 - // 2 - // 3 - // 1 - // 2 - // 3 -} - -func ExampleifElse_Else() { - result1 := If(true, 1). - ElseIf(false, 2). - Else(3) - - result2 := If(false, 1). - ElseIf(true, 2). - Else(3) - - result3 := If(false, 1). - ElseIf(false, 2). - Else(3) - - result4 := IfF(true, func() int { return 1 }). - ElseIfF(false, func() int { return 2 }). - ElseF(func() int { return 3 }) - - result5 := IfF(false, func() int { return 1 }). - ElseIfF(true, func() int { return 2 }). - ElseF(func() int { return 3 }) - - result6 := IfF(false, func() int { return 1 }). - ElseIfF(false, func() int { return 2 }). - ElseF(func() int { return 3 }) - - fmt.Printf("%v\n", result1) - fmt.Printf("%v\n", result2) - fmt.Printf("%v\n", result3) - fmt.Printf("%v\n", result4) - fmt.Printf("%v\n", result5) - fmt.Printf("%v\n", result6) - // Output: - // 1 - // 2 - // 3 - // 1 - // 2 - // 3 -} - -func ExampleifElse_ElseF() { - result1 := If(true, 1). - ElseIf(false, 2). - Else(3) - - result2 := If(false, 1). - ElseIf(true, 2). - Else(3) - - result3 := If(false, 1). - ElseIf(false, 2). - Else(3) - - result4 := IfF(true, func() int { return 1 }). - ElseIfF(false, func() int { return 2 }). - ElseF(func() int { return 3 }) - - result5 := IfF(false, func() int { return 1 }). - ElseIfF(true, func() int { return 2 }). - ElseF(func() int { return 3 }) - - result6 := IfF(false, func() int { return 1 }). - ElseIfF(false, func() int { return 2 }). - ElseF(func() int { return 3 }) - - fmt.Printf("%v\n", result1) - fmt.Printf("%v\n", result2) - fmt.Printf("%v\n", result3) - fmt.Printf("%v\n", result4) - fmt.Printf("%v\n", result5) - fmt.Printf("%v\n", result6) - // Output: - // 1 - // 2 - // 3 - // 1 - // 2 - // 3 -} - -func ExampleSwitch() { - result1 := Switch[int, string](1). - Case(1, "1"). - Case(2, "2"). - Default("3") - - result2 := Switch[int, string](2). - Case(1, "1"). - Case(2, "2"). - Default("3") - - result3 := Switch[int, string](42). - Case(1, "1"). - Case(2, "2"). - Default("3") - - result4 := Switch[int, string](1). - CaseF(1, func() string { return "1" }). - CaseF(2, func() string { return "2" }). - DefaultF(func() string { return "3" }) - - result5 := Switch[int, string](2). - CaseF(1, func() string { return "1" }). - CaseF(2, func() string { return "2" }). - DefaultF(func() string { return "3" }) - - result6 := Switch[int, string](42). - CaseF(1, func() string { return "1" }). - CaseF(2, func() string { return "2" }). - DefaultF(func() string { return "3" }) - - fmt.Printf("%v\n", result1) - fmt.Printf("%v\n", result2) - fmt.Printf("%v\n", result3) - fmt.Printf("%v\n", result4) - fmt.Printf("%v\n", result5) - fmt.Printf("%v\n", result6) - // Output: - // 1 - // 2 - // 3 - // 1 - // 2 - // 3 -} - -func ExampleswitchCase_Case() { - result1 := Switch[int, string](1). - Case(1, "1"). - Case(2, "2"). - Default("3") - - result2 := Switch[int, string](2). - Case(1, "1"). - Case(2, "2"). - Default("3") - - result3 := Switch[int, string](42). - Case(1, "1"). - Case(2, "2"). - Default("3") - - result4 := Switch[int, string](1). - CaseF(1, func() string { return "1" }). - CaseF(2, func() string { return "2" }). - DefaultF(func() string { return "3" }) - - result5 := Switch[int, string](2). - CaseF(1, func() string { return "1" }). - CaseF(2, func() string { return "2" }). - DefaultF(func() string { return "3" }) - - result6 := Switch[int, string](42). - CaseF(1, func() string { return "1" }). - CaseF(2, func() string { return "2" }). - DefaultF(func() string { return "3" }) - - fmt.Printf("%v\n", result1) - fmt.Printf("%v\n", result2) - fmt.Printf("%v\n", result3) - fmt.Printf("%v\n", result4) - fmt.Printf("%v\n", result5) - fmt.Printf("%v\n", result6) - // Output: - // 1 - // 2 - // 3 - // 1 - // 2 - // 3 -} - -func ExampleswitchCase_CaseF() { - result1 := Switch[int, string](1). - Case(1, "1"). - Case(2, "2"). - Default("3") - - result2 := Switch[int, string](2). - Case(1, "1"). - Case(2, "2"). - Default("3") - - result3 := Switch[int, string](42). - Case(1, "1"). - Case(2, "2"). - Default("3") - - result4 := Switch[int, string](1). - CaseF(1, func() string { return "1" }). - CaseF(2, func() string { return "2" }). - DefaultF(func() string { return "3" }) - - result5 := Switch[int, string](2). - CaseF(1, func() string { return "1" }). - CaseF(2, func() string { return "2" }). - DefaultF(func() string { return "3" }) - - result6 := Switch[int, string](42). - CaseF(1, func() string { return "1" }). - CaseF(2, func() string { return "2" }). - DefaultF(func() string { return "3" }) - - fmt.Printf("%v\n", result1) - fmt.Printf("%v\n", result2) - fmt.Printf("%v\n", result3) - fmt.Printf("%v\n", result4) - fmt.Printf("%v\n", result5) - fmt.Printf("%v\n", result6) - // Output: - // 1 - // 2 - // 3 - // 1 - // 2 - // 3 -} - -func ExampleswitchCase_Default() { - result1 := Switch[int, string](1). - Case(1, "1"). - Case(2, "2"). - Default("3") - - result2 := Switch[int, string](2). - Case(1, "1"). - Case(2, "2"). - Default("3") - - result3 := Switch[int, string](42). - Case(1, "1"). - Case(2, "2"). - Default("3") - - result4 := Switch[int, string](1). - CaseF(1, func() string { return "1" }). - CaseF(2, func() string { return "2" }). - DefaultF(func() string { return "3" }) - - result5 := Switch[int, string](2). - CaseF(1, func() string { return "1" }). - CaseF(2, func() string { return "2" }). - DefaultF(func() string { return "3" }) - - result6 := Switch[int, string](42). - CaseF(1, func() string { return "1" }). - CaseF(2, func() string { return "2" }). - DefaultF(func() string { return "3" }) - - fmt.Printf("%v\n", result1) - fmt.Printf("%v\n", result2) - fmt.Printf("%v\n", result3) - fmt.Printf("%v\n", result4) - fmt.Printf("%v\n", result5) - fmt.Printf("%v\n", result6) - // Output: - // 1 - // 2 - // 3 - // 1 - // 2 - // 3 -} - -func ExampleswitchCase_DefaultF() { - result1 := Switch[int, string](1). - Case(1, "1"). - Case(2, "2"). - Default("3") - - result2 := Switch[int, string](2). - Case(1, "1"). - Case(2, "2"). - Default("3") - - result3 := Switch[int, string](42). - Case(1, "1"). - Case(2, "2"). - Default("3") - - result4 := Switch[int, string](1). - CaseF(1, func() string { return "1" }). - CaseF(2, func() string { return "2" }). - DefaultF(func() string { return "3" }) - - result5 := Switch[int, string](2). - CaseF(1, func() string { return "1" }). - CaseF(2, func() string { return "2" }). - DefaultF(func() string { return "3" }) - - result6 := Switch[int, string](42). - CaseF(1, func() string { return "1" }). - CaseF(2, func() string { return "2" }). - DefaultF(func() string { return "3" }) - - fmt.Printf("%v\n", result1) - fmt.Printf("%v\n", result2) - fmt.Printf("%v\n", result3) - fmt.Printf("%v\n", result4) - fmt.Printf("%v\n", result5) - fmt.Printf("%v\n", result6) - // Output: - // 1 - // 2 - // 3 - // 1 - // 2 - // 3 -} diff --git a/condition_test.go b/condition_test.go deleted file mode 100644 index ffb1e542..00000000 --- a/condition_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package lo - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestTernary(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := Ternary(true, "a", "b") - result2 := Ternary(false, "a", "b") - - is.Equal(result1, "a") - is.Equal(result2, "b") -} - -func TestTernaryF(t *testing.T) { - is := assert.New(t) - - result1 := TernaryF(true, func() string { return "a" }, func() string { return "b" }) - result2 := TernaryF(false, func() string { return "a" }, func() string { return "b" }) - - is.Equal(result1, "a") - is.Equal(result2, "b") -} - -func TestIfElse(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := If(true, 1).ElseIf(false, 2).Else(3) - result2 := If(true, 1).ElseIf(true, 2).Else(3) - result3 := If(false, 1).ElseIf(true, 2).Else(3) - result4 := If(false, 1).ElseIf(false, 2).Else(3) - - is.Equal(result1, 1) - is.Equal(result2, 1) - is.Equal(result3, 2) - is.Equal(result4, 3) -} - -func TestIfFElseF(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := IfF(true, func() int { return 1 }).ElseIfF(false, func() int { return 2 }).ElseF(func() int { return 3 }) - result2 := IfF(true, func() int { return 1 }).ElseIfF(true, func() int { return 2 }).ElseF(func() int { return 3 }) - result3 := IfF(false, func() int { return 1 }).ElseIfF(true, func() int { return 2 }).ElseF(func() int { return 3 }) - result4 := IfF(false, func() int { return 1 }).ElseIfF(false, func() int { return 2 }).ElseF(func() int { return 3 }) - - is.Equal(result1, 1) - is.Equal(result2, 1) - is.Equal(result3, 2) - is.Equal(result4, 3) -} - -func TestSwitchCase(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := Switch[int, int](42).Case(42, 1).Case(1, 2).Default(3) - result2 := Switch[int, int](42).Case(42, 1).Case(42, 2).Default(3) - result3 := Switch[int, int](42).Case(1, 1).Case(42, 2).Default(3) - result4 := Switch[int, int](42).Case(1, 1).Case(1, 2).Default(3) - - is.Equal(result1, 1) - is.Equal(result2, 1) - is.Equal(result3, 2) - is.Equal(result4, 3) -} - -func TestSwitchCaseF(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := Switch[int, int](42).CaseF(42, func() int { return 1 }).CaseF(1, func() int { return 2 }).DefaultF(func() int { return 3 }) - result2 := Switch[int, int](42).CaseF(42, func() int { return 1 }).CaseF(42, func() int { return 2 }).DefaultF(func() int { return 3 }) - result3 := Switch[int, int](42).CaseF(1, func() int { return 1 }).CaseF(42, func() int { return 2 }).DefaultF(func() int { return 3 }) - result4 := Switch[int, int](42).CaseF(1, func() int { return 1 }).CaseF(1, func() int { return 2 }).DefaultF(func() int { return 3 }) - - is.Equal(result1, 1) - is.Equal(result2, 1) - is.Equal(result3, 2) - is.Equal(result4, 3) -} diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 511e85fd..00000000 --- a/docker-compose.yml +++ /dev/null @@ -1,9 +0,0 @@ -version: '3' - -services: - dev: - image: golang:1.18-bullseye - volumes: - - ./:/go/src/github.com/samber/lo - working_dir: /go/src/github.com/samber/lo - command: make watch-test diff --git a/errors.go b/errors.go index a99013d9..810c2e72 100644 --- a/errors.go +++ b/errors.go @@ -80,35 +80,35 @@ func Must1[T any](val T, err any, messageArgs ...interface{}) T { // Must2 has the same behavior as Must, but callback returns 2 variables. // Play: https://go.dev/play/p/TMoWrRp3DyC -func Must2[T1 any, T2 any](val1 T1, val2 T2, err any, messageArgs ...interface{}) (T1, T2) { +func Must2[T1, T2 any](val1 T1, val2 T2, err any, messageArgs ...interface{}) (T1, T2) { must(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 any, T2 any, T3 any](val1 T1, val2 T2, val3 T3, err any, messageArgs ...interface{}) (T1, T2, T3) { +func Must3[T1, T2, T3 any](val1 T1, val2 T2, val3 T3, err any, messageArgs ...interface{}) (T1, T2, T3) { must(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 any, T2 any, T3 any, T4 any](val1 T1, val2 T2, val3 T3, val4 T4, err any, messageArgs ...interface{}) (T1, T2, T3, T4) { +func Must4[T1, T2, T3, T4 any](val1 T1, val2 T2, val3 T3, val4 T4, err any, messageArgs ...interface{}) (T1, T2, T3, T4) { must(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 any, T2 any, T3 any, T4 any, T5 any](val1 T1, val2 T2, val3 T3, val4 T4, val5 T5, err any, messageArgs ...interface{}) (T1, T2, T3, T4, T5) { +func Must5[T1, T2, T3, T4, T5 any](val1 T1, val2 T2, val3 T3, val4 T4, val5 T5, err any, messageArgs ...interface{}) (T1, T2, T3, T4, T5) { must(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 any, T2 any, T3 any, T4 any, T5 any, T6 any](val1 T1, val2 T2, val3 T3, val4 T4, val5 T5, val6 T6, err any, messageArgs ...interface{}) (T1, T2, T3, T4, T5, T6) { +func Must6[T1, T2, T3, T4, T5, T6 any](val1 T1, val2 T2, val3 T3, val4 T4, val5 T5, val6 T6, err any, messageArgs ...interface{}) (T1, T2, T3, T4, T5, T6) { must(err, messageArgs...) return val1, val2, val3, val4, val5, val6 } @@ -215,7 +215,7 @@ func TryOr1[A any](callback func() (A, error), fallbackA A) (A, bool) { // TryOr2 has the same behavior as Must, but returns a default value in case of error. // Play: https://go.dev/play/p/B4F7Wg2Zh9X -func TryOr2[A any, B any](callback func() (A, B, error), fallbackA A, fallbackB B) (A, B, bool) { +func TryOr2[A, B any](callback func() (A, B, error), fallbackA A, fallbackB B) (A, B, bool) { ok := false Try0(func() { @@ -232,7 +232,7 @@ func TryOr2[A any, B any](callback func() (A, B, error), fallbackA A, fallbackB // TryOr3 has the same behavior as Must, but returns a default value in case of error. // Play: https://go.dev/play/p/B4F7Wg2Zh9X -func TryOr3[A any, B any, C any](callback func() (A, B, C, error), fallbackA A, fallbackB B, fallbackC C) (A, B, C, bool) { +func TryOr3[A, B, C any](callback func() (A, B, C, error), fallbackA A, fallbackB B, fallbackC C) (A, B, C, bool) { ok := false Try0(func() { @@ -250,7 +250,7 @@ func TryOr3[A any, B any, C any](callback func() (A, B, C, error), fallbackA A, // TryOr4 has the same behavior as Must, but returns a default value in case of error. // Play: https://go.dev/play/p/B4F7Wg2Zh9X -func TryOr4[A any, B any, C any, D any](callback func() (A, B, C, D, error), fallbackA A, fallbackB B, fallbackC C, fallbackD D) (A, B, C, D, bool) { +func TryOr4[A, B, C, D any](callback func() (A, B, C, D, error), fallbackA A, fallbackB B, fallbackC C, fallbackD D) (A, B, C, D, bool) { ok := false Try0(func() { @@ -269,7 +269,7 @@ func TryOr4[A any, B any, C any, D any](callback func() (A, B, C, D, error), fal // TryOr5 has the same behavior as Must, but returns a default value in case of error. // Play: https://go.dev/play/p/B4F7Wg2Zh9X -func TryOr5[A any, B any, C any, D any, E any](callback func() (A, B, C, D, E, error), fallbackA A, fallbackB B, fallbackC C, fallbackD D, fallbackE E) (A, B, C, D, E, bool) { +func TryOr5[A, B, C, D, E any](callback func() (A, B, C, D, E, error), fallbackA A, fallbackB B, fallbackC C, fallbackD D, fallbackE E) (A, B, C, D, E, bool) { ok := false Try0(func() { @@ -289,7 +289,7 @@ func TryOr5[A any, B any, C any, D any, E any](callback func() (A, B, C, D, E, e // TryOr6 has the same behavior as Must, but returns a default value in case of error. // Play: https://go.dev/play/p/B4F7Wg2Zh9X -func TryOr6[A any, B any, C any, D any, E any, F any](callback func() (A, B, C, D, E, F, error), fallbackA A, fallbackB B, fallbackC C, fallbackD D, fallbackE E, fallbackF F) (A, B, C, D, E, F, bool) { +func TryOr6[A, B, C, D, E, F any](callback func() (A, B, C, D, E, F, error), fallbackA A, fallbackB B, fallbackC C, fallbackD D, fallbackE E, fallbackF F) (A, B, C, D, E, F, bool) { ok := false Try0(func() { diff --git a/errors_example_test.go b/errors_example_test.go deleted file mode 100644 index 1594a0c2..00000000 --- a/errors_example_test.go +++ /dev/null @@ -1,429 +0,0 @@ -package lo - -import ( - "fmt" -) - -func ExampleValidate() { - i := 42 - - err1 := Validate(i < 0, "expected %d < 0", i) - err2 := Validate(i > 0, "expected %d > 0", i) - - fmt.Printf("%v\n%v", err1, err2) - // Output: - // expected 42 < 0 - // -} - -func ExampleMust() { - defer func() { - _ = recover() - }() - - // won't panic - Must(42, nil) - - // won't panic - cb := func() (int, error) { - return 42, nil - } - Must(cb()) - - // will panic - Must(42, fmt.Errorf("my error")) - - // will panic with error message - Must(42, fmt.Errorf("world"), "hello") -} - -func ExampleMust0() { - defer func() { - _ = recover() - }() - - // won't panic - Must0(nil) - - // will panic - Must0(fmt.Errorf("my error")) - - // will panic with error message - Must0(fmt.Errorf("world"), "hello") -} - -func ExampleMust1() { - defer func() { - _ = recover() - }() - - // won't panic - Must1(42, nil) - - // won't panic - cb := func() (int, error) { - return 42, nil - } - Must1(cb()) - - // will panic - Must1(42, fmt.Errorf("my error")) - - // will panic with error message - Must1(42, fmt.Errorf("world"), "hello") -} - -func ExampleMust2() { - defer func() { - _ = recover() - }() - - // won't panic - Must2(42, "hello", nil) - - // will panic - Must2(42, "hello", fmt.Errorf("my error")) - - // will panic with error message - Must2(42, "hello", fmt.Errorf("world"), "hello") -} - -func ExampleMust3() { - defer func() { - _ = recover() - }() - - // won't panic - Must3(42, "hello", 4.2, nil) - - // will panic - Must3(42, "hello", 4.2, fmt.Errorf("my error")) - - // will panic with error message - Must3(42, "hello", 4.2, fmt.Errorf("world"), "hello") -} - -func ExampleMust4() { - defer func() { - _ = recover() - }() - - // won't panic - Must4(42, "hello", 4.2, true, nil) - - // will panic - Must4(42, "hello", 4.2, true, fmt.Errorf("my error")) - - // will panic with error message - Must4(42, "hello", 4.2, true, fmt.Errorf("world"), "hello") -} - -func ExampleMust5() { - defer func() { - _ = recover() - }() - - // won't panic - Must5(42, "hello", 4.2, true, foo{}, nil) - - // will panic - Must5(42, "hello", 4.2, true, foo{}, fmt.Errorf("my error")) - - // will panic with error message - Must5(42, "hello", 4.2, true, foo{}, fmt.Errorf("world"), "hello") -} - -func ExampleMust6() { - defer func() { - _ = recover() - }() - - // won't panic - Must5(42, "hello", 4.2, true, foo{}, "foobar", nil) - - // will panic - Must5(42, "hello", 4.2, true, foo{}, "foobar", fmt.Errorf("my error")) - - // will panic with error message - Must5(42, "hello", 4.2, true, foo{}, "foobar", fmt.Errorf("world"), "hello") -} - -func ExampleTry() { - ok1 := Try(func() error { - return nil - }) - ok2 := Try(func() error { - return fmt.Errorf("my error") - }) - ok3 := Try(func() error { - panic("my error") - }) - - fmt.Printf("%v\n", ok1) - fmt.Printf("%v\n", ok2) - fmt.Printf("%v\n", ok3) - // Output: - // true - // false - // false -} - -func ExampleTry1() { - ok1 := Try1(func() error { - return nil - }) - ok2 := Try1(func() error { - return fmt.Errorf("my error") - }) - ok3 := Try1(func() error { - panic("my error") - }) - - fmt.Printf("%v\n", ok1) - fmt.Printf("%v\n", ok2) - fmt.Printf("%v\n", ok3) - // Output: - // true - // false - // false -} - -func ExampleTry2() { - ok1 := Try2(func() (int, error) { - return 42, nil - }) - ok2 := Try2(func() (int, error) { - return 42, fmt.Errorf("my error") - }) - ok3 := Try2(func() (int, error) { - panic("my error") - }) - - fmt.Printf("%v\n", ok1) - fmt.Printf("%v\n", ok2) - fmt.Printf("%v\n", ok3) - // Output: - // true - // false - // false -} - -func ExampleTry3() { - ok1 := Try3(func() (int, string, error) { - return 42, "foobar", nil - }) - ok2 := Try3(func() (int, string, error) { - return 42, "foobar", fmt.Errorf("my error") - }) - ok3 := Try3(func() (int, string, error) { - panic("my error") - }) - - fmt.Printf("%v\n", ok1) - fmt.Printf("%v\n", ok2) - fmt.Printf("%v\n", ok3) - // Output: - // true - // false - // false -} - -func ExampleTry4() { - ok1 := Try4(func() (int, string, float64, error) { - return 42, "foobar", 4.2, nil - }) - ok2 := Try4(func() (int, string, float64, error) { - return 42, "foobar", 4.2, fmt.Errorf("my error") - }) - ok3 := Try4(func() (int, string, float64, error) { - panic("my error") - }) - - fmt.Printf("%v\n", ok1) - fmt.Printf("%v\n", ok2) - fmt.Printf("%v\n", ok3) - // Output: - // true - // false - // false -} - -func ExampleTry5() { - ok1 := Try5(func() (int, string, float64, bool, error) { - return 42, "foobar", 4.2, true, nil - }) - ok2 := Try5(func() (int, string, float64, bool, error) { - return 42, "foobar", 4.2, true, fmt.Errorf("my error") - }) - ok3 := Try5(func() (int, string, float64, bool, error) { - panic("my error") - }) - - fmt.Printf("%v\n", ok1) - fmt.Printf("%v\n", ok2) - fmt.Printf("%v\n", ok3) - // Output: - // true - // false - // false -} - -func ExampleTry6() { - ok1 := Try6(func() (int, string, float64, bool, foo, error) { - return 42, "foobar", 4.2, true, foo{}, nil - }) - ok2 := Try6(func() (int, string, float64, bool, foo, error) { - return 42, "foobar", 4.2, true, foo{}, fmt.Errorf("my error") - }) - ok3 := Try6(func() (int, string, float64, bool, foo, error) { - panic("my error") - }) - - fmt.Printf("%v\n", ok1) - fmt.Printf("%v\n", ok2) - fmt.Printf("%v\n", ok3) - // Output: - // true - // false - // false -} - -func ExampleTryOr() { - value1, ok1 := TryOr(func() (int, error) { - return 42, nil - }, 21) - value2, ok2 := TryOr(func() (int, error) { - return 42, fmt.Errorf("my error") - }, 21) - value3, ok3 := TryOr(func() (int, error) { - panic("my error") - }, 21) - - fmt.Printf("%v %v\n", value1, ok1) - fmt.Printf("%v %v\n", value2, ok2) - fmt.Printf("%v %v\n", value3, ok3) - // Output: - // 42 true - // 21 false - // 21 false -} - -func ExampleTryOr1() { - value1, ok1 := TryOr1(func() (int, error) { - return 42, nil - }, 21) - value2, ok2 := TryOr1(func() (int, error) { - return 42, fmt.Errorf("my error") - }, 21) - value3, ok3 := TryOr1(func() (int, error) { - panic("my error") - }, 21) - - fmt.Printf("%v %v\n", value1, ok1) - fmt.Printf("%v %v\n", value2, ok2) - fmt.Printf("%v %v\n", value3, ok3) - // Output: - // 42 true - // 21 false - // 21 false -} - -func ExampleTryOr2() { - value1, value2, ok3 := TryOr2(func() (int, string, error) { - panic("my error") - }, 21, "hello") - - fmt.Printf("%v %v %v\n", value1, value2, ok3) - // Output: 21 hello false -} - -func ExampleTryOr3() { - value1, value2, value3, ok3 := TryOr3(func() (int, string, bool, error) { - panic("my error") - }, 21, "hello", false) - - fmt.Printf("%v %v %v %v\n", value1, value2, value3, ok3) - // Output: 21 hello false false -} - -func ExampleTryOr4() { - value1, value2, value3, value4, ok3 := TryOr4(func() (int, string, bool, foo, error) { - panic("my error") - }, 21, "hello", false, foo{bar: "bar"}) - - fmt.Printf("%v %v %v %v %v\n", value1, value2, value3, value4, ok3) - // Output: 21 hello false {bar} false -} - -func ExampleTryOr5() { - value1, value2, value3, value4, value5, ok3 := TryOr5(func() (int, string, bool, foo, float64, error) { - panic("my error") - }, 21, "hello", false, foo{bar: "bar"}, 4.2) - - fmt.Printf("%v %v %v %v %v %v\n", value1, value2, value3, value4, value5, ok3) - // Output: 21 hello false {bar} 4.2 false -} -func ExampleTryOr6() { - value1, value2, value3, value4, value5, value6, ok3 := TryOr6(func() (int, string, bool, foo, float64, string, error) { - panic("my error") - }, 21, "hello", false, foo{bar: "bar"}, 4.2, "world") - - fmt.Printf("%v %v %v %v %v %v %v\n", value1, value2, value3, value4, value5, value6, ok3) - // Output: 21 hello false {bar} 4.2 world false -} - -func ExampleTryWithErrorValue() { - err1, ok1 := TryWithErrorValue(func() error { - return nil - }) - err2, ok2 := TryWithErrorValue(func() error { - return fmt.Errorf("my error") - }) - err3, ok3 := TryWithErrorValue(func() error { - panic("my error") - }) - - fmt.Printf("%v %v\n", err1, ok1) - fmt.Printf("%v %v\n", err2, ok2) - fmt.Printf("%v %v\n", err3, ok3) - // Output: - // true - // my error false - // my error false -} - -func ExampleTryCatchWithErrorValue() { - TryCatchWithErrorValue( - func() error { - panic("trigger an error") - }, - func(err any) { - fmt.Printf("catch: %s", err) - }, - ) - - // Output: catch: trigger an error -} - -type myError struct { -} - -func (e myError) Error() string { - return "my error" -} - -func ExampleErrorsAs() { - doSomething := func() error { - return &myError{} - } - - err := doSomething() - - if rateLimitErr, ok := ErrorsAs[*myError](err); ok { - fmt.Printf("is type myError, err: %s", rateLimitErr.Error()) - } else { - fmt.Printf("is not type myError") - } - - // Output: is type myError, err: my error -} diff --git a/errors_test.go b/errors_test.go deleted file mode 100644 index 08cace9d..00000000 --- a/errors_test.go +++ /dev/null @@ -1,599 +0,0 @@ -package lo - -import ( - "errors" - "fmt" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestValidate(t *testing.T) { - is := assert.New(t) - - slice := []string{"a"} - result1 := Validate(len(slice) == 0, "Slice should be empty but contains %v", slice) - - slice = []string{} - result2 := Validate(len(slice) == 0, "Slice should be empty but contains %v", slice) - - is.Error(result1) - is.NoError(result2) -} - -func TestMust(t *testing.T) { - t.Parallel() - is := assert.New(t) - - is.Equal("foo", Must("foo", nil)) - is.PanicsWithValue("something went wrong", func() { - Must("", errors.New("something went wrong")) - }) - is.PanicsWithValue("operation shouldn't fail: something went wrong", func() { - Must("", errors.New("something went wrong"), "operation shouldn't fail") - }) - is.PanicsWithValue("operation shouldn't fail with foo: something went wrong", func() { - Must("", errors.New("something went wrong"), "operation shouldn't fail with %s", "foo") - }) - - is.Equal(1, Must(1, true)) - is.PanicsWithValue("not ok", func() { - Must(1, false) - }) - is.PanicsWithValue("operation shouldn't fail", func() { - Must(1, false, "operation shouldn't fail") - }) - is.PanicsWithValue("operation shouldn't fail with foo", func() { - Must(1, false, "operation shouldn't fail with %s", "foo") - }) - - cb := func() error { - return assert.AnError - } - is.PanicsWithValue("operation should fail: assert.AnError general error for testing", func() { - Must0(cb(), "operation should fail") - }) - - is.PanicsWithValue("must: invalid err type 'int', should either be a bool or an error", func() { - Must0(0) - }) - is.PanicsWithValue("must: invalid err type 'string', should either be a bool or an error", func() { - Must0("error") - }) -} - -func TestMustX(t *testing.T) { - t.Parallel() - is := assert.New(t) - - { - is.PanicsWithValue("something went wrong", func() { - Must0(errors.New("something went wrong")) - }) - is.PanicsWithValue("operation shouldn't fail with foo: something went wrong", func() { - Must0(errors.New("something went wrong"), "operation shouldn't fail with %s", "foo") - }) - is.NotPanics(func() { - Must0(nil) - }) - } - - { - val1 := Must1(1, nil) - is.Equal(1, val1) - is.PanicsWithValue("something went wrong", func() { - Must1(1, errors.New("something went wrong")) - }) - is.PanicsWithValue("operation shouldn't fail with foo: something went wrong", func() { - Must1(1, errors.New("something went wrong"), "operation shouldn't fail with %s", "foo") - }) - } - - { - val1, val2 := Must2(1, 2, nil) - is.Equal(1, val1) - is.Equal(2, val2) - is.PanicsWithValue("something went wrong", func() { - Must2(1, 2, errors.New("something went wrong")) - }) - is.PanicsWithValue("operation shouldn't fail with foo: something went wrong", func() { - Must2(1, 2, errors.New("something went wrong"), "operation shouldn't fail with %s", "foo") - }) - } - - { - val1, val2, val3 := Must3(1, 2, 3, nil) - is.Equal(1, val1) - is.Equal(2, val2) - is.Equal(3, val3) - is.PanicsWithValue("something went wrong", func() { - Must3(1, 2, 3, errors.New("something went wrong")) - }) - is.PanicsWithValue("operation shouldn't fail with foo: something went wrong", func() { - Must3(1, 2, 3, errors.New("something went wrong"), "operation shouldn't fail with %s", "foo") - }) - } - - { - val1, val2, val3, val4 := Must4(1, 2, 3, 4, nil) - is.Equal(1, val1) - is.Equal(2, val2) - is.Equal(3, val3) - is.Equal(4, val4) - is.PanicsWithValue("something went wrong", func() { - Must4(1, 2, 3, 4, errors.New("something went wrong")) - }) - is.PanicsWithValue("operation shouldn't fail with foo: something went wrong", func() { - Must4(1, 2, 3, 4, errors.New("something went wrong"), "operation shouldn't fail with %s", "foo") - }) - } - - { - val1, val2, val3, val4, val5 := Must5(1, 2, 3, 4, 5, nil) - is.Equal(1, val1) - is.Equal(2, val2) - is.Equal(3, val3) - is.Equal(4, val4) - is.Equal(5, val5) - is.PanicsWithValue("something went wrong", func() { - Must5(1, 2, 3, 4, 5, errors.New("something went wrong")) - }) - is.PanicsWithValue("operation shouldn't fail with foo: something went wrong", func() { - Must5(1, 2, 3, 4, 5, errors.New("something went wrong"), "operation shouldn't fail with %s", "foo") - }) - } - - { - val1, val2, val3, val4, val5, val6 := Must6(1, 2, 3, 4, 5, 6, nil) - is.Equal(1, val1) - is.Equal(2, val2) - is.Equal(3, val3) - is.Equal(4, val4) - is.Equal(5, val5) - is.Equal(6, val6) - is.PanicsWithValue("something went wrong", func() { - Must6(1, 2, 3, 4, 5, 6, errors.New("something went wrong")) - }) - is.PanicsWithValue("operation shouldn't fail with foo: something went wrong", func() { - Must6(1, 2, 3, 4, 5, 6, errors.New("something went wrong"), "operation shouldn't fail with %s", "foo") - }) - } - - { - is.PanicsWithValue("not ok", func() { - Must0(false) - }) - is.PanicsWithValue("operation shouldn't fail with foo", func() { - Must0(false, "operation shouldn't fail with %s", "foo") - }) - is.NotPanics(func() { - Must0(true) - }) - } - - { - val1 := Must1(1, true) - is.Equal(1, val1) - is.PanicsWithValue("not ok", func() { - Must1(1, false) - }) - is.PanicsWithValue("operation shouldn't fail with foo", func() { - Must1(1, false, "operation shouldn't fail with %s", "foo") - }) - } - - { - val1, val2 := Must2(1, 2, true) - is.Equal(1, val1) - is.Equal(2, val2) - is.PanicsWithValue("not ok", func() { - Must2(1, 2, false) - }) - is.PanicsWithValue("operation shouldn't fail with foo", func() { - Must2(1, 2, false, "operation shouldn't fail with %s", "foo") - }) - } - - { - val1, val2, val3 := Must3(1, 2, 3, true) - is.Equal(1, val1) - is.Equal(2, val2) - is.Equal(3, val3) - is.PanicsWithValue("not ok", func() { - Must3(1, 2, 3, false) - }) - is.PanicsWithValue("operation shouldn't fail with foo", func() { - Must3(1, 2, 3, false, "operation shouldn't fail with %s", "foo") - }) - } - - { - val1, val2, val3, val4 := Must4(1, 2, 3, 4, true) - is.Equal(1, val1) - is.Equal(2, val2) - is.Equal(3, val3) - is.Equal(4, val4) - is.PanicsWithValue("not ok", func() { - Must4(1, 2, 3, 4, false) - }) - is.PanicsWithValue("operation shouldn't fail with foo", func() { - Must4(1, 2, 3, 4, false, "operation shouldn't fail with %s", "foo") - }) - } - - { - val1, val2, val3, val4, val5 := Must5(1, 2, 3, 4, 5, true) - is.Equal(1, val1) - is.Equal(2, val2) - is.Equal(3, val3) - is.Equal(4, val4) - is.Equal(5, val5) - is.PanicsWithValue("not ok", func() { - Must5(1, 2, 3, 4, 5, false) - }) - is.PanicsWithValue("operation shouldn't fail with foo", func() { - Must5(1, 2, 3, 4, 5, false, "operation shouldn't fail with %s", "foo") - }) - } - - { - val1, val2, val3, val4, val5, val6 := Must6(1, 2, 3, 4, 5, 6, true) - is.Equal(1, val1) - is.Equal(2, val2) - is.Equal(3, val3) - is.Equal(4, val4) - is.Equal(5, val5) - is.Equal(6, val6) - is.PanicsWithValue("not ok", func() { - Must6(1, 2, 3, 4, 5, 6, false) - }) - is.PanicsWithValue("operation shouldn't fail with foo", func() { - Must6(1, 2, 3, 4, 5, 6, false, "operation shouldn't fail with %s", "foo") - }) - } -} - -func TestTry(t *testing.T) { - t.Parallel() - is := assert.New(t) - - is.False(Try(func() error { - panic("error") - })) - is.True(Try(func() error { - return nil - })) - is.False(Try(func() error { - return fmt.Errorf("fail") - })) -} - -func TestTryX(t *testing.T) { - t.Parallel() - is := assert.New(t) - - is.True(Try1(func() error { - return nil - })) - - is.True(Try2(func() (string, error) { - return "", nil - })) - - is.True(Try3(func() (string, string, error) { - return "", "", nil - })) - - is.True(Try4(func() (string, string, string, error) { - return "", "", "", nil - })) - - is.True(Try5(func() (string, string, string, string, error) { - return "", "", "", "", nil - })) - - is.True(Try6(func() (string, string, string, string, string, error) { - return "", "", "", "", "", nil - })) - - is.False(Try1(func() error { - panic("error") - })) - - is.False(Try2(func() (string, error) { - panic("error") - })) - - is.False(Try3(func() (string, string, error) { - panic("error") - })) - - is.False(Try4(func() (string, string, string, error) { - panic("error") - })) - - is.False(Try5(func() (string, string, string, string, error) { - panic("error") - })) - - is.False(Try6(func() (string, string, string, string, string, error) { - panic("error") - })) - - is.False(Try1(func() error { - return errors.New("foo") - })) - - is.False(Try2(func() (string, error) { - return "", errors.New("foo") - })) - - is.False(Try3(func() (string, string, error) { - return "", "", errors.New("foo") - })) - - is.False(Try4(func() (string, string, string, error) { - return "", "", "", errors.New("foo") - })) - - is.False(Try5(func() (string, string, string, string, error) { - return "", "", "", "", errors.New("foo") - })) - - is.False(Try6(func() (string, string, string, string, string, error) { - return "", "", "", "", "", errors.New("foo") - })) -} - -func TestTryOr(t *testing.T) { - t.Parallel() - is := assert.New(t) - - a1, ok1 := TryOr(func() (int, error) { panic("error") }, 42) - a2, ok2 := TryOr(func() (int, error) { return 21, assert.AnError }, 42) - a3, ok3 := TryOr(func() (int, error) { return 21, nil }, 42) - - is.Equal(42, a1) - is.False(ok1) - - is.Equal(42, a2) - is.False(ok2) - - is.Equal(21, a3) - is.True(ok3) -} - -func TestTryOrX(t *testing.T) { - t.Parallel() - is := assert.New(t) - - { - a1, ok1 := TryOr1(func() (int, error) { panic("error") }, 42) - a2, ok2 := TryOr1(func() (int, error) { return 21, assert.AnError }, 42) - a3, ok3 := TryOr1(func() (int, error) { return 21, nil }, 42) - - is.Equal(42, a1) - is.False(ok1) - - is.Equal(42, a2) - is.False(ok2) - - is.Equal(21, a3) - is.True(ok3) - } - - { - a1, b1, ok1 := TryOr2(func() (int, string, error) { panic("error") }, 42, "hello") - a2, b2, ok2 := TryOr2(func() (int, string, error) { return 21, "world", assert.AnError }, 42, "hello") - a3, b3, ok3 := TryOr2(func() (int, string, error) { return 21, "world", nil }, 42, "hello") - - is.Equal(42, a1) - is.Equal("hello", b1) - is.False(ok1) - - is.Equal(42, a2) - is.Equal("hello", b2) - is.False(ok2) - - is.Equal(21, a3) - is.Equal("world", b3) - is.True(ok3) - } - - { - a1, b1, c1, ok1 := TryOr3(func() (int, string, bool, error) { panic("error") }, 42, "hello", false) - a2, b2, c2, ok2 := TryOr3(func() (int, string, bool, error) { return 21, "world", true, assert.AnError }, 42, "hello", false) - a3, b3, c3, ok3 := TryOr3(func() (int, string, bool, error) { return 21, "world", true, nil }, 42, "hello", false) - - is.Equal(42, a1) - is.Equal("hello", b1) - is.Equal(false, c1) - is.False(ok1) - - is.Equal(42, a2) - is.Equal("hello", b2) - is.Equal(false, c2) - is.False(ok2) - - is.Equal(21, a3) - is.Equal("world", b3) - is.Equal(true, c3) - is.True(ok3) - } - - { - a1, b1, c1, d1, ok1 := TryOr4(func() (int, string, bool, int, error) { panic("error") }, 42, "hello", false, 42) - a2, b2, c2, d2, ok2 := TryOr4(func() (int, string, bool, int, error) { return 21, "world", true, 21, assert.AnError }, 42, "hello", false, 42) - a3, b3, c3, d3, ok3 := TryOr4(func() (int, string, bool, int, error) { return 21, "world", true, 21, nil }, 42, "hello", false, 42) - - is.Equal(42, a1) - is.Equal("hello", b1) - is.Equal(false, c1) - is.Equal(42, d1) - is.False(ok1) - - is.Equal(42, a2) - is.Equal("hello", b2) - is.Equal(false, c2) - is.Equal(42, d2) - is.False(ok2) - - is.Equal(21, a3) - is.Equal("world", b3) - is.Equal(true, c3) - is.Equal(21, d3) - is.True(ok3) - } - - { - a1, b1, c1, d1, e1, ok1 := TryOr5(func() (int, string, bool, int, int, error) { panic("error") }, 42, "hello", false, 42, 42) - a2, b2, c2, d2, e2, ok2 := TryOr5(func() (int, string, bool, int, int, error) { return 21, "world", true, 21, 21, assert.AnError }, 42, "hello", false, 42, 42) - a3, b3, c3, d3, e3, ok3 := TryOr5(func() (int, string, bool, int, int, error) { return 21, "world", true, 21, 21, nil }, 42, "hello", false, 42, 42) - - is.Equal(42, a1) - is.Equal("hello", b1) - is.Equal(false, c1) - is.Equal(42, d1) - is.Equal(42, e1) - is.False(ok1) - - is.Equal(42, a2) - is.Equal("hello", b2) - is.Equal(false, c2) - is.Equal(42, d2) - is.Equal(42, e2) - is.False(ok2) - - is.Equal(21, a3) - is.Equal("world", b3) - is.Equal(true, c3) - is.Equal(21, d3) - is.Equal(21, e3) - is.True(ok3) - } - - { - a1, b1, c1, d1, e1, f1, ok1 := TryOr6(func() (int, string, bool, int, int, int, error) { panic("error") }, 42, "hello", false, 42, 42, 42) - a2, b2, c2, d2, e2, f2, ok2 := TryOr6(func() (int, string, bool, int, int, int, error) { return 21, "world", true, 21, 21, 21, assert.AnError }, 42, "hello", false, 42, 42, 42) - a3, b3, c3, d3, e3, f3, ok3 := TryOr6(func() (int, string, bool, int, int, int, error) { return 21, "world", true, 21, 21, 21, nil }, 42, "hello", false, 42, 42, 42) - - is.Equal(42, a1) - is.Equal("hello", b1) - is.Equal(false, c1) - is.Equal(42, d1) - is.Equal(42, e1) - is.Equal(42, f1) - is.False(ok1) - - is.Equal(42, a2) - is.Equal("hello", b2) - is.Equal(false, c2) - is.Equal(42, d2) - is.Equal(42, e2) - is.Equal(42, f2) - is.False(ok2) - - is.Equal(21, a3) - is.Equal("world", b3) - is.Equal(true, c3) - is.Equal(21, d3) - is.Equal(21, e3) - is.Equal(21, f3) - is.True(ok3) - } -} - -func TestTryWithErrorValue(t *testing.T) { - t.Parallel() - is := assert.New(t) - - err, ok := TryWithErrorValue(func() error { - // getting error in case of panic, using recover function - panic("error") - }) - is.False(ok) - is.Equal("error", err) - - err, ok = TryWithErrorValue(func() error { - return errors.New("foo") - }) - is.False(ok) - is.EqualError(err.(error), "foo") - - err, ok = TryWithErrorValue(func() error { - return nil - }) - is.True(ok) - is.Equal(nil, err) -} - -func TestTryCatch(t *testing.T) { - t.Parallel() - is := assert.New(t) - - caught := false - TryCatch(func() error { - panic("error") - }, func() { - //error was caught - caught = true - }) - is.True(caught) - - caught = false - TryCatch(func() error { - return nil - }, func() { - //no error to be caught - caught = true - }) - is.False(caught) -} - -func TestTryCatchWithErrorValue(t *testing.T) { - t.Parallel() - is := assert.New(t) - - caught := false - TryCatchWithErrorValue(func() error { - panic("error") - }, func(val any) { - //error was caught - caught = val == "error" - }) - is.True(caught) - - caught = false - TryCatchWithErrorValue(func() error { - return nil - }, func(val any) { - //no error to be caught - caught = true - }) - is.False(caught) -} - -type internalError struct { - foobar string -} - -func (e *internalError) Error() string { - return "internal error" -} - -func TestErrorsAs(t *testing.T) { - t.Parallel() - is := assert.New(t) - - err, ok := ErrorsAs[*internalError](fmt.Errorf("hello world")) - is.False(ok) - is.Nil(nil, err) - - err, ok = ErrorsAs[*internalError](&internalError{foobar: "foobar"}) - is.True(ok) - is.Equal(&internalError{foobar: "foobar"}, err) - - err, ok = ErrorsAs[*internalError](nil) - is.False(ok) - is.Nil(nil, err) -} diff --git a/find.go b/find.go index f8caeb89..93329a0b 100644 --- a/find.go +++ b/find.go @@ -3,6 +3,7 @@ package lo import ( "fmt" "math/rand" + "time" "golang.org/x/exp/constraints" ) @@ -12,8 +13,8 @@ import ( // IndexOf returns the index at which the first occurrence of a value is found in an array or return -1 // if the value cannot be found. func IndexOf[T comparable](collection []T, element T) int { - for i, item := range collection { - if item == element { + for i := range collection { + if collection[i] == element { return i } } @@ -37,9 +38,9 @@ func LastIndexOf[T comparable](collection []T, element T) int { // Find search an element in a slice based on a predicate. It returns element and true if element was found. func Find[T any](collection []T, predicate func(item T) bool) (T, bool) { - for _, item := range collection { - if predicate(item) { - return item, true + for i := range collection { + if predicate(collection[i]) { + return collection[i], true } } @@ -50,9 +51,9 @@ func Find[T any](collection []T, predicate func(item T) bool) (T, bool) { // FindIndexOf searches an element in a slice based on a predicate and returns the index and true. // It returns -1 and false if the element is not found. func FindIndexOf[T any](collection []T, predicate func(item T) bool) (T, int, bool) { - for i, item := range collection { - if predicate(item) { - return item, i, true + for i := range collection { + if predicate(collection[i]) { + return collection[i], i, true } } @@ -77,9 +78,9 @@ func FindLastIndexOf[T any](collection []T, predicate func(item T) bool) (T, int // FindOrElse search an element in a slice based on a predicate. It returns the element if found or a given fallback value otherwise. func FindOrElse[T any](collection []T, fallback T, predicate func(item T) bool) T { - for _, item := range collection { - if predicate(item) { - return item + for i := range collection { + if predicate(collection[i]) { + return collection[i] } } @@ -88,8 +89,8 @@ func FindOrElse[T any](collection []T, fallback T, predicate func(item T) bool) // FindKey returns the key of the first value matching. func FindKey[K comparable, V comparable](object map[K]V, value V) (K, bool) { - for k, v := range object { - if v == value { + for k := range object { + if object[k] == value { return k, true } } @@ -99,8 +100,8 @@ func FindKey[K comparable, V comparable](object map[K]V, value V) (K, bool) { // FindKeyBy returns the key of the first element predicate returns truthy for. func FindKeyBy[K comparable, V any](object map[K]V, predicate func(key K, value V) bool) (K, bool) { - for k, v := range object { - if predicate(k, v) { + for k := range object { + if predicate(k, object[k]) { return k, true } } @@ -113,20 +114,20 @@ func FindKeyBy[K comparable, V any](object map[K]V, predicate func(key K, value func FindUniques[T comparable](collection []T) []T { isDupl := make(map[T]bool, len(collection)) - for _, item := range collection { - duplicated, ok := isDupl[item] + for i := range collection { + duplicated, ok := isDupl[collection[i]] if !ok { - isDupl[item] = false + isDupl[collection[i]] = false } else if !duplicated { - isDupl[item] = true + isDupl[collection[i]] = true } } result := make([]T, 0, len(collection)-len(isDupl)) - for _, item := range collection { - if duplicated := isDupl[item]; !duplicated { - result = append(result, item) + for i := range collection { + if duplicated := isDupl[collection[i]]; !duplicated { + result = append(result, collection[i]) } } @@ -139,8 +140,8 @@ func FindUniques[T comparable](collection []T) []T { func FindUniquesBy[T any, U comparable](collection []T, iteratee func(item T) U) []T { isDupl := make(map[U]bool, len(collection)) - for _, item := range collection { - key := iteratee(item) + for i := range collection { + key := iteratee(collection[i]) duplicated, ok := isDupl[key] if !ok { @@ -152,11 +153,11 @@ func FindUniquesBy[T any, U comparable](collection []T, iteratee func(item T) U) result := make([]T, 0, len(collection)-len(isDupl)) - for _, item := range collection { - key := iteratee(item) + for i := range collection { + key := iteratee(collection[i]) if duplicated := isDupl[key]; !duplicated { - result = append(result, item) + result = append(result, collection[i]) } } @@ -168,21 +169,21 @@ func FindUniquesBy[T any, U comparable](collection []T, iteratee func(item T) U) func FindDuplicates[T comparable](collection []T) []T { isDupl := make(map[T]bool, len(collection)) - for _, item := range collection { - duplicated, ok := isDupl[item] + for i := range collection { + duplicated, ok := isDupl[collection[i]] if !ok { - isDupl[item] = false + isDupl[collection[i]] = false } else if !duplicated { - isDupl[item] = true + isDupl[collection[i]] = true } } result := make([]T, 0, len(collection)-len(isDupl)) - for _, item := range collection { - if duplicated := isDupl[item]; duplicated { - result = append(result, item) - isDupl[item] = false + for i := range collection { + if duplicated := isDupl[collection[i]]; duplicated { + result = append(result, collection[i]) + isDupl[collection[i]] = false } } @@ -195,8 +196,8 @@ func FindDuplicates[T comparable](collection []T) []T { func FindDuplicatesBy[T any, U comparable](collection []T, iteratee func(item T) U) []T { isDupl := make(map[U]bool, len(collection)) - for _, item := range collection { - key := iteratee(item) + for i := range collection { + key := iteratee(collection[i]) duplicated, ok := isDupl[key] if !ok { @@ -208,11 +209,11 @@ func FindDuplicatesBy[T any, U comparable](collection []T, iteratee func(item T) result := make([]T, 0, len(collection)-len(isDupl)) - for _, item := range collection { - key := iteratee(item) + for i := range collection { + key := iteratee(collection[i]) if duplicated := isDupl[key]; duplicated { - result = append(result, item) + result = append(result, collection[i]) isDupl[key] = false } } @@ -221,7 +222,7 @@ func FindDuplicatesBy[T any, U comparable](collection []T, iteratee func(item T) } // Min search the minimum value of a collection. -// Returns zero value when collection is empty. +// Returns zero value when the collection is empty. func Min[T constraints.Ordered](collection []T) T { var min T @@ -244,7 +245,7 @@ func Min[T constraints.Ordered](collection []T) T { // 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 collection is empty. +// Returns zero value when the collection is empty. func MinBy[T any](collection []T, comparison func(a T, b T) bool) T { var min T @@ -265,8 +266,30 @@ func MinBy[T any](collection []T, comparison func(a T, b T) bool) T { return min } +// Earliest search the minimum time.Time of a collection. +// Returns zero value when the collection is empty. +func Earliest(times ...time.Time) time.Time { + var min time.Time + + if len(times) == 0 { + return min + } + + min = times[0] + + for i := 1; i < len(times); i++ { + item := times[i] + + if item.Before(min) { + min = item + } + } + + return min +} + // Max searches the maximum value of a collection. -// Returns zero value when collection is empty. +// Returns zero value when the collection is empty. func Max[T constraints.Ordered](collection []T) T { var max T @@ -289,7 +312,7 @@ func Max[T constraints.Ordered](collection []T) T { // 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 collection is empty. +// Returns zero value when the collection is empty. func MaxBy[T any](collection []T, comparison func(a T, b T) bool) T { var max T @@ -310,16 +333,82 @@ func MaxBy[T any](collection []T, comparison func(a T, b T) bool) T { return max } +// Latest search the maximum time.Time of a collection. +// Returns zero value when the collection is empty. +func Latest(times ...time.Time) time.Time { + var max time.Time + + if len(times) == 0 { + return max + } + + max = times[0] + + for i := 1; i < len(times); i++ { + item := times[i] + + if item.After(max) { + max = item + } + } + + return max +} + +// First returns the first element of a collection and check for availability of the first element. +func First[T any](collection []T) (T, bool) { + length := len(collection) + + if length == 0 { + var t T + return t, false + } + + return collection[0], true +} + +// FirstOrEmpty returns the first element of a collection or zero value if empty. +func FirstOrEmpty[T any](collection []T) T { + i, _ := First(collection) + return i +} + +// FirstOr returns the first element of a collection or the fallback value if empty. +func FirstOr[T any](collection []T, fallback T) T { + i, ok := First(collection) + if !ok { + return fallback + } + + return i +} + // Last returns the last element of a collection or error if empty. -func Last[T any](collection []T) (T, error) { +func Last[T any](collection []T) (T, bool) { length := len(collection) if length == 0 { var t T - return t, fmt.Errorf("last: cannot extract the last element of an empty slice") + return t, false + } + + return collection[length-1], true +} + +// Returns the last element of a collection or zero value if empty. +func LastOrEmpty[T any](collection []T) T { + i, _ := Last(collection) + return i +} + +// LastOr returns the last element of a collection or the fallback value if empty. +func LastOr[T any](collection []T, fallback T) T { + i, ok := Last(collection) + if !ok { + return fallback } - return collection[length-1], nil + return i } // Nth returns the element at index `nth` of collection. If `nth` is negative, the nth element @@ -345,6 +434,8 @@ func Sample[T any](collection []T) T { return Empty[T]() } + // @TODO: Upgrade to math/rand/v2 as soon as we set the minimum Go version to 1.22. + // bearer:disable go_gosec_crypto_weak_random return collection[rand.Intn(size)] } @@ -359,6 +450,8 @@ func Samples[T any](collection []T, count int) []T { for i := 0; i < size && i < count; i++ { copyLength := size - i + // @TODO: Upgrade to math/rand/v2 as soon as we set the minimum Go version to 1.22. + // bearer:disable go_gosec_crypto_weak_random index := rand.Intn(size - i) results = append(results, copy[index]) diff --git a/find_test.go b/find_test.go deleted file mode 100644 index 35f51799..00000000 --- a/find_test.go +++ /dev/null @@ -1,396 +0,0 @@ -package lo - -import ( - "fmt" - "math/rand" - "sort" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestIndexOf(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := IndexOf([]int{0, 1, 2, 1, 2, 3}, 2) - result2 := IndexOf([]int{0, 1, 2, 1, 2, 3}, 6) - - is.Equal(result1, 2) - is.Equal(result2, -1) -} - -func TestLastIndexOf(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := LastIndexOf([]int{0, 1, 2, 1, 2, 3}, 2) - result2 := LastIndexOf([]int{0, 1, 2, 1, 2, 3}, 6) - - is.Equal(result1, 4) - is.Equal(result2, -1) -} - -func TestFind(t *testing.T) { - t.Parallel() - is := assert.New(t) - - index := 0 - result1, ok1 := Find([]string{"a", "b", "c", "d"}, func(item string) bool { - is.Equal([]string{"a", "b", "c", "d"}[index], item) - index++ - return item == "b" - }) - - result2, ok2 := Find([]string{"foobar"}, func(item string) bool { - is.Equal("foobar", item) - return item == "b" - }) - - is.Equal(ok1, true) - is.Equal(result1, "b") - is.Equal(ok2, false) - is.Equal(result2, "") -} - -func TestFindIndexOf(t *testing.T) { - t.Parallel() - is := assert.New(t) - - index := 0 - item1, index1, ok1 := FindIndexOf([]string{"a", "b", "c", "d", "b"}, func(item string) bool { - is.Equal([]string{"a", "b", "c", "d", "b"}[index], item) - index++ - return item == "b" - }) - item2, index2, ok2 := FindIndexOf([]string{"foobar"}, func(item string) bool { - is.Equal("foobar", item) - return item == "b" - }) - - is.Equal(item1, "b") - is.Equal(ok1, true) - is.Equal(index1, 1) - is.Equal(item2, "") - is.Equal(ok2, false) - is.Equal(index2, -1) -} - -func TestFindLastIndexOf(t *testing.T) { - t.Parallel() - is := assert.New(t) - - index := 0 - item1, index1, ok1 := FindLastIndexOf([]string{"a", "b", "c", "d", "b"}, func(item string) bool { - is.Equal([]string{"b", "d", "c", "b", "a"}[index], item) - index++ - return item == "b" - }) - item2, index2, ok2 := FindLastIndexOf([]string{"foobar"}, func(item string) bool { - is.Equal("foobar", item) - return item == "b" - }) - - is.Equal(item1, "b") - is.Equal(ok1, true) - is.Equal(index1, 4) - is.Equal(item2, "") - is.Equal(ok2, false) - is.Equal(index2, -1) -} - -func TestFindOrElse(t *testing.T) { - t.Parallel() - is := assert.New(t) - - index := 0 - result1 := FindOrElse([]string{"a", "b", "c", "d"}, "x", func(item string) bool { - is.Equal([]string{"a", "b", "c", "d"}[index], item) - index++ - return item == "b" - }) - result2 := FindOrElse([]string{"foobar"}, "x", func(item string) bool { - is.Equal("foobar", item) - return item == "b" - }) - - is.Equal(result1, "b") - is.Equal(result2, "x") -} - -func TestFindKey(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1, ok1 := FindKey(map[string]int{"foo": 1, "bar": 2, "baz": 3}, 2) - is.Equal("bar", result1) - is.True(ok1) - - result2, ok2 := FindKey(map[string]int{"foo": 1, "bar": 2, "baz": 3}, 42) - is.Equal("", result2) - is.False(ok2) - - type test struct { - foobar string - } - - result3, ok3 := FindKey(map[string]test{"foo": {"foo"}, "bar": {"bar"}, "baz": {"baz"}}, test{"foo"}) - is.Equal("foo", result3) - is.True(ok3) - - result4, ok4 := FindKey(map[string]test{"foo": {"foo"}, "bar": {"bar"}, "baz": {"baz"}}, test{"hello world"}) - is.Equal("", result4) - is.False(ok4) -} - -func TestFindKeyBy(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1, ok1 := FindKeyBy(map[string]int{"foo": 1, "bar": 2, "baz": 3}, func(k string, v int) bool { - return k == "foo" - }) - is.Equal("foo", result1) - is.True(ok1) - - result2, ok2 := FindKeyBy(map[string]int{"foo": 1, "bar": 2, "baz": 3}, func(k string, v int) bool { - return false - }) - is.Equal("", result2) - is.False(ok2) -} - -func TestFindUniques(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := FindUniques([]int{1, 2, 3}) - - is.Equal(3, len(result1)) - is.Equal([]int{1, 2, 3}, result1) - - result2 := FindUniques([]int{1, 2, 2, 3, 1, 2}) - - is.Equal(1, len(result2)) - is.Equal([]int{3}, result2) - - result3 := FindUniques([]int{1, 2, 2, 1}) - - is.Equal(0, len(result3)) - is.Equal([]int{}, result3) - - result4 := FindUniques([]int{}) - - is.Equal(0, len(result4)) - is.Equal([]int{}, result4) -} - -func TestFindUniquesBy(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := FindUniquesBy([]int{0, 1, 2}, func(i int) int { - return i % 3 - }) - - is.Equal(3, len(result1)) - is.Equal([]int{0, 1, 2}, result1) - - result2 := FindUniquesBy([]int{0, 1, 2, 3, 4}, func(i int) int { - return i % 3 - }) - - is.Equal(1, len(result2)) - is.Equal([]int{2}, result2) - - result3 := FindUniquesBy([]int{0, 1, 2, 3, 4, 5}, func(i int) int { - return i % 3 - }) - - is.Equal(0, len(result3)) - is.Equal([]int{}, result3) - - result4 := FindUniquesBy([]int{}, func(i int) int { - return i % 3 - }) - - is.Equal(0, len(result4)) - is.Equal([]int{}, result4) -} - -func TestFindDuplicates(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := FindDuplicates([]int{1, 2, 2, 1, 2, 3}) - - is.Equal(2, len(result1)) - is.Equal([]int{1, 2}, result1) - - result2 := FindDuplicates([]int{1, 2, 3}) - - is.Equal(0, len(result2)) - is.Equal([]int{}, result2) - - result3 := FindDuplicates([]int{}) - - is.Equal(0, len(result3)) - is.Equal([]int{}, result3) -} - -func TestFindDuplicatesBy(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := FindDuplicatesBy([]int{3, 4, 5, 6, 7}, func(i int) int { - return i % 3 - }) - - is.Equal(2, len(result1)) - is.Equal([]int{3, 4}, result1) - - result2 := FindDuplicatesBy([]int{0, 1, 2, 3, 4}, func(i int) int { - return i % 5 - }) - - is.Equal(0, len(result2)) - is.Equal([]int{}, result2) - - result3 := FindDuplicatesBy([]int{}, func(i int) int { - return i % 3 - }) - - is.Equal(0, len(result3)) - is.Equal([]int{}, result3) -} - -func TestMin(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := Min([]int{1, 2, 3}) - result2 := Min([]int{3, 2, 1}) - result3 := Min([]int{}) - - is.Equal(result1, 1) - is.Equal(result2, 1) - is.Equal(result3, 0) -} - -func TestMinBy(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := MinBy([]string{"s1", "string2", "s3"}, func(item string, min string) bool { - return len(item) < len(min) - }) - result2 := MinBy([]string{"string1", "string2", "s3"}, func(item string, min string) bool { - return len(item) < len(min) - }) - result3 := MinBy([]string{}, func(item string, min string) bool { - return len(item) < len(min) - }) - - is.Equal(result1, "s1") - is.Equal(result2, "s3") - is.Equal(result3, "") -} - -func TestMax(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := Max([]int{1, 2, 3}) - result2 := Max([]int{3, 2, 1}) - result3 := Max([]int{}) - - is.Equal(result1, 3) - is.Equal(result2, 3) - is.Equal(result3, 0) -} - -func TestMaxBy(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := MaxBy([]string{"s1", "string2", "s3"}, func(item string, max string) bool { - return len(item) > len(max) - }) - result2 := MaxBy([]string{"string1", "string2", "s3"}, func(item string, max string) bool { - return len(item) > len(max) - }) - result3 := MaxBy([]string{}, func(item string, max string) bool { - return len(item) > len(max) - }) - - is.Equal(result1, "string2") - is.Equal(result2, "string1") - is.Equal(result3, "") -} - -func TestLast(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1, err1 := Last([]int{1, 2, 3}) - result2, err2 := Last([]int{}) - - is.Equal(result1, 3) - is.Equal(err1, nil) - is.Equal(result2, 0) - is.Equal(err2, fmt.Errorf("last: cannot extract the last element of an empty slice")) -} - -func TestNth(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1, err1 := Nth([]int{0, 1, 2, 3}, 2) - result2, err2 := Nth([]int{0, 1, 2, 3}, -2) - result3, err3 := Nth([]int{0, 1, 2, 3}, 42) - result4, err4 := Nth([]int{}, 0) - result5, err5 := Nth([]int{42}, 0) - result6, err6 := Nth([]int{42}, -1) - - is.Equal(result1, 2) - is.Equal(err1, nil) - is.Equal(result2, 2) - is.Equal(err2, nil) - is.Equal(result3, 0) - is.Equal(err3, fmt.Errorf("nth: 42 out of slice bounds")) - is.Equal(result4, 0) - is.Equal(err4, fmt.Errorf("nth: 0 out of slice bounds")) - is.Equal(result5, 42) - is.Equal(err5, nil) - is.Equal(result6, 42) - is.Equal(err6, nil) -} - -func TestSample(t *testing.T) { - t.Parallel() - is := assert.New(t) - - rand.Seed(time.Now().UnixNano()) - - result1 := Sample([]string{"a", "b", "c"}) - result2 := Sample([]string{}) - - is.True(Contains([]string{"a", "b", "c"}, result1)) - is.Equal(result2, "") -} - -func TestSamples(t *testing.T) { - t.Parallel() - is := assert.New(t) - - rand.Seed(time.Now().UnixNano()) - - result1 := Samples([]string{"a", "b", "c"}, 3) - result2 := Samples([]string{}, 3) - - sort.Strings(result1) - - is.Equal(result1, []string{"a", "b", "c"}) - is.Equal(result2, []string{}) -} diff --git a/func_test.go b/func_test.go deleted file mode 100644 index 284d24a4..00000000 --- a/func_test.go +++ /dev/null @@ -1,80 +0,0 @@ -package lo - -import ( - "strconv" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestPartial(t *testing.T) { - t.Parallel() - is := assert.New(t) - - add := func(x float64, y int) string { - return strconv.Itoa(int(x) + y) - } - f := Partial(add, 5) - is.Equal("15", f(10)) - is.Equal("0", f(-5)) -} - -func TestPartial1(t *testing.T) { - t.Parallel() - is := assert.New(t) - - add := func(x float64, y int) string { - return strconv.Itoa(int(x) + y) - } - f := Partial1(add, 5) - is.Equal("15", f(10)) - is.Equal("0", f(-5)) -} - -func TestPartial2(t *testing.T) { - t.Parallel() - is := assert.New(t) - - add := func(x float64, y int, z int) string { - return strconv.Itoa(int(x) + y + z) - } - f := Partial2(add, 5) - is.Equal("24", f(10, 9)) - is.Equal("8", f(-5, 8)) -} - -func TestPartial3(t *testing.T) { - t.Parallel() - is := assert.New(t) - - add := func(x float64, y int, z int, a float32) string { - return strconv.Itoa(int(x) + y + z + int(a)) - } - f := Partial3(add, 5) - is.Equal("21", f(10, 9, -3)) - is.Equal("15", f(-5, 8, 7)) -} - -func TestPartial4(t *testing.T) { - t.Parallel() - is := assert.New(t) - - add := func(x float64, y int, z int, a float32, b int32) string { - return strconv.Itoa(int(x) + y + z + int(a) + int(b)) - } - f := Partial4(add, 5) - is.Equal("21", f(10, 9, -3, 0)) - is.Equal("14", f(-5, 8, 7, -1)) -} - -func TestPartial5(t *testing.T) { - t.Parallel() - is := assert.New(t) - - add := func(x float64, y int, z int, a float32, b int32, c int) string { - return strconv.Itoa(int(x) + y + z + int(a) + int(b) + c) - } - f := Partial5(add, 5) - is.Equal("26", f(10, 9, -3, 0, 5)) - is.Equal("21", f(-5, 8, 7, -1, 7)) -} diff --git a/go.mod b/go.mod index 9a767c06..8c2bd55a 100644 --- a/go.mod +++ b/go.mod @@ -7,16 +7,6 @@ go 1.18 // require ( - github.com/stretchr/testify v1.8.0 - github.com/thoas/go-funk v0.9.1 golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 -) - -require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/kr/text v0.2.0 // indirect - github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect + golang.org/x/text v0.16.0 ) diff --git a/go.sum b/go.sum index cc231827..67ae7f5c 100644 --- a/go.sum +++ b/go.sum @@ -1,29 +1,4 @@ -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M= -github.com/thoas/go-funk v0.9.1/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q= golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM= golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= diff --git a/img/README.md b/img/README.md deleted file mode 100644 index 11219739..00000000 --- a/img/README.md +++ /dev/null @@ -1,2 +0,0 @@ - -Credits goes to https://gopherize.me/ \ No newline at end of file diff --git a/img/logo-full.png b/img/logo-full.png deleted file mode 100644 index 8dca0315..00000000 Binary files a/img/logo-full.png and /dev/null differ diff --git a/img/logo-full.xcf b/img/logo-full.xcf deleted file mode 100644 index a2913567..00000000 Binary files a/img/logo-full.xcf and /dev/null differ diff --git a/img/logo.png b/img/logo.png deleted file mode 100644 index 03d535b5..00000000 Binary files a/img/logo.png and /dev/null differ diff --git a/intersect.go b/intersect.go index cf6cab3d..d8105b98 100644 --- a/intersect.go +++ b/intersect.go @@ -2,8 +2,8 @@ package lo // Contains returns true if an element is present in a collection. func Contains[T comparable](collection []T, element T) bool { - for _, item := range collection { - if item == element { + for i := range collection { + if collection[i] == element { return true } } @@ -13,8 +13,8 @@ func Contains[T comparable](collection []T, element T) bool { // ContainsBy returns true if predicate function return true. func ContainsBy[T any](collection []T, predicate func(item T) bool) bool { - for _, item := range collection { - if predicate(item) { + for i := range collection { + if predicate(collection[i]) { return true } } @@ -24,8 +24,8 @@ func ContainsBy[T any](collection []T, predicate func(item T) bool) bool { // Every returns true if all elements of a subset are contained into a collection or if the subset is empty. func Every[T comparable](collection []T, subset []T) bool { - for _, elem := range subset { - if !Contains(collection, elem) { + for i := range subset { + if !Contains(collection, subset[i]) { return false } } @@ -35,8 +35,8 @@ func Every[T comparable](collection []T, subset []T) bool { // EveryBy returns true if the predicate returns true for all of the elements in the collection or if the collection is empty. func EveryBy[T any](collection []T, predicate func(item T) bool) bool { - for _, v := range collection { - if !predicate(v) { + for i := range collection { + if !predicate(collection[i]) { return false } } @@ -47,8 +47,8 @@ func EveryBy[T any](collection []T, predicate func(item T) bool) bool { // Some returns true if at least 1 element of a subset is contained into a collection. // If the subset is empty Some returns false. func Some[T comparable](collection []T, subset []T) bool { - for _, elem := range subset { - if Contains(collection, elem) { + for i := range subset { + if Contains(collection, subset[i]) { return true } } @@ -59,8 +59,8 @@ func Some[T comparable](collection []T, subset []T) bool { // SomeBy returns true if the predicate returns true for any of the elements in the collection. // If the collection is empty SomeBy returns false. func SomeBy[T any](collection []T, predicate func(item T) bool) bool { - for _, v := range collection { - if predicate(v) { + for i := range collection { + if predicate(collection[i]) { return true } } @@ -70,8 +70,8 @@ func SomeBy[T any](collection []T, predicate func(item T) bool) bool { // None returns true if no element of a subset are contained into a collection or if the subset is empty. func None[T comparable](collection []T, subset []T) bool { - for _, elem := range subset { - if Contains(collection, elem) { + for i := range subset { + if Contains(collection, subset[i]) { return false } } @@ -81,8 +81,8 @@ func None[T comparable](collection []T, subset []T) bool { // NoneBy returns true if the predicate returns true for none of the elements in the collection or if the collection is empty. func NoneBy[T any](collection []T, predicate func(item T) bool) bool { - for _, v := range collection { - if predicate(v) { + for i := range collection { + if predicate(collection[i]) { return false } } @@ -95,13 +95,13 @@ func Intersect[T comparable](list1 []T, list2 []T) []T { result := []T{} seen := map[T]struct{}{} - for _, elem := range list1 { - seen[elem] = struct{}{} + for i := range list1 { + seen[list1[i]] = struct{}{} } - for _, elem := range list2 { - if _, ok := seen[elem]; ok { - result = append(result, elem) + for i := range list2 { + if _, ok := seen[list2[i]]; ok { + result = append(result, list2[i]) } } @@ -118,23 +118,23 @@ func Difference[T comparable](list1 []T, list2 []T) ([]T, []T) { seenLeft := map[T]struct{}{} seenRight := map[T]struct{}{} - for _, elem := range list1 { - seenLeft[elem] = struct{}{} + for i := range list1 { + seenLeft[list1[i]] = struct{}{} } - for _, elem := range list2 { - seenRight[elem] = struct{}{} + for i := range list2 { + seenRight[list2[i]] = struct{}{} } - for _, elem := range list1 { - if _, ok := seenRight[elem]; !ok { - left = append(left, elem) + for i := range list1 { + if _, ok := seenRight[list1[i]]; !ok { + left = append(left, list1[i]) } } - for _, elem := range list2 { - if _, ok := seenLeft[elem]; !ok { - right = append(right, elem) + for i := range list2 { + if _, ok := seenLeft[list2[i]]; !ok { + right = append(right, list2[i]) } } @@ -144,14 +144,20 @@ func Difference[T comparable](list1 []T, list2 []T) ([]T, []T) { // Union returns all distinct elements from given collections. // result returns will not change the order of elements relatively. func Union[T comparable](lists ...[]T) []T { - result := []T{} - seen := map[T]struct{}{} + var capLen int for _, list := range lists { - for _, e := range list { - if _, ok := seen[e]; !ok { - seen[e] = struct{}{} - result = append(result, e) + capLen += len(list) + } + + result := make([]T, 0, capLen) + seen := make(map[T]struct{}, capLen) + + for i := range lists { + for j := range lists[i] { + if _, ok := seen[lists[i][j]]; !ok { + seen[lists[i][j]] = struct{}{} + result = append(result, lists[i][j]) } } } @@ -162,9 +168,9 @@ func Union[T comparable](lists ...[]T) []T { // Without returns slice excluding all given values. func Without[T comparable](collection []T, exclude ...T) []T { result := make([]T, 0, len(collection)) - for _, e := range collection { - if !Contains(exclude, e) { - result = append(result, e) + for i := range collection { + if !Contains(exclude, collection[i]) { + result = append(result, collection[i]) } } return result @@ -175,9 +181,9 @@ func WithoutEmpty[T comparable](collection []T) []T { var empty T result := make([]T, 0, len(collection)) - for _, e := range collection { - if e != empty { - result = append(result, e) + for i := range collection { + if collection[i] != empty { + result = append(result, collection[i]) } } diff --git a/intersect_test.go b/intersect_test.go deleted file mode 100644 index 339dbcbb..00000000 --- a/intersect_test.go +++ /dev/null @@ -1,262 +0,0 @@ -package lo - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestContains(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := Contains([]int{0, 1, 2, 3, 4, 5}, 5) - result2 := Contains([]int{0, 1, 2, 3, 4, 5}, 6) - - is.Equal(result1, true) - is.Equal(result2, false) -} - -func TestContainsBy(t *testing.T) { - t.Parallel() - is := assert.New(t) - - type a struct { - A int - B string - } - - a1 := []a{{A: 1, B: "1"}, {A: 2, B: "2"}, {A: 3, B: "3"}} - result1 := ContainsBy(a1, func(t a) bool { return t.A == 1 && t.B == "2" }) - result2 := ContainsBy(a1, func(t a) bool { return t.A == 2 && t.B == "2" }) - - a2 := []string{"aaa", "bbb", "ccc"} - result3 := ContainsBy(a2, func(t string) bool { return t == "ccc" }) - result4 := ContainsBy(a2, func(t string) bool { return t == "ddd" }) - - is.Equal(result1, false) - is.Equal(result2, true) - is.Equal(result3, true) - is.Equal(result4, false) -} - -func TestEvery(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := Every([]int{0, 1, 2, 3, 4, 5}, []int{0, 2}) - result2 := Every([]int{0, 1, 2, 3, 4, 5}, []int{0, 6}) - result3 := Every([]int{0, 1, 2, 3, 4, 5}, []int{-1, 6}) - result4 := Every([]int{0, 1, 2, 3, 4, 5}, []int{}) - - is.True(result1) - is.False(result2) - is.False(result3) - is.True(result4) -} - -func TestEveryBy(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := EveryBy([]int{1, 2, 3, 4}, func(x int) bool { - return x < 5 - }) - - is.True(result1) - - result2 := EveryBy([]int{1, 2, 3, 4}, func(x int) bool { - return x < 3 - }) - - is.False(result2) - - result3 := EveryBy([]int{1, 2, 3, 4}, func(x int) bool { - return x < 0 - }) - - is.False(result3) - - result4 := EveryBy([]int{}, func(x int) bool { - return x < 5 - }) - - is.True(result4) -} - -func TestSome(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := Some([]int{0, 1, 2, 3, 4, 5}, []int{0, 2}) - result2 := Some([]int{0, 1, 2, 3, 4, 5}, []int{0, 6}) - result3 := Some([]int{0, 1, 2, 3, 4, 5}, []int{-1, 6}) - result4 := Some([]int{0, 1, 2, 3, 4, 5}, []int{}) - - is.True(result1) - is.True(result2) - is.False(result3) - is.False(result4) -} - -func TestSomeBy(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := SomeBy([]int{1, 2, 3, 4}, func(x int) bool { - return x < 5 - }) - - is.True(result1) - - result2 := SomeBy([]int{1, 2, 3, 4}, func(x int) bool { - return x < 3 - }) - - is.True(result2) - - result3 := SomeBy([]int{1, 2, 3, 4}, func(x int) bool { - return x < 0 - }) - - is.False(result3) - - result4 := SomeBy([]int{}, func(x int) bool { - return x < 5 - }) - - is.False(result4) -} - -func TestNone(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := None([]int{0, 1, 2, 3, 4, 5}, []int{0, 2}) - result2 := None([]int{0, 1, 2, 3, 4, 5}, []int{0, 6}) - result3 := None([]int{0, 1, 2, 3, 4, 5}, []int{-1, 6}) - result4 := None([]int{0, 1, 2, 3, 4, 5}, []int{}) - - is.False(result1) - is.False(result2) - is.True(result3) - is.True(result4) -} - -func TestNoneBy(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := NoneBy([]int{1, 2, 3, 4}, func(x int) bool { - return x < 5 - }) - - is.False(result1) - - result2 := NoneBy([]int{1, 2, 3, 4}, func(x int) bool { - return x < 3 - }) - - is.False(result2) - - result3 := NoneBy([]int{1, 2, 3, 4}, func(x int) bool { - return x < 0 - }) - - is.True(result3) - - result4 := NoneBy([]int{}, func(x int) bool { - return x < 5 - }) - - is.True(result4) -} - -func TestIntersect(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := Intersect([]int{0, 1, 2, 3, 4, 5}, []int{0, 2}) - result2 := Intersect([]int{0, 1, 2, 3, 4, 5}, []int{0, 6}) - result3 := Intersect([]int{0, 1, 2, 3, 4, 5}, []int{-1, 6}) - result4 := Intersect([]int{0, 6}, []int{0, 1, 2, 3, 4, 5}) - result5 := Intersect([]int{0, 6, 0}, []int{0, 1, 2, 3, 4, 5}) - - is.Equal(result1, []int{0, 2}) - is.Equal(result2, []int{0}) - is.Equal(result3, []int{}) - is.Equal(result4, []int{0}) - is.Equal(result5, []int{0}) -} - -func TestDifference(t *testing.T) { - t.Parallel() - is := assert.New(t) - - left1, right1 := Difference([]int{0, 1, 2, 3, 4, 5}, []int{0, 2, 6}) - is.Equal(left1, []int{1, 3, 4, 5}) - is.Equal(right1, []int{6}) - - left2, right2 := Difference([]int{1, 2, 3, 4, 5}, []int{0, 6}) - is.Equal(left2, []int{1, 2, 3, 4, 5}) - is.Equal(right2, []int{0, 6}) - - left3, right3 := Difference([]int{0, 1, 2, 3, 4, 5}, []int{0, 1, 2, 3, 4, 5}) - is.Equal(left3, []int{}) - is.Equal(right3, []int{}) -} - -func TestUnion(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := Union([]int{0, 1, 2, 3, 4, 5}, []int{0, 2, 10}) - result2 := Union([]int{0, 1, 2, 3, 4, 5}, []int{6, 7}) - result3 := Union([]int{0, 1, 2, 3, 4, 5}, []int{}) - result4 := Union([]int{0, 1, 2}, []int{0, 1, 2}) - result5 := Union([]int{}, []int{}) - is.Equal(result1, []int{0, 1, 2, 3, 4, 5, 10}) - is.Equal(result2, []int{0, 1, 2, 3, 4, 5, 6, 7}) - is.Equal(result3, []int{0, 1, 2, 3, 4, 5}) - is.Equal(result4, []int{0, 1, 2}) - is.Equal(result5, []int{}) - - result11 := Union([]int{0, 1, 2, 3, 4, 5}, []int{0, 2, 10}, []int{0, 1, 11}) - result12 := Union([]int{0, 1, 2, 3, 4, 5}, []int{6, 7}, []int{8, 9}) - result13 := Union([]int{0, 1, 2, 3, 4, 5}, []int{}, []int{}) - result14 := Union([]int{0, 1, 2}, []int{0, 1, 2}, []int{0, 1, 2}) - result15 := Union([]int{}, []int{}, []int{}) - is.Equal(result11, []int{0, 1, 2, 3, 4, 5, 10, 11}) - is.Equal(result12, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}) - is.Equal(result13, []int{0, 1, 2, 3, 4, 5}) - is.Equal(result14, []int{0, 1, 2}) - is.Equal(result15, []int{}) -} - -func TestWithout(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := Without([]int{0, 2, 10}, 0, 1, 2, 3, 4, 5) - result2 := Without([]int{0, 7}, 0, 1, 2, 3, 4, 5) - result3 := Without([]int{}, 0, 1, 2, 3, 4, 5) - result4 := Without([]int{0, 1, 2}, 0, 1, 2) - result5 := Without([]int{}) - is.Equal(result1, []int{10}) - is.Equal(result2, []int{7}) - is.Equal(result3, []int{}) - is.Equal(result4, []int{}) - is.Equal(result5, []int{}) -} - -func TestWithoutEmpty(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := WithoutEmpty([]int{0, 1, 2}) - result2 := WithoutEmpty([]int{1, 2}) - result3 := WithoutEmpty([]int{}) - is.Equal(result1, []int{1, 2}) - is.Equal(result2, []int{1, 2}) - is.Equal(result3, []int{}) -} diff --git a/lo_test.go b/lo_test.go deleted file mode 100644 index 26db4c25..00000000 --- a/lo_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package lo - -import ( - "os" - "testing" - "time" -) - -// https://github.com/stretchr/testify/issues/1101 -func testWithTimeout(t *testing.T, timeout time.Duration) { - t.Helper() - - testFinished := make(chan struct{}) - t.Cleanup(func() { close(testFinished) }) - - go func() { - select { - case <-testFinished: - case <-time.After(timeout): - t.Errorf("test timed out after %s", timeout) - os.Exit(1) - } - }() -} - -type foo struct { - bar string -} - -func (f foo) Clone() foo { - return foo{f.bar} -} diff --git a/map.go b/map.go index 9c0ac482..885977fd 100644 --- a/map.go +++ b/map.go @@ -12,13 +12,20 @@ func Keys[K comparable, V any](in map[K]V) []K { return result } +// HasKey returns whether the given key exists. +// Play: https://go.dev/play/p/aVwubIvECqS +func HasKey[K comparable, V any](in map[K]V, key K) bool { + _, ok := in[key] + return ok +} + // Values creates an array of the map values. // Play: https://go.dev/play/p/nnRTQkzQfF6 func Values[K comparable, V any](in map[K]V) []V { result := make([]V, 0, len(in)) - for _, v := range in { - result = append(result, v) + for k := range in { + result = append(result, in[k]) } return result @@ -37,9 +44,9 @@ func ValueOr[K comparable, V any](in map[K]V, key K, fallback V) V { // Play: https://go.dev/play/p/kdg8GR_QMmf func PickBy[K comparable, V any](in map[K]V, predicate func(key K, value V) bool) map[K]V { r := map[K]V{} - for k, v := range in { - if predicate(k, v) { - r[k] = v + for k := range in { + if predicate(k, in[k]) { + r[k] = in[k] } } return r @@ -49,9 +56,9 @@ func PickBy[K comparable, V any](in map[K]V, predicate func(key K, value V) bool // Play: https://go.dev/play/p/R1imbuci9qU func PickByKeys[K comparable, V any](in map[K]V, keys []K) map[K]V { r := map[K]V{} - for k, v := range in { - if Contains(keys, k) { - r[k] = v + for i := range keys { + if v, ok := in[keys[i]]; ok { + r[keys[i]] = v } } return r @@ -61,9 +68,9 @@ func PickByKeys[K comparable, V any](in map[K]V, keys []K) map[K]V { // Play: https://go.dev/play/p/1zdzSvbfsJc func PickByValues[K comparable, V comparable](in map[K]V, values []V) map[K]V { r := map[K]V{} - for k, v := range in { - if Contains(values, v) { - r[k] = v + for k := range in { + if Contains(values, in[k]) { + r[k] = in[k] } } return r @@ -73,9 +80,9 @@ func PickByValues[K comparable, V comparable](in map[K]V, values []V) map[K]V { // Play: https://go.dev/play/p/EtBsR43bdsd func OmitBy[K comparable, V any](in map[K]V, predicate func(key K, value V) bool) map[K]V { r := map[K]V{} - for k, v := range in { - if !predicate(k, v) { - r[k] = v + for k := range in { + if !predicate(k, in[k]) { + r[k] = in[k] } } return r @@ -85,10 +92,11 @@ func OmitBy[K comparable, V any](in map[K]V, predicate func(key K, value V) bool // Play: https://go.dev/play/p/t1QjCrs-ysk func OmitByKeys[K comparable, V any](in map[K]V, keys []K) map[K]V { r := map[K]V{} - for k, v := range in { - if !Contains(keys, k) { - r[k] = v - } + for k := range in { + r[k] = in[k] + } + for i := range keys { + delete(r, keys[i]) } return r } @@ -97,9 +105,9 @@ func OmitByKeys[K comparable, V any](in map[K]V, keys []K) map[K]V { // Play: https://go.dev/play/p/9UYZi-hrs8j func OmitByValues[K comparable, V comparable](in map[K]V, values []V) map[K]V { r := map[K]V{} - for k, v := range in { - if !Contains(values, v) { - r[k] = v + for k := range in { + if !Contains(values, in[k]) { + r[k] = in[k] } } return r @@ -110,10 +118,10 @@ func OmitByValues[K comparable, V comparable](in map[K]V, values []V) map[K]V { func Entries[K comparable, V any](in map[K]V) []Entry[K, V] { entries := make([]Entry[K, V], 0, len(in)) - for k, v := range in { + for k := range in { entries = append(entries, Entry[K, V]{ Key: k, - Value: v, + Value: in[k], }) } @@ -132,8 +140,8 @@ func ToPairs[K comparable, V any](in map[K]V) []Entry[K, V] { func FromEntries[K comparable, V any](entries []Entry[K, V]) map[K]V { out := make(map[K]V, len(entries)) - for _, v := range entries { - out[v.Key] = v.Value + for i := range entries { + out[entries[i].Key] = entries[i].Value } return out @@ -153,8 +161,8 @@ func FromPairs[K comparable, V any](entries []Entry[K, V]) map[K]V { func Invert[K comparable, V comparable](in map[K]V) map[V]K { out := make(map[V]K, len(in)) - for k, v := range in { - out[v] = k + for k := range in { + out[in[k]] = k } return out @@ -165,9 +173,9 @@ func Invert[K comparable, V comparable](in map[K]V) map[V]K { func Assign[K comparable, V any](maps ...map[K]V) map[K]V { out := map[K]V{} - for _, m := range maps { - for k, v := range m { - out[k] = v + for i := range maps { + for k := range maps[i] { + out[k] = maps[i][k] } } @@ -179,8 +187,8 @@ func Assign[K comparable, V any](maps ...map[K]V) map[K]V { func MapKeys[K comparable, V any, R comparable](in map[K]V, iteratee func(value V, key K) R) map[R]V { result := make(map[R]V, len(in)) - for k, v := range in { - result[iteratee(v, k)] = v + for k := range in { + result[iteratee(in[k], k)] = in[k] } return result @@ -191,8 +199,8 @@ func MapKeys[K comparable, V any, R comparable](in map[K]V, iteratee func(value func MapValues[K comparable, V any, R any](in map[K]V, iteratee func(value V, key K) R) map[K]R { result := make(map[K]R, len(in)) - for k, v := range in { - result[k] = iteratee(v, k) + for k := range in { + result[k] = iteratee(in[k], k) } return result @@ -203,8 +211,8 @@ func MapValues[K comparable, V any, R any](in map[K]V, iteratee func(value V, ke 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 { result := make(map[K2]V2, len(in)) - for k1, v1 := range in { - k2, v2 := iteratee(k1, v1) + for k1 := range in { + k2, v2 := iteratee(k1, in[k1]) result[k2] = v2 } @@ -216,8 +224,8 @@ func MapEntries[K1 comparable, V1 any, K2 comparable, V2 any](in map[K1]V1, iter func MapToSlice[K comparable, V any, R any](in map[K]V, iteratee func(key K, value V) R) []R { result := make([]R, 0, len(in)) - for k, v := range in { - result = append(result, iteratee(k, v)) + for k := range in { + result = append(result, iteratee(k, in[k])) } return result diff --git a/map_example_test.go b/map_example_test.go deleted file mode 100644 index aebca8fd..00000000 --- a/map_example_test.go +++ /dev/null @@ -1,192 +0,0 @@ -package lo - -import ( - "fmt" - "sort" - "strconv" - "strings" -) - -func ExampleKeys() { - kv := map[string]int{"foo": 1, "bar": 2} - - result := Keys(kv) - - sort.StringSlice(result).Sort() - fmt.Printf("%v", result) - // Output: [bar foo] -} - -func ExampleValues() { - kv := map[string]int{"foo": 1, "bar": 2} - - result := Values(kv) - - sort.IntSlice(result).Sort() - fmt.Printf("%v", result) - // Output: [1 2] -} - -func ExampleValueOr() { - kv := map[string]int{"foo": 1, "bar": 2} - - result1 := ValueOr(kv, "foo", 42) - result2 := ValueOr(kv, "baz", 42) - - fmt.Printf("%v %v", result1, result2) - // Output: 1 42 -} - -func ExamplePickBy() { - kv := map[string]int{"foo": 1, "bar": 2, "baz": 3} - - result := PickBy(kv, func(key string, value int) bool { - return value%2 == 1 - }) - - fmt.Printf("%v %v %v", len(result), result["foo"], result["baz"]) - // Output: 2 1 3 -} - -func ExamplePickByKeys() { - kv := map[string]int{"foo": 1, "bar": 2, "baz": 3} - - result := PickByKeys(kv, []string{"foo", "baz"}) - - fmt.Printf("%v %v %v", len(result), result["foo"], result["baz"]) - // Output: 2 1 3 -} - -func ExamplePickByValues() { - kv := map[string]int{"foo": 1, "bar": 2, "baz": 3} - - result := PickByValues(kv, []int{1, 3}) - - fmt.Printf("%v %v %v", len(result), result["foo"], result["baz"]) - // Output: 2 1 3 -} - -func ExampleOmitBy() { - kv := map[string]int{"foo": 1, "bar": 2, "baz": 3} - - result := OmitBy(kv, func(key string, value int) bool { - return value%2 == 1 - }) - - fmt.Printf("%v", result) - // Output: map[bar:2] -} - -func ExampleOmitByKeys() { - kv := map[string]int{"foo": 1, "bar": 2, "baz": 3} - - result := OmitByKeys(kv, []string{"foo", "baz"}) - - fmt.Printf("%v", result) - // Output: map[bar:2] -} - -func ExampleOmitByValues() { - kv := map[string]int{"foo": 1, "bar": 2, "baz": 3} - - result := OmitByValues(kv, []int{1, 3}) - - fmt.Printf("%v", result) - // Output: map[bar:2] -} - -func ExampleEntries() { - kv := map[string]int{"foo": 1, "bar": 2, "baz": 3} - - result := Entries(kv) - - sort.Slice(result, func(i, j int) bool { - return strings.Compare(result[i].Key, result[j].Key) < 0 - }) - fmt.Printf("%v", result) - // Output: [{bar 2} {baz 3} {foo 1}] -} - -func ExampleFromEntries() { - result := FromEntries([]Entry[string, int]{ - { - Key: "foo", - Value: 1, - }, - { - Key: "bar", - Value: 2, - }, - { - Key: "baz", - Value: 3, - }, - }) - - fmt.Printf("%v %v %v %v", len(result), result["foo"], result["bar"], result["baz"]) - // Output: 3 1 2 3 -} - -func ExampleInvert() { - kv := map[string]int{"foo": 1, "bar": 2, "baz": 3} - - result := Invert(kv) - - fmt.Printf("%v %v %v %v", len(result), result[1], result[2], result[3]) - // Output: 3 foo bar baz -} - -func ExampleAssign() { - result := Assign( - map[string]int{"a": 1, "b": 2}, - map[string]int{"b": 3, "c": 4}, - ) - - fmt.Printf("%v %v %v %v", len(result), result["a"], result["b"], result["c"]) - // Output: 3 1 3 4 -} - -func ExampleMapKeys() { - kv := map[int]int{1: 1, 2: 2, 3: 3, 4: 4} - - result := MapKeys(kv, func(_ int, v int) string { - return strconv.FormatInt(int64(v), 10) - }) - - fmt.Printf("%v %v %v %v %v", len(result), result["1"], result["2"], result["3"], result["4"]) - // Output: 4 1 2 3 4 -} - -func ExampleMapValues() { - kv := map[int]int{1: 1, 2: 2, 3: 3, 4: 4} - - result := MapValues(kv, func(_ int, v int) string { - return strconv.FormatInt(int64(v), 10) - }) - - fmt.Printf("%v %v %v %v %v", len(result), result[1], result[2], result[3], result[4]) - // Output: 4 1 2 3 4 -} - -func ExampleMapEntries() { - kv := map[string]int{"foo": 1, "bar": 2} - - result := MapEntries(kv, func(k string, v int) (int, string) { - return v, k - }) - - fmt.Printf("%v\n", result) - // Output: map[1:foo 2:bar] -} - -func ExampleMapToSlice() { - kv := map[int]int64{1: 1, 2: 2, 3: 3, 4: 4} - - result := MapToSlice(kv, func(k int, v int64) string { - return fmt.Sprintf("%d_%d", k, v) - }) - - sort.StringSlice(result).Sort() - fmt.Printf("%v", result) - // Output: [1_1 2_2 3_3 4_4] -} diff --git a/map_test.go b/map_test.go deleted file mode 100644 index 419ca9fb..00000000 --- a/map_test.go +++ /dev/null @@ -1,337 +0,0 @@ -package lo - -import ( - "fmt" - "sort" - "strconv" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestKeys(t *testing.T) { - t.Parallel() - is := assert.New(t) - - r1 := Keys(map[string]int{"foo": 1, "bar": 2}) - sort.Strings(r1) - - is.Equal(r1, []string{"bar", "foo"}) -} - -func TestValues(t *testing.T) { - t.Parallel() - is := assert.New(t) - - r1 := Values(map[string]int{"foo": 1, "bar": 2}) - sort.Ints(r1) - - is.Equal(r1, []int{1, 2}) -} - -func TestValueOr(t *testing.T) { - t.Parallel() - is := assert.New(t) - - r1 := ValueOr(map[string]int{"foo": 1}, "bar", 2) - is.Equal(r1, 2) - - r2 := ValueOr(map[string]int{"foo": 1}, "foo", 2) - is.Equal(r2, 1) -} - -func TestPickBy(t *testing.T) { - t.Parallel() - is := assert.New(t) - - r1 := PickBy(map[string]int{"foo": 1, "bar": 2, "baz": 3}, func(key string, value int) bool { - return value%2 == 1 - }) - - is.Equal(r1, map[string]int{"foo": 1, "baz": 3}) -} - -func TestPickByKeys(t *testing.T) { - t.Parallel() - is := assert.New(t) - - r1 := PickByKeys(map[string]int{"foo": 1, "bar": 2, "baz": 3}, []string{"foo", "baz"}) - - is.Equal(r1, map[string]int{"foo": 1, "baz": 3}) -} - -func TestPickByValues(t *testing.T) { - t.Parallel() - is := assert.New(t) - - r1 := PickByValues(map[string]int{"foo": 1, "bar": 2, "baz": 3}, []int{1, 3}) - - is.Equal(r1, map[string]int{"foo": 1, "baz": 3}) -} - -func TestOmitBy(t *testing.T) { - t.Parallel() - is := assert.New(t) - - r1 := OmitBy(map[string]int{"foo": 1, "bar": 2, "baz": 3}, func(key string, value int) bool { - return value%2 == 1 - }) - - is.Equal(r1, map[string]int{"bar": 2}) -} - -func TestOmitByKeys(t *testing.T) { - t.Parallel() - is := assert.New(t) - - r1 := OmitByKeys(map[string]int{"foo": 1, "bar": 2, "baz": 3}, []string{"foo", "baz"}) - - is.Equal(r1, map[string]int{"bar": 2}) -} - -func TestOmitByValues(t *testing.T) { - t.Parallel() - is := assert.New(t) - - r1 := OmitByValues(map[string]int{"foo": 1, "bar": 2, "baz": 3}, []int{1, 3}) - - is.Equal(r1, map[string]int{"bar": 2}) -} - -func TestEntries(t *testing.T) { - t.Parallel() - is := assert.New(t) - - r1 := Entries(map[string]int{"foo": 1, "bar": 2}) - - sort.Slice(r1, func(i, j int) bool { - return r1[i].Value < r1[j].Value - }) - is.EqualValues(r1, []Entry[string, int]{ - { - Key: "foo", - Value: 1, - }, - { - Key: "bar", - Value: 2, - }, - }) -} - -func TestToPairs(t *testing.T) { - t.Parallel() - is := assert.New(t) - - r1 := ToPairs(map[string]int{"baz": 3, "qux": 4}) - - sort.Slice(r1, func(i, j int) bool { - return r1[i].Value < r1[j].Value - }) - is.EqualValues(r1, []Entry[string, int]{ - { - Key: "baz", - Value: 3, - }, - { - Key: "qux", - Value: 4, - }, - }) -} - -func TestFromEntries(t *testing.T) { - t.Parallel() - is := assert.New(t) - - r1 := FromEntries([]Entry[string, int]{ - { - Key: "foo", - Value: 1, - }, - { - Key: "bar", - Value: 2, - }, - }) - - is.Len(r1, 2) - is.Equal(r1["foo"], 1) - is.Equal(r1["bar"], 2) -} - -func TestFromPairs(t *testing.T) { - t.Parallel() - is := assert.New(t) - - r1 := FromPairs([]Entry[string, int]{ - { - Key: "baz", - Value: 3, - }, - { - Key: "qux", - Value: 4, - }, - }) - - is.Len(r1, 2) - is.Equal(r1["baz"], 3) - is.Equal(r1["qux"], 4) -} - -func TestInvert(t *testing.T) { - t.Parallel() - is := assert.New(t) - - r1 := Invert(map[string]int{"a": 1, "b": 2}) - r2 := Invert(map[string]int{"a": 1, "b": 2, "c": 1}) - - is.Len(r1, 2) - is.EqualValues(map[int]string{1: "a", 2: "b"}, r1) - is.Len(r2, 2) -} - -func TestAssign(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := Assign(map[string]int{"a": 1, "b": 2}, map[string]int{"b": 3, "c": 4}) - - is.Len(result1, 3) - is.Equal(result1, map[string]int{"a": 1, "b": 3, "c": 4}) -} - -func TestMapKeys(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := MapKeys(map[int]int{1: 1, 2: 2, 3: 3, 4: 4}, func(x int, _ int) string { - return "Hello" - }) - result2 := MapKeys(map[int]int{1: 1, 2: 2, 3: 3, 4: 4}, func(_ int, v int) string { - return strconv.FormatInt(int64(v), 10) - }) - - is.Equal(len(result1), 1) - is.Equal(len(result2), 4) - is.Equal(result2, map[string]int{"1": 1, "2": 2, "3": 3, "4": 4}) -} - -func TestMapValues(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := MapValues(map[int]int{1: 1, 2: 2, 3: 3, 4: 4}, func(x int, _ int) string { - return "Hello" - }) - result2 := MapValues(map[int]int{1: 1, 2: 2, 3: 3, 4: 4}, func(x int, _ int) string { - return strconv.FormatInt(int64(x), 10) - }) - - is.Equal(len(result1), 4) - is.Equal(len(result2), 4) - is.Equal(result1, map[int]string{1: "Hello", 2: "Hello", 3: "Hello", 4: "Hello"}) - is.Equal(result2, map[int]string{1: "1", 2: "2", 3: "3", 4: "4"}) -} - -func mapEntriesTest[I any, O any](t *testing.T, in map[string]I, iteratee func(string, I) (string, O), expected map[string]O) { - is := assert.New(t) - result := MapEntries(in, iteratee) - is.Equal(result, expected) -} - -func TestMapEntries(t *testing.T) { - mapEntriesTest(t, map[string]int{"foo": 1, "bar": 2}, func(k string, v int) (string, int) { - return k, v + 1 - }, map[string]int{"foo": 2, "bar": 3}) - mapEntriesTest(t, map[string]int{"foo": 1, "bar": 2}, func(k string, v int) (string, string) { - return k, k + strconv.Itoa(v) - }, map[string]string{"foo": "foo1", "bar": "bar2"}) - mapEntriesTest(t, map[string]int{"foo": 1, "bar": 2}, func(k string, v int) (string, string) { - return k, strconv.Itoa(v) + k - }, map[string]string{"foo": "1foo", "bar": "2bar"}) - - // NoMutation - { - is := assert.New(t) - r1 := map[string]int{"foo": 1, "bar": 2} - MapEntries(r1, func(k string, v int) (string, string) { - return k, strconv.Itoa(v) + "!!" - }) - is.Equal(r1, map[string]int{"foo": 1, "bar": 2}) - } - // EmptyInput - { - mapEntriesTest(t, map[string]int{}, func(k string, v int) (string, string) { - return k, strconv.Itoa(v) + "!!" - }, map[string]string{}) - - mapEntriesTest(t, map[string]any{}, func(k string, v any) (string, any) { - return k, v - }, map[string]any{}) - } - // Identity - { - mapEntriesTest(t, map[string]int{"foo": 1, "bar": 2}, func(k string, v int) (string, int) { - return k, v - }, map[string]int{"foo": 1, "bar": 2}) - mapEntriesTest(t, map[string]any{"foo": 1, "bar": "2", "ccc": true}, func(k string, v any) (string, any) { - return k, v - }, map[string]any{"foo": 1, "bar": "2", "ccc": true}) - } - // ToConstantEntry - { - mapEntriesTest(t, map[string]any{"foo": 1, "bar": "2", "ccc": true}, func(k string, v any) (string, any) { - return "key", "value" - }, map[string]any{"key": "value"}) - mapEntriesTest(t, map[string]any{"foo": 1, "bar": "2", "ccc": true}, func(k string, v any) (string, any) { - return "b", 5 - }, map[string]any{"b": 5}) - } - - //// OverlappingKeys - //// because using range over map, the order is not guaranteed - //// this test is not deterministic - //{ - // mapEntriesTest(t, map[string]any{"foo": 1, "foo2": 2, "Foo": 2, "Foo2": "2", "bar": "2", "ccc": true}, func(k string, v any) (string, any) { - // return string(k[0]), v - // }, map[string]any{"F": "2", "b": "2", "c": true, "f": 2}) - // mapEntriesTest(t, map[string]string{"foo": "1", "foo2": "2", "Foo": "2", "Foo2": "2", "bar": "2", "ccc": "true"}, func(k string, v string) (string, string) { - // return v, k - // }, map[string]string{"1": "foo", "2": "bar", "true": "ccc"}) - //} - //NormalMappers - { - mapEntriesTest(t, map[string]string{"foo": "1", "foo2": "2", "Foo": "2", "Foo2": "2", "bar": "2", "ccc": "true"}, func(k string, v string) (string, string) { - return k, k + v - }, map[string]string{"Foo": "Foo2", "Foo2": "Foo22", "bar": "bar2", "ccc": "ccctrue", "foo": "foo1", "foo2": "foo22"}) - - mapEntriesTest(t, map[string]struct { - name string - age int - }{"1-11-1": {name: "foo", age: 1}, "2-22-2": {name: "bar", age: 2}}, func(k string, v struct { - name string - age int - }) (string, string) { - return v.name, k - }, map[string]string{"bar": "2-22-2", "foo": "1-11-1"}) - } -} - -func TestMapToSlice(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := MapToSlice(map[int]int{1: 5, 2: 6, 3: 7, 4: 8}, func(k int, v int) string { - return fmt.Sprintf("%d_%d", k, v) - }) - result2 := MapToSlice(map[int]int{1: 5, 2: 6, 3: 7, 4: 8}, func(k int, _ int) string { - return strconv.FormatInt(int64(k), 10) - }) - - is.Equal(len(result1), 4) - is.Equal(len(result2), 4) - is.ElementsMatch(result1, []string{"1_5", "2_6", "3_7", "4_8"}) - is.ElementsMatch(result2, []string{"1", "2", "3", "4"}) -} diff --git a/math.go b/math.go index 9dce28cf..3ed839f6 100644 --- a/math.go +++ b/math.go @@ -67,8 +67,8 @@ func Clamp[T constraints.Ordered](value T, min T, max T) T { // Play: https://go.dev/play/p/upfeJVqs4Bt func Sum[T constraints.Float | constraints.Integer | constraints.Complex](collection []T) T { var sum T = 0 - for _, val := range collection { - sum += val + for i := range collection { + sum += collection[i] } return sum } @@ -77,8 +77,28 @@ func Sum[T constraints.Float | constraints.Integer | constraints.Complex](collec // Play: https://go.dev/play/p/Dz_a_7jN_ca func SumBy[T any, R constraints.Float | constraints.Integer | constraints.Complex](collection []T, iteratee func(item T) R) R { var sum R = 0 - for _, item := range collection { - sum = sum + iteratee(item) + for i := range collection { + sum = sum + iteratee(collection[i]) } return sum } + +// Mean calculates the mean of a collection of numbers. +func Mean[T constraints.Float | constraints.Integer](collection []T) T { + var length T = T(len(collection)) + if length == 0 { + return 0 + } + var sum T = Sum(collection) + return sum / length +} + +// MeanBy calculates the mean of a collection of numbers using the given return value from the iteration function. +func MeanBy[T any, R constraints.Float | constraints.Integer](collection []T, iteratee func(item T) R) R { + var length R = R(len(collection)) + if length == 0 { + return 0 + } + var sum R = SumBy(collection, iteratee) + return sum / length +} diff --git a/math_example_test.go b/math_example_test.go deleted file mode 100644 index 00c6d972..00000000 --- a/math_example_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package lo - -import ( - "fmt" -) - -func ExampleRange() { - result1 := Range(4) - result2 := Range(-4) - result3 := RangeFrom(1, 5) - result4 := RangeFrom(1.0, 5) - result5 := RangeWithSteps(0, 20, 5) - result6 := RangeWithSteps[float32](-1.0, -4.0, -1.0) - result7 := RangeWithSteps(1, 4, -1) - result8 := Range(0) - - fmt.Printf("%v\n", result1) - fmt.Printf("%v\n", result2) - fmt.Printf("%v\n", result3) - fmt.Printf("%v\n", result4) - fmt.Printf("%v\n", result5) - fmt.Printf("%v\n", result6) - fmt.Printf("%v\n", result7) - fmt.Printf("%v\n", result8) - // Output: - // [0 1 2 3] - // [0 -1 -2 -3] - // [1 2 3 4 5] - // [1 2 3 4 5] - // [0 5 10 15] - // [-1 -2 -3] - // [] - // [] -} - -func ExampleClamp() { - result1 := Clamp(0, -10, 10) - result2 := Clamp(-42, -10, 10) - result3 := Clamp(42, -10, 10) - - fmt.Printf("%v\n", result1) - fmt.Printf("%v\n", result2) - fmt.Printf("%v\n", result3) - // Output: - // 0 - // -10 - // 10 -} - -func ExampleSum() { - list := []int{1, 2, 3, 4, 5} - - sum := Sum(list) - - fmt.Printf("%v", sum) - // Output: 15 -} - -func ExampleSumBy() { - list := []string{"foo", "bar"} - - result := SumBy(list, func(item string) int { - return len(item) - }) - - fmt.Printf("%v", result) - // Output: 6 -} diff --git a/math_test.go b/math_test.go deleted file mode 100644 index d0bab70f..00000000 --- a/math_test.go +++ /dev/null @@ -1,99 +0,0 @@ -package lo - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestRange(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := Range(4) - result2 := Range(-4) - result3 := Range(0) - is.Equal(result1, []int{0, 1, 2, 3}) - is.Equal(result2, []int{0, -1, -2, -3}) - is.Equal(result3, []int{}) -} - -func TestRangeFrom(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := RangeFrom(1, 5) - result2 := RangeFrom(-1, -5) - result3 := RangeFrom(10, 0) - result4 := RangeFrom(2.0, 3) - result5 := RangeFrom(-2.0, -3) - is.Equal(result1, []int{1, 2, 3, 4, 5}) - is.Equal(result2, []int{-1, -2, -3, -4, -5}) - is.Equal(result3, []int{}) - is.Equal(result4, []float64{2.0, 3.0, 4.0}) - is.Equal(result5, []float64{-2.0, -3.0, -4.0}) -} - -func TestRangeClose(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := RangeWithSteps(0, 20, 6) - result2 := RangeWithSteps(0, 3, -5) - result3 := RangeWithSteps(1, 1, 0) - result4 := RangeWithSteps(3, 2, 1) - result5 := RangeWithSteps(1.0, 4.0, 2.0) - result6 := RangeWithSteps[float32](-1.0, -4.0, -1.0) - is.Equal([]int{0, 6, 12, 18}, result1) - is.Equal([]int{}, result2) - is.Equal([]int{}, result3) - is.Equal([]int{}, result4) - is.Equal([]float64{1.0, 3.0}, result5) - is.Equal([]float32{-1.0, -2.0, -3.0}, result6) -} - -func TestClamp(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := Clamp(0, -10, 10) - result2 := Clamp(-42, -10, 10) - result3 := Clamp(42, -10, 10) - - is.Equal(result1, 0) - is.Equal(result2, -10) - is.Equal(result3, 10) -} - -func TestSum(t *testing.T) { - is := assert.New(t) - - result1 := Sum([]float32{2.3, 3.3, 4, 5.3}) - result2 := Sum([]int32{2, 3, 4, 5}) - result3 := Sum([]uint32{2, 3, 4, 5}) - result4 := Sum([]uint32{}) - result5 := Sum([]complex128{4_4, 2_2}) - - is.Equal(result1, float32(14.900001)) - is.Equal(result2, int32(14)) - is.Equal(result3, uint32(14)) - is.Equal(result4, uint32(0)) - is.Equal(result5, complex128(6_6)) -} - -func TestSumBy(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := SumBy([]float32{2.3, 3.3, 4, 5.3}, func(n float32) float32 { return n }) - result2 := SumBy([]int32{2, 3, 4, 5}, func(n int32) int32 { return n }) - result3 := SumBy([]uint32{2, 3, 4, 5}, func(n uint32) uint32 { return n }) - result4 := SumBy([]uint32{}, func(n uint32) uint32 { return n }) - result5 := SumBy([]complex128{4_4, 2_2}, func(n complex128) complex128 { return n }) - - is.Equal(result1, float32(14.900001)) - is.Equal(result2, int32(14)) - is.Equal(result3, uint32(14)) - is.Equal(result4, uint32(0)) - is.Equal(result5, complex128(6_6)) -} diff --git a/parallel/slice_test.go b/parallel/slice_test.go deleted file mode 100644 index 248b45da..00000000 --- a/parallel/slice_test.go +++ /dev/null @@ -1,100 +0,0 @@ -package parallel - -import ( - "sort" - "strconv" - "sync/atomic" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestMap(t *testing.T) { - is := assert.New(t) - - result1 := Map([]int{1, 2, 3, 4}, func(x int, _ int) string { - return "Hello" - }) - result2 := Map([]int64{1, 2, 3, 4}, func(x int64, _ int) string { - return strconv.FormatInt(x, 10) - }) - - is.Equal(len(result1), 4) - is.Equal(len(result2), 4) - is.Equal(result1, []string{"Hello", "Hello", "Hello", "Hello"}) - is.Equal(result2, []string{"1", "2", "3", "4"}) -} - -func TestForEach(t *testing.T) { - is := assert.New(t) - - var counter uint64 - collection := []int{1, 2, 3, 4} - ForEach(collection, func(x int, i int) { - atomic.AddUint64(&counter, 1) - }) - - is.Equal(uint64(4), atomic.LoadUint64(&counter)) -} - -func TestTimes(t *testing.T) { - is := assert.New(t) - - result1 := Times(3, func(i int) string { - return strconv.FormatInt(int64(i), 10) - }) - - is.Equal(len(result1), 3) - is.Equal(result1, []string{"0", "1", "2"}) -} - -func TestGroupBy(t *testing.T) { - is := assert.New(t) - - result1 := GroupBy([]int{0, 1, 2, 3, 4, 5}, func(i int) int { - return i % 3 - }) - - // order - for x := range result1 { - sort.Slice(result1[x], func(i, j int) bool { - return result1[x][i] < result1[x][j] - }) - } - - is.EqualValues(len(result1), 3) - is.EqualValues(result1, map[int][]int{ - 0: {0, 3}, - 1: {1, 4}, - 2: {2, 5}, - }) -} - -func TestPartitionBy(t *testing.T) { - is := assert.New(t) - - oddEven := func(x int) string { - if x < 0 { - return "negative" - } else if x%2 == 0 { - return "even" - } - return "odd" - } - - result1 := PartitionBy([]int{-2, -1, 0, 1, 2, 3, 4, 5}, oddEven) - result2 := PartitionBy([]int{}, oddEven) - - // order - sort.Slice(result1, func(i, j int) bool { - return result1[i][0] < result1[j][0] - }) - for x := range result1 { - sort.Slice(result1[x], func(i, j int) bool { - return result1[x][i] < result1[x][j] - }) - } - - is.ElementsMatch(result1, [][]int{{-2, -1}, {0, 2, 4}, {1, 3, 5}}) - is.Equal(result2, [][]int{}) -} diff --git a/retry.go b/retry.go index c3c264ff..f026aa33 100644 --- a/retry.go +++ b/retry.go @@ -26,8 +26,8 @@ func (d *debounce) reset() { } d.timer = time.AfterFunc(d.after, func() { - for _, f := range d.callbacks { - f() + for i := range d.callbacks { + d.callbacks[i]() } }) } @@ -101,8 +101,8 @@ func (d *debounceBy[T]) reset(key T) { item.count = 0 item.mu.Unlock() - for _, f := range d.callbacks { - f(key, count) + for i := range d.callbacks { + d.callbacks[i](key, count) } }) @@ -239,7 +239,7 @@ type transactionStep[T any] struct { onRollback func(T) T } -// NewTransaction instanciate a new transaction. +// NewTransaction instantiate a new transaction. func NewTransaction[T any]() *Transaction[T] { return &Transaction[T]{ steps: []transactionStep[T]{}, diff --git a/retry_example_test.go b/retry_example_test.go deleted file mode 100644 index 3560c2f0..00000000 --- a/retry_example_test.go +++ /dev/null @@ -1,251 +0,0 @@ -//go:build !race -// +build !race - -package lo - -import ( - "fmt" - "sync" - "sync/atomic" - "time" -) - -func ExampleNewDebounce() { - i := int32(0) - calls := []int32{} - mu := sync.Mutex{} - - debounce, cancel := NewDebounce(time.Millisecond, func() { - mu.Lock() - defer mu.Unlock() - calls = append(calls, atomic.LoadInt32(&i)) - }) - - debounce() - atomic.AddInt32(&i, 1) - - time.Sleep(5 * time.Millisecond) - - debounce() - atomic.AddInt32(&i, 1) - debounce() - atomic.AddInt32(&i, 1) - debounce() - atomic.AddInt32(&i, 1) - - time.Sleep(5 * time.Millisecond) - - cancel() - - mu.Lock() - fmt.Printf("%v", calls) - mu.Unlock() - // Output: [1 4] -} - -func ExampleNewDebounceBy() { - calls := map[string][]int{} - mu := sync.Mutex{} - - debounce, cancel := NewDebounceBy(time.Millisecond, func(userID string, count int) { - mu.Lock() - defer mu.Unlock() - - if _, ok := calls[userID]; !ok { - calls[userID] = []int{} - } - - calls[userID] = append(calls[userID], count) - }) - - debounce("samuel") - debounce("john") - - time.Sleep(5 * time.Millisecond) - - debounce("john") - debounce("john") - debounce("samuel") - debounce("john") - - time.Sleep(5 * time.Millisecond) - - cancel("samuel") - cancel("john") - - mu.Lock() - fmt.Printf("samuel: %v\n", calls["samuel"]) - fmt.Printf("john: %v\n", calls["john"]) - mu.Unlock() - // Output: - // samuel: [1 1] - // john: [1 3] -} - -func ExampleAttempt() { - count1, err1 := Attempt(2, func(i int) error { - if i == 0 { - return fmt.Errorf("error") - } - - return nil - }) - - count2, err2 := Attempt(2, func(i int) error { - if i < 10 { - return fmt.Errorf("error") - } - - return nil - }) - - fmt.Printf("%v %v\n", count1, err1) - fmt.Printf("%v %v\n", count2, err2) - // Output: - // 2 - // 2 error -} - -func ExampleAttemptWithDelay() { - count1, time1, err1 := AttemptWithDelay(2, time.Millisecond, func(i int, _ time.Duration) error { - if i == 0 { - return fmt.Errorf("error") - } - - return nil - }) - - count2, time2, err2 := AttemptWithDelay(2, time.Millisecond, func(i int, _ time.Duration) error { - if i < 10 { - return fmt.Errorf("error") - } - - return nil - }) - - fmt.Printf("%v %v %v\n", count1, time1.Truncate(time.Millisecond), err1) - fmt.Printf("%v %v %v\n", count2, time2.Truncate(time.Millisecond), err2) - // Output: - // 2 1ms - // 2 1ms error -} - -func ExampleTransaction() { - transaction := NewTransaction[int](). - Then( - func(state int) (int, error) { - fmt.Println("step 1") - return state + 10, nil - }, - func(state int) int { - fmt.Println("rollback 1") - return state - 10 - }, - ). - Then( - func(state int) (int, error) { - fmt.Println("step 2") - return state + 15, nil - }, - func(state int) int { - fmt.Println("rollback 2") - return state - 15 - }, - ). - Then( - func(state int) (int, error) { - fmt.Println("step 3") - - if true { - return state, fmt.Errorf("error") - } - - return state + 42, nil - }, - func(state int) int { - fmt.Println("rollback 3") - return state - 42 - }, - ) - - _, _ = transaction.Process(-5) - - // Output: - // step 1 - // step 2 - // step 3 - // rollback 2 - // rollback 1 -} - -func ExampleTransaction_ok() { - transaction := NewTransaction[int](). - Then( - func(state int) (int, error) { - return state + 10, nil - }, - func(state int) int { - return state - 10 - }, - ). - Then( - func(state int) (int, error) { - return state + 15, nil - }, - func(state int) int { - return state - 15 - }, - ). - Then( - func(state int) (int, error) { - return state + 42, nil - }, - func(state int) int { - return state - 42 - }, - ) - - state, err := transaction.Process(-5) - - fmt.Println(state) - fmt.Println(err) - // Output: - // 62 - // -} - -func ExampleTransaction_error() { - transaction := NewTransaction[int](). - Then( - func(state int) (int, error) { - return state + 10, nil - }, - func(state int) int { - return state - 10 - }, - ). - Then( - func(state int) (int, error) { - return state, fmt.Errorf("error") - }, - func(state int) int { - return state - 15 - }, - ). - Then( - func(state int) (int, error) { - return state + 42, nil - }, - func(state int) int { - return state - 42 - }, - ) - - state, err := transaction.Process(-5) - - fmt.Println(state) - fmt.Println(err) - // Output: - // -5 - // error -} diff --git a/retry_test.go b/retry_test.go deleted file mode 100644 index 91ad651b..00000000 --- a/retry_test.go +++ /dev/null @@ -1,500 +0,0 @@ -package lo - -import ( - "fmt" - "sync" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestAttempt(t *testing.T) { - t.Parallel() - is := assert.New(t) - - err := fmt.Errorf("failed") - - iter1, err1 := Attempt(42, func(i int) error { - return nil - }) - iter2, err2 := Attempt(42, func(i int) error { - if i == 5 { - return nil - } - - return err - }) - iter3, err3 := Attempt(2, func(i int) error { - if i == 5 { - return nil - } - - return err - }) - iter4, err4 := Attempt(0, func(i int) error { - if i < 42 { - return err - } - - return nil - }) - - is.Equal(iter1, 1) - is.Equal(err1, nil) - is.Equal(iter2, 6) - is.Equal(err2, nil) - is.Equal(iter3, 2) - is.Equal(err3, err) - is.Equal(iter4, 43) - is.Equal(err4, nil) -} - -func TestAttemptWithDelay(t *testing.T) { - t.Parallel() - is := assert.New(t) - - err := fmt.Errorf("failed") - - iter1, dur1, err1 := AttemptWithDelay(42, 10*time.Millisecond, func(i int, d time.Duration) error { - return nil - }) - iter2, dur2, err2 := AttemptWithDelay(42, 10*time.Millisecond, func(i int, d time.Duration) error { - if i == 5 { - return nil - } - - return err - }) - iter3, dur3, err3 := AttemptWithDelay(2, 10*time.Millisecond, func(i int, d time.Duration) error { - if i == 5 { - return nil - } - - return err - }) - iter4, dur4, err4 := AttemptWithDelay(0, 10*time.Millisecond, func(i int, d time.Duration) error { - if i < 10 { - return err - } - - return nil - }) - - is.Equal(iter1, 1) - is.Greater(dur1, 0*time.Millisecond) - is.Less(dur1, 1*time.Millisecond) - is.Equal(err1, nil) - is.Equal(iter2, 6) - is.Greater(dur2, 50*time.Millisecond) - is.Less(dur2, 60*time.Millisecond) - is.Equal(err2, nil) - is.Equal(iter3, 2) - is.Greater(dur3, 10*time.Millisecond) - is.Less(dur3, 20*time.Millisecond) - is.Equal(err3, err) - is.Equal(iter4, 11) - is.Greater(dur4, 100*time.Millisecond) - is.Less(dur4, 115*time.Millisecond) - is.Equal(err4, nil) -} - -func TestAttemptWhile(t *testing.T) { - is := assert.New(t) - - err := fmt.Errorf("failed") - - iter1, err1 := AttemptWhile(42, func(i int) (error, bool) { - return nil, true - }) - - is.Equal(iter1, 1) - is.Nil(err1) - - iter2, err2 := AttemptWhile(42, func(i int) (error, bool) { - if i == 5 { - return nil, true - } - - return err, true - }) - - is.Equal(iter2, 6) - is.Nil(err2) - - iter3, err3 := AttemptWhile(2, func(i int) (error, bool) { - if i == 5 { - return nil, true - } - - return err, true - }) - - is.Equal(iter3, 2) - is.Equal(err3, err) - - iter4, err4 := AttemptWhile(0, func(i int) (error, bool) { - if i < 42 { - return err, true - } - - return nil, true - }) - - is.Equal(iter4, 43) - is.Nil(err4) - - iter5, err5 := AttemptWhile(0, func(i int) (error, bool) { - if i == 5 { - return nil, false - } - - return err, true - }) - - is.Equal(iter5, 6) - is.Nil(err5) - - iter6, err6 := AttemptWhile(0, func(i int) (error, bool) { - return nil, false - }) - - is.Equal(iter6, 1) - is.Nil(err6) - - iter7, err7 := AttemptWhile(42, func(i int) (error, bool) { - if i == 42 { - return nil, false - } - if i < 41 { - return err, true - } - - return nil, true - }) - - is.Equal(iter7, 42) - is.Nil(err7) -} - -func TestAttemptWhileWithDelay(t *testing.T) { - is := assert.New(t) - - err := fmt.Errorf("failed") - - iter1, dur1, err1 := AttemptWhileWithDelay(42, 10*time.Millisecond, func(i int, d time.Duration) (error, bool) { - return nil, true - }) - - is.Equal(iter1, 1) - is.Greater(dur1, 0*time.Millisecond) - is.Less(dur1, 1*time.Millisecond) - is.Nil(err1) - - iter2, dur2, err2 := AttemptWhileWithDelay(42, 10*time.Millisecond, func(i int, d time.Duration) (error, bool) { - if i == 5 { - return nil, true - } - - return err, true - }) - - is.Equal(iter2, 6) - is.Greater(dur2, 50*time.Millisecond) - is.Less(dur2, 60*time.Millisecond) - is.Nil(err2) - - iter3, dur3, err3 := AttemptWhileWithDelay(2, 10*time.Millisecond, func(i int, d time.Duration) (error, bool) { - if i == 5 { - return nil, true - } - - return err, true - }) - - is.Equal(iter3, 2) - is.Greater(dur3, 10*time.Millisecond) - is.Less(dur3, 20*time.Millisecond) - is.Equal(err3, err) - - iter4, dur4, err4 := AttemptWhileWithDelay(0, 10*time.Millisecond, func(i int, d time.Duration) (error, bool) { - if i < 10 { - return err, true - } - - return nil, true - }) - - is.Equal(iter4, 11) - is.Greater(dur4, 100*time.Millisecond) - is.Less(dur4, 115*time.Millisecond) - is.Nil(err4) - - iter5, dur5, err5 := AttemptWhileWithDelay(0, 10*time.Millisecond, func(i int, d time.Duration) (error, bool) { - if i == 5 { - return nil, false - } - - return err, true - }) - - is.Equal(iter5, 6) - is.Greater(dur5, 10*time.Millisecond) - is.Less(dur5, 115*time.Millisecond) - is.Nil(err5) - - iter6, dur6, err6 := AttemptWhileWithDelay(0, 10*time.Millisecond, func(i int, d time.Duration) (error, bool) { - return nil, false - }) - - is.Equal(iter6, 1) - is.Less(dur6, 10*time.Millisecond) - is.Less(dur6, 115*time.Millisecond) - is.Nil(err6) - - iter7, dur7, err7 := AttemptWhileWithDelay(42, 10*time.Millisecond, func(i int, d time.Duration) (error, bool) { - if i == 42 { - return nil, false - } - if i < 41 { - return err, true - } - - return nil, true - }) - - is.Equal(iter7, 42) - is.Less(dur7, 500*time.Millisecond) - is.Nil(err7) -} - -func TestDebounce(t *testing.T) { - t.Parallel() - - f1 := func() { - println("1. Called once after 10ms when func stopped invoking!") - } - f2 := func() { - println("2. Called once after 10ms when func stopped invoking!") - } - f3 := func() { - println("3. Called once after 10ms when func stopped invoking!") - } - - d1, _ := NewDebounce(10*time.Millisecond, f1) - - // execute 3 times - for i := 0; i < 3; i++ { - for j := 0; j < 10; j++ { - d1() - } - time.Sleep(20 * time.Millisecond) - } - - d2, _ := NewDebounce(10*time.Millisecond, f2) - - // execute once because it is always invoked and only last invoke is worked after 100ms - for i := 0; i < 3; i++ { - for j := 0; j < 5; j++ { - d2() - } - time.Sleep(5 * time.Millisecond) - } - - time.Sleep(10 * time.Millisecond) - - // execute once because it is canceled after 200ms. - d3, cancel := NewDebounce(10*time.Millisecond, f3) - for i := 0; i < 3; i++ { - for j := 0; j < 10; j++ { - d3() - } - time.Sleep(20 * time.Millisecond) - if i == 0 { - cancel() - } - } -} - -func TestDebounceBy(t *testing.T) { - t.Parallel() - is := assert.New(t) - - mu := sync.Mutex{} - output := map[int]int{0: 0, 1: 0, 2: 0} - - f1 := func(key int, count int) { - mu.Lock() - output[key] += count - mu.Unlock() - // fmt.Printf("[key=%d] 1. Called once after 10ms when func stopped invoking!\n", key) - } - f2 := func(key int, count int) { - mu.Lock() - output[key] += count - mu.Unlock() - // fmt.Printf("[key=%d] 2. Called once after 10ms when func stopped invoking!\n", key) - } - f3 := func(key int, count int) { - mu.Lock() - output[key] += count - mu.Unlock() - // fmt.Printf("[key=%d] 3. Called once after 10ms when func stopped invoking!\n", key) - } - - d1, _ := NewDebounceBy(10*time.Millisecond, f1) - - // execute 3 times - for i := 0; i < 3; i++ { - for j := 0; j < 10; j++ { - for k := 0; k < 3; k++ { - d1(k) - } - } - time.Sleep(20 * time.Millisecond) - } - - mu.Lock() - is.EqualValues(output[0], 30) - is.EqualValues(output[1], 30) - is.EqualValues(output[2], 30) - mu.Unlock() - - d2, _ := NewDebounceBy(10*time.Millisecond, f2) - - // execute once because it is always invoked and only last invoke is worked after 100ms - for i := 0; i < 3; i++ { - for j := 0; j < 5; j++ { - for k := 0; k < 3; k++ { - d2(k) - } - } - time.Sleep(5 * time.Millisecond) - } - - time.Sleep(10 * time.Millisecond) - - mu.Lock() - is.EqualValues(output[0], 45) - is.EqualValues(output[1], 45) - is.EqualValues(output[2], 45) - mu.Unlock() - - // execute once because it is canceled after 200ms. - d3, cancel := NewDebounceBy(10*time.Millisecond, f3) - for i := 0; i < 3; i++ { - for j := 0; j < 10; j++ { - for k := 0; k < 3; k++ { - d3(k) - } - } - - time.Sleep(20 * time.Millisecond) - if i == 0 { - for k := 0; k < 3; k++ { - cancel(k) - } - } - } - - mu.Lock() - is.EqualValues(output[0], 75) - is.EqualValues(output[1], 75) - is.EqualValues(output[2], 75) - mu.Unlock() -} - -func TestTransation(t *testing.T) { - is := assert.New(t) - - // no error - { - transaction := NewTransaction[int](). - Then( - func(state int) (int, error) { - return state + 100, nil - }, - func(state int) int { - return state - 100 - }, - ). - Then( - func(state int) (int, error) { - return state + 21, nil - }, - func(state int) int { - return state - 21 - }, - ) - - state, err := transaction.Process(21) - is.Equal(142, state) - is.Equal(nil, err) - } - - // with error - { - transaction := NewTransaction[int](). - Then( - func(state int) (int, error) { - return state + 100, nil - }, - func(state int) int { - return state - 100 - }, - ). - Then( - func(state int) (int, error) { - return state, assert.AnError - }, - func(state int) int { - return state - 21 - }, - ). - Then( - func(state int) (int, error) { - return state + 42, nil - }, - func(state int) int { - return state - 42 - }, - ) - - state, err := transaction.Process(21) - is.Equal(21, state) - is.Equal(assert.AnError, err) - } - - // with error + update value - { - transaction := NewTransaction[int](). - Then( - func(state int) (int, error) { - return state + 100, nil - }, - func(state int) int { - return state - 100 - }, - ). - Then( - func(state int) (int, error) { - return state + 21, assert.AnError - }, - func(state int) int { - return state - 21 - }, - ). - Then( - func(state int) (int, error) { - return state + 42, nil - }, - func(state int) int { - return state - 42 - }, - ) - - state, err := transaction.Process(21) - is.Equal(42, state) - is.Equal(assert.AnError, err) - } -} diff --git a/slice.go b/slice.go index 49c991f8..7ef820ad 100644 --- a/slice.go +++ b/slice.go @@ -11,9 +11,9 @@ import ( func Filter[V any](collection []V, predicate func(item V, index int) bool) []V { result := make([]V, 0, len(collection)) - for i, item := range collection { - if predicate(item, i) { - result = append(result, item) + for i := range collection { + if predicate(collection[i], i) { + result = append(result, collection[i]) } } @@ -25,8 +25,8 @@ func Filter[V any](collection []V, predicate func(item V, index int) bool) []V { func Map[T any, R any](collection []T, iteratee func(item T, index int) R) []R { result := make([]R, len(collection)) - for i, item := range collection { - result[i] = iteratee(item, i) + for i := range collection { + result[i] = iteratee(collection[i], i) } return result @@ -41,8 +41,8 @@ func Map[T any, R any](collection []T, iteratee func(item T, index int) R) []R { func FilterMap[T any, R any](collection []T, callback func(item T, index int) (R, bool)) []R { result := []R{} - for i, item := range collection { - if r, ok := callback(item, i); ok { + for i := range collection { + if r, ok := callback(collection[i], i); ok { result = append(result, r) } } @@ -57,8 +57,8 @@ func FilterMap[T any, R any](collection []T, callback func(item T, index int) (R func FlatMap[T any, R any](collection []T, iteratee func(item T, index int) []R) []R { result := make([]R, 0, len(collection)) - for i, item := range collection { - result = append(result, iteratee(item, i)...) + for i := range collection { + result = append(result, iteratee(collection[i], i)...) } return result @@ -68,8 +68,8 @@ func FlatMap[T any, R any](collection []T, iteratee func(item T, index int) []R) // through accumulator, where each successive invocation is supplied the return value of the previous. // Play: https://go.dev/play/p/R4UHXZNaaUG func Reduce[T any, R any](collection []T, accumulator func(agg R, item T, index int) R, initial R) R { - for i, item := range collection { - initial = accumulator(initial, item, i) + for i := range collection { + initial = accumulator(initial, collection[i], i) } return initial @@ -88,8 +88,8 @@ func ReduceRight[T any, R any](collection []T, accumulator func(agg R, item T, i // ForEach iterates over elements of collection and invokes iteratee for each element. // Play: https://go.dev/play/p/oofyiUPRf8t func ForEach[T any](collection []T, iteratee func(item T, index int)) { - for i, item := range collection { - iteratee(item, i) + for i := range collection { + iteratee(collection[i], i) } } @@ -113,13 +113,13 @@ func Uniq[T comparable](collection []T) []T { result := make([]T, 0, len(collection)) seen := make(map[T]struct{}, len(collection)) - for _, item := range collection { - if _, ok := seen[item]; ok { + for i := range collection { + if _, ok := seen[collection[i]]; ok { continue } - seen[item] = struct{}{} - result = append(result, item) + seen[collection[i]] = struct{}{} + result = append(result, collection[i]) } return result @@ -133,15 +133,15 @@ func UniqBy[T any, U comparable](collection []T, iteratee func(item T) U) []T { result := make([]T, 0, len(collection)) seen := make(map[U]struct{}, len(collection)) - for _, item := range collection { - key := iteratee(item) + for i := range collection { + key := iteratee(collection[i]) if _, ok := seen[key]; ok { continue } seen[key] = struct{}{} - result = append(result, item) + result = append(result, collection[i]) } return result @@ -152,10 +152,10 @@ func UniqBy[T any, U comparable](collection []T, iteratee func(item T) U) []T { func GroupBy[T any, U comparable](collection []T, iteratee func(item T) U) map[U][]T { result := map[U][]T{} - for _, item := range collection { - key := iteratee(item) + for i := range collection { + key := iteratee(collection[i]) - result[key] = append(result[key], item) + result[key] = append(result[key], collection[i]) } return result @@ -195,8 +195,8 @@ func PartitionBy[T any, K comparable](collection []T, iteratee func(item T) K) [ result := [][]T{} seen := map[K]int{} - for _, item := range collection { - key := iteratee(item) + for i := range collection { + key := iteratee(collection[i]) resultIndex, ok := seen[key] if !ok { @@ -205,7 +205,7 @@ func PartitionBy[T any, K comparable](collection []T, iteratee func(item T) K) [ result = append(result, []T{}) } - result[resultIndex] = append(result[resultIndex], item) + result[resultIndex] = append(result[resultIndex], collection[i]) } return result @@ -232,7 +232,7 @@ func Flatten[T any](collection [][]T) []T { } // Interleave round-robin alternating input slices and sequentially appending value at index into result -// Play: https://go.dev/play/p/DDhlwrShbwe +// Play: https://go.dev/play/p/-RJkTLQEDVt func Interleave[T any](collections ...[]T) []T { if len(collections) == 0 { return []T{} @@ -240,8 +240,8 @@ func Interleave[T any](collections ...[]T) []T { maxSize := 0 totalSize := 0 - for _, c := range collections { - size := len(c) + for i := range collections { + size := len(collections[i]) totalSize += size if size > maxSize { maxSize = size @@ -334,9 +334,9 @@ func RepeatBy[T any](count int, predicate func(index int) T) []T { func KeyBy[K comparable, V any](collection []V, iteratee func(item V) K) map[K]V { result := make(map[K]V, len(collection)) - for _, v := range collection { - k := iteratee(v) - result[k] = v + for i := range collection { + k := iteratee(collection[i]) + result[k] = collection[i] } return result @@ -349,8 +349,8 @@ func KeyBy[K comparable, V any](collection []V, iteratee func(item V) K) map[K]V func Associate[T any, K comparable, V any](collection []T, transform func(item T) (K, V)) map[K]V { result := make(map[K]V, len(collection)) - for _, t := range collection { - k, v := transform(t) + for i := range collection { + k, v := transform(collection[i]) result[k] = v } @@ -422,20 +422,53 @@ func DropRightWhile[T any](collection []T, predicate func(item T) bool) []T { func Reject[V any](collection []V, predicate func(item V, index int) bool) []V { result := []V{} - for i, item := range collection { - if !predicate(item, i) { - result = append(result, item) + for i := range collection { + if !predicate(collection[i], i) { + result = append(result, collection[i]) + } + } + + return result +} + +// RejectMap is the opposite of FilterMap, this method returns a slice which 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 +// - whether the result element should be included or not. +func RejectMap[T any, R any](collection []T, callback func(item T, index int) (R, bool)) []R { + result := []R{} + + for i := range collection { + if r, ok := callback(collection[i], i); !ok { + result = append(result, r) } } return result } +// FilterReject mixes Filter and Reject, this method returns two slices, one for the elements of collection that +// predicate returns truthy for and one for the elements that predicate does not return truthy for. +func FilterReject[V any](collection []V, predicate func(V, int) bool) (kept []V, rejected []V) { + kept = make([]V, 0, len(collection)) + rejected = make([]V, 0, len(collection)) + + for i := range collection { + if predicate(collection[i], i) { + kept = append(kept, collection[i]) + } else { + rejected = append(rejected, collection[i]) + } + } + + return kept, rejected +} + // Count counts the number of elements in the collection that compare equal to value. // Play: https://go.dev/play/p/Y3FlK54yveC func Count[T comparable](collection []T, value T) (count int) { - for _, item := range collection { - if item == value { + for i := range collection { + if collection[i] == value { count++ } } @@ -446,8 +479,8 @@ func Count[T comparable](collection []T, value T) (count int) { // CountBy counts the number of elements in the collection for which predicate is true. // Play: https://go.dev/play/p/ByQbNYQQi4X func CountBy[T any](collection []T, predicate func(item T) bool) (count int) { - for _, item := range collection { - if predicate(item) { + for i := range collection { + if predicate(collection[i]) { count++ } } @@ -460,8 +493,8 @@ func CountBy[T any](collection []T, predicate func(item T) bool) (count int) { func CountValues[T comparable](collection []T) map[T]int { result := make(map[T]int) - for _, item := range collection { - result[item]++ + for i := range collection { + result[collection[i]]++ } return result @@ -473,8 +506,8 @@ func CountValues[T comparable](collection []T) map[T]int { func CountValuesBy[T any, U comparable](collection []T, mapper func(item T) U) map[U]int { result := make(map[U]int) - for _, item := range collection { - result[mapper(item)]++ + for i := range collection { + result[mapper(collection[i])]++ } return result @@ -558,9 +591,9 @@ func Compact[T comparable](collection []T) []T { result := make([]T, 0, len(collection)) - for _, item := range collection { - if item != zero { - result = append(result, item) + for i := range collection { + if collection[i] != zero { + result = append(result, collection[i]) } } diff --git a/slice_benchmark_test.go b/slice_benchmark_test.go deleted file mode 100644 index 38911c08..00000000 --- a/slice_benchmark_test.go +++ /dev/null @@ -1,173 +0,0 @@ -package lo - -import ( - "fmt" - "math/rand" - "strconv" - "testing" -) - -var lengths = []int{10, 100, 1000} - -func BenchmarkChunk(b *testing.B) { - for _, n := range lengths { - strs := genSliceString(n) - b.Run(fmt.Sprintf("strings_%d", n), func(b *testing.B) { - for i := 0; i < b.N; i++ { - _ = Chunk(strs, 5) - } - }) - } - - for _, n := range lengths { - ints := genSliceInt(n) - b.Run(fmt.Sprintf("ints%d", n), func(b *testing.B) { - for i := 0; i < b.N; i++ { - _ = Chunk(ints, 5) - } - }) - } -} - -func genSliceString(n int) []string { - res := make([]string, 0, n) - for i := 0; i < n; i++ { - res = append(res, strconv.Itoa(rand.Intn(100_000))) - } - return res -} - -func genSliceInt(n int) []int { - res := make([]int, 0, n) - for i := 0; i < n; i++ { - res = append(res, rand.Intn(100_000)) - } - return res -} - -func BenchmarkFlatten(b *testing.B) { - for _, n := range lengths { - ints := make([][]int, 0, n) - for i := 0; i < n; i++ { - ints = append(ints, genSliceInt(n)) - } - b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) { - for i := 0; i < b.N; i++ { - _ = Flatten(ints) - } - }) - } - - for _, n := range lengths { - strs := make([][]string, 0, n) - for i := 0; i < n; i++ { - strs = append(strs, genSliceString(n)) - } - b.Run(fmt.Sprintf("strings_%d", n), func(b *testing.B) { - for i := 0; i < b.N; i++ { - _ = Flatten(strs) - } - }) - } -} - -func BenchmarkDrop(b *testing.B) { - for _, n := range lengths { - strs := genSliceString(n) - b.Run(fmt.Sprintf("strings_%d", n), func(b *testing.B) { - for i := 0; i < b.N; i++ { - _ = Drop(strs, n/4) - } - }) - } - - for _, n := range lengths { - ints := genSliceInt(n) - b.Run(fmt.Sprintf("ints%d", n), func(b *testing.B) { - for i := 0; i < b.N; i++ { - _ = Drop(ints, n/4) - } - }) - } -} - -func BenchmarkDropRight(b *testing.B) { - for _, n := range lengths { - strs := genSliceString(n) - b.Run(fmt.Sprintf("strings_%d", n), func(b *testing.B) { - for i := 0; i < b.N; i++ { - _ = DropRight(strs, n/4) - } - }) - } - - for _, n := range lengths { - ints := genSliceInt(n) - b.Run(fmt.Sprintf("ints%d", n), func(b *testing.B) { - for i := 0; i < b.N; i++ { - _ = DropRight(ints, n/4) - } - }) - } -} - -func BenchmarkDropWhile(b *testing.B) { - for _, n := range lengths { - strs := genSliceString(n) - b.Run(fmt.Sprintf("strings_%d", n), func(b *testing.B) { - for i := 0; i < b.N; i++ { - _ = DropWhile(strs, func(v string) bool { return len(v) < 4 }) - } - }) - } - - for _, n := range lengths { - ints := genSliceInt(n) - b.Run(fmt.Sprintf("ints%d", n), func(b *testing.B) { - for i := 0; i < b.N; i++ { - _ = DropWhile(ints, func(v int) bool { return i < 10_000 }) - } - }) - } -} - -func BenchmarkDropRightWhile(b *testing.B) { - for _, n := range lengths { - strs := genSliceString(n) - b.Run(fmt.Sprintf("strings_%d", n), func(b *testing.B) { - for i := 0; i < b.N; i++ { - _ = DropRightWhile(strs, func(v string) bool { return len(v) < 4 }) - } - }) - } - - for _, n := range lengths { - ints := genSliceInt(n) - b.Run(fmt.Sprintf("ints%d", n), func(b *testing.B) { - for i := 0; i < b.N; i++ { - _ = DropRightWhile(ints, func(v int) bool { return i < 10_000 }) - } - }) - } -} - -func BenchmarkReplace(b *testing.B) { - lengths := []int{1_000, 10_000, 100_000} - for _, n := range lengths { - strs := genSliceString(n) - b.Run(fmt.Sprintf("strings_%d", n), func(b *testing.B) { - for i := 0; i < b.N; i++ { - _ = Replace(strs, strs[n/4], "123123", 10) - } - }) - } - - for _, n := range lengths { - ints := genSliceInt(n) - b.Run(fmt.Sprintf("ints%d", n), func(b *testing.B) { - for i := 0; i < b.N; i++ { - _ = Replace(ints, ints[n/4], 123123, 10) - } - }) - } -} diff --git a/slice_example_test.go b/slice_example_test.go deleted file mode 100644 index b5b49d2b..00000000 --- a/slice_example_test.go +++ /dev/null @@ -1,458 +0,0 @@ -package lo - -import ( - "fmt" - "math" - "strconv" -) - -func ExampleFilter() { - list := []int64{1, 2, 3, 4} - - result := Filter(list, func(nbr int64, index int) bool { - return nbr%2 == 0 - }) - - fmt.Printf("%v", result) - // Output: [2 4] -} - -func ExampleMap() { - list := []int64{1, 2, 3, 4} - - result := Map(list, func(nbr int64, index int) string { - return strconv.FormatInt(nbr*2, 10) - }) - - fmt.Printf("%v", result) - // Output: [2 4 6 8] -} - -func ExampleFilterMap() { - list := []int64{1, 2, 3, 4} - - result := FilterMap(list, func(nbr int64, index int) (string, bool) { - return strconv.FormatInt(nbr*2, 10), nbr%2 == 0 - }) - - fmt.Printf("%v", result) - // Output: [4 8] -} - -func ExampleFlatMap() { - list := []int64{1, 2, 3, 4} - - result := FlatMap(list, func(nbr int64, index int) []string { - return []string{ - strconv.FormatInt(nbr, 10), // base 10 - strconv.FormatInt(nbr, 2), // base 2 - } - }) - - fmt.Printf("%v", result) - // Output: [1 1 2 10 3 11 4 100] -} - -func ExampleReduce() { - list := []int64{1, 2, 3, 4} - - result := Reduce(list, func(agg int64, item int64, index int) int64 { - return agg + item - }, 0) - - fmt.Printf("%v", result) - // Output: 10 -} - -func ExampleReduceRight() { - list := [][]int{{0, 1}, {2, 3}, {4, 5}} - - result := ReduceRight(list, func(agg []int, item []int, index int) []int { - return append(agg, item...) - }, []int{}) - - fmt.Printf("%v", result) - // Output: [4 5 2 3 0 1] -} - -func ExampleForEach() { - list := []int64{1, 2, 3, 4} - - ForEach(list, func(x int64, _ int) { - fmt.Println(x) - }) - - // Output: - // 1 - // 2 - // 3 - // 4 -} - -func ExampleTimes() { - result := Times(3, func(i int) string { - return strconv.FormatInt(int64(i), 10) - }) - - fmt.Printf("%v", result) - // Output: [0 1 2] -} - -func ExampleUniq() { - list := []int{1, 2, 2, 1} - - result := Uniq(list) - - fmt.Printf("%v", result) - // Output: [1 2] -} - -func ExampleUniqBy() { - list := []int{0, 1, 2, 3, 4, 5} - - result := UniqBy(list, func(i int) int { - return i % 3 - }) - - fmt.Printf("%v", result) - // Output: [0 1 2] -} - -func ExampleGroupBy() { - list := []int{0, 1, 2, 3, 4, 5} - - result := GroupBy(list, func(i int) int { - return i % 3 - }) - - fmt.Printf("%v\n", result[0]) - fmt.Printf("%v\n", result[1]) - fmt.Printf("%v\n", result[2]) - // Output: - // [0 3] - // [1 4] - // [2 5] -} - -func ExampleChunk() { - list := []int{0, 1, 2, 3, 4} - - result := Chunk(list, 2) - - for _, item := range result { - fmt.Printf("%v\n", item) - } - // Output: - // [0 1] - // [2 3] - // [4] -} - -func ExamplePartitionBy() { - list := []int{-2, -1, 0, 1, 2, 3, 4} - - result := PartitionBy(list, func(x int) string { - if x < 0 { - return "negative" - } else if x%2 == 0 { - return "even" - } - return "odd" - }) - - for _, item := range result { - fmt.Printf("%v\n", item) - } - // Output: - // [-2 -1] - // [0 2 4] - // [1 3] -} - -func ExampleFlatten() { - list := [][]int{{0, 1, 2}, {3, 4, 5}} - - result := Flatten(list) - - fmt.Printf("%v", result) - // Output: [0 1 2 3 4 5] -} - -func ExampleInterleave() { - list1 := [][]int{{1, 4, 7}, {2, 5, 8}, {3, 6, 9}} - list2 := [][]int{{1}, {2, 5, 8}, {3, 6}, {4, 7, 9, 10}} - - result1 := Interleave(list1...) - result2 := Interleave(list2...) - - fmt.Printf("%v\n", result1) - fmt.Printf("%v\n", result2) - // Output: - // [1 2 3 4 5 6 7 8 9] - // [1 2 3 4 5 6 7 8 9 10] -} - -func ExampleShuffle() { - list := []int{0, 1, 2, 3, 4, 5} - - result := Shuffle(list) - - fmt.Printf("%v", result) -} - -func ExampleReverse() { - list := []int{0, 1, 2, 3, 4, 5} - - result := Reverse(list) - - fmt.Printf("%v", result) - // Output: [5 4 3 2 1 0] -} - -func ExampleFill() { - list := []foo{{"a"}, {"a"}} - - result := Fill(list, foo{"b"}) - - fmt.Printf("%v", result) - // Output: [{b} {b}] -} - -func ExampleRepeat() { - result := Repeat(2, foo{"a"}) - - fmt.Printf("%v", result) - // Output: [{a} {a}] -} - -func ExampleRepeatBy() { - result := RepeatBy(5, func(i int) string { - return strconv.FormatInt(int64(math.Pow(float64(i), 2)), 10) - }) - - fmt.Printf("%v", result) - // Output: [0 1 4 9 16] -} - -func ExampleKeyBy() { - list := []string{"a", "aa", "aaa"} - - result := KeyBy(list, func(str string) int { - return len(str) - }) - - fmt.Printf("%v", result) - // Output: map[1:a 2:aa 3:aaa] -} - -func ExampleAssociate() { - list := []string{"a", "aa", "aaa"} - - result := Associate(list, func(str string) (string, int) { - return str, len(str) - }) - - fmt.Printf("%v", result) - // Output: map[a:1 aa:2 aaa:3] -} - -func ExampleDrop() { - list := []int{0, 1, 2, 3, 4, 5} - - result := Drop(list, 2) - - fmt.Printf("%v", result) - // Output: [2 3 4 5] -} - -func ExampleDropRight() { - list := []int{0, 1, 2, 3, 4, 5} - - result := DropRight(list, 2) - - fmt.Printf("%v", result) - // Output: [0 1 2 3] -} - -func ExampleDropWhile() { - list := []int{0, 1, 2, 3, 4, 5} - - result := DropWhile(list, func(val int) bool { - return val < 2 - }) - - fmt.Printf("%v", result) - // Output: [2 3 4 5] -} - -func ExampleDropRightWhile() { - list := []int{0, 1, 2, 3, 4, 5} - - result := DropRightWhile(list, func(val int) bool { - return val > 2 - }) - - fmt.Printf("%v", result) - // Output: [0 1 2] -} - -func ExampleReject() { - list := []int{0, 1, 2, 3, 4, 5} - - result := Reject(list, func(x int, _ int) bool { - return x%2 == 0 - }) - - fmt.Printf("%v", result) - // Output: [1 3 5] -} - -func ExampleCount() { - list := []int{0, 1, 2, 3, 4, 5, 0, 1, 2, 3} - - result := Count(list, 2) - - fmt.Printf("%v", result) - // Output: 2 -} - -func ExampleCountBy() { - list := []int{0, 1, 2, 3, 4, 5, 0, 1, 2, 3} - - result := CountBy(list, func(i int) bool { - return i < 4 - }) - - fmt.Printf("%v", result) - // Output: 8 -} - -func ExampleCountValues() { - result1 := CountValues([]int{}) - result2 := CountValues([]int{1, 2}) - result3 := CountValues([]int{1, 2, 2}) - result4 := CountValues([]string{"foo", "bar", ""}) - result5 := CountValues([]string{"foo", "bar", "bar"}) - - fmt.Printf("%v\n", result1) - fmt.Printf("%v\n", result2) - fmt.Printf("%v\n", result3) - fmt.Printf("%v\n", result4) - fmt.Printf("%v\n", result5) - // Output: - // map[] - // map[1:1 2:1] - // map[1:1 2:2] - // map[:1 bar:1 foo:1] - // map[bar:2 foo:1] -} - -func ExampleCountValuesBy() { - isEven := func(v int) bool { - return v%2 == 0 - } - - result1 := CountValuesBy([]int{}, isEven) - result2 := CountValuesBy([]int{1, 2}, isEven) - result3 := CountValuesBy([]int{1, 2, 2}, isEven) - - length := func(v string) int { - return len(v) - } - - result4 := CountValuesBy([]string{"foo", "bar", ""}, length) - result5 := CountValuesBy([]string{"foo", "bar", "bar"}, length) - - fmt.Printf("%v\n", result1) - fmt.Printf("%v\n", result2) - fmt.Printf("%v\n", result3) - fmt.Printf("%v\n", result4) - fmt.Printf("%v\n", result5) - // Output: - // map[] - // map[false:1 true:1] - // map[false:1 true:2] - // map[0:1 3:2] - // map[3:3] -} - -func ExampleSubset() { - list := []int{0, 1, 2, 3, 4, 5} - - result := Subset(list, 2, 3) - - fmt.Printf("%v", result) - // Output: [2 3 4] -} - -func ExampleSlice() { - list := []int{0, 1, 2, 3, 4, 5} - - result := Slice(list, 1, 4) - fmt.Printf("%v\n", result) - - result = Slice(list, 4, 1) - fmt.Printf("%v\n", result) - - result = Slice(list, 4, 5) - fmt.Printf("%v\n", result) - - // Output: - // [1 2 3] - // [] - // [4] -} - -func ExampleReplace() { - list := []int{0, 1, 0, 1, 2, 3, 0} - - result := Replace(list, 0, 42, 1) - fmt.Printf("%v\n", result) - - result = Replace(list, -1, 42, 1) - fmt.Printf("%v\n", result) - - result = Replace(list, 0, 42, 2) - fmt.Printf("%v\n", result) - - result = Replace(list, 0, 42, -1) - fmt.Printf("%v\n", result) - - // Output: - // [42 1 0 1 2 3 0] - // [0 1 0 1 2 3 0] - // [42 1 42 1 2 3 0] - // [42 1 42 1 2 3 42] -} - -func ExampleReplaceAll() { - list := []string{"", "foo", "", "bar", ""} - - result := Compact(list) - - fmt.Printf("%v", result) - - // Output: [foo bar] -} - -func ExampleIsSorted() { - list := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} - - result := IsSorted(list) - - fmt.Printf("%v", result) - - // Output: true -} - -func ExampleIsSortedByKey() { - list := []string{"a", "bb", "ccc"} - - result := IsSortedByKey(list, func(s string) int { - return len(s) - }) - - fmt.Printf("%v", result) - - // Output: true -} diff --git a/slice_test.go b/slice_test.go deleted file mode 100644 index 03326a01..00000000 --- a/slice_test.go +++ /dev/null @@ -1,761 +0,0 @@ -package lo - -import ( - "fmt" - "math" - "reflect" - "strconv" - "strings" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestFilter(t *testing.T) { - t.Parallel() - is := assert.New(t) - - r1 := Filter([]int{1, 2, 3, 4}, func(x int, _ int) bool { - return x%2 == 0 - }) - - is.Equal(r1, []int{2, 4}) - - r2 := Filter([]string{"", "foo", "", "bar", ""}, func(x string, _ int) bool { - return len(x) > 0 - }) - - is.Equal(r2, []string{"foo", "bar"}) -} - -func TestMap(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := Map([]int{1, 2, 3, 4}, func(x int, _ int) string { - return "Hello" - }) - result2 := Map([]int64{1, 2, 3, 4}, func(x int64, _ int) string { - return strconv.FormatInt(x, 10) - }) - - is.Equal(len(result1), 4) - is.Equal(len(result2), 4) - is.Equal(result1, []string{"Hello", "Hello", "Hello", "Hello"}) - is.Equal(result2, []string{"1", "2", "3", "4"}) -} - -func TestFilterMap(t *testing.T) { - t.Parallel() - is := assert.New(t) - - r1 := FilterMap([]int64{1, 2, 3, 4}, func(x int64, _ int) (string, bool) { - if x%2 == 0 { - return strconv.FormatInt(x, 10), true - } - return "", false - }) - r2 := FilterMap([]string{"cpu", "gpu", "mouse", "keyboard"}, func(x string, _ int) (string, bool) { - if strings.HasSuffix(x, "pu") { - return "xpu", true - } - return "", false - }) - - is.Equal(len(r1), 2) - is.Equal(len(r2), 2) - is.Equal(r1, []string{"2", "4"}) - is.Equal(r2, []string{"xpu", "xpu"}) -} - -func TestFlatMap(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := FlatMap([]int{0, 1, 2, 3, 4}, func(x int, _ int) []string { - return []string{"Hello"} - }) - result2 := FlatMap([]int64{0, 1, 2, 3, 4}, func(x int64, _ int) []string { - result := make([]string, 0, x) - for i := int64(0); i < x; i++ { - result = append(result, strconv.FormatInt(x, 10)) - } - return result - }) - - is.Equal(len(result1), 5) - is.Equal(len(result2), 10) - is.Equal(result1, []string{"Hello", "Hello", "Hello", "Hello", "Hello"}) - is.Equal(result2, []string{"1", "2", "2", "3", "3", "3", "4", "4", "4", "4"}) -} - -func TestTimes(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := Times(3, func(i int) string { - return strconv.FormatInt(int64(i), 10) - }) - - is.Equal(len(result1), 3) - is.Equal(result1, []string{"0", "1", "2"}) -} - -func TestReduce(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := Reduce([]int{1, 2, 3, 4}, func(agg int, item int, _ int) int { - return agg + item - }, 0) - result2 := Reduce([]int{1, 2, 3, 4}, func(agg int, item int, _ int) int { - return agg + item - }, 10) - - is.Equal(result1, 10) - is.Equal(result2, 20) -} - -func TestReduceRight(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := ReduceRight([][]int{{0, 1}, {2, 3}, {4, 5}}, func(agg []int, item []int, _ int) []int { - return append(agg, item...) - }, []int{}) - - is.Equal(result1, []int{4, 5, 2, 3, 0, 1}) -} - -func TestForEach(t *testing.T) { - t.Parallel() - is := assert.New(t) - - // check of callback is called for every element and in proper order - - callParams1 := []string{} - callParams2 := []int{} - - ForEach([]string{"a", "b", "c"}, func(item string, i int) { - callParams1 = append(callParams1, item) - callParams2 = append(callParams2, i) - }) - - is.ElementsMatch([]string{"a", "b", "c"}, callParams1) - is.ElementsMatch([]int{0, 1, 2}, callParams2) - is.IsIncreasing(callParams2) -} - -func TestUniq(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := Uniq([]int{1, 2, 2, 1}) - - is.Equal(len(result1), 2) - is.Equal(result1, []int{1, 2}) -} - -func TestUniqBy(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := UniqBy([]int{0, 1, 2, 3, 4, 5}, func(i int) int { - return i % 3 - }) - - is.Equal(len(result1), 3) - is.Equal(result1, []int{0, 1, 2}) -} - -func TestGroupBy(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := GroupBy([]int{0, 1, 2, 3, 4, 5}, func(i int) int { - return i % 3 - }) - - is.Equal(len(result1), 3) - is.Equal(result1, map[int][]int{ - 0: {0, 3}, - 1: {1, 4}, - 2: {2, 5}, - }) -} - -func TestChunk(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := Chunk([]int{0, 1, 2, 3, 4, 5}, 2) - result2 := Chunk([]int{0, 1, 2, 3, 4, 5, 6}, 2) - result3 := Chunk([]int{}, 2) - result4 := Chunk([]int{0}, 2) - - is.Equal(result1, [][]int{{0, 1}, {2, 3}, {4, 5}}) - is.Equal(result2, [][]int{{0, 1}, {2, 3}, {4, 5}, {6}}) - is.Equal(result3, [][]int{}) - is.Equal(result4, [][]int{{0}}) - is.PanicsWithValue("Second parameter must be greater than 0", func() { - Chunk([]int{0}, 0) - }) -} - -func TestPartitionBy(t *testing.T) { - t.Parallel() - is := assert.New(t) - - oddEven := func(x int) string { - if x < 0 { - return "negative" - } else if x%2 == 0 { - return "even" - } - return "odd" - } - - result1 := PartitionBy([]int{-2, -1, 0, 1, 2, 3, 4, 5}, oddEven) - result2 := PartitionBy([]int{}, oddEven) - - is.Equal(result1, [][]int{{-2, -1}, {0, 2, 4}, {1, 3, 5}}) - is.Equal(result2, [][]int{}) -} - -func TestFlatten(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := Flatten([][]int{{0, 1}, {2, 3, 4, 5}}) - - is.Equal(result1, []int{0, 1, 2, 3, 4, 5}) -} - -func TestInterleave(t *testing.T) { - tests := []struct { - name string - collections [][]int - want []int - }{ - { - "nil", - [][]int{nil}, - []int{}, - }, - { - "empty", - [][]int{}, - []int{}, - }, - { - "empties", - [][]int{{}, {}}, - []int{}, - }, - { - "same length", - [][]int{{1, 3, 5}, {2, 4, 6}}, - []int{1, 2, 3, 4, 5, 6}, - }, - { - "different length", - [][]int{{1, 3, 5, 6}, {2, 4}}, - []int{1, 2, 3, 4, 5, 6}, - }, - { - "many slices", - [][]int{{1}, {2, 5, 8}, {3, 6}, {4, 7, 9, 10}}, - []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := Interleave(tt.collections...); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Interleave() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestShuffle(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := Shuffle([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) - result2 := Shuffle([]int{}) - - is.NotEqual(result1, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) - is.Equal(result2, []int{}) -} - -func TestReverse(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := Reverse([]int{0, 1, 2, 3, 4, 5}) - result2 := Reverse([]int{0, 1, 2, 3, 4, 5, 6}) - result3 := Reverse([]int{}) - - is.Equal(result1, []int{5, 4, 3, 2, 1, 0}) - is.Equal(result2, []int{6, 5, 4, 3, 2, 1, 0}) - is.Equal(result3, []int{}) -} - -func TestFill(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := Fill([]foo{{"a"}, {"a"}}, foo{"b"}) - result2 := Fill([]foo{}, foo{"a"}) - - is.Equal(result1, []foo{{"b"}, {"b"}}) - is.Equal(result2, []foo{}) -} - -func TestRepeat(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := Repeat(2, foo{"a"}) - result2 := Repeat(0, foo{"a"}) - - is.Equal(result1, []foo{{"a"}, {"a"}}) - is.Equal(result2, []foo{}) -} - -func TestRepeatBy(t *testing.T) { - t.Parallel() - is := assert.New(t) - - cb := func(i int) int { - return int(math.Pow(float64(i), 2)) - } - - result1 := RepeatBy(0, cb) - result2 := RepeatBy(2, cb) - result3 := RepeatBy(5, cb) - - is.Equal([]int{}, result1) - is.Equal([]int{0, 1}, result2) - is.Equal([]int{0, 1, 4, 9, 16}, result3) -} - -func TestKeyBy(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := KeyBy([]string{"a", "aa", "aaa"}, func(str string) int { - return len(str) - }) - - is.Equal(result1, map[int]string{1: "a", 2: "aa", 3: "aaa"}) -} - -func TestAssociate(t *testing.T) { - t.Parallel() - - type foo struct { - baz string - bar int - } - transform := func(f *foo) (string, int) { - return f.baz, f.bar - } - testCases := []struct { - in []*foo - expect map[string]int - }{ - { - in: []*foo{{baz: "apple", bar: 1}}, - expect: map[string]int{"apple": 1}, - }, - { - in: []*foo{{baz: "apple", bar: 1}, {baz: "banana", bar: 2}}, - expect: map[string]int{"apple": 1, "banana": 2}, - }, - { - in: []*foo{{baz: "apple", bar: 1}, {baz: "apple", bar: 2}}, - expect: map[string]int{"apple": 2}, - }, - } - for i, testCase := range testCases { - t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) { - is := assert.New(t) - is.Equal(Associate(testCase.in, transform), testCase.expect) - }) - } -} - -func TestSliceToMap(t *testing.T) { - t.Parallel() - - type foo struct { - baz string - bar int - } - transform := func(f *foo) (string, int) { - return f.baz, f.bar - } - testCases := []struct { - in []*foo - expect map[string]int - }{ - { - in: []*foo{{baz: "apple", bar: 1}}, - expect: map[string]int{"apple": 1}, - }, - { - in: []*foo{{baz: "apple", bar: 1}, {baz: "banana", bar: 2}}, - expect: map[string]int{"apple": 1, "banana": 2}, - }, - { - in: []*foo{{baz: "apple", bar: 1}, {baz: "apple", bar: 2}}, - expect: map[string]int{"apple": 2}, - }, - } - for i, testCase := range testCases { - t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) { - is := assert.New(t) - is.Equal(SliceToMap(testCase.in, transform), testCase.expect) - }) - } -} - -func TestDrop(t *testing.T) { - t.Parallel() - is := assert.New(t) - - is.Equal([]int{1, 2, 3, 4}, Drop([]int{0, 1, 2, 3, 4}, 1)) - is.Equal([]int{2, 3, 4}, Drop([]int{0, 1, 2, 3, 4}, 2)) - is.Equal([]int{3, 4}, Drop([]int{0, 1, 2, 3, 4}, 3)) - is.Equal([]int{4}, Drop([]int{0, 1, 2, 3, 4}, 4)) - is.Equal([]int{}, Drop([]int{0, 1, 2, 3, 4}, 5)) - is.Equal([]int{}, Drop([]int{0, 1, 2, 3, 4}, 6)) -} - -func TestDropRight(t *testing.T) { - t.Parallel() - is := assert.New(t) - - is.Equal([]int{0, 1, 2, 3}, DropRight([]int{0, 1, 2, 3, 4}, 1)) - is.Equal([]int{0, 1, 2}, DropRight([]int{0, 1, 2, 3, 4}, 2)) - is.Equal([]int{0, 1}, DropRight([]int{0, 1, 2, 3, 4}, 3)) - is.Equal([]int{0}, DropRight([]int{0, 1, 2, 3, 4}, 4)) - is.Equal([]int{}, DropRight([]int{0, 1, 2, 3, 4}, 5)) - is.Equal([]int{}, DropRight([]int{0, 1, 2, 3, 4}, 6)) -} - -func TestDropWhile(t *testing.T) { - t.Parallel() - is := assert.New(t) - - is.Equal([]int{4, 5, 6}, DropWhile([]int{0, 1, 2, 3, 4, 5, 6}, func(t int) bool { - return t != 4 - })) - - is.Equal([]int{}, DropWhile([]int{0, 1, 2, 3, 4, 5, 6}, func(t int) bool { - return true - })) - - is.Equal([]int{0, 1, 2, 3, 4, 5, 6}, DropWhile([]int{0, 1, 2, 3, 4, 5, 6}, func(t int) bool { - return t == 10 - })) -} - -func TestDropRightWhile(t *testing.T) { - t.Parallel() - is := assert.New(t) - - is.Equal([]int{0, 1, 2, 3}, DropRightWhile([]int{0, 1, 2, 3, 4, 5, 6}, func(t int) bool { - return t != 3 - })) - - is.Equal([]int{0, 1}, DropRightWhile([]int{0, 1, 2, 3, 4, 5, 6}, func(t int) bool { - return t != 1 - })) - - is.Equal([]int{0, 1, 2, 3, 4, 5, 6}, DropRightWhile([]int{0, 1, 2, 3, 4, 5, 6}, func(t int) bool { - return t == 10 - })) - - is.Equal([]int{}, DropRightWhile([]int{0, 1, 2, 3, 4, 5, 6}, func(t int) bool { - return t != 10 - })) -} - -func TestReject(t *testing.T) { - t.Parallel() - is := assert.New(t) - - r1 := Reject([]int{1, 2, 3, 4}, func(x int, _ int) bool { - return x%2 == 0 - }) - - is.Equal(r1, []int{1, 3}) - - r2 := Reject([]string{"Smith", "foo", "Domin", "bar", "Olivia"}, func(x string, _ int) bool { - return len(x) > 3 - }) - - is.Equal(r2, []string{"foo", "bar"}) -} - -func TestCount(t *testing.T) { - t.Parallel() - is := assert.New(t) - - count1 := Count([]int{1, 2, 1}, 1) - count2 := Count([]int{1, 2, 1}, 3) - count3 := Count([]int{}, 1) - - is.Equal(count1, 2) - is.Equal(count2, 0) - is.Equal(count3, 0) -} - -func TestCountBy(t *testing.T) { - t.Parallel() - is := assert.New(t) - - count1 := CountBy([]int{1, 2, 1}, func(i int) bool { - return i < 2 - }) - - count2 := CountBy([]int{1, 2, 1}, func(i int) bool { - return i > 2 - }) - - count3 := CountBy([]int{}, func(i int) bool { - return i <= 2 - }) - - is.Equal(count1, 2) - is.Equal(count2, 0) - is.Equal(count3, 0) -} - -func TestCountValues(t *testing.T) { - t.Parallel() - is := assert.New(t) - - is.Equal(map[int]int{}, CountValues([]int{})) - is.Equal(map[int]int{1: 1, 2: 1}, CountValues([]int{1, 2})) - is.Equal(map[int]int{1: 1, 2: 2}, CountValues([]int{1, 2, 2})) - is.Equal(map[string]int{"": 1, "foo": 1, "bar": 1}, CountValues([]string{"foo", "bar", ""})) - is.Equal(map[string]int{"foo": 1, "bar": 2}, CountValues([]string{"foo", "bar", "bar"})) -} - -func TestCountValuesBy(t *testing.T) { - t.Parallel() - is := assert.New(t) - - oddEven := func(v int) bool { - return v%2 == 0 - } - length := func(v string) int { - return len(v) - } - - result1 := CountValuesBy([]int{}, oddEven) - result2 := CountValuesBy([]int{1, 2}, oddEven) - result3 := CountValuesBy([]int{1, 2, 2}, oddEven) - result4 := CountValuesBy([]string{"foo", "bar", ""}, length) - result5 := CountValuesBy([]string{"foo", "bar", "bar"}, length) - - is.Equal(map[bool]int{}, result1) - is.Equal(map[bool]int{true: 1, false: 1}, result2) - is.Equal(map[bool]int{true: 2, false: 1}, result3) - is.Equal(map[int]int{0: 1, 3: 2}, result4) - is.Equal(map[int]int{3: 3}, result5) -} - -func TestSubset(t *testing.T) { - t.Parallel() - is := assert.New(t) - - in := []int{0, 1, 2, 3, 4} - - out1 := Subset(in, 0, 0) - out2 := Subset(in, 10, 2) - out3 := Subset(in, -10, 2) - out4 := Subset(in, 0, 10) - out5 := Subset(in, 0, 2) - out6 := Subset(in, 2, 2) - out7 := Subset(in, 2, 5) - out8 := Subset(in, 2, 3) - out9 := Subset(in, 2, 4) - out10 := Subset(in, -2, 4) - out11 := Subset(in, -4, 1) - out12 := Subset(in, -4, math.MaxUint) - - is.Equal([]int{}, out1) - is.Equal([]int{}, out2) - is.Equal([]int{0, 1}, out3) - is.Equal([]int{0, 1, 2, 3, 4}, out4) - is.Equal([]int{0, 1}, out5) - is.Equal([]int{2, 3}, out6) - is.Equal([]int{2, 3, 4}, out7) - is.Equal([]int{2, 3, 4}, out8) - is.Equal([]int{2, 3, 4}, out9) - is.Equal([]int{3, 4}, out10) - is.Equal([]int{1}, out11) - is.Equal([]int{1, 2, 3, 4}, out12) -} - -func TestSlice(t *testing.T) { - t.Parallel() - is := assert.New(t) - - in := []int{0, 1, 2, 3, 4} - - out1 := Slice(in, 0, 0) - out2 := Slice(in, 0, 1) - out3 := Slice(in, 0, 5) - out4 := Slice(in, 0, 6) - out5 := Slice(in, 1, 1) - out6 := Slice(in, 1, 5) - out7 := Slice(in, 1, 6) - out8 := Slice(in, 4, 5) - out9 := Slice(in, 5, 5) - out10 := Slice(in, 6, 5) - out11 := Slice(in, 6, 6) - out12 := Slice(in, 1, 0) - out13 := Slice(in, 5, 0) - out14 := Slice(in, 6, 4) - out15 := Slice(in, 6, 7) - out16 := Slice(in, -10, 1) - out17 := Slice(in, -1, 3) - out18 := Slice(in, -10, 7) - - is.Equal([]int{}, out1) - is.Equal([]int{0}, out2) - is.Equal([]int{0, 1, 2, 3, 4}, out3) - is.Equal([]int{0, 1, 2, 3, 4}, out4) - is.Equal([]int{}, out5) - is.Equal([]int{1, 2, 3, 4}, out6) - is.Equal([]int{1, 2, 3, 4}, out7) - is.Equal([]int{4}, out8) - is.Equal([]int{}, out9) - is.Equal([]int{}, out10) - is.Equal([]int{}, out11) - is.Equal([]int{}, out12) - is.Equal([]int{}, out13) - is.Equal([]int{}, out14) - is.Equal([]int{}, out15) - is.Equal([]int{0}, out16) - is.Equal([]int{0, 1, 2}, out17) - is.Equal([]int{0, 1, 2, 3, 4}, out18) -} - -func TestReplace(t *testing.T) { - t.Parallel() - is := assert.New(t) - - in := []int{0, 1, 0, 1, 2, 3, 0} - - out1 := Replace(in, 0, 42, 2) - out2 := Replace(in, 0, 42, 1) - out3 := Replace(in, 0, 42, 0) - out4 := Replace(in, 0, 42, -1) - out5 := Replace(in, 0, 42, -1) - out6 := Replace(in, -1, 42, 2) - out7 := Replace(in, -1, 42, 1) - out8 := Replace(in, -1, 42, 0) - out9 := Replace(in, -1, 42, -1) - out10 := Replace(in, -1, 42, -1) - - is.Equal([]int{42, 1, 42, 1, 2, 3, 0}, out1) - is.Equal([]int{42, 1, 0, 1, 2, 3, 0}, out2) - is.Equal([]int{0, 1, 0, 1, 2, 3, 0}, out3) - is.Equal([]int{42, 1, 42, 1, 2, 3, 42}, out4) - is.Equal([]int{42, 1, 42, 1, 2, 3, 42}, out5) - is.Equal([]int{0, 1, 0, 1, 2, 3, 0}, out6) - is.Equal([]int{0, 1, 0, 1, 2, 3, 0}, out7) - is.Equal([]int{0, 1, 0, 1, 2, 3, 0}, out8) - is.Equal([]int{0, 1, 0, 1, 2, 3, 0}, out9) - is.Equal([]int{0, 1, 0, 1, 2, 3, 0}, out10) -} - -func TestReplaceAll(t *testing.T) { - t.Parallel() - is := assert.New(t) - - in := []int{0, 1, 0, 1, 2, 3, 0} - - out1 := ReplaceAll(in, 0, 42) - out2 := ReplaceAll(in, -1, 42) - - is.Equal([]int{42, 1, 42, 1, 2, 3, 42}, out1) - is.Equal([]int{0, 1, 0, 1, 2, 3, 0}, out2) -} - -func TestCompact(t *testing.T) { - t.Parallel() - is := assert.New(t) - - r1 := Compact([]int{2, 0, 4, 0}) - - is.Equal(r1, []int{2, 4}) - - r2 := Compact([]string{"", "foo", "", "bar", ""}) - - is.Equal(r2, []string{"foo", "bar"}) - - r3 := Compact([]bool{true, false, true, false}) - - is.Equal(r3, []bool{true, true}) - - type foo struct { - bar int - baz string - } - - // slice of structs - // If all fields of an element are zero values, Compact removes it. - - r4 := Compact([]foo{ - {bar: 1, baz: "a"}, // all fields are non-zero values - {bar: 0, baz: ""}, // all fields are zero values - {bar: 2, baz: ""}, // bar is non-zero - }) - - is.Equal(r4, []foo{{bar: 1, baz: "a"}, {bar: 2, baz: ""}}) - - // slice of pointers to structs - // If an element is nil, Compact removes it. - - e1, e2, e3 := foo{bar: 1, baz: "a"}, foo{bar: 0, baz: ""}, foo{bar: 2, baz: ""} - // NOTE: e2 is a zero value of foo, but its pointer &e2 is not a zero value of *foo. - r5 := Compact([]*foo{&e1, &e2, nil, &e3}) - - is.Equal(r5, []*foo{&e1, &e2, &e3}) -} - -func TestIsSorted(t *testing.T) { - t.Parallel() - is := assert.New(t) - - is.True(IsSorted([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})) - is.True(IsSorted([]string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"})) - - is.False(IsSorted([]int{0, 1, 4, 3, 2, 5, 6, 7, 8, 9, 10})) - is.False(IsSorted([]string{"a", "b", "d", "c", "e", "f", "g", "h", "i", "j"})) -} - -func TestIsSortedByKey(t *testing.T) { - t.Parallel() - is := assert.New(t) - - is.True(IsSortedByKey([]string{"a", "bb", "ccc"}, func(s string) int { - return len(s) - })) - - is.False(IsSortedByKey([]string{"aa", "b", "ccc"}, func(s string) int { - return len(s) - })) - - is.True(IsSortedByKey([]string{"1", "2", "3", "11"}, func(s string) int { - ret, _ := strconv.Atoi(s) - return ret - })) -} diff --git a/string.go b/string.go index a7a959a3..2606ec86 100644 --- a/string.go +++ b/string.go @@ -2,8 +2,13 @@ package lo import ( "math/rand" + "regexp" "strings" + "unicode" "unicode/utf8" + + "golang.org/x/text/cases" + "golang.org/x/text/language" ) var ( @@ -14,6 +19,11 @@ var ( AlphanumericCharset = append(LettersCharset, NumbersCharset...) SpecialCharset = []rune("!@#$%^&*()_+-=[]{}|;':\",./<>?") AllCharset = append(AlphanumericCharset, SpecialCharset...) + + // bearer:disable go_lang_permissive_regex_validation + splitWordReg = regexp.MustCompile(`([a-z])([A-Z0-9])|([a-zA-Z])([0-9])|([0-9])([a-zA-Z])|([A-Z])([A-Z])([a-z])`) + // bearer:disable go_lang_permissive_regex_validation + splitNumberLetterReg = regexp.MustCompile(`([0-9])([a-zA-Z])`) ) // RandomString return a random string. @@ -29,6 +39,8 @@ func RandomString(size int, charset []rune) string { b := make([]rune, size) possibleCharactersCount := len(charset) for i := range b { + // @TODO: Upgrade to math/rand/v2 as soon as we set the minimum Go version to 1.22. + // bearer:disable go_gosec_crypto_weak_random b[i] = charset[rand.Intn(possibleCharactersCount)] } return string(b) @@ -94,3 +106,76 @@ func ChunkString[T ~string](str T, size int) []T { func RuneLength(str string) int { return utf8.RuneCountInString(str) } + +// PascalCase converts string to pascal case. +func PascalCase(str string) string { + items := Words(str) + for i := range items { + items[i] = Capitalize(items[i]) + } + return strings.Join(items, "") +} + +// CamelCase converts string to camel case. +func CamelCase(str string) string { + items := Words(str) + for i, item := range items { + item = strings.ToLower(item) + if i > 0 { + item = Capitalize(item) + } + items[i] = item + } + return strings.Join(items, "") +} + +// KebabCase converts string to kebab case. +func KebabCase(str string) string { + items := Words(str) + for i := range items { + items[i] = strings.ToLower(items[i]) + } + return strings.Join(items, "-") +} + +// SnakeCase converts string to snake case. +func SnakeCase(str string) string { + items := Words(str) + for i := range items { + items[i] = strings.ToLower(items[i]) + } + return strings.Join(items, "_") +} + +// Words splits string into an array of its words. +func Words(str string) []string { + str = splitWordReg.ReplaceAllString(str, `$1$3$5$7 $2$4$6$8$9`) + // example: Int8Value => Int 8Value => Int 8 Value + str = splitNumberLetterReg.ReplaceAllString(str, "$1 $2") + var result strings.Builder + for _, r := range str { + if unicode.IsLetter(r) || unicode.IsDigit(r) { + result.WriteRune(r) + } else { + result.WriteRune(' ') + } + } + return strings.Fields(result.String()) +} + +// Capitalize converts the first character of string to upper case and the remaining to lower case. +func Capitalize(str string) string { + return cases.Title(language.English).String(str) +} + +// Elipse truncates a string to a specified length and appends an ellipsis if truncated. +func Elipse(str string, length int) string { + if len(str) > length { + if len(str) < 3 || length < 3 { + return "..." + } + return str[0:length-3] + "..." + } + + return str +} diff --git a/string_example_test.go b/string_example_test.go deleted file mode 100644 index b659ea14..00000000 --- a/string_example_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package lo - -import ( - "fmt" - "math" -) - -func ExampleSubstring() { - result1 := Substring("hello", 2, 3) - result2 := Substring("hello", -4, 3) - result3 := Substring("hello", -2, math.MaxUint) - result4 := Substring("🏠🐶🐱", 0, 2) - result5 := Substring("你好,世界", 0, 3) - - fmt.Printf("%v\n", result1) - fmt.Printf("%v\n", result2) - fmt.Printf("%v\n", result3) - fmt.Printf("%v\n", result4) - fmt.Printf("%v\n", result5) - // Output: - // llo - // ell - // lo - // 🏠🐶 - // 你好, -} - -func ExampleChunkString() { - result1 := ChunkString("123456", 2) - result2 := ChunkString("1234567", 2) - result3 := ChunkString("", 2) - result4 := ChunkString("1", 2) - - fmt.Printf("%v\n", result1) - fmt.Printf("%v\n", result2) - fmt.Printf("%v\n", result3) - fmt.Printf("%v\n", result4) - // Output: - // [12 34 56] - // [12 34 56 7] - // [] - // [1] -} - -func ExampleRuneLength() { - result1, chars1 := RuneLength("hellô"), len("hellô") - result2, chars2 := RuneLength("🤘"), len("🤘") - - fmt.Printf("%v %v\n", result1, chars1) - fmt.Printf("%v %v\n", result2, chars2) - // Output: - // 5 6 - // 1 4 -} diff --git a/string_test.go b/string_test.go deleted file mode 100644 index 42832586..00000000 --- a/string_test.go +++ /dev/null @@ -1,102 +0,0 @@ -package lo - -import ( - "math" - "math/rand" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestRandomString(t *testing.T) { - t.Parallel() - is := assert.New(t) - - rand.Seed(time.Now().UnixNano()) - - str1 := RandomString(100, LowerCaseLettersCharset) - is.Equal(100, RuneLength(str1)) - is.Subset(LowerCaseLettersCharset, []rune(str1)) - - str2 := RandomString(100, LowerCaseLettersCharset) - is.NotEqual(str1, str2) - - noneUtf8Charset := []rune("明1好休2林森") - str3 := RandomString(100, noneUtf8Charset) - is.Equal(100, RuneLength(str3)) - is.Subset(noneUtf8Charset, []rune(str3)) - - is.PanicsWithValue("lo.RandomString: Charset parameter must not be empty", func() { RandomString(100, []rune{}) }) - is.PanicsWithValue("lo.RandomString: Size parameter must be greater than 0", func() { RandomString(0, LowerCaseLettersCharset) }) -} - -func TestChunkString(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := ChunkString("12345", 2) - is.Equal([]string{"12", "34", "5"}, result1) - - result2 := ChunkString("123456", 2) - is.Equal([]string{"12", "34", "56"}, result2) - - result3 := ChunkString("123456", 6) - is.Equal([]string{"123456"}, result3) - - result4 := ChunkString("123456", 10) - is.Equal([]string{"123456"}, result4) - - result5 := ChunkString("", 2) - is.Equal([]string{""}, result5) - - result6 := ChunkString("明1好休2林森", 2) - is.Equal([]string{"明1", "好休", "2林", "森"}, result6) - - is.Panics(func() { - ChunkString("12345", 0) - }) -} - -func TestSubstring(t *testing.T) { - t.Parallel() - is := assert.New(t) - - str1 := Substring("hello", 0, 0) - str2 := Substring("hello", 10, 2) - str3 := Substring("hello", -10, 2) - str4 := Substring("hello", 0, 10) - str5 := Substring("hello", 0, 2) - str6 := Substring("hello", 2, 2) - str7 := Substring("hello", 2, 5) - str8 := Substring("hello", 2, 3) - str9 := Substring("hello", 2, 4) - str10 := Substring("hello", -2, 4) - str11 := Substring("hello", -4, 1) - str12 := Substring("hello", -4, math.MaxUint) - str13 := Substring("🏠🐶🐱", 0, 2) - str14 := Substring("你好,世界", 0, 3) - - is.Equal("", str1) - is.Equal("", str2) - is.Equal("he", str3) - is.Equal("hello", str4) - is.Equal("he", str5) - is.Equal("ll", str6) - is.Equal("llo", str7) - is.Equal("llo", str8) - is.Equal("llo", str9) - is.Equal("lo", str10) - is.Equal("e", str11) - is.Equal("ello", str12) - is.Equal("🏠🐶", str13) - is.Equal("你好,", str14) -} - -func TestRuneLength(t *testing.T) { - t.Parallel() - is := assert.New(t) - - is.Equal(5, RuneLength("hellô")) - is.Equal(6, len("hellô")) -} diff --git a/time.go b/time.go new file mode 100644 index 00000000..e98e80f9 --- /dev/null +++ b/time.go @@ -0,0 +1,85 @@ +package lo + +import "time" + +// Duration returns the time taken to execute a function. +func Duration(cb func()) time.Duration { + return Duration0(cb) +} + +// Duration0 returns the time taken to execute a function. +func Duration0(cb func()) time.Duration { + start := time.Now() + cb() + return time.Since(start) +} + +// Duration1 returns the time taken to execute a function. +func Duration1[A any](cb func() A) (A, time.Duration) { + start := time.Now() + a := cb() + return a, time.Since(start) +} + +// Duration2 returns the time taken to execute a function. +func Duration2[A, B any](cb func() (A, B)) (A, B, time.Duration) { + start := time.Now() + a, b := cb() + return a, b, time.Since(start) +} + +// Duration3 returns the time taken to execute a function. +func Duration3[A, B, C any](cb func() (A, B, C)) (A, B, C, time.Duration) { + start := time.Now() + a, b, c := cb() + return a, b, c, time.Since(start) +} + +// Duration4 returns the time taken to execute a function. +func Duration4[A, B, C, D any](cb func() (A, B, C, D)) (A, B, C, D, time.Duration) { + start := time.Now() + a, b, c, d := cb() + return a, b, c, d, time.Since(start) +} + +// Duration5 returns the time taken to execute a function. +func Duration5[A, B, C, D, E any](cb func() (A, B, C, D, E)) (A, B, C, D, E, time.Duration) { + start := time.Now() + a, b, c, d, e := cb() + return a, b, c, d, e, time.Since(start) +} + +// Duration6 returns the time taken to execute a function. +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) { + start := time.Now() + a, b, c, d, e, f := cb() + return a, b, c, d, e, f, time.Since(start) +} + +// Duration7 returns the time taken to execute a function. +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) { + start := time.Now() + a, b, c, d, e, f, g := cb() + return a, b, c, d, e, f, g, time.Since(start) +} + +// Duration8 returns the time taken to execute a function. +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) { + start := time.Now() + a, b, c, d, e, f, g, h := cb() + return a, b, c, d, e, f, g, h, time.Since(start) +} + +// Duration9 returns the time taken to execute a function. +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) { + start := time.Now() + a, b, c, d, e, f, g, h, i := cb() + return a, b, c, d, e, f, g, h, i, time.Since(start) +} + +// Duration10 returns the time taken to execute a function. +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) { + start := time.Now() + a, b, c, d, e, f, g, h, i, j := cb() + return a, b, c, d, e, f, g, h, i, j, time.Since(start) +} diff --git a/tuples.go b/tuples.go index cdddf6af..18a03009 100644 --- a/tuples.go +++ b/tuples.go @@ -2,97 +2,97 @@ package lo // T2 creates a tuple from a list of values. // Play: https://go.dev/play/p/IllL3ZO4BQm -func T2[A any, B any](a A, b B) Tuple2[A, B] { +func T2[A, B any](a A, b B) Tuple2[A, B] { return Tuple2[A, B]{A: a, B: b} } // T3 creates a tuple from a list of values. // Play: https://go.dev/play/p/IllL3ZO4BQm -func T3[A any, B any, C any](a A, b B, c C) Tuple3[A, B, C] { +func T3[A, B, C any](a A, b B, c C) Tuple3[A, B, C] { return Tuple3[A, B, C]{A: a, B: b, C: c} } // T4 creates a tuple from a list of values. // Play: https://go.dev/play/p/IllL3ZO4BQm -func T4[A any, B any, C any, D any](a A, b B, c C, d D) Tuple4[A, B, C, D] { +func T4[A, B, C, D any](a A, b B, c C, d D) Tuple4[A, B, C, D] { return Tuple4[A, B, C, D]{A: a, B: b, C: c, D: d} } // T5 creates a tuple from a list of values. // Play: https://go.dev/play/p/IllL3ZO4BQm -func T5[A any, B any, C any, D any, E any](a A, b B, c C, d D, e E) Tuple5[A, B, C, D, E] { +func T5[A, B, C, D, E any](a A, b B, c C, d D, e E) Tuple5[A, B, C, D, E] { return Tuple5[A, B, C, D, E]{A: a, B: b, C: c, D: d, E: e} } // T6 creates a tuple from a list of values. // Play: https://go.dev/play/p/IllL3ZO4BQm -func T6[A any, B any, C any, D any, E any, F any](a A, b B, c C, d D, e E, f F) Tuple6[A, B, C, D, E, F] { +func T6[A, B, C, D, E, F any](a A, b B, c C, d D, e E, f F) Tuple6[A, B, C, D, E, F] { return Tuple6[A, B, C, D, E, F]{A: a, B: b, C: c, D: d, E: e, F: f} } // T7 creates a tuple from a list of values. // Play: https://go.dev/play/p/IllL3ZO4BQm -func T7[A any, B any, C any, D any, E any, F any, G any](a A, b B, c C, d D, e E, f F, g G) Tuple7[A, B, C, D, E, F, G] { +func T7[A, B, C, D, E, F, G any](a A, b B, c C, d D, e E, f F, g G) Tuple7[A, B, C, D, E, F, G] { return Tuple7[A, B, C, D, E, F, G]{A: a, B: b, C: c, D: d, E: e, F: f, G: g} } // T8 creates a tuple from a list of values. // Play: https://go.dev/play/p/IllL3ZO4BQm -func T8[A any, B any, C any, D any, E any, F any, G any, H any](a A, b B, c C, d D, e E, f F, g G, h H) Tuple8[A, B, C, D, E, F, G, H] { +func T8[A, B, C, D, E, F, G, H any](a A, b B, c C, d D, e E, f F, g G, h H) Tuple8[A, B, C, D, E, F, G, H] { return Tuple8[A, B, C, D, E, F, G, H]{A: a, B: b, C: c, D: d, E: e, F: f, G: g, H: h} } // T9 creates a tuple from a list of values. // Play: https://go.dev/play/p/IllL3ZO4BQm -func T9[A any, B any, C any, D any, E any, F any, G any, H any, I any](a A, b B, c C, d D, e E, f F, g G, h H, i I) Tuple9[A, B, C, D, E, F, G, H, I] { +func T9[A, B, C, D, E, F, G, H, I any](a A, b B, c C, d D, e E, f F, g G, h H, i I) Tuple9[A, B, C, D, E, F, G, H, I] { return Tuple9[A, B, C, D, E, F, G, H, I]{A: a, B: b, C: c, D: d, E: e, F: f, G: g, H: h, I: i} } // Unpack2 returns values contained in tuple. // Play: https://go.dev/play/p/xVP_k0kJ96W -func Unpack2[A any, B any](tuple Tuple2[A, B]) (A, B) { +func Unpack2[A, B any](tuple Tuple2[A, B]) (A, B) { return tuple.A, tuple.B } // Unpack3 returns values contained in tuple. // Play: https://go.dev/play/p/xVP_k0kJ96W -func Unpack3[A any, B any, C any](tuple Tuple3[A, B, C]) (A, B, C) { +func Unpack3[A, B, C any](tuple Tuple3[A, B, C]) (A, B, C) { return tuple.A, tuple.B, tuple.C } // Unpack4 returns values contained in tuple. // Play: https://go.dev/play/p/xVP_k0kJ96W -func Unpack4[A any, B any, C any, D any](tuple Tuple4[A, B, C, D]) (A, B, C, D) { +func Unpack4[A, B, C, D any](tuple Tuple4[A, B, C, D]) (A, B, C, D) { return tuple.A, tuple.B, tuple.C, tuple.D } // Unpack5 returns values contained in tuple. // Play: https://go.dev/play/p/xVP_k0kJ96W -func Unpack5[A any, B any, C any, D any, E any](tuple Tuple5[A, B, C, D, E]) (A, B, C, D, E) { +func Unpack5[A, B, C, D, E any](tuple Tuple5[A, B, C, D, E]) (A, B, C, D, E) { return tuple.A, tuple.B, tuple.C, tuple.D, tuple.E } // Unpack6 returns values contained in tuple. // Play: https://go.dev/play/p/xVP_k0kJ96W -func Unpack6[A any, B any, C any, D any, E any, F any](tuple Tuple6[A, B, C, D, E, F]) (A, B, C, D, E, F) { +func Unpack6[A, B, C, D, E, F any](tuple Tuple6[A, B, C, D, E, F]) (A, B, C, D, E, F) { return tuple.A, tuple.B, tuple.C, tuple.D, tuple.E, tuple.F } // Unpack7 returns values contained in tuple. // Play: https://go.dev/play/p/xVP_k0kJ96W -func Unpack7[A any, B any, C any, D any, E any, F any, G any](tuple Tuple7[A, B, C, D, E, F, G]) (A, B, C, D, E, F, G) { +func Unpack7[A, B, C, D, E, F, G any](tuple Tuple7[A, B, C, D, E, F, G]) (A, B, C, D, E, F, G) { return tuple.A, tuple.B, tuple.C, tuple.D, tuple.E, tuple.F, tuple.G } // Unpack8 returns values contained in tuple. // Play: https://go.dev/play/p/xVP_k0kJ96W -func Unpack8[A any, B any, C any, D any, E any, F any, G any, H any](tuple Tuple8[A, B, C, D, E, F, G, H]) (A, B, C, D, E, F, G, H) { +func Unpack8[A, B, C, D, E, F, G, H any](tuple Tuple8[A, B, C, D, E, F, G, H]) (A, B, C, D, E, F, G, H) { return tuple.A, tuple.B, tuple.C, tuple.D, tuple.E, tuple.F, tuple.G, tuple.H } // Unpack9 returns values contained in tuple. // Play: https://go.dev/play/p/xVP_k0kJ96W -func Unpack9[A any, B any, C any, D any, E any, F any, G any, H any, I any](tuple Tuple9[A, B, C, D, E, F, G, H, I]) (A, B, C, D, E, F, G, H, I) { +func Unpack9[A, B, C, D, E, F, G, H, I any](tuple Tuple9[A, B, C, D, E, F, G, H, I]) (A, B, C, D, E, F, G, H, I) { return tuple.A, tuple.B, tuple.C, tuple.D, tuple.E, tuple.F, tuple.G, tuple.H, tuple.I } @@ -100,7 +100,7 @@ func Unpack9[A any, B any, C any, D any, E any, F any, G any, H any, I any](tupl // of the given arrays, the second of which contains the second elements of the given arrays, and so on. // When collections have different size, the Tuple attributes are filled with zero value. // Play: https://go.dev/play/p/jujaA6GaJTp -func Zip2[A any, B any](a []A, b []B) []Tuple2[A, B] { +func Zip2[A, B any](a []A, b []B) []Tuple2[A, B] { size := Max([]int{len(a), len(b)}) result := make([]Tuple2[A, B], 0, size) @@ -122,7 +122,7 @@ func Zip2[A any, B any](a []A, b []B) []Tuple2[A, B] { // of the given arrays, the second of which contains the second elements of the given arrays, and so on. // When collections have different size, the Tuple attributes are filled with zero value. // Play: https://go.dev/play/p/jujaA6GaJTp -func Zip3[A any, B any, C any](a []A, b []B, c []C) []Tuple3[A, B, C] { +func Zip3[A, B, C any](a []A, b []B, c []C) []Tuple3[A, B, C] { size := Max([]int{len(a), len(b), len(c)}) result := make([]Tuple3[A, B, C], 0, size) @@ -146,7 +146,7 @@ func Zip3[A any, B any, C any](a []A, b []B, c []C) []Tuple3[A, B, C] { // of the given arrays, the second of which contains the second elements of the given arrays, and so on. // When collections have different size, the Tuple attributes are filled with zero value. // Play: https://go.dev/play/p/jujaA6GaJTp -func Zip4[A any, B any, C any, D any](a []A, b []B, c []C, d []D) []Tuple4[A, B, C, D] { +func Zip4[A, B, C, D any](a []A, b []B, c []C, d []D) []Tuple4[A, B, C, D] { size := Max([]int{len(a), len(b), len(c), len(d)}) result := make([]Tuple4[A, B, C, D], 0, size) @@ -172,7 +172,7 @@ func Zip4[A any, B any, C any, D any](a []A, b []B, c []C, d []D) []Tuple4[A, B, // of the given arrays, the second of which contains the second elements of the given arrays, and so on. // When collections have different size, the Tuple attributes are filled with zero value. // Play: https://go.dev/play/p/jujaA6GaJTp -func Zip5[A any, B any, C any, D any, E any](a []A, b []B, c []C, d []D, e []E) []Tuple5[A, B, C, D, E] { +func Zip5[A, B, C, D, E any](a []A, b []B, c []C, d []D, e []E) []Tuple5[A, B, C, D, E] { size := Max([]int{len(a), len(b), len(c), len(d), len(e)}) result := make([]Tuple5[A, B, C, D, E], 0, size) @@ -200,7 +200,7 @@ func Zip5[A any, B any, C any, D any, E any](a []A, b []B, c []C, d []D, e []E) // of the given arrays, the second of which contains the second elements of the given arrays, and so on. // When collections have different size, the Tuple attributes are filled with zero value. // Play: https://go.dev/play/p/jujaA6GaJTp -func Zip6[A any, B any, C any, D any, E any, F any](a []A, b []B, c []C, d []D, e []E, f []F) []Tuple6[A, B, C, D, E, F] { +func Zip6[A, B, C, D, E, F any](a []A, b []B, c []C, d []D, e []E, f []F) []Tuple6[A, B, C, D, E, F] { size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f)}) result := make([]Tuple6[A, B, C, D, E, F], 0, size) @@ -230,7 +230,7 @@ func Zip6[A any, B any, C any, D any, E any, F any](a []A, b []B, c []C, d []D, // of the given arrays, the second of which contains the second elements of the given arrays, and so on. // When collections have different size, the Tuple attributes are filled with zero value. // Play: https://go.dev/play/p/jujaA6GaJTp -func Zip7[A any, B any, C any, D any, E any, F any, G any](a []A, b []B, c []C, d []D, e []E, f []F, g []G) []Tuple7[A, B, C, D, E, F, G] { +func Zip7[A, B, C, D, E, F, G any](a []A, b []B, c []C, d []D, e []E, f []F, g []G) []Tuple7[A, B, C, D, E, F, G] { size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g)}) result := make([]Tuple7[A, B, C, D, E, F, G], 0, size) @@ -262,7 +262,7 @@ func Zip7[A any, B any, C any, D any, E any, F any, G any](a []A, b []B, c []C, // of the given arrays, the second of which contains the second elements of the given arrays, and so on. // When collections have different size, the Tuple attributes are filled with zero value. // Play: https://go.dev/play/p/jujaA6GaJTp -func Zip8[A any, B any, C any, D any, E any, F any, G any, H any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, h []H) []Tuple8[A, B, C, D, E, F, G, H] { +func Zip8[A, B, C, D, E, F, G, H any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, h []H) []Tuple8[A, B, C, D, E, F, G, H] { size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h)}) result := make([]Tuple8[A, B, C, D, E, F, G, H], 0, size) @@ -296,7 +296,7 @@ func Zip8[A any, B any, C any, D any, E any, F any, G any, H any](a []A, b []B, // of the given arrays, the second of which contains the second elements of the given arrays, and so on. // When collections have different size, the Tuple attributes are filled with zero value. // Play: https://go.dev/play/p/jujaA6GaJTp -func Zip9[A any, B any, C any, D any, E any, F any, G any, H any, I any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, h []H, i []I) []Tuple9[A, B, C, D, E, F, G, H, I] { +func Zip9[A, B, C, D, E, F, G, H, I any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, h []H, i []I) []Tuple9[A, B, C, D, E, F, G, H, I] { size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h), len(i)}) result := make([]Tuple9[A, B, C, D, E, F, G, H, I], 0, size) @@ -328,17 +328,189 @@ func Zip9[A any, B any, C any, D any, E any, F any, G any, H any, I any](a []A, return result } +// ZipBy2 creates a slice of transformed elements, the first of which contains the first elements +// of the given arrays, the second of which contains the second elements of the given arrays, and so on. +// When collections have different size, the Tuple attributes are filled with zero value. +func ZipBy2[A any, B any, Out any](a []A, b []B, iteratee func(a A, b B) Out) []Out { + size := Max([]int{len(a), len(b)}) + + result := make([]Out, 0, size) + + for index := 0; index < size; index++ { + _a, _ := Nth(a, index) + _b, _ := Nth(b, index) + + result = append(result, iteratee(_a, _b)) + } + + return result +} + +// ZipBy3 creates a slice of transformed elements, the first of which contains the first elements +// of the given arrays, the second of which contains the second elements of the given arrays, and so on. +// When collections have different size, the Tuple attributes are filled with zero value. +func ZipBy3[A any, B any, C any, Out any](a []A, b []B, c []C, iteratee func(a A, b B, c C) Out) []Out { + size := Max([]int{len(a), len(b), len(c)}) + + result := make([]Out, 0, size) + + for index := 0; index < size; index++ { + _a, _ := Nth(a, index) + _b, _ := Nth(b, index) + _c, _ := Nth(c, index) + + result = append(result, iteratee(_a, _b, _c)) + } + + return result +} + +// ZipBy4 creates a slice of transformed elements, the first of which contains the first elements +// of the given arrays, the second of which contains the second elements of the given arrays, and so on. +// When collections have different size, the Tuple attributes are filled with zero value. +func ZipBy4[A any, B any, C any, D any, Out any](a []A, b []B, c []C, d []D, iteratee func(a A, b B, c C, d D) Out) []Out { + size := Max([]int{len(a), len(b), len(c), len(d)}) + + result := make([]Out, 0, size) + + for index := 0; index < size; index++ { + _a, _ := Nth(a, index) + _b, _ := Nth(b, index) + _c, _ := Nth(c, index) + _d, _ := Nth(d, index) + + result = append(result, iteratee(_a, _b, _c, _d)) + } + + return result +} + +// ZipBy5 creates a slice of transformed elements, the first of which contains the first elements +// of the given arrays, the second of which contains the second elements of the given arrays, and so on. +// When collections have different size, the Tuple attributes are filled with zero value. +func ZipBy5[A any, B any, C any, D any, E any, Out any](a []A, b []B, c []C, d []D, e []E, iteratee func(a A, b B, c C, d D, e E) Out) []Out { + size := Max([]int{len(a), len(b), len(c), len(d), len(e)}) + + result := make([]Out, 0, size) + + for index := 0; index < size; index++ { + _a, _ := Nth(a, index) + _b, _ := Nth(b, index) + _c, _ := Nth(c, index) + _d, _ := Nth(d, index) + _e, _ := Nth(e, index) + + result = append(result, iteratee(_a, _b, _c, _d, _e)) + } + + return result +} + +// ZipBy6 creates a slice of transformed elements, the first of which contains the first elements +// of the given arrays, the second of which contains the second elements of the given arrays, and so on. +// When collections have different size, the Tuple attributes are filled with zero value. +func ZipBy6[A any, B any, C any, D any, E any, F any, Out any](a []A, b []B, c []C, d []D, e []E, f []F, iteratee func(a A, b B, c C, d D, e E, f F) Out) []Out { + size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f)}) + + result := make([]Out, 0, size) + + for index := 0; index < size; index++ { + _a, _ := Nth(a, index) + _b, _ := Nth(b, index) + _c, _ := Nth(c, index) + _d, _ := Nth(d, index) + _e, _ := Nth(e, index) + _f, _ := Nth(f, index) + + result = append(result, iteratee(_a, _b, _c, _d, _e, _f)) + } + + return result +} + +// ZipBy7 creates a slice of transformed elements, the first of which contains the first elements +// of the given arrays, the second of which contains the second elements of the given arrays, and so on. +// When collections have different size, the Tuple attributes are filled with zero value. +func ZipBy7[A any, B any, C any, D any, E any, F any, G any, Out any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, iteratee func(a A, b B, c C, d D, e E, f F, g G) Out) []Out { + size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f)}) + + result := make([]Out, 0, size) + + for index := 0; index < size; index++ { + _a, _ := Nth(a, index) + _b, _ := Nth(b, index) + _c, _ := Nth(c, index) + _d, _ := Nth(d, index) + _e, _ := Nth(e, index) + _f, _ := Nth(f, index) + _g, _ := Nth(g, index) + + result = append(result, iteratee(_a, _b, _c, _d, _e, _f, _g)) + } + + return result +} + +// ZipBy8 creates a slice of transformed elements, the first of which contains the first elements +// of the given arrays, the second of which contains the second elements of the given arrays, and so on. +// When collections have different size, the Tuple attributes are filled with zero value. +func ZipBy8[A any, B any, C any, D any, E any, F any, G any, H any, Out any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, h []H, iteratee func(a A, b B, c C, d D, e E, f F, g G, h H) Out) []Out { + size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g)}) + + result := make([]Out, 0, size) + + for index := 0; index < size; index++ { + _a, _ := Nth(a, index) + _b, _ := Nth(b, index) + _c, _ := Nth(c, index) + _d, _ := Nth(d, index) + _e, _ := Nth(e, index) + _f, _ := Nth(f, index) + _g, _ := Nth(g, index) + _h, _ := Nth(h, index) + + result = append(result, iteratee(_a, _b, _c, _d, _e, _f, _g, _h)) + } + + return result +} + +// ZipBy9 creates a slice of transformed elements, the first of which contains the first elements +// of the given arrays, the second of which contains the second elements of the given arrays, and so on. +// When collections have different size, the Tuple attributes are filled with zero value. +func ZipBy9[A any, B any, C any, D any, E any, F any, G any, H any, I any, Out any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, h []H, i []I, iteratee func(a A, b B, c C, d D, e E, f F, g G, h H, i I) Out) []Out { + size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h), len(i)}) + + result := make([]Out, 0, size) + + for index := 0; index < size; index++ { + _a, _ := Nth(a, index) + _b, _ := Nth(b, index) + _c, _ := Nth(c, index) + _d, _ := Nth(d, index) + _e, _ := Nth(e, index) + _f, _ := Nth(f, index) + _g, _ := Nth(g, index) + _h, _ := Nth(h, index) + _i, _ := Nth(i, index) + + result = append(result, iteratee(_a, _b, _c, _d, _e, _f, _g, _h, _i)) + } + + return result +} + // Unzip2 accepts an array of grouped elements and creates an array regrouping the elements // to their pre-zip configuration. // Play: https://go.dev/play/p/ciHugugvaAW -func Unzip2[A any, B any](tuples []Tuple2[A, B]) ([]A, []B) { +func Unzip2[A, B any](tuples []Tuple2[A, B]) ([]A, []B) { size := len(tuples) r1 := make([]A, 0, size) r2 := make([]B, 0, size) - for _, tuple := range tuples { - r1 = append(r1, tuple.A) - r2 = append(r2, tuple.B) + for i := range tuples { + r1 = append(r1, tuples[i].A) + r2 = append(r2, tuples[i].B) } return r1, r2 @@ -347,16 +519,16 @@ func Unzip2[A any, B any](tuples []Tuple2[A, B]) ([]A, []B) { // Unzip3 accepts an array of grouped elements and creates an array regrouping the elements // to their pre-zip configuration. // Play: https://go.dev/play/p/ciHugugvaAW -func Unzip3[A any, B any, C any](tuples []Tuple3[A, B, C]) ([]A, []B, []C) { +func Unzip3[A, B, C any](tuples []Tuple3[A, B, C]) ([]A, []B, []C) { size := len(tuples) r1 := make([]A, 0, size) r2 := make([]B, 0, size) r3 := make([]C, 0, size) - for _, tuple := range tuples { - r1 = append(r1, tuple.A) - r2 = append(r2, tuple.B) - r3 = append(r3, tuple.C) + for i := range tuples { + r1 = append(r1, tuples[i].A) + r2 = append(r2, tuples[i].B) + r3 = append(r3, tuples[i].C) } return r1, r2, r3 @@ -365,18 +537,18 @@ func Unzip3[A any, B any, C any](tuples []Tuple3[A, B, C]) ([]A, []B, []C) { // Unzip4 accepts an array of grouped elements and creates an array regrouping the elements // to their pre-zip configuration. // Play: https://go.dev/play/p/ciHugugvaAW -func Unzip4[A any, B any, C any, D any](tuples []Tuple4[A, B, C, D]) ([]A, []B, []C, []D) { +func Unzip4[A, B, C, D any](tuples []Tuple4[A, B, C, D]) ([]A, []B, []C, []D) { size := len(tuples) r1 := make([]A, 0, size) r2 := make([]B, 0, size) r3 := make([]C, 0, size) r4 := make([]D, 0, size) - for _, tuple := range tuples { - r1 = append(r1, tuple.A) - r2 = append(r2, tuple.B) - r3 = append(r3, tuple.C) - r4 = append(r4, tuple.D) + for i := range tuples { + r1 = append(r1, tuples[i].A) + r2 = append(r2, tuples[i].B) + r3 = append(r3, tuples[i].C) + r4 = append(r4, tuples[i].D) } return r1, r2, r3, r4 @@ -385,7 +557,7 @@ func Unzip4[A any, B any, C any, D any](tuples []Tuple4[A, B, C, D]) ([]A, []B, // Unzip5 accepts an array of grouped elements and creates an array regrouping the elements // to their pre-zip configuration. // Play: https://go.dev/play/p/ciHugugvaAW -func Unzip5[A any, B any, C any, D any, E any](tuples []Tuple5[A, B, C, D, E]) ([]A, []B, []C, []D, []E) { +func Unzip5[A, B, C, D, E any](tuples []Tuple5[A, B, C, D, E]) ([]A, []B, []C, []D, []E) { size := len(tuples) r1 := make([]A, 0, size) r2 := make([]B, 0, size) @@ -393,12 +565,12 @@ func Unzip5[A any, B any, C any, D any, E any](tuples []Tuple5[A, B, C, D, E]) ( r4 := make([]D, 0, size) r5 := make([]E, 0, size) - for _, tuple := range tuples { - r1 = append(r1, tuple.A) - r2 = append(r2, tuple.B) - r3 = append(r3, tuple.C) - r4 = append(r4, tuple.D) - r5 = append(r5, tuple.E) + for i := range tuples { + r1 = append(r1, tuples[i].A) + r2 = append(r2, tuples[i].B) + r3 = append(r3, tuples[i].C) + r4 = append(r4, tuples[i].D) + r5 = append(r5, tuples[i].E) } return r1, r2, r3, r4, r5 @@ -407,7 +579,7 @@ func Unzip5[A any, B any, C any, D any, E any](tuples []Tuple5[A, B, C, D, E]) ( // Unzip6 accepts an array of grouped elements and creates an array regrouping the elements // to their pre-zip configuration. // Play: https://go.dev/play/p/ciHugugvaAW -func Unzip6[A any, B any, C any, D any, E any, F any](tuples []Tuple6[A, B, C, D, E, F]) ([]A, []B, []C, []D, []E, []F) { +func Unzip6[A, B, C, D, E, F any](tuples []Tuple6[A, B, C, D, E, F]) ([]A, []B, []C, []D, []E, []F) { size := len(tuples) r1 := make([]A, 0, size) r2 := make([]B, 0, size) @@ -416,13 +588,13 @@ func Unzip6[A any, B any, C any, D any, E any, F any](tuples []Tuple6[A, B, C, D r5 := make([]E, 0, size) r6 := make([]F, 0, size) - for _, tuple := range tuples { - r1 = append(r1, tuple.A) - r2 = append(r2, tuple.B) - r3 = append(r3, tuple.C) - r4 = append(r4, tuple.D) - r5 = append(r5, tuple.E) - r6 = append(r6, tuple.F) + for i := range tuples { + r1 = append(r1, tuples[i].A) + r2 = append(r2, tuples[i].B) + r3 = append(r3, tuples[i].C) + r4 = append(r4, tuples[i].D) + r5 = append(r5, tuples[i].E) + r6 = append(r6, tuples[i].F) } return r1, r2, r3, r4, r5, r6 @@ -431,7 +603,7 @@ func Unzip6[A any, B any, C any, D any, E any, F any](tuples []Tuple6[A, B, C, D // Unzip7 accepts an array of grouped elements and creates an array regrouping the elements // to their pre-zip configuration. // Play: https://go.dev/play/p/ciHugugvaAW -func Unzip7[A any, B any, C any, D any, E any, F any, G any](tuples []Tuple7[A, B, C, D, E, F, G]) ([]A, []B, []C, []D, []E, []F, []G) { +func Unzip7[A, B, C, D, E, F, G any](tuples []Tuple7[A, B, C, D, E, F, G]) ([]A, []B, []C, []D, []E, []F, []G) { size := len(tuples) r1 := make([]A, 0, size) r2 := make([]B, 0, size) @@ -441,14 +613,14 @@ func Unzip7[A any, B any, C any, D any, E any, F any, G any](tuples []Tuple7[A, r6 := make([]F, 0, size) r7 := make([]G, 0, size) - for _, tuple := range tuples { - r1 = append(r1, tuple.A) - r2 = append(r2, tuple.B) - r3 = append(r3, tuple.C) - r4 = append(r4, tuple.D) - r5 = append(r5, tuple.E) - r6 = append(r6, tuple.F) - r7 = append(r7, tuple.G) + for i := range tuples { + r1 = append(r1, tuples[i].A) + r2 = append(r2, tuples[i].B) + r3 = append(r3, tuples[i].C) + r4 = append(r4, tuples[i].D) + r5 = append(r5, tuples[i].E) + r6 = append(r6, tuples[i].F) + r7 = append(r7, tuples[i].G) } return r1, r2, r3, r4, r5, r6, r7 @@ -457,7 +629,7 @@ func Unzip7[A any, B any, C any, D any, E any, F any, G any](tuples []Tuple7[A, // Unzip8 accepts an array of grouped elements and creates an array regrouping the elements // to their pre-zip configuration. // Play: https://go.dev/play/p/ciHugugvaAW -func Unzip8[A any, B any, C any, D any, E any, F any, G any, H any](tuples []Tuple8[A, B, C, D, E, F, G, H]) ([]A, []B, []C, []D, []E, []F, []G, []H) { +func Unzip8[A, B, C, D, E, F, G, H any](tuples []Tuple8[A, B, C, D, E, F, G, H]) ([]A, []B, []C, []D, []E, []F, []G, []H) { size := len(tuples) r1 := make([]A, 0, size) r2 := make([]B, 0, size) @@ -468,15 +640,15 @@ func Unzip8[A any, B any, C any, D any, E any, F any, G any, H any](tuples []Tup r7 := make([]G, 0, size) r8 := make([]H, 0, size) - for _, tuple := range tuples { - r1 = append(r1, tuple.A) - r2 = append(r2, tuple.B) - r3 = append(r3, tuple.C) - r4 = append(r4, tuple.D) - r5 = append(r5, tuple.E) - r6 = append(r6, tuple.F) - r7 = append(r7, tuple.G) - r8 = append(r8, tuple.H) + for i := range tuples { + r1 = append(r1, tuples[i].A) + r2 = append(r2, tuples[i].B) + r3 = append(r3, tuples[i].C) + r4 = append(r4, tuples[i].D) + r5 = append(r5, tuples[i].E) + r6 = append(r6, tuples[i].F) + r7 = append(r7, tuples[i].G) + r8 = append(r8, tuples[i].H) } return r1, r2, r3, r4, r5, r6, r7, r8 @@ -485,7 +657,7 @@ func Unzip8[A any, B any, C any, D any, E any, F any, G any, H any](tuples []Tup // Unzip9 accepts an array of grouped elements and creates an array regrouping the elements // to their pre-zip configuration. // Play: https://go.dev/play/p/ciHugugvaAW -func Unzip9[A any, B any, C any, D any, E any, F any, G any, H any, I any](tuples []Tuple9[A, B, C, D, E, F, G, H, I]) ([]A, []B, []C, []D, []E, []F, []G, []H, []I) { +func Unzip9[A, B, C, D, E, F, G, H, I any](tuples []Tuple9[A, B, C, D, E, F, G, H, I]) ([]A, []B, []C, []D, []E, []F, []G, []H, []I) { size := len(tuples) r1 := make([]A, 0, size) r2 := make([]B, 0, size) @@ -497,16 +669,200 @@ func Unzip9[A any, B any, C any, D any, E any, F any, G any, H any, I any](tuple r8 := make([]H, 0, size) r9 := make([]I, 0, size) - for _, tuple := range tuples { - r1 = append(r1, tuple.A) - r2 = append(r2, tuple.B) - r3 = append(r3, tuple.C) - r4 = append(r4, tuple.D) - r5 = append(r5, tuple.E) - r6 = append(r6, tuple.F) - r7 = append(r7, tuple.G) - r8 = append(r8, tuple.H) - r9 = append(r9, tuple.I) + for i := range tuples { + r1 = append(r1, tuples[i].A) + r2 = append(r2, tuples[i].B) + r3 = append(r3, tuples[i].C) + r4 = append(r4, tuples[i].D) + r5 = append(r5, tuples[i].E) + r6 = append(r6, tuples[i].F) + r7 = append(r7, tuples[i].G) + r8 = append(r8, tuples[i].H) + r9 = append(r9, tuples[i].I) + } + + return r1, r2, r3, r4, r5, r6, r7, r8, r9 +} + +// UnzipBy2 iterates over a collection and creates an array regrouping the elements +// to their pre-zip configuration. +func UnzipBy2[In any, A any, B any](items []In, iteratee func(In) (a A, b B)) ([]A, []B) { + size := len(items) + r1 := make([]A, 0, size) + r2 := make([]B, 0, size) + + for i := range items { + a, b := iteratee(items[i]) + r1 = append(r1, a) + r2 = append(r2, b) + } + + return r1, r2 +} + +// UnzipBy3 iterates over a collection and creates an array regrouping the elements +// to their pre-zip configuration. +func UnzipBy3[In any, A any, B any, C any](items []In, iteratee func(In) (a A, b B, c C)) ([]A, []B, []C) { + size := len(items) + r1 := make([]A, 0, size) + r2 := make([]B, 0, size) + r3 := make([]C, 0, size) + + for i := range items { + a, b, c := iteratee(items[i]) + r1 = append(r1, a) + r2 = append(r2, b) + r3 = append(r3, c) + } + + return r1, r2, r3 +} + +// UnzipBy4 iterates over a collection and creates an array regrouping the elements +// to their pre-zip configuration. +func UnzipBy4[In any, A any, B any, C any, D any](items []In, iteratee func(In) (a A, b B, c C, d D)) ([]A, []B, []C, []D) { + size := len(items) + r1 := make([]A, 0, size) + r2 := make([]B, 0, size) + r3 := make([]C, 0, size) + r4 := make([]D, 0, size) + + for i := range items { + a, b, c, d := iteratee(items[i]) + r1 = append(r1, a) + r2 = append(r2, b) + r3 = append(r3, c) + r4 = append(r4, d) + } + + return r1, r2, r3, r4 +} + +// UnzipBy5 iterates over a collection and creates an array regrouping the elements +// to their pre-zip configuration. +func UnzipBy5[In any, A any, B any, C any, D any, E any](items []In, iteratee func(In) (a A, b B, c C, d D, e E)) ([]A, []B, []C, []D, []E) { + size := len(items) + r1 := make([]A, 0, size) + r2 := make([]B, 0, size) + r3 := make([]C, 0, size) + r4 := make([]D, 0, size) + r5 := make([]E, 0, size) + + for i := range items { + a, b, c, d, e := iteratee(items[i]) + r1 = append(r1, a) + r2 = append(r2, b) + r3 = append(r3, c) + r4 = append(r4, d) + r5 = append(r5, e) + } + + return r1, r2, r3, r4, r5 +} + +// UnzipBy6 iterates over a collection and creates an array regrouping the elements +// to their pre-zip configuration. +func UnzipBy6[In any, A any, B any, C any, D any, E any, F any](items []In, iteratee func(In) (a A, b B, c C, d D, e E, f F)) ([]A, []B, []C, []D, []E, []F) { + size := len(items) + r1 := make([]A, 0, size) + r2 := make([]B, 0, size) + r3 := make([]C, 0, size) + r4 := make([]D, 0, size) + r5 := make([]E, 0, size) + r6 := make([]F, 0, size) + + for i := range items { + a, b, c, d, e, f := iteratee(items[i]) + r1 = append(r1, a) + r2 = append(r2, b) + r3 = append(r3, c) + r4 = append(r4, d) + r5 = append(r5, e) + r6 = append(r6, f) + } + + return r1, r2, r3, r4, r5, r6 +} + +// UnzipBy7 iterates over a collection and creates an array regrouping the elements +// to their pre-zip configuration. +func UnzipBy7[In any, A any, B any, C any, D any, E any, F any, G any](items []In, iteratee func(In) (a A, b B, c C, d D, e E, f F, g G)) ([]A, []B, []C, []D, []E, []F, []G) { + size := len(items) + r1 := make([]A, 0, size) + r2 := make([]B, 0, size) + r3 := make([]C, 0, size) + r4 := make([]D, 0, size) + r5 := make([]E, 0, size) + r6 := make([]F, 0, size) + r7 := make([]G, 0, size) + + for i := range items { + a, b, c, d, e, f, g := iteratee(items[i]) + r1 = append(r1, a) + r2 = append(r2, b) + r3 = append(r3, c) + r4 = append(r4, d) + r5 = append(r5, e) + r6 = append(r6, f) + r7 = append(r7, g) + } + + return r1, r2, r3, r4, r5, r6, r7 +} + +// UnzipBy8 iterates over a collection and creates an array regrouping the elements +// to their pre-zip configuration. +func UnzipBy8[In any, A any, B any, C any, D any, E any, F any, G any, H any](items []In, iteratee func(In) (a A, b B, c C, d D, e E, f F, g G, h H)) ([]A, []B, []C, []D, []E, []F, []G, []H) { + size := len(items) + r1 := make([]A, 0, size) + r2 := make([]B, 0, size) + r3 := make([]C, 0, size) + r4 := make([]D, 0, size) + r5 := make([]E, 0, size) + r6 := make([]F, 0, size) + r7 := make([]G, 0, size) + r8 := make([]H, 0, size) + + for i := range items { + a, b, c, d, e, f, g, h := iteratee(items[i]) + r1 = append(r1, a) + r2 = append(r2, b) + r3 = append(r3, c) + r4 = append(r4, d) + r5 = append(r5, e) + r6 = append(r6, f) + r7 = append(r7, g) + r8 = append(r8, h) + } + + return r1, r2, r3, r4, r5, r6, r7, r8 +} + +// UnzipBy9 iterates over a collection and creates an array regrouping the elements +// to their pre-zip configuration. +func UnzipBy9[In any, A any, B any, C any, D any, E any, F any, G any, H any, I any](items []In, iteratee func(In) (a A, b B, c C, d D, e E, f F, g G, h H, i I)) ([]A, []B, []C, []D, []E, []F, []G, []H, []I) { + size := len(items) + r1 := make([]A, 0, size) + r2 := make([]B, 0, size) + r3 := make([]C, 0, size) + r4 := make([]D, 0, size) + r5 := make([]E, 0, size) + r6 := make([]F, 0, size) + r7 := make([]G, 0, size) + r8 := make([]H, 0, size) + r9 := make([]I, 0, size) + + for i := range items { + a, b, c, d, e, f, g, h, i := iteratee(items[i]) + r1 = append(r1, a) + r2 = append(r2, b) + r3 = append(r3, c) + r4 = append(r4, d) + r5 = append(r5, e) + r6 = append(r6, f) + r7 = append(r7, g) + r8 = append(r8, h) + r9 = append(r9, i) } return r1, r2, r3, r4, r5, r6, r7, r8, r9 diff --git a/tuples_example_test.go b/tuples_example_test.go deleted file mode 100644 index b8eb2e99..00000000 --- a/tuples_example_test.go +++ /dev/null @@ -1,197 +0,0 @@ -package lo - -import ( - "fmt" -) - -func ExampleT2() { - result := T2("hello", 2) - fmt.Printf("%v %v", result.A, result.B) - // Output: hello 2 -} - -func ExampleT3() { - result := T3("hello", 2, true) - fmt.Printf("%v %v %v", result.A, result.B, result.C) - // Output: hello 2 true -} - -func ExampleT4() { - result := T4("hello", 2, true, foo{bar: "bar"}) - fmt.Printf("%v %v %v %v", result.A, result.B, result.C, result.D) - // Output: hello 2 true {bar} -} - -func ExampleT5() { - result := T5("hello", 2, true, foo{bar: "bar"}, 4.2) - fmt.Printf("%v %v %v %v %v", result.A, result.B, result.C, result.D, result.E) - // Output: hello 2 true {bar} 4.2 -} - -func ExampleT6() { - result := T6("hello", 2, true, foo{bar: "bar"}, 4.2, "plop") - fmt.Printf("%v %v %v %v %v %v", result.A, result.B, result.C, result.D, result.E, result.F) - // Output: hello 2 true {bar} 4.2 plop -} - -func ExampleT7() { - result := T7("hello", 2, true, foo{bar: "bar"}, 4.2, "plop", false) - fmt.Printf("%v %v %v %v %v %v %v", result.A, result.B, result.C, result.D, result.E, result.F, result.G) - // Output: hello 2 true {bar} 4.2 plop false -} - -func ExampleT8() { - result := T8("hello", 2, true, foo{bar: "bar"}, 4.2, "plop", false, 42) - fmt.Printf("%v %v %v %v %v %v %v %v", result.A, result.B, result.C, result.D, result.E, result.F, result.G, result.H) - // Output: hello 2 true {bar} 4.2 plop false 42 -} - -func ExampleT9() { - result := T9("hello", 2, true, foo{bar: "bar"}, 4.2, "plop", false, 42, "hello world") - fmt.Printf("%v %v %v %v %v %v %v %v %v", result.A, result.B, result.C, result.D, result.E, result.F, result.G, result.H, result.I) - // Output: hello 2 true {bar} 4.2 plop false 42 hello world -} - -func ExampleUnpack2() { - a, b := Unpack2(T2("hello", 2)) - fmt.Printf("%v %v", a, b) - // Output: hello 2 -} - -func ExampleUnpack3() { - a, b, c := Unpack3(T3("hello", 2, true)) - fmt.Printf("%v %v %v", a, b, c) - // Output: hello 2 true -} - -func ExampleUnpack4() { - a, b, c, d := Unpack4(T4("hello", 2, true, foo{bar: "bar"})) - fmt.Printf("%v %v %v %v", a, b, c, d) - // Output: hello 2 true {bar} -} - -func ExampleUnpack5() { - a, b, c, d, e := Unpack5(T5("hello", 2, true, foo{bar: "bar"}, 4.2)) - fmt.Printf("%v %v %v %v %v", a, b, c, d, e) - // Output: hello 2 true {bar} 4.2 -} - -func ExampleUnpack6() { - a, b, c, d, e, f := Unpack6(T6("hello", 2, true, foo{bar: "bar"}, 4.2, "plop")) - fmt.Printf("%v %v %v %v %v %v", a, b, c, d, e, f) - // Output: hello 2 true {bar} 4.2 plop -} - -func ExampleUnpack7() { - a, b, c, d, e, f, g := Unpack7(T7("hello", 2, true, foo{bar: "bar"}, 4.2, "plop", false)) - fmt.Printf("%v %v %v %v %v %v %v", a, b, c, d, e, f, g) - // Output: hello 2 true {bar} 4.2 plop false -} - -func ExampleUnpack8() { - a, b, c, d, e, f, g, h := Unpack8(T8("hello", 2, true, foo{bar: "bar"}, 4.2, "plop", false, 42)) - fmt.Printf("%v %v %v %v %v %v %v %v", a, b, c, d, e, f, g, h) - // Output: hello 2 true {bar} 4.2 plop false 42 -} - -func ExampleUnpack9() { - a, b, c, d, e, f, g, h, i := Unpack9(T9("hello", 2, true, foo{bar: "bar"}, 4.2, "plop", false, 42, "hello world")) - fmt.Printf("%v %v %v %v %v %v %v %v %v", a, b, c, d, e, f, g, h, i) - // Output: hello 2 true {bar} 4.2 plop false 42 hello world -} - -func ExampleZip2() { - result := Zip2([]string{"hello"}, []int{2}) - fmt.Printf("%v", result) - // Output: [{hello 2}] -} - -func ExampleZip3() { - result := Zip3([]string{"hello"}, []int{2}, []bool{true}) - fmt.Printf("%v", result) - // Output: [{hello 2 true}] -} - -func ExampleZip4() { - result := Zip4([]string{"hello"}, []int{2}, []bool{true}, []foo{{bar: "bar"}}) - fmt.Printf("%v", result) - // Output: [{hello 2 true {bar}}] -} - -func ExampleZip5() { - result := Zip5([]string{"hello"}, []int{2}, []bool{true}, []foo{{bar: "bar"}}, []float64{4.2}) - fmt.Printf("%v", result) - // Output: [{hello 2 true {bar} 4.2}] -} - -func ExampleZip6() { - result := Zip6([]string{"hello"}, []int{2}, []bool{true}, []foo{{bar: "bar"}}, []float64{4.2}, []string{"plop"}) - fmt.Printf("%v", result) - // Output: [{hello 2 true {bar} 4.2 plop}] -} - -func ExampleZip7() { - result := Zip7([]string{"hello"}, []int{2}, []bool{true}, []foo{{bar: "bar"}}, []float64{4.2}, []string{"plop"}, []bool{false}) - fmt.Printf("%v", result) - // Output: [{hello 2 true {bar} 4.2 plop false}] -} - -func ExampleZip8() { - result := Zip8([]string{"hello"}, []int{2}, []bool{true}, []foo{{bar: "bar"}}, []float64{4.2}, []string{"plop"}, []bool{false}, []int{42}) - fmt.Printf("%v", result) - // Output: [{hello 2 true {bar} 4.2 plop false 42}] -} - -func ExampleZip9() { - result := Zip9([]string{"hello"}, []int{2}, []bool{true}, []foo{{bar: "bar"}}, []float64{4.2}, []string{"plop"}, []bool{false}, []int{42}, []string{"hello world"}) - fmt.Printf("%v", result) - // Output: [{hello 2 true {bar} 4.2 plop false 42 hello world}] -} - -func ExampleUnzip2() { - a, b := Unzip2([]Tuple2[string, int]{T2("hello", 2)}) - fmt.Printf("%v %v", a, b) - // Output: [hello] [2] -} - -func ExampleUnzip3() { - a, b, c := Unzip3([]Tuple3[string, int, bool]{T3("hello", 2, true)}) - fmt.Printf("%v %v %v", a, b, c) - // Output: [hello] [2] [true] -} - -func ExampleUnzip4() { - a, b, c, d := Unzip4([]Tuple4[string, int, bool, foo]{T4("hello", 2, true, foo{bar: "bar"})}) - fmt.Printf("%v %v %v %v", a, b, c, d) - // Output: [hello] [2] [true] [{bar}] -} - -func ExampleUnzip5() { - a, b, c, d, e := Unzip5([]Tuple5[string, int, bool, foo, float64]{T5("hello", 2, true, foo{bar: "bar"}, 4.2)}) - fmt.Printf("%v %v %v %v %v", a, b, c, d, e) - // Output: [hello] [2] [true] [{bar}] [4.2] -} - -func ExampleUnzip6() { - a, b, c, d, e, f := Unzip6([]Tuple6[string, int, bool, foo, float64, string]{T6("hello", 2, true, foo{bar: "bar"}, 4.2, "plop")}) - fmt.Printf("%v %v %v %v %v %v", a, b, c, d, e, f) - // Output: [hello] [2] [true] [{bar}] [4.2] [plop] -} - -func ExampleUnzip7() { - a, b, c, d, e, f, g := Unzip7([]Tuple7[string, int, bool, foo, float64, string, bool]{T7("hello", 2, true, foo{bar: "bar"}, 4.2, "plop", false)}) - fmt.Printf("%v %v %v %v %v %v %v", a, b, c, d, e, f, g) - // Output: [hello] [2] [true] [{bar}] [4.2] [plop] [false] -} - -func ExampleUnzip8() { - a, b, c, d, e, f, g, h := Unzip8([]Tuple8[string, int, bool, foo, float64, string, bool, int]{T8("hello", 2, true, foo{bar: "bar"}, 4.2, "plop", false, 42)}) - fmt.Printf("%v %v %v %v %v %v %v %v", a, b, c, d, e, f, g, h) - // Output: [hello] [2] [true] [{bar}] [4.2] [plop] [false] [42] -} - -func ExampleUnzip9() { - a, b, c, d, e, f, g, h, i := Unzip9([]Tuple9[string, int, bool, foo, float64, string, bool, int, string]{T9("hello", 2, true, foo{bar: "bar"}, 4.2, "plop", false, 42, "hello world")}) - fmt.Printf("%v %v %v %v %v %v %v %v %v", a, b, c, d, e, f, g, h, i) - // Output: [hello] [2] [true] [{bar}] [4.2] [plop] [false] [42] [hello world] -} diff --git a/tuples_test.go b/tuples_test.go deleted file mode 100644 index b5aac617..00000000 --- a/tuples_test.go +++ /dev/null @@ -1,353 +0,0 @@ -package lo - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestT(t *testing.T) { - t.Parallel() - is := assert.New(t) - - r1 := T2("a", 1) - r2 := T3[string, int, float32]("b", 2, 3.0) - r3 := T4[string, int, float32]("c", 3, 4.0, true) - r4 := T5[string, int, float32]("d", 4, 5.0, false, "e") - r5 := T6[string, int, float32]("f", 5, 6.0, true, "g", 7) - r6 := T7[string, int, float32]("h", 6, 7.0, false, "i", 8, 9.0) - r7 := T8[string, int, float32]("j", 7, 8.0, true, "k", 9, 10.0, false) - r8 := T9[string, int, float32]("l", 8, 9.0, false, "m", 10, 11.0, true, "n") - - is.Equal(r1, Tuple2[string, int]{A: "a", B: 1}) - is.Equal(r2, Tuple3[string, int, float32]{A: "b", B: 2, C: 3.0}) - is.Equal(r3, Tuple4[string, int, float32, bool]{A: "c", B: 3, C: 4.0, D: true}) - is.Equal(r4, Tuple5[string, int, float32, bool, string]{A: "d", B: 4, C: 5.0, D: false, E: "e"}) - is.Equal(r5, Tuple6[string, int, float32, bool, string, int]{A: "f", B: 5, C: 6.0, D: true, E: "g", F: 7}) - is.Equal(r6, Tuple7[string, int, float32, bool, string, int, float64]{A: "h", B: 6, C: 7.0, D: false, E: "i", F: 8, G: 9.0}) - is.Equal(r7, Tuple8[string, int, float32, bool, string, int, float64, bool]{A: "j", B: 7, C: 8.0, D: true, E: "k", F: 9, G: 10.0, H: false}) - is.Equal(r8, Tuple9[string, int, float32, bool, string, int, float64, bool, string]{A: "l", B: 8, C: 9.0, D: false, E: "m", F: 10, G: 11.0, H: true, I: "n"}) -} - -func TestUnpack(t *testing.T) { - t.Parallel() - is := assert.New(t) - - { - tuple := Tuple2[string, int]{"a", 1} - - r1, r2 := Unpack2(tuple) - - is.Equal("a", r1) - is.Equal(1, r2) - - r1, r2 = tuple.Unpack() - - is.Equal("a", r1) - is.Equal(1, r2) - } - - { - tuple := Tuple3[string, int, float64]{"a", 1, 1.0} - - r1, r2, r3 := Unpack3(tuple) - - is.Equal("a", r1) - is.Equal(1, r2) - is.Equal(1.0, r3) - - r1, r2, r3 = tuple.Unpack() - - is.Equal("a", r1) - is.Equal(1, r2) - is.Equal(1.0, r3) - } - - { - tuple := Tuple4[string, int, float64, bool]{"a", 1, 1.0, true} - - r1, r2, r3, r4 := Unpack4(tuple) - - is.Equal("a", r1) - is.Equal(1, r2) - is.Equal(1.0, r3) - is.Equal(true, r4) - - r1, r2, r3, r4 = tuple.Unpack() - - is.Equal("a", r1) - is.Equal(1, r2) - is.Equal(1.0, r3) - is.Equal(true, r4) - } - - { - tuple := Tuple5[string, int, float64, bool, string]{"a", 1, 1.0, true, "b"} - - r1, r2, r3, r4, r5 := Unpack5(tuple) - - is.Equal("a", r1) - is.Equal(1, r2) - is.Equal(1.0, r3) - is.Equal(true, r4) - is.Equal("b", r5) - - r1, r2, r3, r4, r5 = tuple.Unpack() - - is.Equal("a", r1) - is.Equal(1, r2) - is.Equal(1.0, r3) - is.Equal(true, r4) - is.Equal("b", r5) - } - - { - tuple := Tuple6[string, int, float64, bool, string, int]{"a", 1, 1.0, true, "b", 2} - - r1, r2, r3, r4, r5, r6 := Unpack6(tuple) - - is.Equal("a", r1) - is.Equal(1, r2) - is.Equal(1.0, r3) - is.Equal(true, r4) - is.Equal("b", r5) - is.Equal(2, r6) - - r1, r2, r3, r4, r5, r6 = tuple.Unpack() - - is.Equal("a", r1) - is.Equal(1, r2) - is.Equal(1.0, r3) - is.Equal(true, r4) - is.Equal("b", r5) - is.Equal(2, r6) - } - - { - tuple := Tuple7[string, int, float64, bool, string, int, float64]{"a", 1, 1.0, true, "b", 2, 3.0} - - r1, r2, r3, r4, r5, r6, r7 := Unpack7(tuple) - - is.Equal("a", r1) - is.Equal(1, r2) - is.Equal(1.0, r3) - is.Equal(true, r4) - is.Equal("b", r5) - is.Equal(2, r6) - is.Equal(3.0, r7) - - r1, r2, r3, r4, r5, r6, r7 = tuple.Unpack() - - is.Equal("a", r1) - is.Equal(1, r2) - is.Equal(1.0, r3) - is.Equal(true, r4) - is.Equal("b", r5) - is.Equal(2, r6) - is.Equal(3.0, r7) - } - - { - tuple := Tuple8[string, int, float64, bool, string, int, float64, bool]{"a", 1, 1.0, true, "b", 2, 3.0, true} - - r1, r2, r3, r4, r5, r6, r7, r8 := Unpack8(tuple) - - is.Equal("a", r1) - is.Equal(1, r2) - is.Equal(1.0, r3) - is.Equal(true, r4) - is.Equal("b", r5) - is.Equal(2, r6) - is.Equal(3.0, r7) - is.Equal(true, r8) - - r1, r2, r3, r4, r5, r6, r7, r8 = tuple.Unpack() - - is.Equal("a", r1) - is.Equal(1, r2) - is.Equal(1.0, r3) - is.Equal(true, r4) - is.Equal("b", r5) - is.Equal(2, r6) - is.Equal(3.0, r7) - is.Equal(true, r8) - } - - { - tuple := Tuple9[string, int, float64, bool, string, int, float64, bool, string]{"a", 1, 1.0, true, "b", 2, 3.0, true, "c"} - - r1, r2, r3, r4, r5, r6, r7, r8, r9 := Unpack9(tuple) - - is.Equal("a", r1) - is.Equal(1, r2) - is.Equal(1.0, r3) - is.Equal(true, r4) - is.Equal("b", r5) - is.Equal(2, r6) - is.Equal(3.0, r7) - is.Equal(true, r8) - is.Equal("c", r9) - - r1, r2, r3, r4, r5, r6, r7, r8, r9 = tuple.Unpack() - - is.Equal("a", r1) - is.Equal(1, r2) - is.Equal(1.0, r3) - is.Equal(true, r4) - is.Equal("b", r5) - is.Equal(2, r6) - is.Equal(3.0, r7) - is.Equal(true, r8) - is.Equal("c", r9) - } -} - -func TestZip(t *testing.T) { - t.Parallel() - is := assert.New(t) - - r1 := Zip2( - []string{"a", "b"}, - []int{1, 2}, - ) - - r2 := Zip3( - []string{"a", "b", "c"}, - []int{1, 2, 3}, []int{4, 5, 6}, - ) - - r3 := Zip4( - []string{"a", "b", "c", "d"}, - []int{1, 2, 3, 4}, - []int{5, 6, 7, 8}, - []bool{true, true, true, true}, - ) - - r4 := Zip5( - []string{"a", "b", "c", "d", "e"}, - []int{1, 2, 3, 4, 5}, - []int{6, 7, 8, 9, 10}, - []bool{true, true, true, true, true}, - []float32{0.1, 0.2, 0.3, 0.4, 0.5}, - ) - - r5 := Zip6( - []string{"a", "b", "c", "d", "e", "f"}, - []int{1, 2, 3, 4, 5, 6}, - []int{7, 8, 9, 10, 11, 12}, - []bool{true, true, true, true, true, true}, - []float32{0.1, 0.2, 0.3, 0.4, 0.5, 0.6}, - []float64{0.01, 0.02, 0.03, 0.04, 0.05, 0.06}, - ) - - r6 := Zip7( - []string{"a", "b", "c", "d", "e", "f", "g"}, - []int{1, 2, 3, 4, 5, 6, 7}, - []int{8, 9, 10, 11, 12, 13, 14}, - []bool{true, true, true, true, true, true, true}, - []float32{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7}, - []float64{0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07}, - []int8{1, 2, 3, 4, 5, 6, 7}, - ) - - r7 := Zip8( - []string{"a", "b", "c", "d", "e", "f", "g", "h"}, - []int{1, 2, 3, 4, 5, 6, 7, 8}, - []int{9, 10, 11, 12, 13, 14, 15, 16}, - []bool{true, true, true, true, true, true, true, true}, - []float32{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8}, - []float64{0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08}, - []int8{1, 2, 3, 4, 5, 6, 7, 8}, - []int16{1, 2, 3, 4, 5, 6, 7, 8}, - ) - - r8 := Zip9( - []string{"a", "b", "c", "d", "e", "f", "g", "h", "i"}, - []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, - []int{10, 11, 12, 13, 14, 15, 16, 17, 18}, - []bool{true, true, true, true, true, true, true, true, true}, - []float32{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9}, - []float64{0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09}, - []int8{1, 2, 3, 4, 5, 6, 7, 8, 9}, - []int16{1, 2, 3, 4, 5, 6, 7, 8, 9}, - []int32{1, 2, 3, 4, 5, 6, 7, 8, 9}, - ) - - is.Equal(r1, []Tuple2[string, int]{ - {A: "a", B: 1}, - {A: "b", B: 2}, - }) - - is.Equal(r2, []Tuple3[string, int, int]{ - {A: "a", B: 1, C: 4}, - {A: "b", B: 2, C: 5}, - {A: "c", B: 3, C: 6}, - }) - - is.Equal(r3, []Tuple4[string, int, int, bool]{ - {A: "a", B: 1, C: 5, D: true}, - {A: "b", B: 2, C: 6, D: true}, - {A: "c", B: 3, C: 7, D: true}, - {A: "d", B: 4, C: 8, D: true}, - }) - - is.Equal(r4, []Tuple5[string, int, int, bool, float32]{ - {A: "a", B: 1, C: 6, D: true, E: 0.1}, - {A: "b", B: 2, C: 7, D: true, E: 0.2}, - {A: "c", B: 3, C: 8, D: true, E: 0.3}, - {A: "d", B: 4, C: 9, D: true, E: 0.4}, - {A: "e", B: 5, C: 10, D: true, E: 0.5}, - }) - - is.Equal(r5, []Tuple6[string, int, int, bool, float32, float64]{ - {A: "a", B: 1, C: 7, D: true, E: 0.1, F: 0.01}, - {A: "b", B: 2, C: 8, D: true, E: 0.2, F: 0.02}, - {A: "c", B: 3, C: 9, D: true, E: 0.3, F: 0.03}, - {A: "d", B: 4, C: 10, D: true, E: 0.4, F: 0.04}, - {A: "e", B: 5, C: 11, D: true, E: 0.5, F: 0.05}, - {A: "f", B: 6, C: 12, D: true, E: 0.6, F: 0.06}, - }) - - is.Equal(r6, []Tuple7[string, int, int, bool, float32, float64, int8]{ - {A: "a", B: 1, C: 8, D: true, E: 0.1, F: 0.01, G: 1}, - {A: "b", B: 2, C: 9, D: true, E: 0.2, F: 0.02, G: 2}, - {A: "c", B: 3, C: 10, D: true, E: 0.3, F: 0.03, G: 3}, - {A: "d", B: 4, C: 11, D: true, E: 0.4, F: 0.04, G: 4}, - {A: "e", B: 5, C: 12, D: true, E: 0.5, F: 0.05, G: 5}, - {A: "f", B: 6, C: 13, D: true, E: 0.6, F: 0.06, G: 6}, - {A: "g", B: 7, C: 14, D: true, E: 0.7, F: 0.07, G: 7}, - }) - - is.Equal(r7, []Tuple8[string, int, int, bool, float32, float64, int8, int16]{ - {A: "a", B: 1, C: 9, D: true, E: 0.1, F: 0.01, G: 1, H: 1}, - {A: "b", B: 2, C: 10, D: true, E: 0.2, F: 0.02, G: 2, H: 2}, - {A: "c", B: 3, C: 11, D: true, E: 0.3, F: 0.03, G: 3, H: 3}, - {A: "d", B: 4, C: 12, D: true, E: 0.4, F: 0.04, G: 4, H: 4}, - {A: "e", B: 5, C: 13, D: true, E: 0.5, F: 0.05, G: 5, H: 5}, - {A: "f", B: 6, C: 14, D: true, E: 0.6, F: 0.06, G: 6, H: 6}, - {A: "g", B: 7, C: 15, D: true, E: 0.7, F: 0.07, G: 7, H: 7}, - {A: "h", B: 8, C: 16, D: true, E: 0.8, F: 0.08, G: 8, H: 8}, - }) - - is.Equal(r8, []Tuple9[string, int, int, bool, float32, float64, int8, int16, int32]{ - {A: "a", B: 1, C: 10, D: true, E: 0.1, F: 0.01, G: 1, H: 1, I: 1}, - {A: "b", B: 2, C: 11, D: true, E: 0.2, F: 0.02, G: 2, H: 2, I: 2}, - {A: "c", B: 3, C: 12, D: true, E: 0.3, F: 0.03, G: 3, H: 3, I: 3}, - {A: "d", B: 4, C: 13, D: true, E: 0.4, F: 0.04, G: 4, H: 4, I: 4}, - {A: "e", B: 5, C: 14, D: true, E: 0.5, F: 0.05, G: 5, H: 5, I: 5}, - {A: "f", B: 6, C: 15, D: true, E: 0.6, F: 0.06, G: 6, H: 6, I: 6}, - {A: "g", B: 7, C: 16, D: true, E: 0.7, F: 0.07, G: 7, H: 7, I: 7}, - {A: "h", B: 8, C: 17, D: true, E: 0.8, F: 0.08, G: 8, H: 8, I: 8}, - {A: "i", B: 9, C: 18, D: true, E: 0.9, F: 0.09, G: 9, H: 9, I: 9}, - }) -} - -func TestUnzip(t *testing.T) { - t.Parallel() - is := assert.New(t) - - r1, r2 := Unzip2([]Tuple2[string, int]{{A: "a", B: 1}, {A: "b", B: 2}}) - - is.Equal(r1, []string{"a", "b"}) - is.Equal(r2, []int{1, 2}) -} diff --git a/type_manipulation.go b/type_manipulation.go index ac31c0f5..3e965183 100644 --- a/type_manipulation.go +++ b/type_manipulation.go @@ -2,15 +2,20 @@ package lo import "reflect" +// IsNil checks if a value is nil or if it's a reference type with a nil underlying value. +func IsNil(x any) bool { + defer func() { recover() }() // nolint:errcheck + return x == nil || reflect.ValueOf(x).IsNil() +} + // ToPtr returns a pointer copy of value. func ToPtr[T any](x T) *T { return &x } -// IsNil checks if a value is nil or if it's a reference type with a nil underlying value. -func IsNil(x any) bool { - defer func() { recover() }() - return x == nil || reflect.ValueOf(x).IsNil() +// Nil returns a nil pointer of type. +func Nil[T any]() *T { + return nil } // EmptyableToPtr returns a pointer copy of value if it's nonzero. @@ -45,16 +50,19 @@ func FromPtrOr[T any](x *T, fallback T) T { // ToSlicePtr returns a slice of pointer copy of value. func ToSlicePtr[T any](collection []T) []*T { - return Map(collection, func(x T, _ int) *T { - return &x - }) + result := make([]*T, len(collection)) + + for i := range collection { + result[i] = &collection[i] + } + return result } // ToAnySlice returns a slice with all elements mapped to `any` type func ToAnySlice[T any](collection []T) []any { result := make([]any, len(collection)) - for i, item := range collection { - result[i] = item + for i := range collection { + result[i] = collection[i] } return result } @@ -70,8 +78,8 @@ func FromAnySlice[T any](in []any) (out []T, ok bool) { }() result := make([]T, len(in)) - for i, item := range in { - result[i] = item.(T) + for i := range in { + result[i] = in[i].(T) } return result, true } @@ -95,10 +103,10 @@ func IsNotEmpty[T comparable](v T) bool { } // Coalesce returns the first non-empty arguments. Arguments must be comparable. -func Coalesce[T comparable](v ...T) (result T, ok bool) { - for _, e := range v { - if e != result { - result = e +func Coalesce[T comparable](values ...T) (result T, ok bool) { + for i := range values { + if values[i] != result { + result = values[i] ok = true return } @@ -106,3 +114,9 @@ func Coalesce[T comparable](v ...T) (result T, ok bool) { return } + +// CoalesceOrEmpty returns the first non-empty arguments. Arguments must be comparable. +func CoalesceOrEmpty[T comparable](v ...T) T { + result, _ := Coalesce(v...) + return result +} diff --git a/type_manipulation_test.go b/type_manipulation_test.go deleted file mode 100644 index ee4fa42e..00000000 --- a/type_manipulation_test.go +++ /dev/null @@ -1,235 +0,0 @@ -package lo - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestToPtr(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := ToPtr([]int{1, 2}) - - is.Equal(*result1, []int{1, 2}) -} - -func TestIsNil(t *testing.T) { - t.Parallel() - is := assert.New(t) - - var x int - is.False(IsNil(x)) - - var k struct{} - is.False(IsNil(k)) - - var s *string - is.True(IsNil(s)) - - var i *int - is.True(IsNil(i)) - - var b *bool - is.True(IsNil(b)) - - var ifaceWithNilValue interface{} = (*string)(nil) - is.True(IsNil(ifaceWithNilValue)) - is.True(ifaceWithNilValue != nil) -} - -func TestEmptyableToPtr(t *testing.T) { - t.Parallel() - is := assert.New(t) - - is.Nil(EmptyableToPtr(0)) - is.Nil(EmptyableToPtr("")) - is.Nil(EmptyableToPtr[[]int](nil)) - is.Nil(EmptyableToPtr[map[int]int](nil)) - is.Nil(EmptyableToPtr[error](nil)) - - is.Equal(*EmptyableToPtr(42), 42) - is.Equal(*EmptyableToPtr("nonempty"), "nonempty") - is.Equal(*EmptyableToPtr([]int{}), []int{}) - is.Equal(*EmptyableToPtr([]int{1, 2}), []int{1, 2}) - is.Equal(*EmptyableToPtr(map[int]int{}), map[int]int{}) - is.Equal(*EmptyableToPtr(assert.AnError), assert.AnError) -} - -func TestFromPtr(t *testing.T) { - t.Parallel() - is := assert.New(t) - - str1 := "foo" - ptr := &str1 - - is.Equal("foo", FromPtr(ptr)) - is.Equal("", FromPtr[string](nil)) - is.Equal(0, FromPtr[int](nil)) - is.Nil(FromPtr[*string](nil)) - is.EqualValues(ptr, FromPtr(&ptr)) -} - -func TestFromPtrOr(t *testing.T) { - t.Parallel() - is := assert.New(t) - - const fallbackStr = "fallback" - str := "foo" - ptrStr := &str - - const fallbackInt = -1 - i := 9 - ptrInt := &i - - is.Equal(str, FromPtrOr(ptrStr, fallbackStr)) - is.Equal(fallbackStr, FromPtrOr(nil, fallbackStr)) - is.Equal(i, FromPtrOr(ptrInt, fallbackInt)) - is.Equal(fallbackInt, FromPtrOr(nil, fallbackInt)) -} - -func TestToSlicePtr(t *testing.T) { - t.Parallel() - is := assert.New(t) - - str1 := "foo" - str2 := "bar" - result1 := ToSlicePtr([]string{str1, str2}) - - is.Equal(result1, []*string{&str1, &str2}) -} - -func TestToAnySlice(t *testing.T) { - t.Parallel() - is := assert.New(t) - - in1 := []int{0, 1, 2, 3} - in2 := []int{} - out1 := ToAnySlice(in1) - out2 := ToAnySlice(in2) - - is.Equal([]any{0, 1, 2, 3}, out1) - is.Equal([]any{}, out2) -} - -func TestFromAnySlice(t *testing.T) { - t.Parallel() - is := assert.New(t) - - is.NotPanics(func() { - out1, ok1 := FromAnySlice[string]([]any{"foobar", 42}) - out2, ok2 := FromAnySlice[string]([]any{"foobar", "42"}) - - is.Equal([]string{}, out1) - is.False(ok1) - is.Equal([]string{"foobar", "42"}, out2) - is.True(ok2) - }) -} - -func TestEmpty(t *testing.T) { - t.Parallel() - is := assert.New(t) - - //nolint:unused - type test struct{} - - is.Empty(Empty[string]()) - is.Empty(Empty[int64]()) - is.Empty(Empty[test]()) - is.Empty(Empty[chan string]()) -} - -func TestIsEmpty(t *testing.T) { - t.Parallel() - is := assert.New(t) - - //nolint:unused - type test struct { - foobar string - } - - is.True(IsEmpty("")) - is.False(IsEmpty("foo")) - is.True(IsEmpty[int64](0)) - is.False(IsEmpty[int64](42)) - is.True(IsEmpty(test{foobar: ""})) - is.False(IsEmpty(test{foobar: "foo"})) -} - -func TestIsNotEmpty(t *testing.T) { - t.Parallel() - is := assert.New(t) - - //nolint:unused - type test struct { - foobar string - } - - is.False(IsNotEmpty("")) - is.True(IsNotEmpty("foo")) - is.False(IsNotEmpty[int64](0)) - is.True(IsNotEmpty[int64](42)) - is.False(IsNotEmpty(test{foobar: ""})) - is.True(IsNotEmpty(test{foobar: "foo"})) -} - -func TestCoalesce(t *testing.T) { - t.Parallel() - is := assert.New(t) - - newStr := func(v string) *string { return &v } - var nilStr *string - str1 := newStr("str1") - str2 := newStr("str2") - - type structType struct { - field1 int - field2 float64 - } - var zeroStruct structType - struct1 := structType{1, 1.0} - struct2 := structType{2, 2.0} - - result1, ok1 := Coalesce[int]() - result2, ok2 := Coalesce(3) - result3, ok3 := Coalesce(nil, nilStr) - result4, ok4 := Coalesce(nilStr, str1) - result5, ok5 := Coalesce(nilStr, str1, str2) - result6, ok6 := Coalesce(str1, str2, nilStr) - result7, ok7 := Coalesce(0, 1, 2, 3) - result8, ok8 := Coalesce(zeroStruct) - result9, ok9 := Coalesce(zeroStruct, struct1) - result10, ok10 := Coalesce(zeroStruct, struct1, struct2) - - is.Equal(0, result1) - is.False(ok1) - - is.Equal(3, result2) - is.True(ok2) - - is.Nil(result3) - is.False(ok3) - - is.Equal(str1, result4) - is.True(ok4) - - is.Equal(str1, result5) - is.True(ok5) - - is.Equal(str1, result6) - is.True(ok6) - - is.Equal(result7, 1) - is.True(ok7) - - is.Equal(result8, zeroStruct) - is.False(ok8) - - is.Equal(result9, struct1) - is.True(ok9) - - is.Equal(result10, struct1) - is.True(ok10) -} diff --git a/types.go b/types.go index 271c5b4f..1c6f0d00 100644 --- a/types.go +++ b/types.go @@ -7,7 +7,7 @@ type Entry[K comparable, V any] struct { } // Tuple2 is a group of 2 elements (pair). -type Tuple2[A any, B any] struct { +type Tuple2[A, B any] struct { A A B B } @@ -18,7 +18,7 @@ func (t Tuple2[A, B]) Unpack() (A, B) { } // Tuple3 is a group of 3 elements. -type Tuple3[A any, B any, C any] struct { +type Tuple3[A, B, C any] struct { A A B B C C @@ -30,7 +30,7 @@ func (t Tuple3[A, B, C]) Unpack() (A, B, C) { } // Tuple4 is a group of 4 elements. -type Tuple4[A any, B any, C any, D any] struct { +type Tuple4[A, B, C, D any] struct { A A B B C C @@ -43,7 +43,7 @@ func (t Tuple4[A, B, C, D]) Unpack() (A, B, C, D) { } // Tuple5 is a group of 5 elements. -type Tuple5[A any, B any, C any, D any, E any] struct { +type Tuple5[A, B, C, D, E any] struct { A A B B C C @@ -57,7 +57,7 @@ func (t Tuple5[A, B, C, D, E]) Unpack() (A, B, C, D, E) { } // Tuple6 is a group of 6 elements. -type Tuple6[A any, B any, C any, D any, E any, F any] struct { +type Tuple6[A, B, C, D, E, F any] struct { A A B B C C @@ -72,7 +72,7 @@ func (t Tuple6[A, B, C, D, E, F]) Unpack() (A, B, C, D, E, F) { } // Tuple7 is a group of 7 elements. -type Tuple7[A any, B any, C any, D any, E any, F any, G any] struct { +type Tuple7[A, B, C, D, E, F, G any] struct { A A B B C C @@ -88,7 +88,7 @@ func (t Tuple7[A, B, C, D, E, F, G]) Unpack() (A, B, C, D, E, F, G) { } // Tuple8 is a group of 8 elements. -type Tuple8[A any, B any, C any, D any, E any, F any, G any, H any] struct { +type Tuple8[A, B, C, D, E, F, G, H any] struct { A A B B C C @@ -105,7 +105,7 @@ func (t Tuple8[A, B, C, D, E, F, G, H]) Unpack() (A, B, C, D, E, F, G, H) { } // Tuple9 is a group of 9 elements. -type Tuple9[A any, B any, C any, D any, E any, F any, G any, H any, I any] struct { +type Tuple9[A, B, C, D, E, F, G, H, I any] struct { A A B B C C