https://github.com/tiendc/gofn
High performance utility functions using Generics
https://github.com/tiendc/gofn
filter functional generics go golang searching transform typesafe
Last synced: 5 months ago
JSON representation
High performance utility functions using Generics
- Host: GitHub
- URL: https://github.com/tiendc/gofn
- Owner: tiendc
- License: mit
- Created: 2022-12-14T18:43:21.000Z (almost 3 years ago)
- Default Branch: master
- Last Pushed: 2025-02-13T10:19:39.000Z (10 months ago)
- Last Synced: 2025-04-06T07:37:42.842Z (8 months ago)
- Topics: filter, functional, generics, go, golang, searching, transform, typesafe
- Language: Go
- Homepage:
- Size: 288 KB
- Stars: 52
- Watchers: 3
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-go - gofn - High performance utility functions written using Generics for Go 1.18+. (Utilities / Utility/Miscellaneous)
- awesome-go-cn - gofn
- awesome-go - tiendc/gofn
- awesome-go-plus - gofn - High performance utility functions written using Generics for Go 1.18+.  (Utilities / Utility/Miscellaneous)
- awesome-go-with-stars - gofn - High performance utility functions written using Generics for Go 1.18+. (Utilities / Utility/Miscellaneous)
- awesome-go - gofn - High performance utility functions written using Generics for Go 1.18+. (Utilities / Utility/Miscellaneous)
- awesome-go - gofn - High performance utility functions written using Generics for Go 1.18+. (Utilities / Utility/Miscellaneous)
- fucking-awesome-go - gofn - High performance utility functions written using Generics for Go 1.18+. (Utilities / Utility/Miscellaneous)
- awesome-go-cn - gofn
- awesome-go - gofn - High performance utility functions written using Generics for Go 1.18+. (Utilities / Utility/Miscellaneous)
README
[![Go Version][gover-img]][gover] [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] [![GoReport][rpt-img]][rpt]
# gofn - Utility functions for Go 1.18+
This is a collection of generics utility functions for Go 1.18+.
## Functionalities
`gofn` consists of useful and convenient functions for most common needs when working on slices, maps, structs, transformation, conversion, and so on.
Check out my other libs:
- [go-deepcopy](https://github.com/tiendc/go-deepcopy): True deep copy function
- [go-validator](https://github.com/tiendc/go-validator): Validation library
- [go-apperrors](https://github.com/tiendc/go-apperrors): Manipulating application errors
- [go-csvlib](https://github.com/tiendc/go-csvlib): High-level rich functionality CSV encoding and decoding library
## Installation
```shell
go get github.com/tiendc/gofn
```
## Function list
**Slice**
- [Equal / EqualBy](#equal--equalby)
- [ContentEqual / ContentEqualBy](#contentequal--contentequalby)
- [Sort / IsSorted](#sort--issorted)
- [RemoveAt](#removeat)
- [FastRemoveAt](#fastremoveat)
- [Remove](#remove)
- [FastRemove](#fastremove)
- [RemoveLastOf](#removelastof)
- [FastRemoveLastOf](#fastremovelastof)
- [RemoveAll](#removeall)
- [Replace / ReplaceN / ReplaceAll](#replace--replacen--replaceall)
- [Splice / SpliceEx](#splice--spliceex)
- [Concat](#concat)
- [Fill](#fill)
- [SliceByRange](#slicebyrange)
- [First / FirstOr](#first--firstor)
- [Last / LastOr](#last--lastor)
**Slice searching**
- [Contain](#contain--containby)
- [ContainAll](#containall)
- [ContainAny](#containany)
- [Find / FindEx](#find--findex)
- [FindLast / FindLastEx](#findlast--findlastex)
- [IndexOf / IndexOfBy](#indexof--indexofby)
- [LastIndexOf / LastIndexOfBy](#lastindexof--lastindexofby)
- [CountValue / CountValueBy](#countvalue--countvalueby)
**Slice filtering**
- [Filter](#filter)
- [FilterNE](#filter)
- [FilterLT / FilterLTE](#filter)
- [FilterGT / FilterGTE](#filter)
- [FilterGT / FilterGTE](#filter)
- [FilterIN / FilterNIN](#filter)
- [FilterLIKE / FilterILIKE](#filter)
**Slice iteration**
- [ForEach / ForEachReverse](#foreach--foreachreverse)
- [Iter / IterReverse](#iter--iterreverse)
**Slice uniqueness**
- [IsUnique / IsUniqueBy](#isunique--isuniqueby)
- [FindUniques / FindUniquesBy](#finduniques--finduniquesby)
- [FindDuplicates / FindDuplicatesBy](#findduplicates--findduplicatesby)
- [ToSet / ToSetBy / ToSetByReverse ](#toset--tosetby--tosetbyreverse)
**Slice transformation**
- [MapSlice / MapSliceEx](#mapslice--mapsliceex)
- [MapSliceToMap / MapSliceToMapEx](#mapslicetomap--mapslicetomapex)
- [MapSliceToMapKeys](#mapslicetomapkeys)
**Slice algo**
- [Compact](#compact)
- [Drop](#drop)
- [Reverse / ReverseCopy](#reverse--reversecopy)
- [Shuffle](#shuffle)
- [Chunk / ChunkByPieces](#chunk--chunkbypieces)
- [Union / UnionBy](#union--unionby)
- [Intersection / IntersectionBy](#intersection--intersectionby)
- [Difference / DifferenceBy](#difference--differenceby)
- [Reduce / ReduceEx](#reduce--reduceex)
- [ReduceReverse / ReduceReverseEx](#reducereverse--reducereverseex)
- [Partition / PartitionN](#partition--partitionn)
- [Flatten / Flatten3](#flatten--flatten3)
- [Zip / Zip\](#zip--zipn)
**Slice conversion**
- [ToIfaceSlice](#toifaceslice)
- [ToStringSlice](#tostringslice)
- [ToNumberSlice](#tonumberslice)
- [ToSlice](#toslice)
- [ToPtrSlice](#toptrslice)
**Slice subset**
- [ContainSlice](#containslice)
- [IndexOfSlice](#indexofslice)
- [LastIndexOfSlice](#lastindexofslice)
- [SubSlice](#subslice)
**Map**
- [MapEqual / MapEqualBy](#mapequal--mapequalby)
- [MapContainKeys](#mapcontainkeys)
- [MapContainValues](#mapcontainvalues)
- [MapIter](#mapiter)
- [MapKeys](#mapkeys)
- [MapValues](#mapvalues)
- [MapEntries](#mapentries)
- [MapGet](#mapget)
- [MapPop](#mappop)
- [MapSetDefault](#mapsetdefault)
- [MapUpdate / MapUpdateExistingOnly / MapUpdateNewOnly](#mapupdate--mapupdateexistingonly--mapupdatenewonly)
- [MapCopy](#mapcopy)
- [MapPick](#mappick)
- [MapOmit](#mapomit)
- [MapOmitCopy](#mapomitcopy)
- [MapReverse](#mapreverse)
**Struct**
- [StructToMap](#structtomap)
- [ParseTag / ParseTagOf / ParseTagsOf](#parsetag--parsetagof--parsetagsof)
**String**
- [RuneLength](#runelength)
- [RandString / RandStringEx](#randstring--randstringex)
- [StringJoin / StringJoinEx / StringJoinBy](#stringjoin--stringjoinex--stringjoinby)
- [StringLexJoin / StringLexJoinEx](#stringlexjoin--stringlexjoinex)
- [StringWrap / StringUnwrap](#stringwrap--stringunwrap)
**Number**
- [ParseInt / ParseUint / ParseFloat](#parseint--parseuint--parsefloat)
- [FormatInt / FormatUint / FormatFloat](#formatint--formatuint--formatfloat)
- [NumberFmtGroup](#numberfmtgroup)
- [ToSafeInt\ / ToSafeUint\](#tosafeintn--tosafeuintn)
**Time**
- [MinTime / MaxTime / MinMaxTime](#mintime--maxtime--minmaxtime)
- [ExecDuration / ExecDurationN](#execduration--execdurationn)
- [ExecDelay](#execdelay)
**Math**
- [All](#all)
- [Any](#any)
- [Abs](#abs)
- [Clamp](#clamp)
- [Min / Max / MinMax](#min--max--minmax)
- [Sum / SumAs](#sum--sumas)
- [Product / ProductAs](#product--productas)
**Concurrency**
- [ExecTasks / ExecTasksEx](#exectasks--exectasksex)
- [ExecTaskFunc / ExecTaskFuncEx](#exectaskfunc--exectaskfuncex)
**Function**
- [Bind\Arg\Ret ](#bindnargmret)
- [Partial\Arg\Ret ](#partialnargmret)
**Randomization**
- [RandChoice](#randchoice)
- [RandChoiceMaker](#randchoicemaker)
**Error handling**
- [ErrWrap / ErrWrapL](#errwrap--errwrapl)
- [ErrUnwrap](#errunwrap)
- [ErrUnwrapToRoot](#errunwraptoroot)
**Utility**
- [FirstNonEmpty](#firstnonempty)
- [Coalesce](#coalesce)
- [If](#if)
- [Must\](#mustn)
- [ToPtr](#toptr)
- [Head](#head)
- [Tail](#tail)
### Slice
---
#### Equal / EqualBy
Returns true if 2 slices have the same size and all elements of them equal in the current order.
```go
Equal([]int{1, 2, 3}, []int{1, 2, 3}) // true
Equal([]int{1, 2, 3}, []int{3, 2, 1}) // false
// Use EqualBy for custom equal comparison
EqualBy([]string{"one", "TWO"}, []string{"ONE", "two"}, strings.EqualFold) // true
```
#### ContentEqual / ContentEqualBy
Returns true if 2 slices have the same size and contents equal regardless of the order of elements.
```go
ContentEqual([]int{1, 2, 3}, []int{2, 1, 3}) // true
ContentEqual([]int{1, 2, 2, 3}, []int{2, 1, 3}) // false
ContentEqual([]int{1, 2, 2, 3}, []int{2, 1, 2, 3}) // true
ContentEqual([]int{1, 2, 2, 3}, []int{1, 1, 2, 3}) // false
// Use ContentEqualBy for custom key function
ContentEqualBy([]string{"one", "TWO"}, []string{"two", "ONE"}, strings.ToLower) // true
```
#### RemoveAt
Removes element at the specified index.
```go
s := []int{1, 2, 3}
RemoveAt(&s, 1) // s == []int{1, 3}
```
#### FastRemoveAt
Removes element at the specified index by swapping it with the last element of the slice.
This function is fast as it doesn't cause copying of slice content.
```go
s := []int{1, 2, 3, 4}
FastRemoveAt(&s, 1) // s == []int{1, 4, 3} (2 and 4 are exchanged)
```
#### Sort / IsSorted
Convenient wrappers of the built-in `sort.Slice`.
```go
Sort([]int{1, 3, 2}) // []int{1, 2, 3}
SortDesc([]int{1, 3, 2}) // []int{3, 2, 1}
IsSorted([]int{1, 3, 2}) // false
IsSortedDesc([]int{3, 2, 1}) // true
```
#### Remove
Removes a value from a slice.
```go
s := []int{1, 2, 3}
Remove(&s, 1) // s == []int{2, 3}
```
#### FastRemove
Removes a value from a slice by swapping it with the last element of the slice.
```go
s := []int{1, 2, 3, 4}
FastRemove(&s, 2) // s == []int{1, 4, 3} (2 and 4 are exchanged)
```
#### RemoveLastOf
Removes last occurrence of a value from a slice.
```go
s := []int{1, 2, 1, 3}
RemoveLastOf(&s, 1) // s == []int{1, 2, 3}
```
#### FastRemoveLastOf
Removes last occurrence of a value from a slice by swapping it with the last element of the slice.
```go
s := []int{1, 2, 1, 3, 4}
FastRemoveLastOf(&s, 1) // s == []int{1, 2, 4, 3} (1 and 4 are exchanged)
```
#### RemoveAll
Removes all occurrences of a value from a slice.
```go
s := []int{1, 2, 1, 3, 1}
RemoveAll(&s, 1) // s == []int{2, 3}
```
#### Replace / ReplaceN / ReplaceAll
Replaces a value with another value.
```go
// Replaces first occurrence of the value
Replace([]int{1, 2, 1, 3, 1}, 1, 11) // []int{11, 2, 1, 3, 1}
// Replaces first n occurrences of the value (use -1 to replace all)
ReplaceN([]int{1, 2, 1, 3, 1}, 1, 11, 2) // []int{11, 2, 11, 3, 1}
// Replaces all occurrences of the value
ReplaceAll([]int{1, 2, 1, 3, 1}, 1, 11) // []int{11, 2, 11, 3, 11}
```
#### Splice / SpliceEx
Removes a portion of the given slice and inserts elements of another slice into that position.
Usage: `Splice(s []T, start int, deleteCount int, insertingElements ...T)`.
If `start < -len(s)`, `0` is used.
If `-len(s) <= start < 0`, `start + len(s)` is used.
If `start >= len(s)`, no element will be deleted, but the new elements are still added to the end.
If `deleteCount <= 0`, no elements will be removed.
```go
s := Splice([]int{1, 2, 3}, 1, 1, 100) // s == []int{1, 100, 3}
s := Splice([]int{1, 2, 3}, 1, 0, 100) // s == []int{1, 100, 2, 3}
s := Splice([]int{1, 2, 3}, 1, 1, 100, 101) // s == []int{1, 100, 101, 3}
s := Splice([]int{1, 2, 3}, 0, 2, 100, 101) // s == []int{100, 101, 3}
// Use SpliceEx to get deleted items as the 2nd returning value
s, delItems := SpliceEx([]int{1, 2, 3}, 1, 1, 100) // s == []int{1, 100, 3}, delItems == []int{2}
s, delItems := SpliceEx([]int{1, 2, 3}, 1, 0, 100) // s == []int{1, 100, 2, 3}, delItems == []int{}
s, delItems := SpliceEx([]int{1, 2, 3}, 1, 1, 100, 101) // s == []int{1, 100, 101, 3}, delItems == []int{2}
s, delItems := SpliceEx([]int{1, 2, 3}, 0, 2, 100, 101) // s == []int{100, 101, 3}, delItems == []int{1, 2}
```
#### Concat
Concatenates two or more slices.
```go
Concat([]int{1}, []int{2}, []int{2, 3}) // []int{1, 2, 2, 3}
```
#### Fill
Fills a slice with specified value.
```go
s := make([]int, 5)
Fill(s, 1) // s == []int{1, 1, 1, 1, 1}
s2 := s[2:4]
Fill(s2, 1) // s2 == []int{1, 1}, s == []int{0, 0, 1, 1, 0}
```
#### First / FirstOr
Returns the first element of slice.
```go
First([]int{1, 2, 3}) // 1, true
First([]string{}) // "", false
// Return the default value if slice is empty
FirstOr([]int{1, 2, 3}, 4) // 1
FirstOr([]int{}, 11) // 11
```
#### Last / LastOr
Returns the last element of slice.
```go
Last([]int{1, 2, 3}) // 3, true
Last([]string{}) // "", false
// Return the default value if slice is empty
LastOr([]int{1, 2, 3}, 4) // 3
LastOr([]int{}, 11) // 11
```
#### SliceByRange
Generates a slice for the given range.
```go
s := SliceByRange(0, 5, 1) // []int{0, 1, 2, 3, 4}
s := SliceByRange(0.0, 5, 2) // []float64{0, 2, 4}
s := SliceByRange(int32(5), 0, -2) // []int32{5, 3, 1}
```
### Slice searching
---
#### Contain / ContainBy
Returns true if a slice contains a value.
```go
Contain([]int{1, 2, 3}, 2) // true
Contain([]int{1, 2, 3}, 0) // false
// Use ContainBy for custom function
ContainBy([]string{"one", "TWO"}, func(elem string) bool {
return strings.ToLower(elem) == "two"
}) // true
```
#### ContainAll
Returns true if a slice contains all given values.
```go
ContainAll([]int{1, 2, 3, 4, 5}, 2, 4, 3) // true
ContainAll([]int{1, 2, 3, 4, 5}, 2, 7) // false
```
#### ContainAny
Returns true if a slice contains any of the given values.
```go
ContainAny([]int{1, 2, 3, 4, 5}, 2, 4, 7) // true
ContainAny([]int{1, 2, 3, 4, 5}, 7, 8, 9) // false
```
#### Find / FindEx
Finds a value in a slice.
```go
v, found := Find([]string{"one", "TWO"}, func(elem string) bool {
return strings.ToLower(elem) == "two"
}) // v == "TWO", found == true
// FindEx lets the given function decide which value to return
type Person struct {
Name string
Age int
}
// Finds age of person having name "Tim"
ageOfTim, found := FindEx([]Person{{"John", 40}, {"Tim", 50}}, func(p Person) (int, bool) {
return p.Age, p.Name == "Tim"
}) // ageOfTim == 50, found == true
```
#### FindLast / FindLastEx
Finds a value in a slice from the end.
```go
v, found := FindLast([]string{"one", "TWO", "ONe"}, func(elem string) bool {
return strings.ToLower(elem) == "one"
}) // v == "ONe", found == true
// FindLastEx lets the given function decide which value to return
type Person struct {
Name string
Age int
}
// Finds age of the last person having name "tim"
ageOfTim, found := FindLastEx([]Person{{"John", 40}, {"Tim", 50}, {"TIM", 60}}, func(p Person) (int, bool) {
return p.Age, strings.ToLower(p.Name) == "tim"
}) // ageOfTim == 60, found == true
```
#### IndexOf / IndexOfBy
Finds the index of a value in a slice, returns -1 if not found.
```go
IndexOf([]int{1, 2, 3}, 4) // -1
IndexOf([]int{1, 2, 3}, 2) // 1
// Use IndexOfBy for custom function
IndexOfBy([]string{"one", "TWO"}, func(elem string) bool {
return strings.ToLower(elem) == "two"
}) // 1
```
#### LastIndexOf / LastIndexOfBy
Finds the last index of an element in a slice, returns -1 if not found.
```go
LastIndexOf([]int{1, 2, 3}, 4) // -1
LastIndexOf([]int{1, 2, 1, 3}, 1) // 2
```
#### CountValue / CountValueBy
Counts the number of occurrences of a value in a slice.
```go
CountValue([]int{1, 2, 3}, 4) // 0
CountValue([]int{1, 2, 3, 2}, 2) // 2
```
### Slice filtering
---
#### Filter
Filters a slice with condition.
```go
Filter([]int{1, 2, 3, 4}, func (i int) bool {
return i % 2 == 0
}) // []int{2, 4}
FilterLT([]int{1, 2, 3, 4}, 3) // []int{1, 2}
FilterLTE([]int{1, 2, 3, 4}, 3) // []int{1, 2, 3}
FilterGT([]int{1, 2, 3, 4}, 3) // []int{4}
FilterGTE([]int{1, 2, 3, 4}, 3) // []int{3, 4}
FilterNE([]int{1, 2, 3, 4}, 3) // []int{1, 2, 4}
FilterIN([]int{1, 2, 3, 4}, 3, 2, 7) // []int{2, 3}
FilterNIN([]int{1, 2, 3, 4}, 3, 2, 7) // []int{1, 4}
FilterLIKE([]string{"*Abc*", "*abc*", "abc*", "*abc"}, "Abc") // []string{"*Abc*"}
FilterILIKE([]string{"*Abc*", "*abc*", "abc*", "*abc"}, "Abc") // []string{"*Abc*", "*abc*", "abc*", "*abc"}
```
### Slice iteration
---
#### ForEach / ForEachReverse
Iterates over slice content.
```go
ForEach([]int{1, 2, 3}, func (i, v int) {
fmt.Printf("%d ", v)
}) // prints 1 2 3
ForEachReverse([]int{1, 2, 3}, func (i, v int) {
fmt.Printf("%d ", v)
}) // prints 3 2 1
// ForEachPtr can be faster if you iterate over a slice of big structs
ForEachPtr([]BigStruct{...}, func (i, v *BigStruct) { ... })
ForEachPtrReverse([]BigStruct{...}, func (i, v *BigStruct) { ... })
```
#### Iter / IterReverse
Iterates over one or multiple slices with ability to stop.
```go
Iter(func (i, v int) bool {
fmt.Printf("%d ", v)
return i < 3
}, []int{1, 2, 3}, []int{4, 5}) // prints 1 2 3 4
IterReverse(func (i, v int) bool {
fmt.Printf("%d ", v)
return true
}, []int{1, 2, 3}, []int{4, 5}) // prints 5 4 3 2 1
// IterPtr can be faster if you iterate over a slice of big structs
IterPtr(func (i, v *BigStruct) bool { ... }, []BigStruct{...})
IterPtrReverse(func (i, v *BigStruct) bool { ... }, []BigStruct{...})
```
### Slice uniqueness
---
#### IsUnique / IsUniqueBy
Returns true if a slice contains unique values.
```go
IsUnique([]int{1, 2, 3}) // true
IsUnique([]int{1, 2, 1}) // false
// Use IsUniqueBy for custom function
IsUniqueBy([]string{"one", "ONE"}, strings.ToLower) // false
```
#### FindUniques / FindUniquesBy
Finds all unique elements in the given slice. The order of elements in the result is the same
as they appear in the input.
```go
FindUniques([]int{1, 2, 3, 2}) // []int{1, 3}
FindUniques([]int{1, 2, 2, 1}) // []int{}
// FindUniquesBy supports custom key function
FindUniquesBy([]string{"one", "ONE", "Two"}, func (s string) string {
return strings.ToLower(s)
}) // []string{"Two"}
```
#### FindDuplicates / FindDuplicatesBy
Finds all elements which are duplicated in the given slice. The order of elements in the result is the same
as they appear in the input.
```go
FindDuplicates([]int{1, 2, 3, 2}) // []int{2}
FindDuplicates([]int{1, 2, 3}) // []int{}
// FindDuplicatesBy supports custom key function
FindDuplicatesBy([]string{"one", "ONE", "Two"}, func (s string) string {
return strings.ToLower(s)
}) // []string{"one"}
```
#### ToSet / ToSetBy / ToSetByReverse
Calculates unique values of a slice.
```go
ToSet([]int{1, 2, 3, 1, 2}) // []int{1, 2, 3}
ToSet([]string{"one", "2", "one"}) // []string{"one", "2"}
// Use ToSetBy for custom key function
ToSetBy([]string{"one", "TWO", "two", "One"}, strings.ToLower) // []string{"one", "TWO"}
// Use ToSetByReverse for iterating items from the end of the list
ToSetByReverse([]string{"one", "TWO", "two", "One"}, strings.ToLower) // []string{"One", "two"}
```
### Slice transformation
---
#### MapSlice / MapSliceEx
Transforms a slice to a slice.
```go
MapSlice([]string{"1", "2 ", " 3"}, strings.TrimSpace) // []string{"1", "2", "3"}
// Use MapSliceEx to transform with error handling
MapSliceEx([]string{"1","2","3"}, gofn.ParseInt[int]) // []int{1, 2, 3}
MapSliceEx([]string{"1","x","3"}, gofn.ParseInt[int]) // strconv.ErrSyntax
MapSliceEx([]string{"1","200","3"}, gofn.ParseInt[int8]) // strconv.ErrRange
```
#### MapSliceToMap / MapSliceToMapEx
Transforms a slice to a map.
```go
MapSliceToMap([]int{1, 2, 3}, func (i int) (int, string) {
return i, fmt.Sprintf("%d", i)
}) // map[int]string{1: "1", 2: "2", 3: "3"}
// Use MapSliceToMapEx to transform with error handling
MapSliceToMapEx([]string{"1","300","3"}, func (s string) (string, int, bool) {
v, e := gofn.ParseInt[int8](s)
return s, v, e
}) // strconv.ErrRange
```
#### MapSliceToMapKeys
Transforms a slice to a map with using slice items as map keys.
```go
MapSliceToMapKeys([]int{1, 2, 3, 2}, "x") // map[int]string{1: "x", 2: "x", 3: "x"}
MapSliceToMapKeys([]int{1, 2, 1}, struct{}{}) // map[int]struct{}{1: struct{}{}, 2: struct{}{}}
```
### Slice algo
---
#### Compact
Compacts a slice by removing zero elements. Not change the source slice.
```go
s := Compact([]int{1, 0, 3}) // s == []int{1, 3}
```
#### Reverse / ReverseCopy
Reverses slice content.
```go
Reverse([]int{1, 2, 3}) // []int{3, 2, 1}
s1 := []int{1, 2, 3}
s2 := ReverseCopy(s1) // s1 == []int{1, 2, 3}, s2 == []int{3, 2, 1}
```
#### Drop
Returns a new slice with dropping values in the specified list.
NOTE: this function is just a call to `FilterNIN()`.
```go
Drop([]int{1, 2, 3, 4}, 3, 1) // []int{2, 4}
```
#### Shuffle
Shuffle items of a slice. Not change the source slice.
```go
s := Shuffle([]int{1, 2, 3}) // s is a new slice with random items of the input
```
#### Chunk / ChunkByPieces
Splits slice content into chunks.
```go
Chunk([]int{1, 2, 3, 4, 5}, 2) // [][]int{[]int{1, 2}, []int{3, 4}, []int{5}}
ChunkByPieces([]int{1, 2, 3, 4, 5}, 2) // [][]int{[]int{1, 2, 3}, []int{4, 5}}
```
#### Union / UnionBy
Finds all unique values from multiple slices.
```go
Union([]int{1, 3, 2}, []int{1, 2, 2, 4}) // []int{1, 3, 2, 4}
```
#### Intersection / IntersectionBy
Finds all unique shared values from multiple slices.
```go
Intersection([]int{1, 3, 2}, []int{1, 2, 2, 4}) // []int{1, 2}
```
#### Difference / DifferenceBy
Finds all different values from 2 slices.
```go
left, right := Difference([]int{1, 3, 2}, []int{2, 2, 4}) // left == []int{1, 3}, right == []int{4}
```
#### Reduce / ReduceEx
Reduces a slice to a value.
```go
Reduce([]int{1, 2, 3}, func (accumulator int, currentValue int) int {
return accumulator + currentValue
}) // 6
ReduceEx([]int{1, 2, 3}, func (accumulator int, currentValue int, i index) int {
return accumulator + currentValue
}, 10) // 16
```
#### ReduceReverse / ReduceReverseEx
Reduces a slice to a value with iterating from the end.
```go
ReduceReverse([]int{1, 2, 3}, func (accumulator int, currentValue int) int {
return accumulator + currentValue
}) // 6
ReduceReverseEx([]int{1, 2, 3}, func (accumulator int, currentValue int, i index) int {
return accumulator + currentValue
}, 10) // 16
```
#### Partition / PartitionN
Splits a slice into multiple partitions.
```go
// Splits a slice into 2 partitions
p0, p1 := Partition([]int{1, 2, 3, 4, 5}, func (v int, index int) bool {return v%2==0})) // p0 == []int{2, 4}, p1 == []int{1, 3, 5}
// Splits a slice into 3 partitions
p := PartitionN([]int{1, 2, 3, 4, 5}, 3, func (v int, index int) int {return v%3})) // p == [[3], [1, 4], [2, 5]]
```
#### Flatten / Flatten3
Flattens multi-dimension slice.
```go
Flatten([]int{1, 2, 3}, []int{4, 5}) // []int{1, 2, 3, 4, 5}
Flatten3([][]int{{1, 2}, {3, 4}, [][]int{{5, 6}, {7}}}) // []int{1, 2, 3, 4, 5, 6, 7}
```
#### Zip / Zip\
Combines values from multiple slices by each position (`N` is from 3 to 5).
```go
Zip([]int{1, 2, 3}, []int{4, 5}) // []*Tuple2{{1, 4), {2, 5}}
Zip3([]int{1, 2, 3}, []string{"4", "5"}, []float32{6.0, 7.0}) // []*Tuple3{{1, "4", 6.0), {2, "5", 7.0}}
```
### Slice conversion
---
#### ToIfaceSlice
Converts a slice of any type to a slice of interfaces.
```go
ToIfaceSlice([]int{1, 2, 3}) // []any{1, 2, 3}
ToIfaceSlice([]string{"foo", "bar"}) // []any{"foo", "bar"}
```
#### ToStringSlice
Converts a slice of string-approximate type to a slice of strings.
```go
type XType string
ToStringSlice([]XType{XType("foo"), XType("bar")}) // []string{"foo", "bar"}
```
#### ToNumberSlice
Converts a slice of number type to a slice of specified number type.
```go
ToNumberSlice[int]([]int8{1, 2, 3}) // []int{1, 2, 3}
ToNumberSlice[float32]([]int{1, 2, 3}) // []float32{1.0, 2.0, 3.0}
type XType int
ToNumberSlice[int]([]XType{XType(1), XType(2)}) // []int{1, 2}
```
#### ToSlice
Creates a slice for individual values.
```go
ToSlice(1, 2, 3) // []int{1, 2, 3}
```
#### ToPtrSlice
Creates a slice of pointers point to the given elements.
```go
s1 := []int{1, 2, 3}
s2 := ToPtrSlice(s1) // []*int{&s1[0], &s1[1], &s1[2]}
```
### Slice subset
---
#### ContainSlice
Returns true if a slice contains another slice.
```go
ContainSlice([]int{1, 2, 3, 4, 5}, []int{2, 3, 4}) // true
ContainSlice([]int{1, 2, 3, 4, 5}, []int{2, 4}) // false
```
#### IndexOfSlice
Finds the first occurrence of a sub-slice in a slice.
```go
IndexOfSlice([]int{1, 2, 3, 4, 5}, []int{2, 3, 4}) // 1
IndexOfSlice([]int{1, 2, 3, 4, 5}, []int{2, 4}) // -1
```
#### LastIndexOfSlice
Finds the last occurrence of a sub-slice in a slice.
```go
LastIndexOfSlice([]int{1, 2, 3, 1, 2, 3, 4}, []int{1, 2, 3}) // 3
LastIndexOfSlice([]int{1, 2, 3, 4, 5}, []int{2, 4}) // -1
```
#### SubSlice
Returns sub slice of a slice in range [start, end). `end` param is exclusive. This function doesn't raise error.
Passing negative numbers for `start` and `end` to get items from the end of the slice.
```go
SubSlice([]int{1, 2, 3}, 0, 2) // []{1, 2}
SubSlice([]int{1, 2, 3}, -1, -2) // []{3}
SubSlice([]int{1, 2, 3}, -1, -3) // []{2, 3}
```
### Map
---
#### MapEqual / MapEqualBy
Returns true if 2 maps equal.
```go
MapEqual(map[int]string{1: "one", 2: "two"}, map[int]string{2: "two", 1: "one"}) // true
MapEqual(map[int]string{1: "one", 2: "two"}, map[int]string{1: "one", 2: "TWO"}) // false
```
#### MapContainKeys
Returns true if a map contains all given keys.
```go
MapContainKeys(map[int]int{1: 11, 2: 22}, 1) // true
MapContainKeys(map[int]int{1: 11, 2: 22}, 1, 2) // true
MapContainKeys(map[int]int{1: 11, 2: 22}, 1, 3) // false
```
#### MapContainValues
Returns true if a map contains all given values.
```go
MapContainValues(map[int]int{1: 11, 2: 22}, 11) // true
MapContainValues(map[int]int{1: 11, 2: 22}, 11, 22) // true
MapContainValues(map[int]int{1: 11, 2: 22}, 11, 33) // false
```
#### MapIter
Iterates over the entries of one or multiple maps.
```go
MapIter(func(k int, v string) { ... }, map[int]string{1: "11", 2: "22"}, map[int]string{3: "33", 4: "44"})
```
#### MapKeys
Gets all keys of a map.
```go
MapKeys(map[int]int{1: 11, 2: 22}) // []int{1, 2} (note: values may be in different order)
```
#### MapValues
Gets all values of a map.
```go
MapValues(map[int]int{1: 11, 2: 22, 3: 22}) // []int{11, 22, 22} (note: values may be in different order)
```
#### MapEntries
Gets all entries (key, value) of a map.
```go
MapEntries(map[int]int{1: 11, 2: 22}) // []*Tuple2[int,int]{{1,11}, {2,22}} (note: values may be in different order)
```
#### MapGet
Retrieves map value for a key, returns the default value if not exist.
```go
MapGet(map[int]int{1: 11, 2: 22}, 1, 0) // 11 (found)
MapGet(map[int]int{1: 11, 2: 22}, 3, 0) // 0 (not found)
```
#### MapPop
Removes entry from a map and returns the current value if found.
```go
MapPop(map[int]int{1: 11, 2: 22}, 1, 0) // 11 (found)
MapPop(map[int]int{1: 11, 2: 22}, 3, 0) // 0 (not found)
```
#### MapSetDefault
Sets default value for a key and returns the current value.
```go
MapSetDefault(map[int]int{1: 11, 2: 22}, 1, 0) // 11 (no value added to the map)
MapSetDefault(map[int]int{1: 11, 2: 22}, 3, 0) // 0 (entry [3, 0] is added to the map)
```
#### MapUpdate / MapUpdateExistingOnly / MapUpdateNewOnly
Updates a map content with another map.
```go
s := map[int]int{1: 11, 2: 22}
MapUpdate(s, map[int]int{1: 111, 3: 33}) // s == map[int]int{1: 111, 2: 22, 3: 33}
MapUpdateExistingOnly(s, map[int]int{2: 222, 3: 33}) // s == map[int]int{1: 11, 2: 222}
MapUpdateNewOnly(s, map[int]int{2: 222, 3: 33}) // s == map[int]int{1: 11, 2: 22, 3: 33}
```
#### MapCopy
Copies a map.
```go
MapCopy(map[int]int{1: 11, 2: 22, 3: 33}) // map[int]int{1: 11, 2: 22, 3: 33}
```
#### MapPick
Copies map content for specified keys only.
```go
MapPick(map[int]int{1: 11, 2: 22, 3: 33}, 2, 3, 2) // map[int]int{2: 22, 3: 33}
```
#### MapOmit
Omits keys from a map.
```go
m := map[int]int{1: 11, 2: 22, 3: 33}
MapOmit(m, 2, 3, 4) // m == map[int]int{1: 11}
```
#### MapOmitCopy
Copies map content with omitting specified keys.
```go
MapOmitCopy(map[int]int{1: 11, 2: 22, 3: 33}, 2, 3, 4) // map[int]int{1: 11}
```
#### MapReverse
Returns a new map with exchanging keys and values of the given map.
```go
m, dupKeys := MapReverse(map[int]int{1: 11, 2: 22, 3: 33}) // m == map[int]int{11: 1, 22: 2, 33: 3} and dupKeys == []int{}
m, dupKeys := MapReverse(map[int]int{1: 11, 2: 11, 3: 33}) // m == map[int]int{11: <1 or 2>, 33: 3} and dupKeys == []int{1, 2}
```
### Struct
---
#### StructToMap
Converts struct contents to a map. This function is a shortcut to [rflutil.StructToMap](https://github.com/tiendc/go-rflutil#structtomap).
#### ParseTag / ParseTagOf / ParseTagsOf
Parses struct tags. These functions are shortcuts to [rflutil.ParseTag](https://github.com/tiendc/go-rflutil#parsetag--parsetagof--parsetagsof).
### String
---
#### RuneLength
Alias of `utf8.RuneCountInString`.
```go
len("café") // 5
RuneLength("café") // 4
```
#### RandString / RandStringEx
Generates a random string.
```go
RandString(10) // Generates a string of 10 characters from alphabets and digits
RandStringEx(10, []rune("0123456789")) // Generates a string of 10 characters from the specified ones
```
#### StringJoin / StringJoinEx / StringJoinBy
Joins a slice of any element type.
```go
s := StringJoin([]int{1,2,3}, ", ") // s == "1, 2, 3"
type Struct struct {
I int
S string
}
s := StringJoinBy([]Struct{{I:1, s:"a"}, {I:2, s:"b"}}, ", ", func (v Struct) string {
return fmt.Sprintf("%d:%s", v.I, v.S)
}) // s == "1:a, 2:b"
```
#### StringLexJoin / StringLexJoinEx
Joins a slice of any element type in lexical manner.
```go
StringLexJoin([]int{1,2,3}, ", ", " and ") // return "1, 2 and 3"
// Use a custom format string
StringLexJoinEx([]int64{254, 255}, ", ", " or ", "%#x") // returns "0xfe or 0xff"
```
#### StringWrap / StringUnwrap
Wraps/Unwraps a string with the given tokens.
```go
StringWrap("abc", "*") // "*abc*"
StringWrapLR("abc", "[", "]") // "[abc]"
StringUnwrap("*abc*", "*") // "abc"
StringUnwrapLR("[abc]", "[", "]") // "abc"
```
### Number
---
#### ParseInt / ParseUint / ParseFloat
Parses a number using **strconv.ParseInt** then converts the value to a specific type.
```go
ParseInt[int16]("111") // int16(111)
ParseInt[int8]("128") // strconv.ErrRange
// Return default value on failure
ParseIntDef("200", 10) // int(200)
ParseIntDef("200", int8(10)) // int8(10)
// Parse integer with specific base
ParseIntEx[int8]("eeff1234", 16) // strconv.ErrRange
ParseIntEx[int]("eeff1234", 16) // int value for "eeff1234"
// Parse string containing commas
ParseInt[int]("1,234,567") // strconv.ErrSyntax
ParseIntUngroup[int]("1,234,567") // int(1234567)
```
- **NOTE**: There are also **ParseUint** for unsigned integers and **ParseFloat** for floating numbers.
#### FormatInt / FormatUint / FormatFloat
Formats a number value.
```go
FormatInt(123) // "123"
// Format number with specific format string (use fmt.Sprintf)
FormatIntEx(123, "%05d") // "00123"
// Format number with decimal grouping
FormatIntGroup(1234567) // "1,234,567"
```
- **NOTE**: There are also **FormatUint** for unsigned integers and **FormatFloat** for floating numbers.
#### NumberFmtGroup
Groups digits of a number. Input number is of type string.
```go
NumberFmtGroup("1234567", '.', ',') // "1,234,567"
NumberFmtGroup("1234567", ',', '.') // "1.234.567"
NumberFmtGroup("1234567.12345", '.', ',') // "1,234,567.12345"
```
#### ToSafeInt\ / ToSafeUint\
Safely casts an integer of any type to a specific type.
```go
v, err := ToSafeInt8(-129) // err is ErrOverflow
v, err := ToSafeInt16(math.MaxUint16) // err is ErrOverflow
v, err := ToSafeUint32(math.MaxUint64) // err is ErrOverflow
v, err := ToSafeUint64(-1) // err is ErrOverflow
v, err := ToSafeInt8(127) // err == nil, v == int8(127)
v, err := ToSafeUint16(math.MaxInt16) // err == nil, v == uint16(math.MaxInt16)
v, err := ToSafeInt32(math.MaxInt32) // err == nil, v == int32(math.MaxInt32)
v, err := ToSafeUint64(uint64(math.MaxUint64)) // err == nil, v == uint64(math.MaxUint64)
```
### Concurrency
---
#### ExecTasks / ExecTasksEx
Executes tasks concurrently with ease. This function provides a convenient way for one of the most
popular use case in practical.
```go
// In case you want to store the task results into a shared variable,
// make sure you use enough synchronization
var task1Result any
var task2Result []any
// Allow spending maximum 10s to finish all the tasks
ctx := context.WithTimeout(context.Background(), 10 * time.Second)
err := ExecTasks(ctx, 0 /* max concurrent tasks */,
// Task 1st:
func(ctx context.Context) (err error) {
task1Result, err = getDataFromDB()
return err
},
// Task 2nd:
func(ctx context.Context) (err error) {
for i:=0; i<10; i++ {
if err := ctx.Err(); err != nil {
return err
}
task2Result = append(task2Result, )
return nil
}
},
)
if err != nil {
// one or more tasks failed
}
```
#### ExecTaskFunc / ExecTaskFuncEx
Executes a task function on every target objects concurrently. This function is similar to `ExecTasks()`, but it
takes only one function and a list of target objects.
```go
var mu sync.Mutex
var evens []int
var odds []any
taskFunc := func(ctx context.Context, v int) error {
mu.Lock()
if v%2 == 0 {
evens = append(evens, v)
} else {
odds = append(odds, v)
}
mu.Unlock()
return nil
}
err := ExecTaskFunc(ctx, 0 /* max concurrent tasks */, taskFunc, 1, 2, 3, 4, 5)
if err != nil {
// one or more tasks failed
}
// Result is: evens has [2, 4], odds has [1, 3, 5] (with undetermined order of items)
```
### Function
---
#### Bind\Arg\Ret
Fully binds a function with returning a function which requires no argument.
```go
func myCalc(a1 int, a2 string) error { ... }
myQuickCalc := Bind2Arg1Ret(myCalc, 100, "hello")
err := myQuickCalc()
```
#### Partial\Arg\Ret
Partially binds the first argument of a function.
```go
func myCalc(a1 int, a2 string) error { ... }
myQuickCalc := Partial2Arg1Ret(myCalc, 100)
err := myQuickCalc("hello")
```
### Randomization
**NOTE**: Should not use these functions for crypto purpose.
---
#### RandChoice
Picks up an item randomly from a list of items.
```go
val, valid := RandChoice(1, 2, 3) // valid == true and `val` is one of the input items
val, valid := RandChoice[int]() // valid == false and `val` is int(0)
```
#### RandChoiceMaker
Provides a method to pick up items randomly from a list of items without duplication of choice.
```go
choiceMaker := NewRandChoiceMaker([]int{1, 2, 3})
for choiceMaker.HasNext() {
randItem, _ := choiceMaker.Next()
}
// OR
for {
randItem, valid := choiceMaker.Next()
if !valid {
break
}
}
```
### Error handling
---
#### ErrWrap / ErrWrapL
Convenient functions to wrap a single error with a single message.
```go
// shortcut call of fmt.Errorf("%w: %s")
e := ErrWrap(err, "failed to do something")
// shortcut call of fmt.Errorf("%s: %w")
e := ErrWrapL("failed to do something", err)
```
#### ErrUnWrap
Unwraps an error into a slice of errors. This function can unwrap an error created by `errors.Join()` and
`fmt.Errorf()`.
```go
e1 := errors.New("e1")
e2 := errors.New("e2")
e3 := errors.Join(e1, e2)
e4 := fmt.Errorf("%w, %w", e1, e2)
errs := ErrUnwrap(e1) // errs == []error{e1}
errs := ErrUnwrap(e3) // errs == []error{e1,e2}
errs := ErrUnwrap(e4) // errs == []error{e1,e2}
```
#### ErrUnwrapToRoot
Unwraps an error until the deepest one.
```go
e1 := errors.New("e1")
e2 := fmt.Errorf("e2: %w", e1)
e3 := fmt.Errorf("e3: %w", e2)
e := ErrUnwrapToRoot(e3) // e == e1
e := ErrUnwrapToRoot(e2) // e == e1
```
### Time
---
#### MinTime / MaxTime / MinMaxTime
Finds minimum/maximum time value in a slice.
```go
t0 := time.Time{}
t1 := time.Date(2000, time.December, 1, 0, 0, 0, 0, time.UTC)
t2 := time.Date(2001, time.December, 1, 0, 0, 0, 0, time.UTC)
MinTime(t0, t1, t2) // t0
MinTime(t1, t2) // t1
MaxTime(t0, t1, t2) // t2
MinMaxTime(t0, t1, t2) // t0, t2
```
#### ExecDuration / ExecDurationN
Measures time executing a function.
```go
duration := ExecDuration(func() { // do something })
// The given function returns a value
outVal, duration := ExecDuration1(func() string { return "hello" }) // outVal == "hello"
// The given function returns 2 values
outVal1, err, duration := ExecDuration2(func() (int, error) { return 123, nil }) // outVal1 == 123, err == nil
```
#### ExecDelay
Executes a function after a time duration. This function is just an alias of `time.AfterFunc`.
```go
timer := ExecDelay(3*time.Second, func() {
// do something after waiting 3 seconds
})
```
### Math
---
#### All
Returns `true` if all given values are evaluated `true`.
```go
All(1, "1", 0.5) // true
All(1, "1", 0.0) // false
All(1, "", -1) // false
All() // true
```
#### Any
Returns `true` if any of the given values is evaluated `true`.
```go
Any(1, "", 0.5) // true
Any(1, "1", 0.0) // true
Any(0, "", 0.0) // false
Any() // false
```
#### Abs
Calculates absolute value of an integer.
```go
Abs(-123) // int64(123)
Abs(123) // int64(123)
Abs(math.MinInt64) // math.MinInt64 (special case)
```
#### Clamp
Clamps a value within a range (lower and upper bounds are inclusive).
```go
Clamp(3, 10, 20) // 10
Clamp(30, 10, 20) // 20
Clamp(15, 10, 20) // 15
```
#### Min / Max / MinMax
Finds minimum/maximum value in a slice.
```go
Min(1, 2, 3, -1) // -1
MinIn([]int{1, 2, 3, -1}) // -1
MinInBy([]string{"a", "B"}, func(a, b int) bool {
return strings.ToLower(a) < strings.ToLower(b)
}) // "a"
Max(1, 2, 3, -1) // 3
MaxIn([]int{1, 2, 3, -1}) // 3
MaxInBy([]string{"a", "B"}, func(a, b int) bool {
return strings.ToLower(a) < strings.ToLower(b)
}) // "B"
MinMax(1, 2, 3, -1) // -1, 3
```
#### Sum / SumAs
Calculates `sum` value of slice elements.
```go
Sum([]int{1, 2, 3}) // 6
SumAs[int]([]int8{50, 60, 70}) // 180 (Sum() will fail as the result overflows int8)
```
#### Product / ProductAs
Calculates `product` value of slice elements.
```go
Product([]int{1, 2, 3}) // 6
ProductAs[int]([]int8{5, 6, 7}) // 210 (Product() will fail as the result overflows int8)
```
### Utility
---
#### FirstNonEmpty
Returns the first non-empty value in the given arguments if found, otherwise returns the zero value.
This function uses `reflection`. You can connsider using `Coalesce` for generic types.
Values considered "non-empty" are:
- not empty values (0, empty string, false, nil, ...)
- not empty containers (slice, array, map, channel)
- not pointers that point to zero/empty values
```go
FirstNonEmpty(0, -1, 2, 3) // -1
FirstNonEmpty("", " ", "b") // " "
FirstNonEmpty([]int{}, nil, []int{1}, []int{2, 3}) // []int{1}
FirstNonEmpty([]int{}, nil, &[]int{}, []int{2, 3}) // []int{2, 3}
FirstNonEmpty[any](nil, 0, 0.0, "", struct{}{}) // nil (the first zero value)
```
#### Coalesce
Returns the first non-zero argument. Input type must be comparable.
```go
Coalesce(0, -1, 2) // -1
Coalesce("", " ", "s") // " "
Coalesce[*int](ptr1, ptr2) // the first non-nil pointer
```
#### If
A convenient function works like C ternary operator.
**NOTE**: This function is deprecated as it may cause the program to crash on misuses due to both passing
expressions are evaluated regardless of the condition.
For example: `firstItem := If(len(slice) > 0, slice[0], defaultVal)` will crash if `slice` is empty as
the expression `slice[0]` is always evaluated. Use it at your own risk.
```go
val := If(x > 100, val1, val2) // If x > 100, val == val1, otherwise val == val2
```
#### Must\
Must\ ( N is from 1 to 6) functions accept a number of arguments with the last one is of `error` type.
Must\ functions return the first N-1 arguments if the error is `nil`, otherwise they panic.
```go
func CalculateAmount() (int, error) {}
amount := Must(CalculateAmount()) // panic on error, otherwise returns the amount
func CalculateData() (int, string, float64, error) {}
v1, v2, v3 := Must4(CalculateData()) // panic on error, otherwise returns the 3 first values
```
#### ToPtr
Returns a pointer to the input argument.
```go
func aFunc(ptr *int) {}
// Use ToPtr to pass a value inline
aFunc(ToPtr(10))
```
#### Head
Takes the first argument.
```go
Head(1, "2", 1.0, 3) // 1
```
#### Tail
Takes the last argument.
```go
Tail[string](true, "2", 1.0, "3") // "3"
```
## Benchmarks
#### Equal vs ContentEqual vs reflect.DeepEqual
___
```
Benchmark_Slice_Equal/StructSlice/Equal
Benchmark_Slice_Equal/StructSlice/Equal-8 510845715 2.047 ns/op
Benchmark_Slice_Equal/StructSlice/ContentEqual
Benchmark_Slice_Equal/StructSlice/ContentEqual-8 583167950 2.061 ns/op
Benchmark_Slice_Equal/StructSlice/DeepEqual
Benchmark_Slice_Equal/StructSlice/DeepEqual-8 15403771 79.19 ns/op
Benchmark_Slice_Equal/IntSlice/Equal
Benchmark_Slice_Equal/IntSlice/Equal-8 589706185 2.087 ns/op
Benchmark_Slice_Equal/IntSlice/ContentEqual
Benchmark_Slice_Equal/IntSlice/ContentEqual-8 523120755 2.194 ns/op
Benchmark_Slice_Equal/IntSlice/DeepEqual
Benchmark_Slice_Equal/IntSlice/DeepEqual-8 15243183 77.93 ns/op
```
## Contributing
- You are welcome to make pull requests for new functions and bug fixes.
## License
- [MIT License](LICENSE)
[doc-img]: https://pkg.go.dev/badge/github.com/tiendc/gofn
[doc]: https://pkg.go.dev/github.com/tiendc/gofn
[gover-img]: https://img.shields.io/badge/Go-%3E%3D%201.18-blue
[gover]: https://img.shields.io/badge/Go-%3E%3D%201.18-blue
[ci-img]: https://github.com/tiendc/gofn/actions/workflows/go.yml/badge.svg
[ci]: https://github.com/tiendc/gofn/actions/workflows/go.yml
[cov-img]: https://codecov.io/gh/tiendc/gofn/branch/master/graph/badge.svg
[cov]: https://codecov.io/gh/tiendc/gofn
[rpt-img]: https://goreportcard.com/badge/github.com/tiendc/gofn
[rpt]: https://goreportcard.com/report/github.com/tiendc/gofn