pax_global_header00006660000000000000000000000064144062235340014515gustar00rootroot0000000000000052 comment=3ae362dbb60ff6a2a1cb226a35079ea182c5f7c2 lo-1.38.1/000077500000000000000000000000001440622353400122215ustar00rootroot00000000000000lo-1.38.1/.github/000077500000000000000000000000001440622353400135615ustar00rootroot00000000000000lo-1.38.1/.github/workflows/000077500000000000000000000000001440622353400156165ustar00rootroot00000000000000lo-1.38.1/.github/workflows/lint.yml000066400000000000000000000024511440622353400173110ustar00rootroot00000000000000name: Lint on: push: tags: branches: pull_request: jobs: golangci: name: lint runs-on: ubuntu-latest steps: - uses: actions/setup-go@v2 with: go-version: 1.18 stable: false - uses: actions/checkout@v2 - name: golangci-lint uses: golangci/golangci-lint-action@v2 with: # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version version: latest # Optional: working directory, useful for monorepos working-directory: ./ # Optional: golangci-lint command line arguments. args: --timeout 60s --max-same-issues 50 # Optional: show only new issues if it's a pull request. The default value is `false`. # only-new-issues: true # Optional: if set to true then the action will use pre-installed Go. # skip-go-installation: true # Optional: if set to true then the action don't cache or restore ~/go/pkg. # skip-pkg-cache: true # Optional: if set to true then the action don't cache or restore ~/.cache/go-build. # skip-build-cache: true # optionally use a specific version of Go rather than the latest one go_version: '1.18' lo-1.38.1/.github/workflows/release.yml000066400000000000000000000025151440622353400177640ustar00rootroot00000000000000name: Release on: workflow_dispatch: inputs: semver: type: string description: Semver required: true jobs: release: if: github.triggering_actor == 'samber' runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Go uses: actions/setup-go@v2 with: go-version: 1.18 stable: false - name: Test run: make test # remove tests in order to clean dependencies - name: Remove xxx_test.go files run: rm -rf *_test.go ./parallel/*_test.go docker-compose.yml img/ # cleanup test dependencies - name: Cleanup dependencies run: go mod tidy - name: List files run: tree -Cfi - name: Write new go.mod into logs run: cat go.mod - name: Write new go.sum into logs run: cat go.sum - name: Create tag run: | git config --global user.name '${{ github.triggering_actor }}' git config --global user.email "${{ github.triggering_actor}}@users.noreply.github.com" git add . git commit -m 'bump ${{ inputs.semver }}' git tag ${{ inputs.semver }} git push origin ${{ inputs.semver }} - name: Release uses: softprops/action-gh-release@v1 with: name: ${{ inputs.semver }} tag_name: ${{ inputs.semver }} lo-1.38.1/.github/workflows/test.yml000066400000000000000000000011001440622353400173100ustar00rootroot00000000000000name: Tests on: push: tags: branches: pull_request: jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Go uses: actions/setup-go@v2 with: go-version: 1.18 stable: false - name: Build run: make build - name: Test run: make test - name: Test run: make coverage - name: Codecov uses: codecov/codecov-action@v2 with: token: ${{ secrets.CODECOV_TOKEN }} file: ./cover.out flags: unittests verbose: true lo-1.38.1/.gitignore000066400000000000000000000013651440622353400142160ustar00rootroot00000000000000 # Created by https://www.toptal.com/developers/gitignore/api/go # Edit at https://www.toptal.com/developers/gitignore?templates=go ### Go ### # If you prefer the allow list template instead of the deny list, see community template: # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore # # Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, built with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Dependency directories (remove the comment below to include it) # vendor/ # Go workspace file go.work ### Go Patch ### /vendor/ /Godeps/ # End of https://www.toptal.com/developers/gitignore/api/go cover.out cover.html .vscode .idea/ lo-1.38.1/.travis.yml000066400000000000000000000001411440622353400143260ustar00rootroot00000000000000language: go before_install: - go mod download - make tools go: - "1.18" script: make test lo-1.38.1/CHANGELOG.md000066400000000000000000000131511440622353400140330ustar00rootroot00000000000000# Changelog @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.38.1 (2023-03-20) Improvement: - Async and AsyncX: now returns `<-chan T` instead of `chan T` ## 1.38.0 (2023-03-20) Adding: - lo.ValueOr - lo.DebounceBy - lo.EmptyableToPtr Improvement: - Substring: add support for non-english chars Fix: - Async: Fix goroutine leak ## 1.37.0 (2022-12-15) Adding: - lo.PartialX - lo.Transaction Improvement: - lo.Associate / lo.SliceToMap: faster memory allocation Chore: - Remove *_test.go files from releases, in order to cleanup dev dependencies ## 1.36.0 (2022-11-28) Adding: - lo.AttemptWhile - lo.AttemptWhileWithDelay ## 1.35.0 (2022-11-15) Adding: - lo.RandomString - lo.BufferWithTimeout (alias to lo.BatchWithTimeout) - lo.Buffer (alias to lo.Batch) Change: - lo.Slice: avoid panic caused by out-of-bounds Deprecation: - lo.BatchWithTimeout - lo.Batch ## 1.34.0 (2022-11-12) Improving: - lo.Union: faster and can receive more than 2 lists Adding: - lo.FanIn (alias to lo.ChannelMerge) - lo.FanOut Deprecation: - lo.ChannelMerge ## 1.33.0 (2022-10-14) Adding: - lo.ChannelMerge Improving: - helpers with callbacks/predicates/iteratee now have named arguments, for easier autocompletion ## 1.32.0 (2022-10-10) Adding: - lo.ChannelToSlice - lo.CountValues - lo.CountValuesBy - lo.MapEntries - lo.Sum - lo.Interleave - TupleX.Unpack() ## 1.31.0 (2022-10-06) Adding: - lo.SliceToChannel - lo.Generator - lo.Batch - lo.BatchWithTimeout ## 1.30.1 (2022-10-06) Fix: - lo.Try1: remove generic type - lo.Validate: format error properly ## 1.30.0 (2022-10-04) Adding: - lo.TernaryF - lo.Validate ## 1.29.0 (2022-10-02) Adding: - lo.ErrorAs - lo.TryOr - lo.TryOrX ## 1.28.0 (2022-09-05) Adding: - lo.ChannelDispatcher with 6 dispatching strategies: - lo.DispatchingStrategyRoundRobin - lo.DispatchingStrategyRandom - lo.DispatchingStrategyWeightedRandom - lo.DispatchingStrategyFirst - lo.DispatchingStrategyLeast - lo.DispatchingStrategyMost ## 1.27.1 (2022-08-15) Bugfix: - Removed comparable constraint for lo.FindKeyBy ## 1.27.0 (2022-07-29) Breaking: - Change of MapToSlice prototype: `MapToSlice[K comparable, V any, R any](in map[K]V, iteratee func(V, K) R) []R` -> `MapToSlice[K comparable, V any, R any](in map[K]V, iteratee func(K, V) R) []R` Added: - lo.ChunkString - lo.SliceToMap (alias to lo.Associate) ## 1.26.0 (2022-07-24) Adding: - lo.Associate - lo.ReduceRight - lo.FromPtrOr - lo.MapToSlice - lo.IsSorted - lo.IsSortedByKey ## 1.25.0 (2022-07-04) Adding: - lo.FindUniques - lo.FindUniquesBy - lo.FindDuplicates - lo.FindDuplicatesBy - lo.IsNotEmpty ## 1.24.0 (2022-07-04) Adding: - lo.Without - lo.WithoutEmpty ## 1.23.0 (2022-07-04) Adding: - lo.FindKey - lo.FindKeyBy ## 1.22.0 (2022-07-04) Adding: - lo.Slice - lo.FromPtr - lo.IsEmpty - lo.Compact - lo.ToPairs: alias to lo.Entries - lo.FromPairs: alias to lo.FromEntries - lo.Partial Change: - lo.Must + lo.MustX: add context to panic message Fix: - lo.Nth: out of bound exception (#137) ## 1.21.0 (2022-05-10) Adding: - lo.ToAnySlice - lo.FromAnySlice ## 1.20.0 (2022-05-02) Adding: - lo.Synchronize - lo.SumBy Change: - Removed generic type definition for lo.Try0: `lo.Try0[T]()` -> `lo.Try0()` ## 1.19.0 (2022-04-30) Adding: - lo.RepeatBy - lo.Subset - lo.Replace - lo.ReplaceAll - lo.Substring - lo.RuneLength ## 1.18.0 (2022-04-28) Adding: - lo.SomeBy - lo.EveryBy - lo.None - lo.NoneBy ## 1.17.0 (2022-04-27) Adding: - lo.Unpack2 -> lo.Unpack3 - lo.Async0 -> lo.Async6 ## 1.16.0 (2022-04-26) Adding: - lo.AttemptWithDelay ## 1.15.0 (2022-04-22) Improvement: - lo.Must: error or boolean value ## 1.14.0 (2022-04-21) Adding: - lo.Coalesce ## 1.13.0 (2022-04-14) Adding: - PickBy - PickByKeys - PickByValues - OmitBy - OmitByKeys - OmitByValues - Clamp - MapKeys - Invert - IfF + ElseIfF + ElseF - T0() + T1() + T2() + T3() + ... ## 1.12.0 (2022-04-12) Adding: - Must - Must{0-6} - FindOrElse - Async - MinBy - MaxBy - Count - CountBy - FindIndexOf - FindLastIndexOf - FilterMap ## 1.11.0 (2022-03-11) Adding: - Try - Try{0-6} - TryWitchValue - TryCatch - TryCatchWitchValue - Debounce - Reject ## 1.10.0 (2022-03-11) Adding: - Range - RangeFrom - RangeWithSteps ## 1.9.0 (2022-03-10) Added - Drop - DropRight - DropWhile - DropRightWhile ## 1.8.0 (2022-03-10) Adding Union. ## 1.7.0 (2022-03-09) Adding ContainBy Adding MapValues Adding FlatMap ## 1.6.0 (2022-03-07) Fixed PartitionBy. Adding Sample Adding Samples ## 1.5.0 (2022-03-07) Adding Times Adding Attempt Adding Repeat ## 1.4.0 (2022-03-07) - adding tuple types (2->9) - adding Zip + Unzip - adding lo.PartitionBy + lop.PartitionBy - adding lop.GroupBy - fixing Nth ## 1.3.0 (2022-03-03) Last and Nth return errors ## 1.2.0 (2022-03-03) Adding `lop.Map` and `lop.ForEach`. ## 1.1.0 (2022-03-03) Adding `i int` param to `lo.Map()`, `lo.Filter()`, `lo.ForEach()` and `lo.Reduce()` predicates. ## 1.0.0 (2022-03-02) *Initial release* Supported helpers for slices: - Filter - Map - Reduce - ForEach - Uniq - UniqBy - GroupBy - Chunk - Flatten - Shuffle - Reverse - Fill - ToMap Supported helpers for maps: - Keys - Values - Entries - FromEntries - Assign (maps merge) Supported intersection helpers: - Contains - Every - Some - Intersect - Difference Supported search helpers: - IndexOf - LastIndexOf - Find - Min - Max - Last - Nth Other functional programming helpers: - Ternary (1 line if/else statement) - If / ElseIf / Else - Switch / Case / Default - ToPtr - ToSlicePtr Constraints: - Clonable lo-1.38.1/Dockerfile000066400000000000000000000001371440622353400142140ustar00rootroot00000000000000 FROM golang:1.18 WORKDIR /go/src/github.com/samber/lo COPY Makefile go.* ./ RUN make tools lo-1.38.1/LICENSE000066400000000000000000000020561440622353400132310ustar00rootroot00000000000000MIT License Copyright (c) 2022 Samuel Berthe Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. lo-1.38.1/Makefile000066400000000000000000000021261440622353400136620ustar00rootroot00000000000000 BIN=go build: ${BIN} build -v ./... test: go test -race -v ./... watch-test: reflex -t 50ms -s -- sh -c 'gotest -race -v ./...' bench: go test -benchmem -count 3 -bench ./... 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 # 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 mod tidy lint: golangci-lint run --timeout 60s --max-same-issues 50 ./... lint-fix: golangci-lint run --timeout 60s --max-same-issues 50 --fix ./... audit: tools ${BIN} list -json -m all | nancy sleuth outdated: tools ${BIN} list -u -m -json all | go-mod-outdated -update -direct weight: tools goweight lo-1.38.1/README.md000066400000000000000000001755701440622353400135170ustar00rootroot00000000000000# lo - Iterate over slices, maps, channels... [![tag](https://img.shields.io/github/tag/samber/lo.svg)](https://github.com/samber/lo/releases) ![Go Version](https://img.shields.io/badge/Go-%3E%3D%201.18-%23007d9c) [![GoDoc](https://godoc.org/github.com/samber/lo?status.svg)](https://pkg.go.dev/github.com/samber/lo) ![Build Status](https://github.com/samber/lo/actions/workflows/test.yml/badge.svg) [![Go report](https://goreportcard.com/badge/github.com/samber/lo)](https://goreportcard.com/report/github.com/samber/lo) [![Coverage](https://img.shields.io/codecov/c/github/samber/lo)](https://codecov.io/gh/samber/lo) [![Contributors](https://img.shields.io/github/contributors/samber/lo)](https://github.com/samber/lo/graphs/contributors) [![License](https://img.shields.io/github/license/samber/lo)](./LICENSE) โœจ **`samber/lo` is a Lodash-style Go library based on Go 1.18+ Generics.** This project started as an experiment with the new generics implementation. It may look like [Lodash](https://github.com/lodash/lodash) in some aspects. I used to code with the fantastic ["go-funk"](https://github.com/thoas/go-funk) package, but "go-funk" uses reflection and therefore is not typesafe. As expected, benchmarks demonstrate that generics are much faster than implementations based on the "reflect" package. Benchmarks also show similar performance gains compared to pure `for` loops. [See below](#-benchmark). In the future, 5 to 10 helpers will overlap with those coming into the Go standard library (under package names `slices` and `maps`). I feel this library is legitimate and offers many more valuable abstractions. **See also:** - [samber/do](https://github.com/samber/do): A dependency injection toolkit based on Go 1.18+ Generics - [samber/mo](https://github.com/samber/mo): Monads based on Go 1.18+ Generics (Option, Result, Either...) **Why this name?** I wanted a **short name**, similar to "Lodash" and no Go package currently uses this name. ![lo](img/logo-full.png) ## ๐Ÿš€ Install ```sh go get github.com/samber/lo@v1 ``` This library is v1 and follows SemVer strictly. No breaking changes will be made to exported APIs before v2.0.0. This library has no dependencies outside the Go standard library. ## ๐Ÿ’ก Usage You can import `lo` using: ```go import ( "github.com/samber/lo" lop "github.com/samber/lo/parallel" ) ``` Then use one of the helpers below: ```go names := lo.Uniq[string]([]string{"Samuel", "John", "Samuel"}) // []string{"Samuel", "John"} ``` Most of the time, the compiler will be able to infer the type so that you can call: `lo.Uniq([]string{...})`. ### Tips for lazy developers I cannot recommend it, but in case you are too lazy for repeating `lo.` everywhere, you can import the entire library into the namespace. ```go import ( . "github.com/samber/lo" ) ``` I take no responsibility on this junk. ๐Ÿ˜ ๐Ÿ’ฉ ## ๐Ÿค  Spec GoDoc: [https://godoc.org/github.com/samber/lo](https://godoc.org/github.com/samber/lo) Supported helpers for slices: - [Filter](#filter) - [Map](#map) - [FilterMap](#filtermap) - [FlatMap](#flatmap) - [Reduce](#reduce) - [ReduceRight](#reduceright) - [ForEach](#foreach) - [Times](#times) - [Uniq](#uniq) - [UniqBy](#uniqby) - [GroupBy](#groupby) - [Chunk](#chunk) - [PartitionBy](#partitionby) - [Flatten](#flatten) - [Interleave](#interleave) - [Shuffle](#shuffle) - [Reverse](#reverse) - [Fill](#fill) - [Repeat](#repeat) - [RepeatBy](#repeatby) - [KeyBy](#keyby) - [Associate / SliceToMap](#associate-alias-slicetomap) - [Drop](#drop) - [DropRight](#dropright) - [DropWhile](#dropwhile) - [DropRightWhile](#droprightwhile) - [Reject](#reject) - [Count](#count) - [CountBy](#countby) - [CountValues](#countvalues) - [CountValuesBy](#countvaluesby) - [Subset](#subset) - [Slice](#slice) - [Replace](#replace) - [ReplaceAll](#replaceall) - [Compact](#compact) - [IsSorted](#issorted) - [IsSortedByKey](#issortedbykey) Supported helpers for maps: - [Keys](#keys) - [ValueOr](#valueor) - [Values](#values) - [PickBy](#pickby) - [PickByKeys](#pickbykeys) - [PickByValues](#pickbyvalues) - [OmitBy](#omitby) - [OmitByKeys](#omitbykeys) - [OmitByValues](#omitbyvalues) - [Entries / ToPairs](#entries-alias-topairs) - [FromEntries / FromPairs](#fromentries-alias-frompairs) - [Invert](#invert) - [Assign (merge of maps)](#assign) - [MapKeys](#mapkeys) - [MapValues](#mapvalues) - [MapEntries](#mapentries) - [MapToSlice](#maptoslice) Supported math helpers: - [Range / RangeFrom / RangeWithSteps](#range--rangefrom--rangewithsteps) - [Clamp](#clamp) - [Sum](#sum) - [SumBy](#sumby) Supported helpers for strings: - [RandomString](#randomstring) - [Substring](#substring) - [ChunkString](#chunkstring) - [RuneLength](#runelength) Supported helpers for tuples: - [T2 -> T9](#t2---t9) - [Unpack2 -> Unpack9](#unpack2---unpack9) - [Zip2 -> Zip9](#zip2---zip9) - [Unzip2 -> Unzip9](#unzip2---unzip9) Supported helpers for channels: - [ChannelDispatcher](#channeldispatcher) - [SliceToChannel](#slicetochannel) - [Generator](#generator) - [Buffer](#buffer) - [BufferWithTimeout](#bufferwithtimeout) - [FanIn](#fanin) - [FanOut](#fanout) Supported intersection helpers: - [Contains](#contains) - [ContainsBy](#containsby) - [Every](#every) - [EveryBy](#everyby) - [Some](#some) - [SomeBy](#someby) - [None](#none) - [NoneBy](#noneby) - [Intersect](#intersect) - [Difference](#difference) - [Union](#union) - [Without](#without) - [WithoutEmpty](#withoutempty) Supported search helpers: - [IndexOf](#indexof) - [LastIndexOf](#lastindexof) - [Find](#find) - [FindIndexOf](#findindexof) - [FindLastIndexOf](#findlastindexof) - [FindOrElse](#findorelse) - [FindKey](#findkey) - [FindKeyBy](#findkeyby) - [FindUniques](#finduniques) - [FindUniquesBy](#finduniquesby) - [FindDuplicates](#findduplicates) - [FindDuplicatesBy](#findduplicatesby) - [Min](#min) - [MinBy](#minby) - [Max](#max) - [MaxBy](#maxby) - [Last](#last) - [Nth](#nth) - [Sample](#sample) - [Samples](#samples) Conditional helpers: - [Ternary](#ternary) - [TernaryF](#ternaryf) - [If / ElseIf / Else](#if--elseif--else) - [Switch / Case / Default](#switch--case--default) Type manipulation helpers: - [ToPtr](#toptr) - [EmptyableToPtr](#emptyabletoptr) - [FromPtr](#fromptr) - [FromPtrOr](#fromptror) - [ToSlicePtr](#tosliceptr) - [ToAnySlice](#toanyslice) - [FromAnySlice](#fromanyslice) - [Empty](#empty) - [IsEmpty](#isempty) - [IsNotEmpty](#isnotempty) - [Coalesce](#coalesce) Function helpers: - [Partial](#partial) - [Partial2 -> Partial5](#partial2---partial5) Concurrency helpers: - [Attempt](#attempt) - [AttemptWhile](#attemptwhile) - [AttemptWithDelay](#attemptwithdelay) - [AttemptWhileWithDelay](#attemptwhilewithdelay) - [Debounce](#debounce) - [DebounceBy](#debounceby) - [Synchronize](#synchronize) - [Async](#async) - [Transaction](#transaction) Error handling: - [Validate](#validate) - [Must](#must) - [Try](#try) - [Try1 -> Try6](#try0-6) - [TryOr](#tryor) - [TryOr1 -> TryOr6](#tryor0-6) - [TryCatch](#trycatch) - [TryWithErrorValue](#trywitherrorvalue) - [TryCatchWithErrorValue](#trycatchwitherrorvalue) - [ErrorsAs](#errorsas) Constraints: - Clonable ### Filter Iterates over a collection and returns an array of all the elements the predicate function returns `true` for. ```go even := lo.Filter([]int{1, 2, 3, 4}, func(x int, index int) bool { return x%2 == 0 }) // []int{2, 4} ``` [[play](https://go.dev/play/p/Apjg3WeSi7K)] ### Map Manipulates a slice of one type and transforms it into a slice of another type: ```go import "github.com/samber/lo" lo.Map([]int64{1, 2, 3, 4}, func(x int64, index int) string { return strconv.FormatInt(x, 10) }) // []string{"1", "2", "3", "4"} ``` [[play](https://go.dev/play/p/OkPcYAhBo0D)] Parallel processing: like `lo.Map()`, but the mapper function is called in a goroutine. Results are returned in the same order. ```go import lop "github.com/samber/lo/parallel" lop.Map([]int64{1, 2, 3, 4}, func(x int64, _ int) string { return strconv.FormatInt(x, 10) }) // []string{"1", "2", "3", "4"} ``` ### FilterMap 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 matching := lo.FilterMap([]string{"cpu", "gpu", "mouse", "keyboard"}, func(x string, _ int) (string, bool) { if strings.HasSuffix(x, "pu") { return "xpu", true } return "", false }) // []string{"xpu", "xpu"} ``` [[play](https://go.dev/play/p/-AuYXfy7opz)] ### FlatMap 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), } }) // []string{"0", "0", "1", "1", "2", "2"} ``` [[play](https://go.dev/play/p/YSoYmQTA8-U)] ### Reduce Reduces a collection to a single value. The value is calculated by accumulating the result of running each element in the collection through an accumulator function. Each successive invocation is supplied with the return value returned by the previous call. ```go sum := lo.Reduce([]int{1, 2, 3, 4}, func(agg int, item int, _ int) int { return agg + item }, 0) // 10 ``` [[play](https://go.dev/play/p/R4UHXZNaaUG)] ### ReduceRight Like `lo.Reduce` except that it iterates over elements of collection from right to left. ```go result := lo.ReduceRight([][]int{{0, 1}, {2, 3}, {4, 5}}, func(agg []int, item []int, _ int) []int { return append(agg, item...) }, []int{}) // []int{4, 5, 2, 3, 0, 1} ``` [[play](https://go.dev/play/p/Fq3W70l7wXF)] ### ForEach Iterates over elements of a collection and invokes the function over each element. ```go import "github.com/samber/lo" lo.ForEach([]string{"hello", "world"}, func(x string, _ int) { println(x) }) // prints "hello\nworld\n" ``` [[play](https://go.dev/play/p/oofyiUPRf8t)] Parallel processing: like `lo.ForEach()`, but the callback is called as a goroutine. ```go import lop "github.com/samber/lo/parallel" lop.ForEach([]string{"hello", "world"}, func(x string, _ int) { println(x) }) // prints "hello\nworld\n" or "world\nhello\n" ``` ### Times Times invokes the iteratee n times, returning an array of the results of each invocation. The iteratee is invoked with index as argument. ```go import "github.com/samber/lo" lo.Times(3, func(i int) string { return strconv.FormatInt(int64(i), 10) }) // []string{"0", "1", "2"} ``` [[play](https://go.dev/play/p/vgQj3Glr6lT)] Parallel processing: like `lo.Times()`, but callback is called in goroutine. ```go import lop "github.com/samber/lo/parallel" lop.Times(3, func(i int) string { return strconv.FormatInt(int64(i), 10) }) // []string{"0", "1", "2"} ``` ### Uniq Returns a duplicate-free version of an array, in which only the first occurrence of each element is kept. The order of result values is determined by the order they occur in the array. ```go uniqValues := lo.Uniq([]int{1, 2, 2, 1}) // []int{1, 2} ``` [[play](https://go.dev/play/p/DTzbeXZ6iEN)] ### UniqBy Returns a duplicate-free version of an array, in which only the first occurrence of each element is kept. The order of result values is determined by the order they occur in the array. It accepts `iteratee` which is invoked for each element in array to generate the criterion by which uniqueness is computed. ```go uniqValues := lo.UniqBy([]int{0, 1, 2, 3, 4, 5}, func(i int) int { return i%3 }) // []int{0, 1, 2} ``` [[play](https://go.dev/play/p/g42Z3QSb53u)] ### GroupBy Returns an object composed of keys generated from the results of running each element of collection through iteratee. ```go import lo "github.com/samber/lo" groups := lo.GroupBy([]int{0, 1, 2, 3, 4, 5}, func(i int) int { return i%3 }) // map[int][]int{0: []int{0, 3}, 1: []int{1, 4}, 2: []int{2, 5}} ``` [[play](https://go.dev/play/p/XnQBd_v6brd)] Parallel processing: like `lo.GroupBy()`, but callback is called in goroutine. ```go import lop "github.com/samber/lo/parallel" lop.GroupBy([]int{0, 1, 2, 3, 4, 5}, func(i int) int { return i%3 }) // map[int][]int{0: []int{0, 3}, 1: []int{1, 4}, 2: []int{2, 5}} ``` ### Chunk Returns an array of elements split into groups the length of size. If array can't be split evenly, the final chunk will be the remaining elements. ```go lo.Chunk([]int{0, 1, 2, 3, 4, 5}, 2) // [][]int{{0, 1}, {2, 3}, {4, 5}} lo.Chunk([]int{0, 1, 2, 3, 4, 5, 6}, 2) // [][]int{{0, 1}, {2, 3}, {4, 5}, {6}} lo.Chunk([]int{}, 2) // [][]int{} lo.Chunk([]int{0}, 2) // [][]int{{0}} ``` [[play](https://go.dev/play/p/EeKl0AuTehH)] ### PartitionBy Returns an array of elements split into groups. The order of grouped values is determined by the order they occur in collection. The grouping is generated from the results of running each element of collection through iteratee. ```go import lo "github.com/samber/lo" partitions := lo.PartitionBy([]int{-2, -1, 0, 1, 2, 3, 4, 5}, func(x int) string { if x < 0 { return "negative" } else if x%2 == 0 { return "even" } return "odd" }) // [][]int{{-2, -1}, {0, 2, 4}, {1, 3, 5}} ``` [[play](https://go.dev/play/p/NfQ_nGjkgXW)] Parallel processing: like `lo.PartitionBy()`, but callback is called in goroutine. Results are returned in the same order. ```go import lop "github.com/samber/lo/parallel" partitions := lop.PartitionBy([]int{-2, -1, 0, 1, 2, 3, 4, 5}, func(x int) string { if x < 0 { return "negative" } else if x%2 == 0 { return "even" } return "odd" }) // [][]int{{-2, -1}, {0, 2, 4}, {1, 3, 5}} ``` ### Flatten Returns an array a single level deep. ```go flat := lo.Flatten([][]int{{0, 1}, {2, 3, 4, 5}}) // []int{0, 1, 2, 3, 4, 5} ``` [[play](https://go.dev/play/p/rbp9ORaMpjw)] ### Interleave Round-robin alternating input slices and sequentially appending value at index into result. ```go interleaved := lo.Interleave([]int{1, 4, 7}, []int{2, 5, 8}, []int{3, 6, 9}) // []int{1, 2, 3, 4, 5, 6, 7, 8, 9} interleaved := lo.Interleave([]int{1}, []int{2, 5, 8}, []int{3, 6}, []int{4, 7, 9, 10}) // []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} ``` [[play](https://go.dev/play/p/DDhlwrShbwe)] ### Shuffle Returns an array of shuffled values. Uses the Fisher-Yates shuffle algorithm. ```go randomOrder := lo.Shuffle([]int{0, 1, 2, 3, 4, 5}) // []int{1, 4, 0, 3, 5, 2} ``` [[play](https://go.dev/play/p/Qp73bnTDnc7)] ### Reverse Reverses array so that the first element becomes the last, the second element becomes the second to last, and so on. โš ๏ธ This helper is **mutable**. This behavior might change in `v2.0.0`. See [#160](https://github.com/samber/lo/issues/160). ```go reverseOrder := lo.Reverse([]int{0, 1, 2, 3, 4, 5}) // []int{5, 4, 3, 2, 1, 0} ``` [[play](https://go.dev/play/p/fhUMLvZ7vS6)] ### Fill Fills elements of array with `initial` value. ```go type foo struct { bar string } func (f foo) Clone() foo { return foo{f.bar} } initializedSlice := lo.Fill([]foo{foo{"a"}, foo{"a"}}, foo{"b"}) // []foo{foo{"b"}, foo{"b"}} ``` [[play](https://go.dev/play/p/VwR34GzqEub)] ### Repeat Builds a slice with N copies of initial value. ```go type foo struct { bar string } func (f foo) Clone() foo { return foo{f.bar} } slice := lo.Repeat(2, foo{"a"}) // []foo{foo{"a"}, foo{"a"}} ``` [[play](https://go.dev/play/p/g3uHXbmc3b6)] ### RepeatBy Builds a slice with values returned by N calls of callback. ```go slice := lo.RepeatBy(0, func (i int) string { return strconv.FormatInt(int64(math.Pow(float64(i), 2)), 10) }) // []string{} slice := lo.RepeatBy(5, func(i int) string { return strconv.FormatInt(int64(math.Pow(float64(i), 2)), 10) }) // []string{"0", "1", "4", "9", "16"} ``` [[play](https://go.dev/play/p/ozZLCtX_hNU)] ### KeyBy Transforms a slice or an array of structs to a map based on a pivot callback. ```go m := lo.KeyBy([]string{"a", "aa", "aaa"}, func(str string) int { return len(str) }) // map[int]string{1: "a", 2: "aa", 3: "aaa"} type Character struct { dir string code int } characters := []Character{ {dir: "left", code: 97}, {dir: "right", code: 100}, } result := lo.KeyBy(characters, func(char Character) string { return string(rune(char.code)) }) //map[a:{dir:left code:97} d:{dir:right code:100}] ``` [[play](https://go.dev/play/p/mdaClUAT-zZ)] ### Associate (alias: SliceToMap) Returns a map containing key-value pairs provided by transform function applied to elements of the given slice. If any of two pairs would have the same key the last one gets added to the map. The order of keys in returned map is not specified and is not guaranteed to be the same from the original array. ```go in := []*foo{{baz: "apple", bar: 1}, {baz: "banana", bar: 2}} aMap := lo.Associate(in, func (f *foo) (string, int) { return f.baz, f.bar }) // map[string][int]{ "apple":1, "banana":2 } ``` [[play](https://go.dev/play/p/WHa2CfMO3Lr)] ### Drop Drops n elements from the beginning of a slice or array. ```go l := lo.Drop([]int{0, 1, 2, 3, 4, 5}, 2) // []int{2, 3, 4, 5} ``` [[play](https://go.dev/play/p/JswS7vXRJP2)] ### DropRight Drops n elements from the end of a slice or array. ```go l := lo.DropRight([]int{0, 1, 2, 3, 4, 5}, 2) // []int{0, 1, 2, 3} ``` [[play](https://go.dev/play/p/GG0nXkSJJa3)] ### DropWhile Drop elements from the beginning of a slice or array while the predicate returns true. ```go l := lo.DropWhile([]string{"a", "aa", "aaa", "aa", "aa"}, func(val string) bool { return len(val) <= 2 }) // []string{"aaa", "aa", "aa"} ``` [[play](https://go.dev/play/p/7gBPYw2IK16)] ### DropRightWhile Drop elements from the end of a slice or array while the predicate returns true. ```go l := lo.DropRightWhile([]string{"a", "aa", "aaa", "aa", "aa"}, func(val string) bool { return len(val) <= 2 }) // []string{"a", "aa", "aaa"} ``` [[play](https://go.dev/play/p/3-n71oEC0Hz)] ### Reject The opposite of Filter, this method returns the elements of collection that predicate does not return truthy for. ```go odd := lo.Reject([]int{1, 2, 3, 4}, func(x int, _ int) bool { return x%2 == 0 }) // []int{1, 3} ``` [[play](https://go.dev/play/p/YkLMODy1WEL)] ### Count Counts the number of elements in the collection that compare equal to value. ```go count := lo.Count([]int{1, 5, 1}, 1) // 2 ``` [[play](https://go.dev/play/p/Y3FlK54yveC)] ### CountBy Counts the number of elements in the collection for which predicate is true. ```go count := lo.CountBy([]int{1, 5, 1}, func(i int) bool { return i < 4 }) // 2 ``` [[play](https://go.dev/play/p/ByQbNYQQi4X)] ### CountValues Counts the number of each element in the collection. ```go lo.CountValues([]int{}) // map[int]int{} lo.CountValues([]int{1, 2}) // map[int]int{1: 1, 2: 1} lo.CountValues([]int{1, 2, 2}) // map[int]int{1: 1, 2: 2} lo.CountValues([]string{"foo", "bar", ""}) // map[string]int{"": 1, "foo": 1, "bar": 1} lo.CountValues([]string{"foo", "bar", "bar"}) // map[string]int{"foo": 1, "bar": 2} ``` [[play](https://go.dev/play/p/-p-PyLT4dfy)] ### CountValuesBy Counts the number of each element in the collection. It ss equivalent to chaining lo.Map and lo.CountValues. ```go isEven := func(v int) bool { return v%2==0 } lo.CountValuesBy([]int{}, isEven) // map[bool]int{} lo.CountValuesBy([]int{1, 2}, isEven) // map[bool]int{false: 1, true: 1} lo.CountValuesBy([]int{1, 2, 2}, isEven) // map[bool]int{false: 1, true: 2} length := func(v string) int { return len(v) } lo.CountValuesBy([]string{"foo", "bar", ""}, length) // map[int]int{0: 1, 3: 2} lo.CountValuesBy([]string{"foo", "bar", "bar"}, length) // map[int]int{3: 3} ``` [[play](https://go.dev/play/p/2U0dG1SnOmS)] ### Subset Returns a copy of a slice from `offset` up to `length` elements. Like `slice[start:start+length]`, but does not panic on overflow. ```go in := []int{0, 1, 2, 3, 4} sub := lo.Subset(in, 2, 3) // []int{2, 3, 4} sub := lo.Subset(in, -4, 3) // []int{1, 2, 3} sub := lo.Subset(in, -2, math.MaxUint) // []int{3, 4} ``` [[play](https://go.dev/play/p/tOQu1GhFcog)] ### Slice Returns a copy of a slice from `start` up to, but not including `end`. Like `slice[start:end]`, but does not panic on overflow. ```go in := []int{0, 1, 2, 3, 4} slice := lo.Slice(in, 0, 5) // []int{0, 1, 2, 3, 4} slice := lo.Slice(in, 2, 3) // []int{2} slice := lo.Slice(in, 2, 6) // []int{2, 3, 4} slice := lo.Slice(in, 4, 3) // []int{} ``` [[play](https://go.dev/play/p/8XWYhfMMA1h)] ### Replace Returns a copy of the slice with the first n non-overlapping instances of old replaced by new. ```go in := []int{0, 1, 0, 1, 2, 3, 0} slice := lo.Replace(in, 0, 42, 1) // []int{42, 1, 0, 1, 2, 3, 0} slice := lo.Replace(in, -1, 42, 1) // []int{0, 1, 0, 1, 2, 3, 0} slice := lo.Replace(in, 0, 42, 2) // []int{42, 1, 42, 1, 2, 3, 0} slice := lo.Replace(in, 0, 42, -1) // []int{42, 1, 42, 1, 2, 3, 42} ``` [[play](https://go.dev/play/p/XfPzmf9gql6)] ### ReplaceAll Returns a copy of the slice with all non-overlapping instances of old replaced by new. ```go in := []int{0, 1, 0, 1, 2, 3, 0} slice := lo.ReplaceAll(in, 0, 42) // []int{42, 1, 42, 1, 2, 3, 42} slice := lo.ReplaceAll(in, -1, 42) // []int{0, 1, 0, 1, 2, 3, 0} ``` [[play](https://go.dev/play/p/a9xZFUHfYcV)] ### Compact Returns a slice of all non-zero elements. ```go in := []string{"", "foo", "", "bar", ""} slice := lo.Compact[string](in) // []string{"foo", "bar"} ``` [[play](https://go.dev/play/p/tXiy-iK6PAc)] ### IsSorted Checks if a slice is sorted. ```go slice := lo.IsSorted([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}) // true ``` [[play](https://go.dev/play/p/mc3qR-t4mcx)] ### IsSortedByKey Checks if a slice is sorted by iteratee. ```go slice := lo.IsSortedByKey([]string{"a", "bb", "ccc"}, func(s string) int { return len(s) }) // true ``` [[play](https://go.dev/play/p/wiG6XyBBu49)] ### Keys Creates an array of the map keys. ```go keys := lo.Keys[string, int](map[string]int{"foo": 1, "bar": 2}) // []string{"foo", "bar"} ``` [[play](https://go.dev/play/p/Uu11fHASqrU)] ### Values Creates an array of the map values. ```go values := lo.Values[string, int](map[string]int{"foo": 1, "bar": 2}) // []int{1, 2} ``` [[play](https://go.dev/play/p/nnRTQkzQfF6)] ### ValueOr Creates an array of the map values. ```go value := lo.ValueOr[string, int](map[string]int{"foo": 1, "bar": 2}, "foo", 42) // 1 value := lo.ValueOr[string, int](map[string]int{"foo": 1, "bar": 2}, "baz", 42) // 42 ``` [[play](https://go.dev/play/p/bAq9mHErB4V)] ### PickBy Returns same map type filtered by given predicate. ```go m := lo.PickBy(map[string]int{"foo": 1, "bar": 2, "baz": 3}, func(key string, value int) bool { return value%2 == 1 }) // map[string]int{"foo": 1, "baz": 3} ``` [[play](https://go.dev/play/p/kdg8GR_QMmf)] ### PickByKeys Returns same map type filtered by given keys. ```go m := lo.PickByKeys(map[string]int{"foo": 1, "bar": 2, "baz": 3}, []string{"foo", "baz"}) // map[string]int{"foo": 1, "baz": 3} ``` [[play](https://go.dev/play/p/R1imbuci9qU)] ### PickByValues Returns same map type filtered by given values. ```go m := lo.PickByValues(map[string]int{"foo": 1, "bar": 2, "baz": 3}, []int{1, 3}) // map[string]int{"foo": 1, "baz": 3} ``` [[play](https://go.dev/play/p/1zdzSvbfsJc)] ### OmitBy Returns same map type filtered by given predicate. ```go m := lo.OmitBy(map[string]int{"foo": 1, "bar": 2, "baz": 3}, func(key string, value int) bool { return value%2 == 1 }) // map[string]int{"bar": 2} ``` [[play](https://go.dev/play/p/EtBsR43bdsd)] ### OmitByKeys Returns same map type filtered by given keys. ```go m := lo.OmitByKeys(map[string]int{"foo": 1, "bar": 2, "baz": 3}, []string{"foo", "baz"}) // map[string]int{"bar": 2} ``` [[play](https://go.dev/play/p/t1QjCrs-ysk)] ### OmitByValues Returns same map type filtered by given values. ```go m := lo.OmitByValues(map[string]int{"foo": 1, "bar": 2, "baz": 3}, []int{1, 3}) // map[string]int{"bar": 2} ``` [[play](https://go.dev/play/p/9UYZi-hrs8j)] ### Entries (alias: ToPairs) Transforms a map into array of key/value pairs. ```go entries := lo.Entries(map[string]int{"foo": 1, "bar": 2}) // []lo.Entry[string, int]{ // { // Key: "foo", // Value: 1, // }, // { // Key: "bar", // Value: 2, // }, // } ``` [[play](https://go.dev/play/p/3Dhgx46gawJ)] ### FromEntries (alias: FromPairs) Transforms an array of key/value pairs into a map. ```go m := lo.FromEntries([]lo.Entry[string, int]{ { Key: "foo", Value: 1, }, { Key: "bar", Value: 2, }, }) // map[string]int{"foo": 1, "bar": 2} ``` [[play](https://go.dev/play/p/oIr5KHFGCEN)] ### Invert Creates a map composed of the inverted keys and values. If map contains duplicate values, subsequent values overwrite property assignments of previous values. ```go m1 := lo.Invert(map[string]int{"a": 1, "b": 2}) // map[int]string{1: "a", 2: "b"} m2 := lo.Invert(map[string]int{"a": 1, "b": 2, "c": 1}) // map[int]string{1: "c", 2: "b"} ``` [[play](https://go.dev/play/p/rFQ4rak6iA1)] ### Assign Merges multiple maps from left to right. ```go mergedMaps := lo.Assign[string, int]( map[string]int{"a": 1, "b": 2}, map[string]int{"b": 3, "c": 4}, ) // map[string]int{"a": 1, "b": 3, "c": 4} ``` [[play](https://go.dev/play/p/VhwfJOyxf5o)] ### MapKeys Manipulates a map keys and transforms it to a map of another type. ```go m2 := lo.MapKeys(map[int]int{1: 1, 2: 2, 3: 3, 4: 4}, func(_ int, v int) string { return strconv.FormatInt(int64(v), 10) }) // map[string]int{"1": 1, "2": 2, "3": 3, "4": 4} ``` [[play](https://go.dev/play/p/9_4WPIqOetJ)] ### MapValues Manipulates a map values and transforms it to a map of another type. ```go m1 := map[int]int64{1: 1, 2: 2, 3: 3} m2 := lo.MapValues(m1, func(x int64, _ int) string { return strconv.FormatInt(x, 10) }) // map[int]string{1: "1", 2: "2", 3: "3"} ``` [[play](https://go.dev/play/p/T_8xAfvcf0W)] ### MapEntries Manipulates a map entries and transforms it to a map of another type. ```go in := map[string]int{"foo": 1, "bar": 2} out := lo.MapEntries(in, func(k string, v int) (int, string) { return v,k }) // map[int]string{1: "foo", 2: "bar"} ``` [[play](https://go.dev/play/p/VuvNQzxKimT)] ### MapToSlice Transforms a map into a slice based on specific iteratee. ```go m := map[int]int64{1: 4, 2: 5, 3: 6} s := lo.MapToSlice(m, func(k int, v int64) string { return fmt.Sprintf("%d_%d", k, v) }) // []string{"1_4", "2_5", "3_6"} ``` [[play](https://go.dev/play/p/ZuiCZpDt6LD)] ### Range / RangeFrom / RangeWithSteps Creates an array of numbers (positive and/or negative) progressing from start up to, but not including end. ```go result := lo.Range(4) // [0, 1, 2, 3] result := lo.Range(-4) // [0, -1, -2, -3] result := lo.RangeFrom(1, 5) // [1, 2, 3, 4, 5] result := lo.RangeFrom[float64](1.0, 5) // [1.0, 2.0, 3.0, 4.0, 5.0] result := lo.RangeWithSteps(0, 20, 5) // [0, 5, 10, 15] result := lo.RangeWithSteps[float32](-1.0, -4.0, -1.0) // [-1.0, -2.0, -3.0] result := lo.RangeWithSteps(1, 4, -1) // [] result := lo.Range(0) // [] ``` [[play](https://go.dev/play/p/0r6VimXAi9H)] ### Clamp Clamps number within the inclusive lower and upper bounds. ```go r1 := lo.Clamp(0, -10, 10) // 0 r2 := lo.Clamp(-42, -10, 10) // -10 r3 := lo.Clamp(42, -10, 10) // 10 ``` [[play](https://go.dev/play/p/RU4lJNC2hlI)] ### Sum Sums the values in a collection. If collection is empty 0 is returned. ```go list := []int{1, 2, 3, 4, 5} sum := lo.Sum(list) // 15 ``` [[play](https://go.dev/play/p/upfeJVqs4Bt)] ### SumBy Summarizes the values in a collection using the given return value from the iteration function. If collection is empty 0 is returned. ```go strings := []string{"foo", "bar"} sum := lo.SumBy(strings, func(item string) int { return len(item) }) // 6 ``` [[play](https://go.dev/play/p/Dz_a_7jN_ca)] ### RandomString Returns a random string of the specified length and made of the specified charset. ```go str := lo.RandomString(5, lo.LettersCharset) // example: "eIGbt" ``` [[play](https://go.dev/play/p/rRseOQVVum4)] ### Substring Return part of a string. ```go sub := lo.Substring("hello", 2, 3) // "llo" sub := lo.Substring("hello", -4, 3) // "ell" sub := lo.Substring("hello", -2, math.MaxUint) // "lo" ``` [[play](https://go.dev/play/p/TQlxQi82Lu1)] ### ChunkString Returns an array of strings split into groups the length of size. If array can't be split evenly, the final chunk will be the remaining elements. ```go lo.ChunkString("123456", 2) // []string{"12", "34", "56"} lo.ChunkString("1234567", 2) // []string{"12", "34", "56", "7"} lo.ChunkString("", 2) // []string{""} lo.ChunkString("1", 2) // []string{"1"} ``` [[play](https://go.dev/play/p/__FLTuJVz54)] ### RuneLength An alias to utf8.RuneCountInString which returns the number of runes in string. ```go sub := lo.RuneLength("hellรด") // 5 sub := len("hellรด") // 6 ``` [[play](https://go.dev/play/p/tuhgW_lWY8l)] ### T2 -> T9 Creates a tuple from a list of values. ```go tuple1 := lo.T2("x", 1) // Tuple2[string, int]{A: "x", B: 1} func example() (string, int) { return "y", 2 } tuple2 := lo.T2(example()) // Tuple2[string, int]{A: "y", B: 2} ``` [[play](https://go.dev/play/p/IllL3ZO4BQm)] ### Unpack2 -> Unpack9 Returns values contained in tuple. ```go r1, r2 := lo.Unpack2(lo.Tuple2[string, int]{"a", 1}) // "a", 1 ``` Unpack is also available as a method of TupleX. ```go tuple2 := lo.T2("a", 1) a, b := tuple2.Unpack() // "a" 1 ``` [[play](https://go.dev/play/p/xVP_k0kJ96W)] ### Zip2 -> Zip9 Zip creates a slice of grouped 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 tuples := lo.Zip2([]string{"a", "b"}, []int{1, 2}) // []Tuple2[string, int]{{A: "a", B: 1}, {A: "b", B: 2}} ``` [[play](https://go.dev/play/p/jujaA6GaJTp)] ### Unzip2 -> Unzip9 Unzip accepts an array of grouped elements and creates an array regrouping the elements to their pre-zip configuration. ```go a, b := lo.Unzip2([]Tuple2[string, int]{{A: "a", B: 1}, {A: "b", B: 2}}) // []string{"a", "b"} // []int{1, 2} ``` [[play](https://go.dev/play/p/ciHugugvaAW)] ### ChannelDispatcher Distributes messages from input channels into N child channels. Close events are propagated to children. Underlying channels can have a fixed buffer capacity or be unbuffered when cap is 0. ```go ch := make(chan int, 42) for i := 0; i <= 10; i++ { ch <- i } children := lo.ChannelDispatcher(ch, 5, 10, DispatchingStrategyRoundRobin[int]) // []<-chan int{...} consumer := func(c <-chan int) { for { msg, ok := <-c if !ok { println("closed") break } println(msg) } } for i := range children { go consumer(children[i]) } ``` Many distributions strategies are available: - [lo.DispatchingStrategyRoundRobin](./channel.go): Distributes messages in a rotating sequential manner. - [lo.DispatchingStrategyRandom](./channel.go): Distributes messages in a random manner. - [lo.DispatchingStrategyWeightedRandom](./channel.go): Distributes messages in a weighted manner. - [lo.DispatchingStrategyFirst](./channel.go): Distributes messages in the first non-full channel. - [lo.DispatchingStrategyLeast](./channel.go): Distributes messages in the emptiest channel. - [lo.DispatchingStrategyMost](./channel.go): Distributes to the fullest channel. Some strategies bring fallback, in order to favor non-blocking behaviors. See implementations. For custom strategies, just implement the `lo.DispatchingStrategy` prototype: ```go type DispatchingStrategy[T any] func(message T, messageIndex uint64, channels []<-chan T) int ``` Eg: ```go type Message struct { TenantID uuid.UUID } func hash(id uuid.UUID) int { h := fnv.New32a() h.Write([]byte(id.String())) return int(h.Sum32()) } // Routes messages per TenantID. customStrategy := func(message string, messageIndex uint64, channels []<-chan string) int { destination := hash(message) % len(channels) // check if channel is full if len(channels[destination]) < cap(channels[destination]) { return destination } // fallback when child channel is full return utils.DispatchingStrategyRoundRobin(message, uint64(destination), channels) } children := lo.ChannelDispatcher(ch, 5, 10, customStrategy) ... ``` ### SliceToChannel Returns a read-only channels of collection elements. Channel is closed after last element. Channel capacity can be customized. ```go list := []int{1, 2, 3, 4, 5} for v := range lo.SliceToChannel(2, list) { println(v) } // prints 1, then 2, then 3, then 4, then 5 ``` ### ChannelToSlice Returns a slice built from channels items. Blocks until channel closes. ```go list := []int{1, 2, 3, 4, 5} ch := lo.SliceToChannel(2, list) items := ChannelToSlice(ch) // []int{1, 2, 3, 4, 5} ``` ### Generator Implements the generator design pattern. Channel is closed after last element. Channel capacity can be customized. ```go generator := func(yield func(int)) { yield(1) yield(2) yield(3) } for v := range lo.Generator(2, generator) { println(v) } // prints 1, then 2, then 3 ``` ### Buffer Creates a slice of n elements from a channel. Returns the slice, the slice length, the read time and the channel status (opened/closed). ```go ch := lo.SliceToChannel(2, []int{1, 2, 3, 4, 5}) items1, length1, duration1, ok1 := lo.Buffer(ch, 3) // []int{1, 2, 3}, 3, 0s, true items2, length2, duration2, ok2 := lo.Buffer(ch, 3) // []int{4, 5}, 2, 0s, false ``` Example: RabbitMQ consumer ๐Ÿ‘‡ ```go ch := readFromQueue() for { // read 1k items items, length, _, ok := lo.Buffer(ch, 1000) // do batching stuff if !ok { break } } ``` ### BufferWithTimeout Creates a slice of n elements from a channel, with timeout. Returns the slice, the slice length, the read time and the channel status (opened/closed). ```go generator := func(yield func(int)) { for i := 0; i < 5; i++ { yield(i) time.Sleep(35*time.Millisecond) } } ch := lo.Generator(0, generator) items1, length1, duration1, ok1 := lo.BufferWithTimeout(ch, 3, 100*time.Millisecond) // []int{1, 2}, 2, 100ms, true items2, length2, duration2, ok2 := lo.BufferWithTimeout(ch, 3, 100*time.Millisecond) // []int{3, 4, 5}, 3, 75ms, true items3, length3, duration2, ok3 := lo.BufferWithTimeout(ch, 3, 100*time.Millisecond) // []int{}, 0, 10ms, false ``` Example: RabbitMQ consumer ๐Ÿ‘‡ ```go ch := readFromQueue() for { // read 1k items // wait up to 1 second items, length, _, ok := lo.BufferWithTimeout(ch, 1000, 1*time.Second) // do batching stuff if !ok { break } } ``` Example: Multithreaded RabbitMQ consumer ๐Ÿ‘‡ ```go ch := readFromQueue() // 5 workers // prefetch 1k messages per worker children := lo.ChannelDispatcher(ch, 5, 1000, lo.DispatchingStrategyFirst[int]) consumer := func(c <-chan int) { for { // read 1k items // wait up to 1 second items, length, _, ok := lo.BufferWithTimeout(ch, 1000, 1*time.Second) // do batching stuff if !ok { break } } } for i := range children { go consumer(children[i]) } ``` ### FanIn Merge messages from multiple input channels into a single buffered channel. Output messages has no priority. When all upstream channels reach EOF, downstream channel closes. ```go stream1 := make(chan int, 42) stream2 := make(chan int, 42) stream3 := make(chan int, 42) all := lo.FanIn(100, stream1, stream2, stream3) // <-chan int ``` ### FanOut Broadcasts all the upstream messages to multiple downstream channels. When upstream channel reach EOF, downstream channels close. If any downstream channels is full, broadcasting is paused. ```go stream := make(chan int, 42) all := lo.FanOut(5, 100, stream) // [5]<-chan int ``` ### Contains Returns true if an element is present in a collection. ```go present := lo.Contains([]int{0, 1, 2, 3, 4, 5}, 5) // true ``` ### ContainsBy Returns true if the predicate function returns `true`. ```go present := lo.ContainsBy([]int{0, 1, 2, 3, 4, 5}, func(x int) bool { return x == 3 }) // true ``` ### Every Returns true if all elements of a subset are contained into a collection or if the subset is empty. ```go ok := lo.Every([]int{0, 1, 2, 3, 4, 5}, []int{0, 2}) // true ok := lo.Every([]int{0, 1, 2, 3, 4, 5}, []int{0, 6}) // false ``` ### EveryBy Returns true if the predicate returns true for all of the elements in the collection or if the collection is empty. ```go b := EveryBy([]int{1, 2, 3, 4}, func(x int) bool { return x < 5 }) // true ``` ### Some Returns true if at least 1 element of a subset is contained into a collection. If the subset is empty Some returns false. ```go ok := lo.Some([]int{0, 1, 2, 3, 4, 5}, []int{0, 2}) // true ok := lo.Some([]int{0, 1, 2, 3, 4, 5}, []int{-1, 6}) // false ``` ### SomeBy Returns true if the predicate returns true for any of the elements in the collection. If the collection is empty SomeBy returns false. ```go b := SomeBy([]int{1, 2, 3, 4}, func(x int) bool { return x < 3 }) // true ``` ### None Returns true if no element of a subset are contained into a collection or if the subset is empty. ```go b := None([]int{0, 1, 2, 3, 4, 5}, []int{0, 2}) // false b := None([]int{0, 1, 2, 3, 4, 5}, []int{-1, 6}) // true ``` ### NoneBy Returns true if the predicate returns true for none of the elements in the collection or if the collection is empty. ```go b := NoneBy([]int{1, 2, 3, 4}, func(x int) bool { return x < 0 }) // true ``` ### Intersect Returns the intersection between two collections. ```go result1 := lo.Intersect([]int{0, 1, 2, 3, 4, 5}, []int{0, 2}) // []int{0, 2} result2 := lo.Intersect([]int{0, 1, 2, 3, 4, 5}, []int{0, 6}) // []int{0} result3 := lo.Intersect([]int{0, 1, 2, 3, 4, 5}, []int{-1, 6}) // []int{} ``` ### Difference Returns the difference between two collections. - The first value is the collection of element absent of list2. - The second value is the collection of element absent of list1. ```go left, right := lo.Difference([]int{0, 1, 2, 3, 4, 5}, []int{0, 2, 6}) // []int{1, 3, 4, 5}, []int{6} left, right := lo.Difference([]int{0, 1, 2, 3, 4, 5}, []int{0, 1, 2, 3, 4, 5}) // []int{}, []int{} ``` ### Union Returns all distinct elements from given collections. Result will not change the order of elements relatively. ```go union := lo.Union([]int{0, 1, 2, 3, 4, 5}, []int{0, 2}, []int{0, 10}) // []int{0, 1, 2, 3, 4, 5, 10} ``` ### Without Returns slice excluding all given values. ```go subset := lo.Without([]int{0, 2, 10}, 2) // []int{0, 10} subset := lo.Without([]int{0, 2, 10}, 0, 1, 2, 3, 4, 5) // []int{10} ``` ### WithoutEmpty Returns slice excluding empty values. ```go subset := lo.WithoutEmpty([]int{0, 2, 10}) // []int{2, 10} ``` ### 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. ```go found := lo.IndexOf([]int{0, 1, 2, 1, 2, 3}, 2) // 2 notFound := lo.IndexOf([]int{0, 1, 2, 1, 2, 3}, 6) // -1 ``` ### LastIndexOf Returns the index at which the last occurrence of a value is found in an array or return -1 if the value cannot be found. ```go found := lo.LastIndexOf([]int{0, 1, 2, 1, 2, 3}, 2) // 4 notFound := lo.LastIndexOf([]int{0, 1, 2, 1, 2, 3}, 6) // -1 ``` ### Find Search an element in a slice based on a predicate. It returns element and true if element was found. ```go str, ok := lo.Find([]string{"a", "b", "c", "d"}, func(i string) bool { return i == "b" }) // "b", true str, ok := lo.Find([]string{"foobar"}, func(i string) bool { return i == "b" }) // "", false ``` ### FindIndexOf 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. ```go str, index, ok := lo.FindIndexOf([]string{"a", "b", "a", "b"}, func(i string) bool { return i == "b" }) // "b", 1, true str, index, ok := lo.FindIndexOf([]string{"foobar"}, func(i string) bool { return i == "b" }) // "", -1, false ``` ### FindLastIndexOf FindLastIndexOf 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. ```go str, index, ok := lo.FindLastIndexOf([]string{"a", "b", "a", "b"}, func(i string) bool { return i == "b" }) // "b", 4, true str, index, ok := lo.FindLastIndexOf([]string{"foobar"}, func(i string) bool { return i == "b" }) // "", -1, false ``` ### FindOrElse Search an element in a slice based on a predicate. It returns element and true if element was found. ```go str := lo.FindOrElse([]string{"a", "b", "c", "d"}, "x", func(i string) bool { return i == "b" }) // "b" str := lo.FindOrElse([]string{"foobar"}, "x", func(i string) bool { return i == "b" }) // "x" ``` ### FindKey Returns the key of the first value matching. ```go result1, ok1 := lo.FindKey(map[string]int{"foo": 1, "bar": 2, "baz": 3}, 2) // "bar", true result2, ok2 := lo.FindKey(map[string]int{"foo": 1, "bar": 2, "baz": 3}, 42) // "", false type test struct { foobar string } result3, ok3 := lo.FindKey(map[string]test{"foo": test{"foo"}, "bar": test{"bar"}, "baz": test{"baz"}}, test{"foo"}) // "foo", true ``` ### FindKeyBy Returns the key of the first element predicate returns truthy for. ```go result1, ok1 := lo.FindKeyBy(map[string]int{"foo": 1, "bar": 2, "baz": 3}, func(k string, v int) bool { return k == "foo" }) // "foo", true result2, ok2 := lo.FindKeyBy(map[string]int{"foo": 1, "bar": 2, "baz": 3}, func(k string, v int) bool { return false }) // "", false ``` ### FindUniques Returns a slice with all the unique elements of the collection. The order of result values is determined by the order they occur in the array. ```go uniqueValues := lo.FindUniques([]int{1, 2, 2, 1, 2, 3}) // []int{3} ``` ### FindUniquesBy Returns a slice with all the unique elements of the collection. The order of result values is determined by the order they occur in the array. It accepts `iteratee` which is invoked for each element in array to generate the criterion by which uniqueness is computed. ```go uniqueValues := lo.FindUniquesBy([]int{3, 4, 5, 6, 7}, func(i int) int { return i%3 }) // []int{5} ``` ### FindDuplicates Returns a slice with the first occurrence of each duplicated elements of the collection. The order of result values is determined by the order they occur in the array. ```go duplicatedValues := lo.FindDuplicates([]int{1, 2, 2, 1, 2, 3}) // []int{1, 2} ``` ### FindDuplicatesBy Returns a slice with the first occurrence of each duplicated elements of the collection. The order of result values is determined by the order they occur in the array. It accepts `iteratee` which is invoked for each element in array to generate the criterion by which uniqueness is computed. ```go duplicatedValues := lo.FindDuplicatesBy([]int{3, 4, 5, 6, 7}, func(i int) int { return i%3 }) // []int{3, 4} ``` ### Min Search the minimum value of a collection. Returns zero value when collection is empty. ```go min := lo.Min([]int{1, 2, 3}) // 1 min := lo.Min([]int{}) // 0 ``` ### 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. ```go min := lo.MinBy([]string{"s1", "string2", "s3"}, func(item string, min string) bool { return len(item) < len(min) }) // "s1" min := lo.MinBy([]string{}, func(item string, min string) bool { return len(item) < len(min) }) // "" ``` ### Max Search the maximum value of a collection. Returns zero value when collection is empty. ```go max := lo.Max([]int{1, 2, 3}) // 3 max := lo.Max([]int{}) // 0 ``` ### 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. ```go max := lo.MaxBy([]string{"string1", "s2", "string3"}, func(item string, max string) bool { return len(item) > len(max) }) // "string1" max := lo.MaxBy([]string{}, func(item string, max string) bool { return len(item) > len(max) }) // "" ``` ### Last Returns the last element of a collection or error if empty. ```go last, err := lo.Last([]int{1, 2, 3}) // 3 ``` ### Nth Returns the element at index `nth` of collection. If `nth` is negative, the nth element from the end is returned. An error is returned when nth is out of slice bounds. ```go nth, err := lo.Nth([]int{0, 1, 2, 3}, 2) // 2 nth, err := lo.Nth([]int{0, 1, 2, 3}, -2) // 2 ``` ### Sample Returns a random item from collection. ```go lo.Sample([]string{"a", "b", "c"}) // a random string from []string{"a", "b", "c"} lo.Sample([]string{}) // "" ``` ### Samples Returns N random unique items from collection. ```go lo.Samples([]string{"a", "b", "c"}, 3) // []string{"a", "b", "c"} in random order ``` ### Ternary A 1 line if/else statement. ```go result := lo.Ternary(true, "a", "b") // "a" result := lo.Ternary(false, "a", "b") // "b" ``` [[play](https://go.dev/play/p/t-D7WBL44h2)] ### TernaryF A 1 line if/else statement whose options are functions. ```go result := lo.TernaryF(true, func() string { return "a" }, func() string { return "b" }) // "a" result := lo.TernaryF(false, func() string { return "a" }, func() string { return "b" }) // "b" ``` Useful to avoid nil-pointer dereferencing in intializations, or avoid running unnecessary code ```go var s *string someStr := TernaryF(s == nil, func() string { return uuid.New().String() }, func() string { return *s }) // ef782193-c30c-4e2e-a7ae-f8ab5e125e02 ``` [[play](https://go.dev/play/p/AO4VW20JoqM)] ### If / ElseIf / Else ```go result := lo.If(true, 1). ElseIf(false, 2). Else(3) // 1 result := lo.If(false, 1). ElseIf(true, 2). Else(3) // 2 result := lo.If(false, 1). ElseIf(false, 2). Else(3) // 3 ``` Using callbacks: ```go result := lo.IfF(true, func () int { return 1 }). ElseIfF(false, func () int { return 2 }). ElseF(func () int { return 3 }) // 1 ``` Mixed: ```go result := lo.IfF(true, func () int { return 1 }). Else(42) // 1 ``` [[play](https://go.dev/play/p/WSw3ApMxhyW)] ### Switch / Case / Default ```go result := lo.Switch(1). Case(1, "1"). Case(2, "2"). Default("3") // "1" result := lo.Switch(2). Case(1, "1"). Case(2, "2"). Default("3") // "2" result := lo.Switch(42). Case(1, "1"). Case(2, "2"). Default("3") // "3" ``` Using callbacks: ```go result := lo.Switch(1). CaseF(1, func() string { return "1" }). CaseF(2, func() string { return "2" }). DefaultF(func() string { return "3" }) // "1" ``` Mixed: ```go result := lo.Switch(1). CaseF(1, func() string { return "1" }). Default("42") // "1" ``` [[play](https://go.dev/play/p/TGbKUMAeRUd)] ### ToPtr Returns a pointer copy of value. ```go ptr := lo.ToPtr("hello world") // *string{"hello world"} ``` ### EmptyableToPtr Returns a pointer copy of value if it's nonzero. Otherwise, returns nil pointer. ```go ptr := lo.EmptyableToPtr[[]int](nil) // nil ptr := lo.EmptyableToPtr[string]("") // nil ptr := lo.EmptyableToPtr[[]int]([]int{}) // *[]int{} ptr := lo.EmptyableToPtr[string]("hello world") // *string{"hello world"} ``` ### FromPtr Returns the pointer value or empty. ```go str := "hello world" value := lo.FromPtr(&str) // "hello world" value := lo.FromPtr[string](nil) // "" ``` ### FromPtrOr Returns the pointer value or the fallback value. ```go str := "hello world" value := lo.FromPtrOr(&str, "empty") // "hello world" value := lo.FromPtrOr[string](nil, "empty") // "empty" ``` ### ToSlicePtr Returns a slice of pointer copy of value. ```go ptr := lo.ToSlicePtr([]string{"hello", "world"}) // []*string{"hello", "world"} ``` ### ToAnySlice Returns a slice with all elements mapped to `any` type. ```go elements := lo.ToAnySlice([]int{1, 5, 1}) // []any{1, 5, 1} ``` ### FromAnySlice Returns an `any` slice with all elements mapped to a type. Returns false in case of type conversion failure. ```go elements, ok := lo.FromAnySlice([]any{"foobar", 42}) // []string{}, false elements, ok := lo.FromAnySlice([]any{"foobar", "42"}) // []string{"foobar", "42"}, true ``` ### Empty Returns an empty value. ```go lo.Empty[int]() // 0 lo.Empty[string]() // "" lo.Empty[bool]() // false ``` ### IsEmpty Returns true if argument is a zero value. ```go lo.IsEmpty(0) // true lo.IsEmpty(42) // false lo.IsEmpty("") // true lo.IsEmpty("foobar") // false type test struct { foobar string } lo.IsEmpty(test{foobar: ""}) // true lo.IsEmpty(test{foobar: "foobar"}) // false ``` ### IsNotEmpty Returns true if argument is a zero value. ```go lo.IsNotEmpty(0) // false lo.IsNotEmpty(42) // true lo.IsNotEmpty("") // false lo.IsNotEmpty("foobar") // true type test struct { foobar string } lo.IsNotEmpty(test{foobar: ""}) // false lo.IsNotEmpty(test{foobar: "foobar"}) // true ``` ### Coalesce Returns the first non-empty arguments. Arguments must be comparable. ```go result, ok := lo.Coalesce(0, 1, 2, 3) // 1 true result, ok := lo.Coalesce("") // "" false var nilStr *string str := "foobar" result, ok := lo.Coalesce[*string](nil, nilStr, &str) // &"foobar" true ``` ### Partial Returns new function that, when called, has its first argument set to the provided value. ```go add := func(x, y int) int { return x + y } f := lo.Partial(add, 5) f(10) // 15 f(42) // 47 ``` ### Partial2 -> Partial5 Returns new function that, when called, has its first argument set to the provided value. ```go add := func(x, y, z int) int { return x + y + z } f := lo.Partial2(add, 42) f(10, 5) // 57 f(42, -4) // 80 ``` ### Attempt Invokes a function N times until it returns valid output. Returning either the caught error or nil. When first argument is less than `1`, the function runs until a successful response is returned. ```go iter, err := lo.Attempt(42, func(i int) error { if i == 5 { return nil } return fmt.Errorf("failed") }) // 6 // nil iter, err := lo.Attempt(2, func(i int) error { if i == 5 { return nil } return fmt.Errorf("failed") }) // 2 // error "failed" iter, err := lo.Attempt(0, func(i int) error { if i < 42 { return fmt.Errorf("failed") } return nil }) // 43 // nil ``` For more advanced retry strategies (delay, exponential backoff...), please take a look on [cenkalti/backoff](https://github.com/cenkalti/backoff). [[play](https://go.dev/play/p/3ggJZ2ZKcMj)] ### AttemptWithDelay Invokes a function N times until it returns valid output, with a pause between each call. Returning either the caught error or nil. When first argument is less than `1`, the function runs until a successful response is returned. ```go iter, duration, err := lo.AttemptWithDelay(5, 2*time.Second, func(i int, duration time.Duration) error { if i == 2 { return nil } return fmt.Errorf("failed") }) // 3 // ~ 4 seconds // nil ``` For more advanced retry strategies (delay, exponential backoff...), please take a look on [cenkalti/backoff](https://github.com/cenkalti/backoff). [[play](https://go.dev/play/p/tVs6CygC7m1)] ### AttemptWhile Invokes a function N times until it returns valid output. Returning either the caught error or nil, and along with a bool value to identifying whether it needs invoke function continuously. It will terminate the invoke immediately if second bool value is returned with falsy value. When first argument is less than `1`, the function runs until a successful response is returned. ```go count1, err1 := lo.AttemptWhile(5, func(i int) (error, bool) { err := doMockedHTTPRequest(i) if err != nil { if errors.Is(err, ErrBadRequest) { // lets assume ErrBadRequest is a critical error that needs to terminate the invoke return err, false // flag the second return value as false to terminate the invoke } return err, true } return nil, false }) ``` For more advanced retry strategies (delay, exponential backoff...), please take a look on [cenkalti/backoff](https://github.com/cenkalti/backoff). [[play](https://go.dev/play/p/M2wVq24PaZM)] ### AttemptWhileWithDelay Invokes a function N times until it returns valid output, with a pause between each call. Returning either the caught error or nil, and along with a bool value to identifying whether it needs to invoke function continuously. It will terminate the invoke immediately if second bool value is returned with falsy value. When first argument is less than `1`, the function runs until a successful response is returned. ```go count1, time1, err1 := lo.AttemptWhileWithDelay(5, time.Millisecond, func(i int, d time.Duration) (error, bool) { err := doMockedHTTPRequest(i) if err != nil { if errors.Is(err, ErrBadRequest) { // lets assume ErrBadRequest is a critical error that needs to terminate the invoke return err, false // flag the second return value as false to terminate the invoke } return err, true } return nil, false }) ``` For more advanced retry strategies (delay, exponential backoff...), please take a look on [cenkalti/backoff](https://github.com/cenkalti/backoff). [[play](https://go.dev/play/p/cfcmhvLO-nv)] ### Debounce `NewDebounce` creates a debounced instance that delays invoking functions given until after wait milliseconds have elapsed, until `cancel` is called. ```go f := func() { println("Called once after 100ms when debounce stopped invoking!") } debounce, cancel := lo.NewDebounce(100 * time.Millisecond, f) for j := 0; j < 10; j++ { debounce() } time.Sleep(1 * time.Second) cancel() ``` [[play](https://go.dev/play/p/mz32VMK2nqe)] ### DebounceBy `NewDebounceBy` creates a debounced instance for each distinct key, that delays invoking functions given until after wait milliseconds have elapsed, until `cancel` is called. ```go f := func(key string, count int) { println(key + ": Called once after 100ms when debounce stopped invoking!") } debounce, cancel := lo.NewDebounceBy(100 * time.Millisecond, f) for j := 0; j < 10; j++ { debounce("first key") debounce("second key") } time.Sleep(1 * time.Second) cancel("first key") cancel("second key") ``` [[play](https://go.dev/play/p/d3Vpt6pxhY8)] ### Synchronize Wraps the underlying callback in a mutex. It receives an optional mutex. ```go s := lo.Synchronize() for i := 0; i < 10; i++ { go s.Do(func () { println("will be called sequentially") }) } ``` It is equivalent to: ```go mu := sync.Mutex{} func foobar() { mu.Lock() defer mu.Unlock() // ... } ``` ### Async Executes a function in a goroutine and returns the result in a channel. ```go ch := lo.Async(func() error { time.Sleep(10 * time.Second); return nil }) // chan error (nil) ``` ### Async{0->6} Executes a function in a goroutine and returns the result in a channel. For function with multiple return values, the results will be returned as a tuple inside the channel. For function without return, struct{} will be returned in the channel. ```go ch := lo.Async0(func() { time.Sleep(10 * time.Second) }) // chan struct{} ch := lo.Async1(func() int { time.Sleep(10 * time.Second); return 42 }) // chan int (42) ch := lo.Async2(func() (int, string) { time.Sleep(10 * time.Second); return 42, "Hello" }) // chan lo.Tuple2[int, string] ({42, "Hello"}) ``` ### Transaction Implements a Saga pattern. ```go 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 ``` ### Validate Helper function that creates an error when a condition is not met. ```go slice := []string{"a"} val := lo.Validate(len(slice) == 0, "Slice should be empty but contains %v", slice) // error("Slice should be empty but contains [a]") slice := []string{} val := lo.Validate(len(slice) == 0, "Slice should be empty but contains %v", slice) // nil ``` [[play](https://go.dev/play/p/vPyh51XpCBt)] ### Must Wraps a function call to panics if second argument is `error` or `false`, returns the value otherwise. ```go val := lo.Must(time.Parse("2006-01-02", "2022-01-15")) // 2022-01-15 val := lo.Must(time.Parse("2006-01-02", "bad-value")) // panics ``` [[play](https://go.dev/play/p/TMoWrRp3DyC)] ### Must{0->6} Must\* has the same behavior as Must, but returns multiple values. ```go func example0() (error) func example1() (int, error) func example2() (int, string, error) func example3() (int, string, time.Date, error) func example4() (int, string, time.Date, bool, error) func example5() (int, string, time.Date, bool, float64, error) func example6() (int, string, time.Date, bool, float64, byte, error) lo.Must0(example0()) val1 := lo.Must1(example1()) // alias to Must val1, val2 := lo.Must2(example2()) val1, val2, val3 := lo.Must3(example3()) val1, val2, val3, val4 := lo.Must4(example4()) val1, val2, val3, val4, val5 := lo.Must5(example5()) val1, val2, val3, val4, val5, val6 := lo.Must6(example6()) ``` You can wrap functions like `func (...) (..., ok bool)`. ```go // math.Signbit(float64) bool lo.Must0(math.Signbit(v)) // bytes.Cut([]byte,[]byte) ([]byte, []byte, bool) before, after := lo.Must2(bytes.Cut(s, sep)) ``` You can give context to the panic message by adding some printf-like arguments. ```go val, ok := lo.Find(myString, func(i string) bool { return i == requiredChar }) 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) ... ``` [[play](https://go.dev/play/p/TMoWrRp3DyC)] ### Try Calls the function and return false in case of error and on panic. ```go ok := lo.Try(func() error { panic("error") return nil }) // false ok := lo.Try(func() error { return nil }) // true ok := lo.Try(func() error { return fmt.Errorf("error") }) // false ``` [[play](https://go.dev/play/p/mTyyWUvn9u4)] ### Try{0->6} The same behavior than `Try`, but callback returns 2 variables. ```go ok := lo.Try2(func() (string, error) { panic("error") return "", nil }) // false ``` [[play](https://go.dev/play/p/mTyyWUvn9u4)] ### TryOr Calls the function and return a default value in case of error and on panic. ```go str, ok := lo.TryOr(func() (string, error) { panic("error") return "hello", nil }, "world") // world // false str, ok := lo.TryOr(func() error { return "hello", nil }, "world") // hello // true str, ok := lo.TryOr(func() error { return "hello", fmt.Errorf("error") }, "world") // world // false ``` [[play](https://go.dev/play/p/B4F7Wg2Zh9X)] ### TryOr{0->6} The same behavior than `TryOr`, but callback returns `X` variables. ```go str, nbr, ok := lo.TryOr2(func() (string, int, error) { panic("error") return "hello", 42, nil }, "world", 21) // world // 21 // false ``` [[play](https://go.dev/play/p/B4F7Wg2Zh9X)] ### TryWithErrorValue The same behavior than `Try`, but also returns value passed to panic. ```go err, ok := lo.TryWithErrorValue(func() error { panic("error") return nil }) // "error", false ``` [[play](https://go.dev/play/p/Kc7afQIT2Fs)] ### TryCatch The same behavior than `Try`, but calls the catch function in case of error. ```go caught := false ok := lo.TryCatch(func() error { panic("error") return nil }, func() { caught = true }) // false // caught == true ``` [[play](https://go.dev/play/p/PnOON-EqBiU)] ### TryCatchWithErrorValue The same behavior than `TryWithErrorValue`, but calls the catch function in case of error. ```go caught := false ok := lo.TryCatchWithErrorValue(func() error { panic("error") return nil }, func(val any) { caught = val == "error" }) // false // caught == true ``` [[play](https://go.dev/play/p/8Pc9gwX_GZO)] ### ErrorsAs A shortcut for: ```go err := doSomething() var rateLimitErr *RateLimitError if ok := errors.As(err, &rateLimitErr); ok { // retry later } ``` 1 line `lo` helper: ```go err := doSomething() if rateLimitErr, ok := lo.ErrorsAs[*RateLimitError](err); ok { // retry later } ``` [[play](https://go.dev/play/p/8wk5rH8UfrE)] ## ๐Ÿ›ฉ Benchmark We executed a simple benchmark with the a dead-simple `lo.Map` loop: See the full implementation [here](./benchmark_test.go). ```go _ = lo.Map[int64](arr, func(x int64, i int) string { return strconv.FormatInt(x, 10) }) ``` **Result:** Here is a comparison between `lo.Map`, `lop.Map`, `go-funk` library and a simple Go `for` loop. ```shell $ go test -benchmem -bench ./... goos: linux goarch: amd64 pkg: github.com/samber/lo cpu: Intel(R) Core(TM) i5-7267U CPU @ 3.10GHz cpu: Intel(R) Core(TM) i7 CPU 920 @ 2.67GHz BenchmarkMap/lo.Map-8 8 132728237 ns/op 39998945 B/op 1000002 allocs/op BenchmarkMap/lop.Map-8 2 503947830 ns/op 119999956 B/op 3000007 allocs/op BenchmarkMap/reflect-8 2 826400560 ns/op 170326512 B/op 4000042 allocs/op BenchmarkMap/for-8 9 126252954 ns/op 39998674 B/op 1000001 allocs/op PASS ok github.com/samber/lo 6.657s ``` - `lo.Map` is way faster (x7) than `go-funk`, a reflection-based Map implementation. - `lo.Map` have the same allocation profile than `for`. - `lo.Map` is 4% slower than `for`. - `lop.Map` is slower than `lo.Map` because it implies more memory allocation and locks. `lop.Map` will be useful for long-running callbacks, such as i/o bound processing. - `for` beats other implementations for memory and CPU. ## ๐Ÿค Contributing - 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. ### With Docker ```bash docker-compose run --rm dev ``` ### Without Docker ```bash # Install some dev dependencies make tools # Run tests make test # or make watch-test ``` ## ๐Ÿ‘ค Contributors ![Contributors](https://contrib.rocks/image?repo=samber/lo) ## ๐Ÿ’ซ Show your support 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) ## ๐Ÿ“ License Copyright ยฉ 2022 [Samuel Berthe](https://github.com/samber). This project is [MIT](./LICENSE) licensed. lo-1.38.1/channel.go000066400000000000000000000205151440622353400141630ustar00rootroot00000000000000package lo import ( "math/rand" "sync" "time" ) type DispatchingStrategy[T any] func(msg T, index uint64, channels []<-chan T) int // ChannelDispatcher distributes messages from input channels into N child channels. // Close events are propagated to children. // Underlying channels can have a fixed buffer capacity or be unbuffered when cap is 0. func ChannelDispatcher[T any](stream <-chan T, count int, channelBufferCap int, strategy DispatchingStrategy[T]) []<-chan T { children := createChannels[T](count, channelBufferCap) roChildren := channelsToReadOnly(children) go func() { // propagate channel closing to children defer closeChannels(children) var i uint64 = 0 for { msg, ok := <-stream if !ok { return } destination := strategy(msg, i, roChildren) % count children[destination] <- msg i++ } }() return roChildren } func createChannels[T any](count int, channelBufferCap int) []chan T { children := make([]chan T, 0, count) for i := 0; i < count; i++ { children = append(children, make(chan T, channelBufferCap)) } return children } func channelsToReadOnly[T any](children []chan T) []<-chan T { roChildren := make([]<-chan T, 0, len(children)) for i := range children { roChildren = append(roChildren, children[i]) } return roChildren } func closeChannels[T any](children []chan T) { for i := 0; i < len(children); i++ { close(children[i]) } } func channelIsNotFull[T any](ch <-chan T) bool { return cap(ch) == 0 || len(ch) < cap(ch) } // DispatchingStrategyRoundRobin distributes messages in a rotating sequential manner. // If the channel capacity is exceeded, the next channel will be selected and so on. func DispatchingStrategyRoundRobin[T any](msg T, index uint64, channels []<-chan T) int { for { i := int(index % uint64(len(channels))) if channelIsNotFull(channels[i]) { return i } index++ time.Sleep(10 * time.Microsecond) // prevent CPU from burning ๐Ÿ”ฅ } } // DispatchingStrategyRandom distributes messages in a random manner. // 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 { i := rand.Intn(len(channels)) if channelIsNotFull(channels[i]) { return i } time.Sleep(10 * time.Microsecond) // prevent CPU from burning ๐Ÿ”ฅ } } // DispatchingStrategyWeightedRandom distributes messages in a weighted manner. // If the channel capacity is exceeded, another random channel will be selected and so on. func DispatchingStrategyWeightedRandom[T any](weights []int) DispatchingStrategy[T] { seq := []int{} for i := 0; i < len(weights); i++ { for j := 0; j < weights[i]; j++ { seq = append(seq, i) } } return func(msg T, index uint64, channels []<-chan T) int { for { i := seq[rand.Intn(len(seq))] if channelIsNotFull(channels[i]) { return i } time.Sleep(10 * time.Microsecond) // prevent CPU from burning ๐Ÿ”ฅ } } } // DispatchingStrategyFirst distributes messages in the first non-full channel. // If the capacity of the first channel is exceeded, the second channel will be selected and so on. func DispatchingStrategyFirst[T any](msg T, index uint64, channels []<-chan T) int { for { for i := range channels { if channelIsNotFull(channels[i]) { return i } } time.Sleep(10 * time.Microsecond) // prevent CPU from burning ๐Ÿ”ฅ } } // DispatchingStrategyLeast distributes messages in the emptiest channel. func DispatchingStrategyLeast[T any](msg T, index uint64, channels []<-chan T) int { seq := Range(len(channels)) return MinBy(seq, func(item int, min int) bool { return len(channels[item]) < len(channels[min]) }) } // DispatchingStrategyMost distributes messages in the fullest channel. // If the channel capacity is exceeded, the next channel will be selected and so on. func DispatchingStrategyMost[T any](msg T, index uint64, channels []<-chan T) int { seq := Range(len(channels)) return MaxBy(seq, func(item int, max int) bool { return len(channels[item]) > len(channels[max]) && channelIsNotFull(channels[item]) }) } // SliceToChannel returns a read-only channels of collection elements. func SliceToChannel[T any](bufferSize int, collection []T) <-chan T { ch := make(chan T, bufferSize) go func() { for _, item := range collection { ch <- item } close(ch) }() return ch } // ChannelToSlice returns a slice built from channels items. Blocks until channel closes. func ChannelToSlice[T any](ch <-chan T) []T { collection := []T{} for item := range ch { collection = append(collection, item) } return collection } // Generator implements the generator design pattern. func Generator[T any](bufferSize int, generator func(yield func(T))) <-chan T { ch := make(chan T, bufferSize) go func() { // WARNING: infinite loop generator(func(t T) { ch <- t }) close(ch) }() return ch } // Buffer creates a slice of n elements from a channel. Returns the slice and the slice length. // @TODO: we should probably provide an helper that reuse the same buffer. func Buffer[T any](ch <-chan T, size int) (collection []T, length int, readTime time.Duration, ok bool) { buffer := make([]T, 0, size) index := 0 now := time.Now() for ; index < size; index++ { item, ok := <-ch if !ok { return buffer, index, time.Since(now), false } buffer = append(buffer, item) } return buffer, index, time.Since(now), true } // Batch creates a slice of n elements from a channel. Returns the slice and the slice length. // // Deprecated: Use [Buffer] instead. func Batch[T any](ch <-chan T, size int) (collection []T, length int, readTime time.Duration, ok bool) { return Buffer(ch, size) } // BufferWithTimeout creates a slice of n elements from a channel, with timeout. Returns the slice and the slice length. // @TODO: we should probably provide an helper that reuse the same buffer. func BufferWithTimeout[T any](ch <-chan T, size int, timeout time.Duration) (collection []T, length int, readTime time.Duration, ok bool) { expire := time.NewTimer(timeout) defer expire.Stop() buffer := make([]T, 0, size) index := 0 now := time.Now() for ; index < size; index++ { select { case item, ok := <-ch: if !ok { return buffer, index, time.Since(now), false } buffer = append(buffer, item) case <-expire.C: return buffer, index, time.Since(now), true } } return buffer, index, time.Since(now), true } // BatchWithTimeout creates a slice of n elements from a channel, with timeout. Returns the slice and the slice length. // // Deprecated: Use [BufferWithTimeout] instead. func BatchWithTimeout[T any](ch <-chan T, size int, timeout time.Duration) (collection []T, length int, readTime time.Duration, ok bool) { return BufferWithTimeout(ch, size, timeout) } // FanIn collects messages from multiple input channels into a single buffered channel. // Output messages has no priority. When all upstream channels reach EOF, downstream channel closes. func FanIn[T any](channelBufferCap int, upstreams ...<-chan T) <-chan T { out := make(chan T, channelBufferCap) var wg sync.WaitGroup // 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 { out <- n } wg.Done() }(c) } // Start a goroutine to close out once all the output goroutines are done. go func() { wg.Wait() close(out) }() return out } // ChannelMerge collects messages from multiple input channels into a single buffered channel. // Output messages has no priority. When all upstream channels reach EOF, downstream channel closes. // // Deprecated: Use [FanIn] instead. func ChannelMerge[T any](channelBufferCap int, upstreams ...<-chan T) <-chan T { return FanIn(channelBufferCap, upstreams...) } // FanOut broadcasts all the upstream messages to multiple downstream channels. // When upstream channel reach EOF, downstream channels close. If any downstream // channels is full, broadcasting is paused. func FanOut[T any](count int, channelsBufferCap int, upstream <-chan T) []<-chan T { downstreams := createChannels[T](count, channelsBufferCap) go func() { for msg := range upstream { for i := range downstreams { downstreams[i] <- msg } } // Close out once all the output goroutines are done. for i := range downstreams { close(downstreams[i]) } }() return channelsToReadOnly(downstreams) } lo-1.38.1/concurrency.go000066400000000000000000000044741440622353400151130ustar00rootroot00000000000000package lo import "sync" type synchronize struct { locker sync.Locker } func (s *synchronize) Do(cb func()) { s.locker.Lock() Try0(cb) s.locker.Unlock() } // Synchronize wraps the underlying callback in a mutex. It receives an optional mutex. func Synchronize(opt ...sync.Locker) *synchronize { if len(opt) > 1 { panic("unexpected arguments") } else if len(opt) == 0 { opt = append(opt, &sync.Mutex{}) } return &synchronize{ locker: opt[0], } } // Async executes a function in a goroutine and returns the result in a channel. func Async[A any](f func() A) <-chan A { ch := make(chan A, 1) go func() { ch <- f() }() return ch } // Async0 executes a function in a goroutine and returns a channel set once the function finishes. func Async0(f func()) <-chan struct{} { ch := make(chan struct{}, 1) go func() { f() ch <- struct{}{} }() return ch } // Async1 is an alias to Async. func Async1[A any](f func() A) <-chan A { return Async(f) } // Async2 has the same behavior as Async, but returns the 2 results as a tuple inside the channel. func Async2[A any, B any](f func() (A, B)) <-chan Tuple2[A, B] { ch := make(chan Tuple2[A, B], 1) go func() { ch <- T2(f()) }() return ch } // 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] { ch := make(chan Tuple3[A, B, C], 1) go func() { ch <- T3(f()) }() return ch } // 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] { ch := make(chan Tuple4[A, B, C, D], 1) go func() { ch <- T4(f()) }() return ch } // 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] { ch := make(chan Tuple5[A, B, C, D, E], 1) go func() { ch <- T5(f()) }() return ch } // 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] { ch := make(chan Tuple6[A, B, C, D, E, F], 1) go func() { ch <- T6(f()) }() return ch } lo-1.38.1/condition.go000066400000000000000000000052671440622353400145500ustar00rootroot00000000000000package lo // Ternary is a 1 line if/else statement. // Play: https://go.dev/play/p/t-D7WBL44h2 func Ternary[T any](condition bool, ifOutput T, elseOutput T) T { if condition { return ifOutput } return elseOutput } // TernaryF is a 1 line if/else statement whose options are functions // Play: https://go.dev/play/p/AO4VW20JoqM func TernaryF[T any](condition bool, ifFunc func() T, elseFunc func() T) T { if condition { return ifFunc() } return elseFunc() } type ifElse[T any] struct { result T done bool } // If. // Play: https://go.dev/play/p/WSw3ApMxhyW func If[T any](condition bool, result T) *ifElse[T] { if condition { return &ifElse[T]{result, true} } var t T return &ifElse[T]{t, false} } // IfF. // Play: https://go.dev/play/p/WSw3ApMxhyW func IfF[T any](condition bool, resultF func() T) *ifElse[T] { if condition { return &ifElse[T]{resultF(), true} } var t T return &ifElse[T]{t, false} } // ElseIf. // Play: https://go.dev/play/p/WSw3ApMxhyW func (i *ifElse[T]) ElseIf(condition bool, result T) *ifElse[T] { if !i.done && condition { i.result = result i.done = true } return i } // ElseIfF. // Play: https://go.dev/play/p/WSw3ApMxhyW func (i *ifElse[T]) ElseIfF(condition bool, resultF func() T) *ifElse[T] { if !i.done && condition { i.result = resultF() i.done = true } return i } // Else. // Play: https://go.dev/play/p/WSw3ApMxhyW func (i *ifElse[T]) Else(result T) T { if i.done { return i.result } return result } // ElseF. // Play: https://go.dev/play/p/WSw3ApMxhyW func (i *ifElse[T]) ElseF(resultF func() T) T { if i.done { return i.result } return resultF() } type switchCase[T comparable, R any] struct { predicate T result R done bool } // Switch is a pure functional switch/case/default statement. // Play: https://go.dev/play/p/TGbKUMAeRUd func Switch[T comparable, R any](predicate T) *switchCase[T, R] { var result R return &switchCase[T, R]{ predicate, result, false, } } // Case. // Play: https://go.dev/play/p/TGbKUMAeRUd func (s *switchCase[T, R]) Case(val T, result R) *switchCase[T, R] { if !s.done && s.predicate == val { s.result = result s.done = true } return s } // CaseF. // Play: https://go.dev/play/p/TGbKUMAeRUd func (s *switchCase[T, R]) CaseF(val T, cb func() R) *switchCase[T, R] { if !s.done && s.predicate == val { s.result = cb() s.done = true } return s } // Default. // Play: https://go.dev/play/p/TGbKUMAeRUd func (s *switchCase[T, R]) Default(result R) R { if !s.done { s.result = result } return s.result } // DefaultF. // Play: https://go.dev/play/p/TGbKUMAeRUd func (s *switchCase[T, R]) DefaultF(cb func() R) R { if !s.done { s.result = cb() } return s.result } lo-1.38.1/constraints.go000066400000000000000000000001751440622353400151220ustar00rootroot00000000000000package lo // Clonable defines a constraint of types having Clone() T method. type Clonable[T any] interface { Clone() T } lo-1.38.1/errors.go000066400000000000000000000224361440622353400140730ustar00rootroot00000000000000package lo import ( "errors" "fmt" "reflect" ) // Validate is a helper that creates an error when a condition is not met. // Play: https://go.dev/play/p/vPyh51XpCBt func Validate(ok bool, format string, args ...any) error { if !ok { return fmt.Errorf(fmt.Sprintf(format, args...)) } return nil } func messageFromMsgAndArgs(msgAndArgs ...interface{}) string { if len(msgAndArgs) == 1 { if msgAsStr, ok := msgAndArgs[0].(string); ok { return msgAsStr } return fmt.Sprintf("%+v", msgAndArgs[0]) } if len(msgAndArgs) > 1 { return fmt.Sprintf(msgAndArgs[0].(string), msgAndArgs[1:]...) } return "" } // must panics if err is error or false. func must(err any, messageArgs ...interface{}) { if err == nil { return } switch e := err.(type) { case bool: if !e { message := messageFromMsgAndArgs(messageArgs...) if message == "" { message = "not ok" } panic(message) } case error: message := messageFromMsgAndArgs(messageArgs...) if message != "" { panic(message + ": " + e.Error()) } else { panic(e.Error()) } default: panic("must: invalid err type '" + reflect.TypeOf(err).Name() + "', should either be a bool or an error") } } // Must is a helper that wraps a call to a function returning a value and an error // and panics if err is error or false. // Play: https://go.dev/play/p/TMoWrRp3DyC func Must[T any](val T, err any, messageArgs ...interface{}) T { must(err, messageArgs...) return val } // Must0 has the same behavior as Must, but callback returns no variable. // Play: https://go.dev/play/p/TMoWrRp3DyC func Must0(err any, messageArgs ...interface{}) { must(err, messageArgs...) } // Must1 is an alias to Must // Play: https://go.dev/play/p/TMoWrRp3DyC func Must1[T any](val T, err any, messageArgs ...interface{}) T { return Must(val, err, messageArgs...) } // 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) { 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) { 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) { 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) { 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) { must(err, messageArgs...) return val1, val2, val3, val4, val5, val6 } // Try calls the function and return false in case of error. func Try(callback func() error) (ok bool) { ok = true defer func() { if r := recover(); r != nil { ok = false } }() err := callback() if err != nil { ok = false } return } // Try0 has the same behavior as Try, but callback returns no variable. // Play: https://go.dev/play/p/mTyyWUvn9u4 func Try0(callback func()) bool { return Try(func() error { callback() return nil }) } // Try1 is an alias to Try. // Play: https://go.dev/play/p/mTyyWUvn9u4 func Try1(callback func() error) bool { return Try(callback) } // Try2 has the same behavior as Try, but callback returns 2 variables. // Play: https://go.dev/play/p/mTyyWUvn9u4 func Try2[T any](callback func() (T, error)) bool { return Try(func() error { _, err := callback() return err }) } // Try3 has the same behavior as Try, but callback returns 3 variables. // Play: https://go.dev/play/p/mTyyWUvn9u4 func Try3[T, R any](callback func() (T, R, error)) bool { return Try(func() error { _, _, err := callback() return err }) } // Try4 has the same behavior as Try, but callback returns 4 variables. // Play: https://go.dev/play/p/mTyyWUvn9u4 func Try4[T, R, S any](callback func() (T, R, S, error)) bool { return Try(func() error { _, _, _, err := callback() return err }) } // Try5 has the same behavior as Try, but callback returns 5 variables. // Play: https://go.dev/play/p/mTyyWUvn9u4 func Try5[T, R, S, Q any](callback func() (T, R, S, Q, error)) bool { return Try(func() error { _, _, _, _, err := callback() return err }) } // Try6 has the same behavior as Try, but callback returns 6 variables. // Play: https://go.dev/play/p/mTyyWUvn9u4 func Try6[T, R, S, Q, U any](callback func() (T, R, S, Q, U, error)) bool { return Try(func() error { _, _, _, _, _, err := callback() return err }) } // TryOr has the same behavior as Must, but returns a default value in case of error. // Play: https://go.dev/play/p/B4F7Wg2Zh9X func TryOr[A any](callback func() (A, error), fallbackA A) (A, bool) { return TryOr1(callback, fallbackA) } // TryOr1 has the same behavior as Must, but returns a default value in case of error. // Play: https://go.dev/play/p/B4F7Wg2Zh9X func TryOr1[A any](callback func() (A, error), fallbackA A) (A, bool) { ok := false Try0(func() { a, err := callback() if err == nil { fallbackA = a ok = true } }) return fallbackA, ok } // 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) { ok := false Try0(func() { a, b, err := callback() if err == nil { fallbackA = a fallbackB = b ok = true } }) return fallbackA, fallbackB, ok } // 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) { ok := false Try0(func() { a, b, c, err := callback() if err == nil { fallbackA = a fallbackB = b fallbackC = c ok = true } }) return fallbackA, fallbackB, fallbackC, ok } // 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) { ok := false Try0(func() { a, b, c, d, err := callback() if err == nil { fallbackA = a fallbackB = b fallbackC = c fallbackD = d ok = true } }) return fallbackA, fallbackB, fallbackC, fallbackD, ok } // 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) { ok := false Try0(func() { a, b, c, d, e, err := callback() if err == nil { fallbackA = a fallbackB = b fallbackC = c fallbackD = d fallbackE = e ok = true } }) return fallbackA, fallbackB, fallbackC, fallbackD, fallbackE, ok } // 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) { ok := false Try0(func() { a, b, c, d, e, f, err := callback() if err == nil { fallbackA = a fallbackB = b fallbackC = c fallbackD = d fallbackE = e fallbackF = f ok = true } }) return fallbackA, fallbackB, fallbackC, fallbackD, fallbackE, fallbackF, ok } // TryWithErrorValue has the same behavior as Try, but also returns value passed to panic. // Play: https://go.dev/play/p/Kc7afQIT2Fs func TryWithErrorValue(callback func() error) (errorValue any, ok bool) { ok = true defer func() { if r := recover(); r != nil { ok = false errorValue = r } }() err := callback() if err != nil { ok = false errorValue = err } return } // TryCatch has the same behavior as Try, but calls the catch function in case of error. // Play: https://go.dev/play/p/PnOON-EqBiU func TryCatch(callback func() error, catch func()) { if !Try(callback) { catch() } } // TryCatchWithErrorValue has the same behavior as TryWithErrorValue, but calls the catch function in case of error. // Play: https://go.dev/play/p/8Pc9gwX_GZO func TryCatchWithErrorValue(callback func() error, catch func(any)) { if err, ok := TryWithErrorValue(callback); !ok { catch(err) } } // ErrorsAs is a shortcut for errors.As(err, &&T). // Play: https://go.dev/play/p/8wk5rH8UfrE func ErrorsAs[T error](err error) (T, bool) { var t T ok := errors.As(err, &t) return t, ok } lo-1.38.1/find.go000066400000000000000000000212441440622353400134730ustar00rootroot00000000000000package lo import ( "fmt" "math/rand" "golang.org/x/exp/constraints" ) // import "golang.org/x/exp/constraints" // 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 { return i } } return -1 } // LastIndexOf returns the index at which the last occurrence of a value is found in an array or return -1 // if the value cannot be found. func LastIndexOf[T comparable](collection []T, element T) int { length := len(collection) for i := length - 1; i >= 0; i-- { if collection[i] == element { return i } } return -1 } // 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 } } var result T return result, false } // 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 } } var result T return result, -1, false } // FindLastIndexOf searches last 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 FindLastIndexOf[T any](collection []T, predicate func(item T) bool) (T, int, bool) { length := len(collection) for i := length - 1; i >= 0; i-- { if predicate(collection[i]) { return collection[i], i, true } } var result T return result, -1, false } // 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 } } return fallback } // 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 { return k, true } } return Empty[K](), false } // 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) { return k, true } } return Empty[K](), false } // FindUniques returns a slice with all the unique elements of the collection. // The order of result values is determined by the order they occur in the collection. func FindUniques[T comparable](collection []T) []T { isDupl := make(map[T]bool, len(collection)) for _, item := range collection { duplicated, ok := isDupl[item] if !ok { isDupl[item] = false } else if !duplicated { isDupl[item] = true } } result := make([]T, 0, len(collection)-len(isDupl)) for _, item := range collection { if duplicated := isDupl[item]; !duplicated { result = append(result, item) } } return result } // FindUniquesBy returns a slice with all the unique elements of the collection. // The order of result values is determined by the order they occur in the array. It accepts `iteratee` which is // invoked for each element in array to generate the criterion by which uniqueness is computed. 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) duplicated, ok := isDupl[key] if !ok { isDupl[key] = false } else if !duplicated { isDupl[key] = true } } result := make([]T, 0, len(collection)-len(isDupl)) for _, item := range collection { key := iteratee(item) if duplicated := isDupl[key]; !duplicated { result = append(result, item) } } return result } // FindDuplicates returns a slice with the first occurrence of each duplicated elements of the collection. // The order of result values is determined by the order they occur in the collection. func FindDuplicates[T comparable](collection []T) []T { isDupl := make(map[T]bool, len(collection)) for _, item := range collection { duplicated, ok := isDupl[item] if !ok { isDupl[item] = false } else if !duplicated { isDupl[item] = 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 } } return result } // FindDuplicatesBy returns a slice with the first occurrence of each duplicated elements of the collection. // The order of result values is determined by the order they occur in the array. It accepts `iteratee` which is // invoked for each element in array to generate the criterion by which uniqueness is computed. 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) duplicated, ok := isDupl[key] if !ok { isDupl[key] = false } else if !duplicated { isDupl[key] = true } } result := make([]T, 0, len(collection)-len(isDupl)) for _, item := range collection { key := iteratee(item) if duplicated := isDupl[key]; duplicated { result = append(result, item) isDupl[key] = false } } return result } // Min search the minimum value of a collection. // Returns zero value when collection is empty. func Min[T constraints.Ordered](collection []T) T { var min T if len(collection) == 0 { return min } min = collection[0] for i := 1; i < len(collection); i++ { item := collection[i] if item < min { min = item } } return min } // 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. func MinBy[T any](collection []T, comparison func(a T, b T) bool) T { var min T if len(collection) == 0 { return min } min = collection[0] for i := 1; i < len(collection); i++ { item := collection[i] if comparison(item, min) { min = item } } return min } // Max searches the maximum value of a collection. // Returns zero value when collection is empty. func Max[T constraints.Ordered](collection []T) T { var max T if len(collection) == 0 { return max } max = collection[0] for i := 1; i < len(collection); i++ { item := collection[i] if item > max { max = item } } return max } // 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. func MaxBy[T any](collection []T, comparison func(a T, b T) bool) T { var max T if len(collection) == 0 { return max } max = collection[0] for i := 1; i < len(collection); i++ { item := collection[i] if comparison(item, max) { max = item } } return max } // Last returns the last element of a collection or error if empty. func Last[T any](collection []T) (T, error) { length := len(collection) if length == 0 { var t T return t, fmt.Errorf("last: cannot extract the last element of an empty slice") } return collection[length-1], nil } // Nth returns the element at index `nth` of collection. If `nth` is negative, the nth element // from the end is returned. An error is returned when nth is out of slice bounds. func Nth[T any, N constraints.Integer](collection []T, nth N) (T, error) { n := int(nth) l := len(collection) if n >= l || -n > l { var t T return t, fmt.Errorf("nth: %d out of slice bounds", n) } if n >= 0 { return collection[n], nil } return collection[l+n], nil } // Sample returns a random item from collection. func Sample[T any](collection []T) T { size := len(collection) if size == 0 { return Empty[T]() } return collection[rand.Intn(size)] } // Samples returns N random unique items from collection. func Samples[T any](collection []T, count int) []T { size := len(collection) copy := append([]T{}, collection...) results := []T{} for i := 0; i < size && i < count; i++ { copyLength := size - i index := rand.Intn(size - i) results = append(results, copy[index]) // Removes element. // It is faster to swap with last element and remove it. copy[index] = copy[copyLength-1] copy = copy[:copyLength-1] } return results } lo-1.38.1/func.go000066400000000000000000000030171440622353400135040ustar00rootroot00000000000000package lo // Partial returns new function that, when called, has its first argument set to the provided value. func Partial[T1, T2, R any](f func(a T1, b T2) R, arg1 T1) func(T2) R { return func(t2 T2) R { return f(arg1, t2) } } // Partial1 returns new function that, when called, has its first argument set to the provided value. func Partial1[T1, T2, R any](f func(T1, T2) R, arg1 T1) func(T2) R { return Partial(f, arg1) } // Partial2 returns new function that, when called, has its first argument set to the provided value. func Partial2[T1, T2, T3, R any](f func(T1, T2, T3) R, arg1 T1) func(T2, T3) R { return func(t2 T2, t3 T3) R { return f(arg1, t2, t3) } } // Partial3 returns new function that, when called, has its first argument set to the provided value. func Partial3[T1, T2, T3, T4, R any](f func(T1, T2, T3, T4) R, arg1 T1) func(T2, T3, T4) R { return func(t2 T2, t3 T3, t4 T4) R { return f(arg1, t2, t3, t4) } } // Partial4 returns new function that, when called, has its first argument set to the provided value. func Partial4[T1, T2, T3, T4, T5, R any](f func(T1, T2, T3, T4, T5) R, arg1 T1) func(T2, T3, T4, T5) R { return func(t2 T2, t3 T3, t4 T4, t5 T5) R { return f(arg1, t2, t3, t4, t5) } } // Partial5 returns new function that, when called, has its first argument set to the provided value func Partial5[T1, T2, T3, T4, T5, T6, R any](f func(T1, T2, T3, T4, T5, T6) R, arg1 T1) func(T2, T3, T4, T5, T6) R { return func(t2 T2, t3 T3, t4 T4, t5 T5, t6 T6) R { return f(arg1, t2, t3, t4, t5, t6) } } lo-1.38.1/go.mod000066400000000000000000000002461440622353400133310ustar00rootroot00000000000000module github.com/samber/lo go 1.18 // // Dependencies are excluded from releases. Please check CI. // require golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 lo-1.38.1/go.sum000066400000000000000000000003171440622353400133550ustar00rootroot00000000000000golang.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= lo-1.38.1/intersect.go000066400000000000000000000101511440622353400145460ustar00rootroot00000000000000package 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 { return true } } return false } // 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) { return true } } return false } // 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) { return false } } return true } // 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) { return false } } return true } // 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) { return true } } return false } // 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) { return true } } return false } // 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) { return false } } return true } // 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) { return false } } return true } // Intersect returns the intersection between two collections. func Intersect[T comparable](list1 []T, list2 []T) []T { result := []T{} seen := map[T]struct{}{} for _, elem := range list1 { seen[elem] = struct{}{} } for _, elem := range list2 { if _, ok := seen[elem]; ok { result = append(result, elem) } } return result } // Difference returns the difference between two collections. // The first value is the collection of element absent of list2. // The second value is the collection of element absent of list1. func Difference[T comparable](list1 []T, list2 []T) ([]T, []T) { left := []T{} right := []T{} seenLeft := map[T]struct{}{} seenRight := map[T]struct{}{} for _, elem := range list1 { seenLeft[elem] = struct{}{} } for _, elem := range list2 { seenRight[elem] = struct{}{} } for _, elem := range list1 { if _, ok := seenRight[elem]; !ok { left = append(left, elem) } } for _, elem := range list2 { if _, ok := seenLeft[elem]; !ok { right = append(right, elem) } } return left, right } // 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{}{} for _, list := range lists { for _, e := range list { if _, ok := seen[e]; !ok { seen[e] = struct{}{} result = append(result, e) } } } return result } // 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) } } return result } // WithoutEmpty returns slice excluding empty values. 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) } } return result } lo-1.38.1/map.go000066400000000000000000000126011440622353400133250ustar00rootroot00000000000000package lo // Keys creates an array of the map keys. // Play: https://go.dev/play/p/Uu11fHASqrU func Keys[K comparable, V any](in map[K]V) []K { result := make([]K, 0, len(in)) for k := range in { result = append(result, k) } return result } // 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) } return result } // ValueOr returns the value of the given key or the fallback value if the key is not present. // Play: https://go.dev/play/p/bAq9mHErB4V func ValueOr[K comparable, V any](in map[K]V, key K, fallback V) V { if v, ok := in[key]; ok { return v } return fallback } // PickBy returns same map type filtered by given predicate. // 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 } } return r } // PickByKeys returns same map type filtered by given keys. // 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 } } return r } // PickByValues returns same map type filtered by given values. // 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 } } return r } // OmitBy returns same map type filtered by given predicate. // 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 } } return r } // OmitByKeys returns same map type filtered by given keys. // 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 } } return r } // OmitByValues returns same map type filtered by given values. // 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 } } return r } // Entries transforms a map into array of key/value pairs. // Play: 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 { entries = append(entries, Entry[K, V]{ Key: k, Value: v, }) } return entries } // ToPairs transforms a map into array of key/value pairs. // Alias of Entries(). // Play: https://go.dev/play/p/3Dhgx46gawJ func ToPairs[K comparable, V any](in map[K]V) []Entry[K, V] { return Entries(in) } // FromEntries transforms an array of key/value pairs into a map. // Play: https://go.dev/play/p/oIr5KHFGCEN 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 } return out } // FromPairs transforms an array of key/value pairs into a map. // Alias of FromEntries(). // Play: https://go.dev/play/p/oIr5KHFGCEN func FromPairs[K comparable, V any](entries []Entry[K, V]) map[K]V { return FromEntries(entries) } // Invert creates a map composed of the inverted keys and values. If map // contains duplicate values, subsequent values overwrite property assignments // of previous values. // Play: https://go.dev/play/p/rFQ4rak6iA1 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 } return out } // Assign merges multiple maps from left to right. // Play: https://go.dev/play/p/VhwfJOyxf5o 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 } } return out } // MapKeys manipulates a map keys and transforms it to a map of another type. // Play: https://go.dev/play/p/9_4WPIqOetJ 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 } return result } // MapValues manipulates a map values and transforms it to a map of another type. // Play: https://go.dev/play/p/T_8xAfvcf0W 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) } return result } // MapEntries manipulates a map entries and transforms it to a map of another type. // Play: https://go.dev/play/p/VuvNQzxKimT func MapEntries[K1 comparable, V1 any, K2 comparable, V2 any](in map[K1]V1, iteratee func(key K1, value V1) (K2, V2)) map[K2]V2 { result := make(map[K2]V2, len(in)) for k1, v1 := range in { k2, v2 := iteratee(k1, v1) result[k2] = v2 } return result } // MapToSlice transforms a map into a slice based on specific iteratee // Play: https://go.dev/play/p/ZuiCZpDt6LD 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)) } return result } lo-1.38.1/math.go000066400000000000000000000046461440622353400135130ustar00rootroot00000000000000package lo import "golang.org/x/exp/constraints" // Range creates an array of numbers (positive and/or negative) with given length. // Play: https://go.dev/play/p/0r6VimXAi9H func Range(elementNum int) []int { length := If(elementNum < 0, -elementNum).Else(elementNum) result := make([]int, length) step := If(elementNum < 0, -1).Else(1) for i, j := 0, 0; i < length; i, j = i+1, j+step { result[i] = j } return result } // RangeFrom creates an array of numbers from start with specified length. // Play: https://go.dev/play/p/0r6VimXAi9H func RangeFrom[T constraints.Integer | constraints.Float](start T, elementNum int) []T { length := If(elementNum < 0, -elementNum).Else(elementNum) result := make([]T, length) step := If(elementNum < 0, -1).Else(1) for i, j := 0, start; i < length; i, j = i+1, j+T(step) { result[i] = j } return result } // RangeWithSteps creates an array of numbers (positive and/or negative) progressing from start up to, but not including end. // step set to zero will return empty array. // Play: https://go.dev/play/p/0r6VimXAi9H func RangeWithSteps[T constraints.Integer | constraints.Float](start, end, step T) []T { result := []T{} if start == end || step == 0 { return result } if start < end { if step < 0 { return result } for i := start; i < end; i += step { result = append(result, i) } return result } if step > 0 { return result } for i := start; i > end; i += step { result = append(result, i) } return result } // Clamp clamps number within the inclusive lower and upper bounds. // Play: https://go.dev/play/p/RU4lJNC2hlI func Clamp[T constraints.Ordered](value T, min T, max T) T { if value < min { return min } else if value > max { return max } return value } // Sum sums the values in a collection. If collection is empty 0 is returned. // 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 } return sum } // SumBy summarizes the values in a collection using the given return value from the iteration function. If collection is empty 0 is returned. // 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) } return sum } lo-1.38.1/parallel/000077500000000000000000000000001440622353400140155ustar00rootroot00000000000000lo-1.38.1/parallel/slice.go000066400000000000000000000053061440622353400154470ustar00rootroot00000000000000package parallel import "sync" // Map manipulates a slice and transforms it to a slice of another type. // `iteratee` is call in parallel. Result keep the same order. func Map[T any, R any](collection []T, iteratee func(item T, index int) R) []R { result := make([]R, len(collection)) var wg sync.WaitGroup wg.Add(len(collection)) for i, item := range collection { go func(_item T, _i int) { res := iteratee(_item, _i) result[_i] = res wg.Done() }(item, i) } wg.Wait() return result } // ForEach iterates over elements of collection and invokes iteratee for each element. // `iteratee` is call in parallel. func ForEach[T any](collection []T, iteratee func(item T, index int)) { var wg sync.WaitGroup wg.Add(len(collection)) for i, item := range collection { go func(_item T, _i int) { iteratee(_item, _i) wg.Done() }(item, i) } wg.Wait() } // Times invokes the iteratee n times, returning an array of the results of each invocation. // The iteratee is invoked with index as argument. // `iteratee` is call in parallel. func Times[T any](count int, iteratee func(index int) T) []T { result := make([]T, count) var wg sync.WaitGroup wg.Add(count) for i := 0; i < count; i++ { go func(_i int) { item := iteratee(_i) result[_i] = item wg.Done() }(i) } wg.Wait() return result } // GroupBy returns an object composed of keys generated from the results of running each element of collection through iteratee. // `iteratee` is call in parallel. func GroupBy[T any, U comparable](collection []T, iteratee func(item T) U) map[U][]T { result := map[U][]T{} var mu sync.Mutex var wg sync.WaitGroup wg.Add(len(collection)) for _, item := range collection { go func(_item T) { key := iteratee(_item) mu.Lock() result[key] = append(result[key], _item) mu.Unlock() wg.Done() }(item) } wg.Wait() return result } // PartitionBy returns an array of elements split into groups. The order of grouped values is // determined by the order they occur in collection. The grouping is generated from the results // of running each element of collection through iteratee. // `iteratee` is call in parallel. func PartitionBy[T any, K comparable](collection []T, iteratee func(item T) K) [][]T { result := [][]T{} seen := map[K]int{} var mu sync.Mutex var wg sync.WaitGroup wg.Add(len(collection)) for _, item := range collection { go func(_item T) { key := iteratee(_item) mu.Lock() resultIndex, ok := seen[key] if !ok { resultIndex = len(result) seen[key] = resultIndex result = append(result, []T{}) } result[resultIndex] = append(result[resultIndex], _item) mu.Unlock() wg.Done() }(item) } wg.Wait() return result } lo-1.38.1/retry.go000066400000000000000000000150201440622353400137130ustar00rootroot00000000000000package lo import ( "sync" "time" ) type debounce struct { after time.Duration mu *sync.Mutex timer *time.Timer done bool callbacks []func() } func (d *debounce) reset() { d.mu.Lock() defer d.mu.Unlock() if d.done { return } if d.timer != nil { d.timer.Stop() } d.timer = time.AfterFunc(d.after, func() { for _, f := range d.callbacks { f() } }) } func (d *debounce) cancel() { d.mu.Lock() defer d.mu.Unlock() if d.timer != nil { d.timer.Stop() d.timer = nil } d.done = true } // NewDebounce creates a debounced instance that delays invoking functions given until after wait milliseconds have elapsed. // Play: https://go.dev/play/p/mz32VMK2nqe func NewDebounce(duration time.Duration, f ...func()) (func(), func()) { d := &debounce{ after: duration, mu: new(sync.Mutex), timer: nil, done: false, callbacks: f, } return func() { d.reset() }, d.cancel } type debounceByItem struct { mu *sync.Mutex timer *time.Timer count int } type debounceBy[T comparable] struct { after time.Duration mu *sync.Mutex items map[T]*debounceByItem callbacks []func(key T, count int) } func (d *debounceBy[T]) reset(key T) { d.mu.Lock() if _, ok := d.items[key]; !ok { d.items[key] = &debounceByItem{ mu: new(sync.Mutex), timer: nil, } } item := d.items[key] d.mu.Unlock() item.mu.Lock() defer item.mu.Unlock() item.count++ if item.timer != nil { item.timer.Stop() } item.timer = time.AfterFunc(d.after, func() { item.mu.Lock() count := item.count item.count = 0 item.mu.Unlock() for _, f := range d.callbacks { f(key, count) } }) } func (d *debounceBy[T]) cancel(key T) { d.mu.Lock() defer d.mu.Unlock() if item, ok := d.items[key]; ok { item.mu.Lock() if item.timer != nil { item.timer.Stop() item.timer = nil } item.mu.Unlock() delete(d.items, key) } } // NewDebounceBy creates a debounced instance for each distinct key, that delays invoking functions given until after wait milliseconds have elapsed. // Play: https://go.dev/play/p/d3Vpt6pxhY8 func NewDebounceBy[T comparable](duration time.Duration, f ...func(key T, count int)) (func(key T), func(key T)) { d := &debounceBy[T]{ after: duration, mu: new(sync.Mutex), items: map[T]*debounceByItem{}, callbacks: f, } return func(key T) { d.reset(key) }, d.cancel } // Attempt invokes a function N times until it returns valid output. Returning either the caught error or nil. When first argument is less than `1`, the function runs until a successful response is returned. // Play: https://go.dev/play/p/3ggJZ2ZKcMj func Attempt(maxIteration int, f func(index int) error) (int, error) { var err error for i := 0; maxIteration <= 0 || i < maxIteration; i++ { // for retries >= 0 { err = f(i) if err == nil { return i + 1, nil } } return maxIteration, err } // AttemptWithDelay invokes a function N times until it returns valid output, // with a pause between each call. Returning either the caught error or nil. // When first argument is less than `1`, the function runs until a successful // response is returned. // Play: https://go.dev/play/p/tVs6CygC7m1 func AttemptWithDelay(maxIteration int, delay time.Duration, f func(index int, duration time.Duration) error) (int, time.Duration, error) { var err error start := time.Now() for i := 0; maxIteration <= 0 || i < maxIteration; i++ { err = f(i, time.Since(start)) if err == nil { return i + 1, time.Since(start), nil } if maxIteration <= 0 || i+1 < maxIteration { time.Sleep(delay) } } return maxIteration, time.Since(start), err } // AttemptWhile invokes a function N times until it returns valid output. // Returning either the caught error or nil, and along with a bool value to identify // whether it needs invoke function continuously. It will terminate the invoke // immediately if second bool value is returned with falsy value. When first // argument is less than `1`, the function runs until a successful response is // returned. func AttemptWhile(maxIteration int, f func(int) (error, bool)) (int, error) { var err error var shouldContinueInvoke bool for i := 0; maxIteration <= 0 || i < maxIteration; i++ { // for retries >= 0 { err, shouldContinueInvoke = f(i) if !shouldContinueInvoke { // if shouldContinueInvoke is false, then return immediately return i + 1, err } if err == nil { return i + 1, nil } } return maxIteration, err } // AttemptWhileWithDelay invokes a function N times until it returns valid output, // with a pause between each call. Returning either the caught error or nil, and along // with a bool value to identify whether it needs to invoke function continuously. // It will terminate the invoke immediately if second bool value is returned with falsy // value. When first argument is less than `1`, the function runs until a successful // response is returned. func AttemptWhileWithDelay(maxIteration int, delay time.Duration, f func(int, time.Duration) (error, bool)) (int, time.Duration, error) { var err error var shouldContinueInvoke bool start := time.Now() for i := 0; maxIteration <= 0 || i < maxIteration; i++ { err, shouldContinueInvoke = f(i, time.Since(start)) if !shouldContinueInvoke { // if shouldContinueInvoke is false, then return immediately return i + 1, time.Since(start), err } if err == nil { return i + 1, time.Since(start), nil } if maxIteration <= 0 || i+1 < maxIteration { time.Sleep(delay) } } return maxIteration, time.Since(start), err } type transactionStep[T any] struct { exec func(T) (T, error) onRollback func(T) T } // NewTransaction instanciate a new transaction. func NewTransaction[T any]() *Transaction[T] { return &Transaction[T]{ steps: []transactionStep[T]{}, } } // Transaction implements a Saga pattern type Transaction[T any] struct { steps []transactionStep[T] } // Then adds a step to the chain of callbacks. It returns the same Transaction. func (t *Transaction[T]) Then(exec func(T) (T, error), onRollback func(T) T) *Transaction[T] { t.steps = append(t.steps, transactionStep[T]{ exec: exec, onRollback: onRollback, }) return t } // Process runs the Transaction steps and rollbacks in case of errors. func (t *Transaction[T]) Process(state T) (T, error) { var i int var err error for i < len(t.steps) { state, err = t.steps[i].exec(state) if err != nil { break } i++ } if err == nil { return state, nil } for i > 0 { i-- state = t.steps[i].onRollback(state) } return state, err } // throttle ? lo-1.38.1/slice.go000066400000000000000000000373551440622353400136640ustar00rootroot00000000000000package lo import ( "math/rand" "golang.org/x/exp/constraints" ) // Filter iterates over elements of collection, returning an array of all elements predicate returns truthy for. // Play: https://go.dev/play/p/Apjg3WeSi7K 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) } } return result } // Map manipulates a slice and transforms it to a slice of another type. // Play: https://go.dev/play/p/OkPcYAhBo0D 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) } return result } // FilterMap 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. // // Play: https://go.dev/play/p/-AuYXfy7opz 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 { result = append(result, r) } } return result } // FlatMap 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. // Play: https://go.dev/play/p/YSoYmQTA8-U 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)...) } return result } // Reduce reduces collection to a value which is the accumulated result of running each element in collection // through accumulator, where each successive invocation is supplied the return value of the previous. // Play: https://go.dev/play/p/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) } return initial } // ReduceRight helper is like Reduce except that it iterates over elements of collection from right to left. // Play: https://go.dev/play/p/Fq3W70l7wXF func ReduceRight[T any, R any](collection []T, accumulator func(agg R, item T, index int) R, initial R) R { for i := len(collection) - 1; i >= 0; i-- { initial = accumulator(initial, collection[i], i) } return initial } // 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) } } // Times invokes the iteratee n times, returning an array of the results of each invocation. // The iteratee is invoked with index as argument. // Play: https://go.dev/play/p/vgQj3Glr6lT func Times[T any](count int, iteratee func(index int) T) []T { result := make([]T, count) for i := 0; i < count; i++ { result[i] = iteratee(i) } return result } // Uniq returns a duplicate-free version of an array, in which only the first occurrence of each element is kept. // The order of result values is determined by the order they occur in the array. // Play: https://go.dev/play/p/DTzbeXZ6iEN 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 { continue } seen[item] = struct{}{} result = append(result, item) } return result } // UniqBy returns a duplicate-free version of an array, in which only the first occurrence of each element is kept. // The order of result values is determined by the order they occur in the array. It accepts `iteratee` which is // invoked for each element in array to generate the criterion by which uniqueness is computed. // Play: https://go.dev/play/p/g42Z3QSb53u 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) if _, ok := seen[key]; ok { continue } seen[key] = struct{}{} result = append(result, item) } return result } // GroupBy returns an object composed of keys generated from the results of running each element of collection through iteratee. // Play: https://go.dev/play/p/XnQBd_v6brd func GroupBy[T any, U comparable](collection []T, iteratee func(item T) U) map[U][]T { result := map[U][]T{} for _, item := range collection { key := iteratee(item) result[key] = append(result[key], item) } return result } // Chunk returns an array of elements split into groups the length of size. If array can't be split evenly, // the final chunk will be the remaining elements. // Play: https://go.dev/play/p/EeKl0AuTehH func Chunk[T any](collection []T, size int) [][]T { if size <= 0 { panic("Second parameter must be greater than 0") } chunksNum := len(collection) / size if len(collection)%size != 0 { chunksNum += 1 } result := make([][]T, 0, chunksNum) for i := 0; i < chunksNum; i++ { last := (i + 1) * size if last > len(collection) { last = len(collection) } result = append(result, collection[i*size:last]) } return result } // PartitionBy returns an array of elements split into groups. The order of grouped values is // determined by the order they occur in collection. The grouping is generated from the results // of running each element of collection through iteratee. // Play: https://go.dev/play/p/NfQ_nGjkgXW func PartitionBy[T any, K comparable](collection []T, iteratee func(item T) K) [][]T { result := [][]T{} seen := map[K]int{} for _, item := range collection { key := iteratee(item) resultIndex, ok := seen[key] if !ok { resultIndex = len(result) seen[key] = resultIndex result = append(result, []T{}) } result[resultIndex] = append(result[resultIndex], item) } return result // unordered: // groups := GroupBy[T, K](collection, iteratee) // return Values[K, []T](groups) } // Flatten returns an array a single level deep. // Play: https://go.dev/play/p/rbp9ORaMpjw func Flatten[T any](collection [][]T) []T { totalLen := 0 for i := range collection { totalLen += len(collection[i]) } result := make([]T, 0, totalLen) for i := range collection { result = append(result, collection[i]...) } return result } // Interleave round-robin alternating input slices and sequentially appending value at index into result // Play: https://go.dev/play/p/DDhlwrShbwe func Interleave[T any](collections ...[]T) []T { if len(collections) == 0 { return []T{} } maxSize := 0 totalSize := 0 for _, c := range collections { size := len(c) totalSize += size if size > maxSize { maxSize = size } } if maxSize == 0 { return []T{} } result := make([]T, totalSize) resultIdx := 0 for i := 0; i < maxSize; i++ { for j := range collections { if len(collections[j])-1 < i { continue } result[resultIdx] = collections[j][i] resultIdx++ } } return result } // Shuffle returns an array of shuffled values. Uses the Fisher-Yates shuffle algorithm. // Play: https://go.dev/play/p/Qp73bnTDnc7 func Shuffle[T any](collection []T) []T { rand.Shuffle(len(collection), func(i, j int) { collection[i], collection[j] = collection[j], collection[i] }) return collection } // Reverse reverses array so that the first element becomes the last, the second element becomes the second to last, and so on. // Play: https://go.dev/play/p/fhUMLvZ7vS6 func Reverse[T any](collection []T) []T { length := len(collection) half := length / 2 for i := 0; i < half; i = i + 1 { j := length - 1 - i collection[i], collection[j] = collection[j], collection[i] } return collection } // Fill fills elements of array with `initial` value. // Play: https://go.dev/play/p/VwR34GzqEub func Fill[T Clonable[T]](collection []T, initial T) []T { result := make([]T, 0, len(collection)) for range collection { result = append(result, initial.Clone()) } return result } // Repeat builds a slice with N copies of initial value. // Play: https://go.dev/play/p/g3uHXbmc3b6 func Repeat[T Clonable[T]](count int, initial T) []T { result := make([]T, 0, count) for i := 0; i < count; i++ { result = append(result, initial.Clone()) } return result } // RepeatBy builds a slice with values returned by N calls of callback. // Play: https://go.dev/play/p/ozZLCtX_hNU func RepeatBy[T any](count int, predicate func(index int) T) []T { result := make([]T, 0, count) for i := 0; i < count; i++ { result = append(result, predicate(i)) } return result } // KeyBy transforms a slice or an array of structs to a map based on a pivot callback. // Play: https://go.dev/play/p/mdaClUAT-zZ 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 } return result } // Associate returns a map containing key-value pairs provided by transform function applied to elements of the given slice. // If any of two pairs would have the same key the last one gets added to the map. // The order of keys in returned map is not specified and is not guaranteed to be the same from the original array. // Play: https://go.dev/play/p/WHa2CfMO3Lr 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) result[k] = v } return result } // SliceToMap returns a map containing key-value pairs provided by transform function applied to elements of the given slice. // If any of two pairs would have the same key the last one gets added to the map. // The order of keys in returned map is not specified and is not guaranteed to be the same from the original array. // Alias of Associate(). // Play: https://go.dev/play/p/WHa2CfMO3Lr func SliceToMap[T any, K comparable, V any](collection []T, transform func(item T) (K, V)) map[K]V { return Associate(collection, transform) } // Drop drops n elements from the beginning of a slice or array. // Play: https://go.dev/play/p/JswS7vXRJP2 func Drop[T any](collection []T, n int) []T { if len(collection) <= n { return make([]T, 0) } result := make([]T, 0, len(collection)-n) return append(result, collection[n:]...) } // DropRight drops n elements from the end of a slice or array. // Play: https://go.dev/play/p/GG0nXkSJJa3 func DropRight[T any](collection []T, n int) []T { if len(collection) <= n { return []T{} } result := make([]T, 0, len(collection)-n) return append(result, collection[:len(collection)-n]...) } // DropWhile drops elements from the beginning of a slice or array while the predicate returns true. // Play: https://go.dev/play/p/7gBPYw2IK16 func DropWhile[T any](collection []T, predicate func(item T) bool) []T { i := 0 for ; i < len(collection); i++ { if !predicate(collection[i]) { break } } result := make([]T, 0, len(collection)-i) return append(result, collection[i:]...) } // DropRightWhile drops elements from the end of a slice or array while the predicate returns true. // Play: https://go.dev/play/p/3-n71oEC0Hz func DropRightWhile[T any](collection []T, predicate func(item T) bool) []T { i := len(collection) - 1 for ; i >= 0; i-- { if !predicate(collection[i]) { break } } result := make([]T, 0, i+1) return append(result, collection[:i+1]...) } // Reject is the opposite of Filter, this method returns the elements of collection that predicate does not return truthy for. // Play: https://go.dev/play/p/YkLMODy1WEL 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) } } return result } // 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 { count++ } } return count } // 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) { count++ } } return count } // CountValues counts the number of each element in the collection. // Play: https://go.dev/play/p/-p-PyLT4dfy func CountValues[T comparable](collection []T) map[T]int { result := make(map[T]int) for _, item := range collection { result[item]++ } return result } // CountValuesBy counts the number of each element return from mapper function. // Is equivalent to chaining lo.Map and lo.CountValues. // Play: https://go.dev/play/p/2U0dG1SnOmS func CountValuesBy[T any, U comparable](collection []T, mapper func(item T) U) map[U]int { result := make(map[U]int) for _, item := range collection { result[mapper(item)]++ } return result } // Subset returns a copy of a slice from `offset` up to `length` elements. Like `slice[start:start+length]`, but does not panic on overflow. // Play: https://go.dev/play/p/tOQu1GhFcog func Subset[T any](collection []T, offset int, length uint) []T { size := len(collection) if offset < 0 { offset = size + offset if offset < 0 { offset = 0 } } if offset > size { return []T{} } if length > uint(size)-uint(offset) { length = uint(size - offset) } return collection[offset : offset+int(length)] } // Slice returns a copy of a slice from `start` up to, but not including `end`. Like `slice[start:end]`, but does not panic on overflow. // Play: https://go.dev/play/p/8XWYhfMMA1h func Slice[T any](collection []T, start int, end int) []T { size := len(collection) if start >= end { return []T{} } if start > size { start = size } if start < 0 { start = 0 } if end > size { end = size } if end < 0 { end = 0 } return collection[start:end] } // Replace returns a copy of the slice with the first n non-overlapping instances of old replaced by new. // Play: https://go.dev/play/p/XfPzmf9gql6 func Replace[T comparable](collection []T, old T, new T, n int) []T { result := make([]T, len(collection)) copy(result, collection) for i := range result { if result[i] == old && n != 0 { result[i] = new n-- } } return result } // ReplaceAll returns a copy of the slice with all non-overlapping instances of old replaced by new. // Play: https://go.dev/play/p/a9xZFUHfYcV func ReplaceAll[T comparable](collection []T, old T, new T) []T { return Replace(collection, old, new, -1) } // Compact returns a slice of all non-zero elements. // Play: https://go.dev/play/p/tXiy-iK6PAc func Compact[T comparable](collection []T) []T { var zero T result := make([]T, 0, len(collection)) for _, item := range collection { if item != zero { result = append(result, item) } } return result } // IsSorted checks if a slice is sorted. // Play: https://go.dev/play/p/mc3qR-t4mcx func IsSorted[T constraints.Ordered](collection []T) bool { for i := 1; i < len(collection); i++ { if collection[i-1] > collection[i] { return false } } return true } // IsSortedByKey checks if a slice is sorted by iteratee. // Play: https://go.dev/play/p/wiG6XyBBu49 func IsSortedByKey[T any, K constraints.Ordered](collection []T, iteratee func(item T) K) bool { size := len(collection) for i := 0; i < size-1; i++ { if iteratee(collection[i]) > iteratee(collection[i+1]) { return false } } return true } lo-1.38.1/string.go000066400000000000000000000046061440622353400140640ustar00rootroot00000000000000package lo import ( "math/rand" "strings" "unicode/utf8" ) var ( LowerCaseLettersCharset = []rune("abcdefghijklmnopqrstuvwxyz") UpperCaseLettersCharset = []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ") LettersCharset = append(LowerCaseLettersCharset, UpperCaseLettersCharset...) NumbersCharset = []rune("0123456789") AlphanumericCharset = append(LettersCharset, NumbersCharset...) SpecialCharset = []rune("!@#$%^&*()_+-=[]{}|;':\",./<>?") AllCharset = append(AlphanumericCharset, SpecialCharset...) ) // RandomString return a random string. // Play: https://go.dev/play/p/rRseOQVVum4 func RandomString(size int, charset []rune) string { if size <= 0 { panic("lo.RandomString: Size parameter must be greater than 0") } if len(charset) <= 0 { panic("lo.RandomString: Charset parameter must not be empty") } b := make([]rune, size) possibleCharactersCount := len(charset) for i := range b { b[i] = charset[rand.Intn(possibleCharactersCount)] } return string(b) } // Substring return part of a string. // Play: https://go.dev/play/p/TQlxQi82Lu1 func Substring[T ~string](str T, offset int, length uint) T { rs := []rune(str) size := len(rs) if offset < 0 { offset = size + offset if offset < 0 { offset = 0 } } if offset > size { return Empty[T]() } if length > uint(size)-uint(offset) { length = uint(size - offset) } return T(strings.Replace(string(rs[offset:offset+int(length)]), "\x00", "", -1)) } // ChunkString returns an array of strings split into groups the length of size. If array can't be split evenly, // the final chunk will be the remaining elements. // Play: https://go.dev/play/p/__FLTuJVz54 func ChunkString[T ~string](str T, size int) []T { if size <= 0 { panic("lo.ChunkString: Size parameter must be greater than 0") } if len(str) == 0 { return []T{""} } if size >= len(str) { return []T{str} } var chunks []T = make([]T, 0, ((len(str)-1)/size)+1) currentLen := 0 currentStart := 0 for i := range str { if currentLen == size { chunks = append(chunks, str[currentStart:i]) currentLen = 0 currentStart = i } currentLen++ } chunks = append(chunks, str[currentStart:]) return chunks } // RuneLength is an alias to utf8.RuneCountInString which returns the number of runes in string. // Play: https://go.dev/play/p/tuhgW_lWY8l func RuneLength(str string) int { return utf8.RuneCountInString(str) } lo-1.38.1/tuples.go000066400000000000000000000377631440622353400141040ustar00rootroot00000000000000package 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] { 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] { 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] { 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] { 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] { 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] { 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] { 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] { 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) { 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) { 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) { 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) { 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) { 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) { 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) { 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) { return tuple.A, tuple.B, tuple.C, tuple.D, tuple.E, tuple.F, tuple.G, tuple.H, tuple.I } // Zip2 creates a slice of grouped 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. // Play: https://go.dev/play/p/jujaA6GaJTp func Zip2[A any, B any](a []A, b []B) []Tuple2[A, B] { size := Max([]int{len(a), len(b)}) result := make([]Tuple2[A, B], 0, size) for index := 0; index < size; index++ { _a, _ := Nth(a, index) _b, _ := Nth(b, index) result = append(result, Tuple2[A, B]{ A: _a, B: _b, }) } return result } // Zip3 creates a slice of grouped 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. // 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] { size := Max([]int{len(a), len(b), len(c)}) result := make([]Tuple3[A, B, C], 0, size) for index := 0; index < size; index++ { _a, _ := Nth(a, index) _b, _ := Nth(b, index) _c, _ := Nth(c, index) result = append(result, Tuple3[A, B, C]{ A: _a, B: _b, C: _c, }) } return result } // Zip4 creates a slice of grouped 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. // 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] { size := Max([]int{len(a), len(b), len(c), len(d)}) result := make([]Tuple4[A, B, C, D], 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, Tuple4[A, B, C, D]{ A: _a, B: _b, C: _c, D: _d, }) } return result } // Zip5 creates a slice of grouped 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. // 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] { size := Max([]int{len(a), len(b), len(c), len(d), len(e)}) result := make([]Tuple5[A, B, C, D, E], 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, Tuple5[A, B, C, D, E]{ A: _a, B: _b, C: _c, D: _d, E: _e, }) } return result } // Zip6 creates a slice of grouped 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. // 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] { 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) for index := 0; index < size; index++ { _a, _ := Nth(a, index) _b, _ := Nth(b, index) _c, _ := Nth(c, index) _d, _ := Nth(d, index) _e, _ := Nth(e, index) _f, _ := Nth(f, index) result = append(result, Tuple6[A, B, C, D, E, F]{ A: _a, B: _b, C: _c, D: _d, E: _e, F: _f, }) } return result } // Zip7 creates a slice of grouped 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. // 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] { size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g)}) result := make([]Tuple7[A, B, C, D, E, F, G], 0, size) for index := 0; index < size; index++ { _a, _ := Nth(a, index) _b, _ := Nth(b, index) _c, _ := Nth(c, index) _d, _ := Nth(d, index) _e, _ := Nth(e, index) _f, _ := Nth(f, index) _g, _ := Nth(g, index) result = append(result, Tuple7[A, B, C, D, E, F, G]{ A: _a, B: _b, C: _c, D: _d, E: _e, F: _f, G: _g, }) } return result } // Zip8 creates a slice of grouped 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. // 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] { size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h)}) result := make([]Tuple8[A, B, C, D, E, F, G, H], 0, size) for index := 0; index < size; index++ { _a, _ := Nth(a, index) _b, _ := Nth(b, index) _c, _ := Nth(c, index) _d, _ := Nth(d, index) _e, _ := Nth(e, index) _f, _ := Nth(f, index) _g, _ := Nth(g, index) _h, _ := Nth(h, index) result = append(result, Tuple8[A, B, C, D, E, F, G, H]{ A: _a, B: _b, C: _c, D: _d, E: _e, F: _f, G: _g, H: _h, }) } return result } // Zip9 creates a slice of grouped 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. // 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] { size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h), len(i)}) result := make([]Tuple9[A, B, C, D, E, F, G, H, I], 0, size) for index := 0; index < size; index++ { _a, _ := Nth(a, index) _b, _ := Nth(b, index) _c, _ := Nth(c, index) _d, _ := Nth(d, index) _e, _ := Nth(e, index) _f, _ := Nth(f, index) _g, _ := Nth(g, index) _h, _ := Nth(h, index) _i, _ := Nth(i, index) result = append(result, Tuple9[A, B, C, D, E, F, G, H, I]{ A: _a, B: _b, C: _c, D: _d, E: _e, F: _f, G: _g, H: _h, I: _i, }) } 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) { 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) } return r1, r2 } // 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) { 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) } return r1, r2, r3 } // 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) { 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) } return r1, r2, r3, r4 } // 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) { size := len(tuples) 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 _, 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) } return r1, r2, r3, r4, r5 } // 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) { size := len(tuples) 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 _, 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) } return r1, r2, r3, r4, r5, r6 } // 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) { size := len(tuples) 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 _, 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) } return r1, r2, r3, r4, r5, r6, r7 } // 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) { size := len(tuples) 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 _, 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) } return r1, r2, r3, r4, r5, r6, r7, r8 } // 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) { size := len(tuples) 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 _, 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) } return r1, r2, r3, r4, r5, r6, r7, r8, r9 } lo-1.38.1/type_manipulation.go000066400000000000000000000037151440622353400163170ustar00rootroot00000000000000package lo import "reflect" // ToPtr returns a pointer copy of value. func ToPtr[T any](x T) *T { return &x } // EmptyableToPtr returns a pointer copy of value if it's nonzero. // Otherwise, returns nil pointer. func EmptyableToPtr[T any](x T) *T { // ๐Ÿคฎ isZero := reflect.ValueOf(&x).Elem().IsZero() if isZero { return nil } return &x } // FromPtr returns the pointer value or empty. func FromPtr[T any](x *T) T { if x == nil { return Empty[T]() } return *x } // FromPtrOr returns the pointer value or the fallback value. func FromPtrOr[T any](x *T, fallback T) T { if x == nil { return fallback } return *x } // 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 }) } // 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 } return result } // FromAnySlice returns an `any` slice with all elements mapped to a type. // Returns false in case of type conversion failure. func FromAnySlice[T any](in []any) (out []T, ok bool) { defer func() { if r := recover(); r != nil { out = []T{} ok = false } }() result := make([]T, len(in)) for i, item := range in { result[i] = item.(T) } return result, true } // Empty returns an empty value. func Empty[T any]() T { var zero T return zero } // IsEmpty returns true if argument is a zero value. func IsEmpty[T comparable](v T) bool { var zero T return zero == v } // IsNotEmpty returns true if argument is not a zero value. func IsNotEmpty[T comparable](v T) bool { var zero T return zero != v } // 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 ok = true return } } return } lo-1.38.1/types.go000066400000000000000000000043401440622353400137150ustar00rootroot00000000000000package lo // Entry defines a key/value pairs. type Entry[K comparable, V any] struct { Key K Value V } // Tuple2 is a group of 2 elements (pair). type Tuple2[A any, B any] struct { A A B B } // Unpack returns values contained in tuple. func (t Tuple2[A, B]) Unpack() (A, B) { return t.A, t.B } // Tuple3 is a group of 3 elements. type Tuple3[A any, B any, C any] struct { A A B B C C } // Unpack returns values contained in tuple. func (t Tuple3[A, B, C]) Unpack() (A, B, C) { return t.A, t.B, t.C } // Tuple4 is a group of 4 elements. type Tuple4[A any, B any, C any, D any] struct { A A B B C C D D } // Unpack returns values contained in tuple. func (t Tuple4[A, B, C, D]) Unpack() (A, B, C, D) { return t.A, t.B, t.C, t.D } // Tuple5 is a group of 5 elements. type Tuple5[A any, B any, C any, D any, E any] struct { A A B B C C D D E E } // Unpack returns values contained in tuple. func (t Tuple5[A, B, C, D, E]) Unpack() (A, B, C, D, E) { return t.A, t.B, t.C, t.D, t.E } // Tuple6 is a group of 6 elements. type Tuple6[A any, B any, C any, D any, E any, F any] struct { A A B B C C D D E E F F } // Unpack returns values contained in tuple. func (t Tuple6[A, B, C, D, E, F]) Unpack() (A, B, C, D, E, F) { return t.A, t.B, t.C, t.D, t.E, t.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 { A A B B C C D D E E F F G G } // Unpack returns values contained in tuple. func (t Tuple7[A, B, C, D, E, F, G]) Unpack() (A, B, C, D, E, F, G) { return t.A, t.B, t.C, t.D, t.E, t.F, t.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 { A A B B C C D D E E F F G G H H } // Unpack returns values contained in tuple. func (t Tuple8[A, B, C, D, E, F, G, H]) Unpack() (A, B, C, D, E, F, G, H) { return t.A, t.B, t.C, t.D, t.E, t.F, t.G, t.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 { A A B B C C D D E E F F G G H H I I } // Unpack returns values contained in tuple. func (t Tuple9[A, B, C, D, E, F, G, H, I]) Unpack() (A, B, C, D, E, F, G, H, I) { return t.A, t.B, t.C, t.D, t.E, t.F, t.G, t.H, t.I }