{"id":29117203,"url":"https://github.com/tiendc/gofn","last_synced_at":"2026-01-12T08:32:07.900Z","repository":{"id":65142981,"uuid":"578309079","full_name":"tiendc/gofn","owner":"tiendc","description":"High performance utility functions using Generics","archived":false,"fork":false,"pushed_at":"2025-02-13T10:19:39.000Z","size":295,"stargazers_count":52,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-06T07:37:42.842Z","etag":null,"topics":["filter","functional","generics","go","golang","searching","transform","typesafe"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tiendc.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-12-14T18:43:21.000Z","updated_at":"2025-03-23T14:14:39.000Z","dependencies_parsed_at":"2023-11-25T09:32:13.683Z","dependency_job_id":"905fa931-0d8d-4a5b-9468-acb765c63ab0","html_url":"https://github.com/tiendc/gofn","commit_stats":null,"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"purl":"pkg:github/tiendc/gofn","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tiendc%2Fgofn","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tiendc%2Fgofn/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tiendc%2Fgofn/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tiendc%2Fgofn/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tiendc","download_url":"https://codeload.github.com/tiendc/gofn/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tiendc%2Fgofn/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262581514,"owners_count":23331925,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["filter","functional","generics","go","golang","searching","transform","typesafe"],"created_at":"2025-06-29T11:14:14.644Z","updated_at":"2026-01-12T08:32:07.893Z","avatar_url":"https://github.com/tiendc.png","language":"Go","funding_links":[],"categories":["Utilities","公用事业公司"],"sub_categories":["Utility/Miscellaneous","实用程序/Miscellaneous"],"readme":"\n[![Go Version][gover-img]][gover] [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] [![GoReport][rpt-img]][rpt]\n\n# gofn - Utility functions for Go 1.18+\n\nThis is a collection of generics utility functions for Go 1.18+.\n\n## Functionalities\n\n`gofn` consists of useful and convenient functions for most common needs when working on slices, maps, structs, transformation, conversion, and so on.\n\nCheck out my other libs:\n- [go-deepcopy](https://github.com/tiendc/go-deepcopy): True deep copy function\n- [go-validator](https://github.com/tiendc/go-validator): Validation library\n- [go-apperrors](https://github.com/tiendc/go-apperrors): Manipulating application errors\n- [go-csvlib](https://github.com/tiendc/go-csvlib): High-level rich functionality CSV encoding and decoding library\n\n## Installation\n\n```shell\ngo get github.com/tiendc/gofn\n```\n\n## Function list\n\n**Slice**\n  - [Equal / EqualBy](#equal--equalby)\n  - [ContentEqual / ContentEqualBy](#contentequal--contentequalby)\n  - [Sort / IsSorted](#sort--issorted)\n  - [RemoveAt](#removeat)\n  - [FastRemoveAt](#fastremoveat)\n  - [Remove](#remove)\n  - [FastRemove](#fastremove)\n  - [RemoveLastOf](#removelastof)\n  - [FastRemoveLastOf](#fastremovelastof)\n  - [RemoveAll](#removeall)\n  - [Replace / ReplaceN / ReplaceAll](#replace--replacen--replaceall)\n  - [Splice / SpliceEx](#splice--spliceex)\n  - [Concat](#concat)\n  - [Fill](#fill)\n  - [SliceByRange](#slicebyrange)\n  - [First / FirstOr](#first--firstor)\n  - [Last / LastOr](#last--lastor)\n\n**Slice searching**\n  - [Contain](#contain--containby)\n  - [ContainAll](#containall)\n  - [ContainAny](#containany)\n  - [Find / FindEx](#find--findex)\n  - [FindLast / FindLastEx](#findlast--findlastex)\n  - [IndexOf / IndexOfBy](#indexof--indexofby)\n  - [LastIndexOf / LastIndexOfBy](#lastindexof--lastindexofby)\n  - [CountValue / CountValueBy](#countvalue--countvalueby)\n\n**Slice filtering**\n  - [Filter](#filter)\n  - [FilterNE](#filter)\n  - [FilterLT / FilterLTE](#filter)\n  - [FilterGT / FilterGTE](#filter)\n  - [FilterRange](#filter)\n  - [FilterIN / FilterNIN](#filter)\n  - [FilterLIKE / FilterILIKE](#filter)\n\n**Slice iteration**\n  - [ForEach / ForEachReverse](#foreach--foreachreverse)\n  - [Iter / IterReverse](#iter--iterreverse)\n\n**Slice uniqueness**\n  - [IsUnique / IsUniqueBy](#isunique--isuniqueby)\n  - [FindUniques / FindUniquesBy](#finduniques--finduniquesby)\n  - [FindDuplicates / FindDuplicatesBy](#findduplicates--findduplicatesby)\n  - [ToSet / ToSetBy / ToSetByReverse ](#toset--tosetby--tosetbyreverse)\n\n**Slice transformation**\n  - [MapSlice / MapSliceEx](#mapslice--mapsliceex)\n  - [MapSliceToMap / MapSliceToMapEx](#mapslicetomap--mapslicetomapex)\n  - [MapSliceToMapKeys](#mapslicetomapkeys)\n\n**Slice algo**\n  - [Compact](#compact)\n  - [Drop](#drop)\n  - [Reverse / ReverseCopy](#reverse--reversecopy)\n  - [Shuffle](#shuffle)\n  - [Chunk / ChunkByPieces](#chunk--chunkbypieces)\n  - [Union / UnionBy](#union--unionby)\n  - [Intersection / IntersectionBy](#intersection--intersectionby)\n  - [Difference / DifferenceBy](#difference--differenceby)\n  - [Reduce / ReduceEx](#reduce--reduceex)\n  - [ReduceReverse / ReduceReverseEx](#reducereverse--reducereverseex)\n  - [Partition / PartitionN](#partition--partitionn)\n  - [Flatten / Flatten3](#flatten--flatten3)\n  - [Zip / Zip\\\u003cN\\\u003e](#zip--zipn)\n\n**Slice conversion**\n  - [ToIfaceSlice](#toifaceslice)\n  - [ToStringSlice](#tostringslice)\n  - [ToNumberSlice](#tonumberslice)\n  - [ToSlice](#toslice)\n  - [ToSliceSkippingNil / ToSliceSkippingZero](#tosliceskippingnil--tosliceskippingzero)\n  - [ToPtrSlice](#toptrslice)\n\n**Slice subset**\n  - [ContainSlice](#containslice)\n  - [IndexOfSlice](#indexofslice)\n  - [LastIndexOfSlice](#lastindexofslice)\n  - [SubSlice](#subslice)\n\n**Map**\n  - [MapEqual / MapEqualBy](#mapequal--mapequalby)\n  - [MapContainKeys](#mapcontainkeys)\n  - [MapContainValues](#mapcontainvalues)\n  - [MapIter](#mapiter)\n  - [MapKeys](#mapkeys)\n  - [MapValues](#mapvalues)\n  - [MapEntries](#mapentries)\n  - [MapGet](#mapget)\n  - [MapPop](#mappop)\n  - [MapSetDefault](#mapsetdefault)\n  - [MapUpdate / MapUpdateExistingOnly / MapUpdateNewOnly](#mapupdate--mapupdateexistingonly--mapupdatenewonly)\n  - [MapCopy](#mapcopy)\n  - [MapPick](#mappick)\n  - [MapOmit](#mapomit)\n  - [MapOmitCopy](#mapomitcopy)\n  - [MapReverse](#mapreverse)\n\n**Struct**\n  - [StructToMap](#structtomap)\n  - [ParseTag / ParseTagOf / ParseTagsOf](#parsetag--parsetagof--parsetagsof)\n\n**String**\n  - [RuneLength](#runelength)\n  - [RandString / RandStringEx](#randstring--randstringex)\n  - [StringSplit / StringSplitN](#stringsplit--stringsplitn)\n  - [StringJoin / StringJoinEx / StringJoinBy](#stringjoin--stringjoinex--stringjoinby)\n  - [StringLexJoin / StringLexJoinEx](#stringlexjoin--stringlexjoinex)\n  - [StringWrap / StringUnwrap](#stringwrap--stringunwrap)\n  - [StringToUpper1stLetter / StringToLower1stLetter](#stringtoupper1stletter--stringtolower1stletter)\n\n**Number**\n  - [ParseInt / ParseUint / ParseFloat](#parseint--parseuint--parsefloat)\n  - [FormatInt / FormatUint / FormatFloat](#formatint--formatuint--formatfloat)\n  - [NumberFmtGroup](#numberfmtgroup)\n  - [ToSafeInt\\\u003cN\\\u003e / ToSafeUint\\\u003cN\\\u003e](#tosafeintn--tosafeuintn)\n\n**Time**\n  - [MinTime / MaxTime / MinMaxTime](#mintime--maxtime--minmaxtime)\n  - [ExecDuration / ExecDurationN](#execduration--execdurationn)\n  - [ExecDelay](#execdelay)\n\n**Math**\n  - [All](#all)\n  - [Any](#any)\n  - [Abs](#abs)\n  - [Clamp](#clamp)\n  - [Min / Max / MinMax](#min--max--minmax)\n  - [Sum / SumAs](#sum--sumas)\n  - [Product / ProductAs](#product--productas)\n\n**Concurrency**\n  - [ExecTasks / ExecTasksEx](#exectasks--exectasksex)\n  - [ExecTaskFunc / ExecTaskFuncEx](#exectaskfunc--exectaskfuncex)\n\n**Function**\n  - [Bind\\\u003cN\\\u003eArg\\\u003cM\\\u003eRet ](#bindnargmret)\n  - [Partial\\\u003cN\\\u003eArg\\\u003cM\\\u003eRet ](#partialnargmret)\n\n**Randomization**\n  - [RandChoice](#randchoice)\n  - [RandChoiceMaker](#randchoicemaker)\n  - [RandToken / RandTokenAsHex](#randtoken--randtokenashex)\n\n**Error handling**\n  - [ErrWrap / ErrWrapL](#errwrap--errwrapl)\n  - [ErrUnwrap](#errunwrap)\n  - [ErrUnwrapToRoot](#errunwraptoroot)\n\n**Utility**\n  - [FirstNonEmpty](#firstnonempty)\n  - [Coalesce](#coalesce)\n  - [If](#if)\n  - [Must\\\u003cN\\\u003e](#mustn)\n  - [ToPtr](#toptr)\n  - [PtrValueOrEmpty](#ptrvalueorempty)\n  - [Head](#head)\n  - [Tail](#tail)\n\n\n### Slice\n---\n \n#### Equal / EqualBy\n\nReturns true if 2 slices have the same size and all elements of them equal in the current order.\n\n```go\nEqual([]int{1, 2, 3}, []int{1, 2, 3}) // true\nEqual([]int{1, 2, 3}, []int{3, 2, 1}) // false\n\n// Use EqualBy for custom equal comparison\nEqualBy([]string{\"one\", \"TWO\"}, []string{\"ONE\", \"two\"}, strings.EqualFold) // true\n```\n \n#### ContentEqual / ContentEqualBy\n\nReturns true if 2 slices have the same size and contents equal regardless of the order of elements.\n\n```go\nContentEqual([]int{1, 2, 3}, []int{2, 1, 3})       // true\nContentEqual([]int{1, 2, 2, 3}, []int{2, 1, 3})    // false\nContentEqual([]int{1, 2, 2, 3}, []int{2, 1, 2, 3}) // true\nContentEqual([]int{1, 2, 2, 3}, []int{1, 1, 2, 3}) // false\n\n// Use ContentEqualBy for custom key function\nContentEqualBy([]string{\"one\", \"TWO\"}, []string{\"two\", \"ONE\"}, strings.ToLower) // true\n```\n\n#### RemoveAt\n\nRemoves element at the specified index.\n\n```go\ns := []int{1, 2, 3}\nRemoveAt(\u0026s, 1) // s == []int{1, 3}\n```\n\n#### FastRemoveAt\n\nRemoves element at the specified index by swapping it with the last element of the slice.\nThis function is fast as it doesn't cause copying of slice content.\n\n```go\ns := []int{1, 2, 3, 4}\nFastRemoveAt(\u0026s, 1) // s == []int{1, 4, 3} (2 and 4 are exchanged)\n```\n \n#### Sort / IsSorted\n \nConvenient wrappers of the built-in `sort.Slice`.\n\n```go\nSort([]int{1, 3, 2})         // []int{1, 2, 3}\nSortDesc([]int{1, 3, 2})     // []int{3, 2, 1}\nIsSorted([]int{1, 3, 2})     // false\nIsSortedDesc([]int{3, 2, 1}) // true\n```\n\n#### Remove\n\nRemoves a value from a slice.\n\n```go\ns := []int{1, 2, 3}\nRemove(\u0026s, 1) // s == []int{2, 3}\n```\n\n#### FastRemove\n\nRemoves a value from a slice by swapping it with the last element of the slice.\n\n```go\ns := []int{1, 2, 3, 4}\nFastRemove(\u0026s, 2) // s == []int{1, 4, 3} (2 and 4 are exchanged)\n```\n\n#### RemoveLastOf\n\nRemoves last occurrence of a value from a slice.\n\n```go\ns := []int{1, 2, 1, 3}\nRemoveLastOf(\u0026s, 1) // s == []int{1, 2, 3}\n```\n\n#### FastRemoveLastOf\n\nRemoves last occurrence of a value from a slice by swapping it with the last element of the slice.\n\n```go\ns := []int{1, 2, 1, 3, 4}\nFastRemoveLastOf(\u0026s, 1) // s == []int{1, 2, 4, 3} (1 and 4 are exchanged)\n```\n\n#### RemoveAll\n\nRemoves all occurrences of a value from a slice.\n\n```go\ns := []int{1, 2, 1, 3, 1}\nRemoveAll(\u0026s, 1) // s == []int{2, 3}\n```\n\n#### Replace / ReplaceN / ReplaceAll\n\nReplaces a value with another value.\n\n```go\n// Replaces first occurrence of the value\nReplace([]int{1, 2, 1, 3, 1}, 1, 11)     // []int{11, 2, 1, 3, 1}\n// Replaces first n occurrences of the value (use -1 to replace all)\nReplaceN([]int{1, 2, 1, 3, 1}, 1, 11, 2) // []int{11, 2, 11, 3, 1}\n// Replaces all occurrences of the value\nReplaceAll([]int{1, 2, 1, 3, 1}, 1, 11)  // []int{11, 2, 11, 3, 11}\n```\n\n#### Splice / SpliceEx\n\nRemoves a portion of the given slice and inserts elements of another slice into that position.\n\nUsage: `Splice(s []T, start int, deleteCount int, insertingElements ...T)`.\nIf `start \u003c -len(s)`, `0` is used.\nIf `-len(s) \u003c= start \u003c 0`, `start + len(s)` is used.\nIf `start \u003e= len(s)`, no element will be deleted, but the new elements are still added to the end.\nIf `deleteCount \u003c= 0`, no elements will be removed.\n\n```go\ns := Splice([]int{1, 2, 3}, 1, 1, 100)         // s == []int{1, 100, 3}\ns := Splice([]int{1, 2, 3}, 1, 0, 100)         // s == []int{1, 100, 2, 3}\ns := Splice([]int{1, 2, 3}, 1, 1, 100, 101)    // s == []int{1, 100, 101, 3}\ns := Splice([]int{1, 2, 3}, 0, 2, 100, 101)    // s == []int{100, 101, 3}\n\n// Use SpliceEx to get deleted items as the 2nd returning value\ns, delItems := SpliceEx([]int{1, 2, 3}, 1, 1, 100)        // s == []int{1, 100, 3}, delItems == []int{2}\ns, delItems := SpliceEx([]int{1, 2, 3}, 1, 0, 100)        // s == []int{1, 100, 2, 3}, delItems == []int{}\ns, delItems := SpliceEx([]int{1, 2, 3}, 1, 1, 100, 101)   // s == []int{1, 100, 101, 3}, delItems == []int{2}\ns, delItems := SpliceEx([]int{1, 2, 3}, 0, 2, 100, 101)   // s == []int{100, 101, 3}, delItems == []int{1, 2}\n```\n\n#### Concat\n \nConcatenates two or more slices.\n\n```go\nConcat([]int{1}, []int{2}, []int{2, 3}) // []int{1, 2, 2, 3}\n```\n\n#### Fill\n\nFills a slice with specified value.\n\n```go\ns := make([]int, 5)\nFill(s, 1)  // s == []int{1, 1, 1, 1, 1}\n\ns2 := s[2:4]\nFill(s2, 1) // s2 == []int{1, 1}, s == []int{0, 0, 1, 1, 0}\n```\n\n#### First / FirstOr\n\nReturns the first element of slice.\n\n```go\nFirst([]int{1, 2, 3})      // 1, true\nFirst([]string{})          // \"\", false\n\n// Return the default value if slice is empty\nFirstOr([]int{1, 2, 3}, 4) // 1\nFirstOr([]int{}, 11)       // 11\n```\n\n#### Last / LastOr\n\nReturns the last element of slice.\n\n```go\nLast([]int{1, 2, 3})       // 3, true\nLast([]string{})           // \"\", false\n\n// Return the default value if slice is empty\nLastOr([]int{1, 2, 3}, 4)  // 3\nLastOr([]int{}, 11)        // 11\n```\n\n#### SliceByRange\n\nGenerates a slice for the given range.\n\n```go\ns := SliceByRange(0, 5, 1)         // []int{0, 1, 2, 3, 4}\ns := SliceByRange(0.0, 5, 2)       // []float64{0, 2, 4}\ns := SliceByRange(int32(5), 0, -2) // []int32{5, 3, 1}\n```\n \n### Slice searching\n---\n\n#### Contain / ContainBy\n\nReturns true if a slice contains a value.\n\n```go\nContain([]int{1, 2, 3}, 2) // true\nContain([]int{1, 2, 3}, 0) // false\n\n// Use ContainBy for custom function\nContainBy([]string{\"one\", \"TWO\"}, func(elem string) bool {\n    return strings.ToLower(elem) == \"two\"\n}) // true\n```\n\n#### ContainAll\n\nReturns true if a slice contains all given values.\n\n```go\nContainAll([]int{1, 2, 3, 4, 5}, 2, 4, 3) // true\nContainAll([]int{1, 2, 3, 4, 5}, 2, 7)    // false\n```\n\n#### ContainAny\n\nReturns true if a slice contains any of the given values.\n\n```go\nContainAny([]int{1, 2, 3, 4, 5}, 2, 4, 7) // true\nContainAny([]int{1, 2, 3, 4, 5}, 7, 8, 9) // false\n```\n \n#### Find / FindEx\n\nFinds a value in a slice.\n\n```go\nv, found := Find([]string{\"one\", \"TWO\"}, func(elem string) bool {\n    return strings.ToLower(elem) == \"two\"\n}) // v == \"TWO\", found == true\n\n// FindEx lets the given function decide which value to return\ntype Person struct {\n    Name string\n    Age  int\n}\n// Finds age of person having name \"Tim\"\nageOfTim, found := FindEx([]Person{{\"John\", 40}, {\"Tim\", 50}}, func(p Person) (int, bool) {\n    return p.Age, p.Name == \"Tim\"\n}) // ageOfTim == 50, found == true\n```\n\n#### FindLast / FindLastEx\n\nFinds a value in a slice from the end.\n\n```go\nv, found := FindLast([]string{\"one\", \"TWO\", \"ONe\"}, func(elem string) bool {\n    return strings.ToLower(elem) == \"one\"\n}) // v == \"ONe\", found == true\n\n// FindLastEx lets the given function decide which value to return\ntype Person struct {\n    Name string\n    Age  int\n}\n// Finds age of the last person having name \"tim\"\nageOfTim, found := FindLastEx([]Person{{\"John\", 40}, {\"Tim\", 50}, {\"TIM\", 60}}, func(p Person) (int, bool) {\n    return p.Age, strings.ToLower(p.Name) == \"tim\"\n}) // ageOfTim == 60, found == true\n```\n\n#### IndexOf / IndexOfBy\n\nFinds the index of a value in a slice, returns -1 if not found.\n\n```go\nIndexOf([]int{1, 2, 3}, 4) // -1\nIndexOf([]int{1, 2, 3}, 2) // 1\n\n// Use IndexOfBy for custom function\nIndexOfBy([]string{\"one\", \"TWO\"}, func(elem string) bool {\n    return strings.ToLower(elem) == \"two\"\n}) // 1\n```\n\n#### LastIndexOf / LastIndexOfBy\n\nFinds the last index of an element in a slice, returns -1 if not found.\n\n```go\nLastIndexOf([]int{1, 2, 3}, 4)    // -1\nLastIndexOf([]int{1, 2, 1, 3}, 1) // 2\n```\n \n#### CountValue / CountValueBy\n\nCounts the number of occurrences of a value in a slice.\n\n```go\nCountValue([]int{1, 2, 3}, 4)    // 0\nCountValue([]int{1, 2, 3, 2}, 2) // 2\n```\n\n### Slice filtering\n---\n\n#### Filter\n\nFilters a slice with condition.\n\n```go\nFilter([]int{1, 2, 3, 4}, func (i int) bool {\n    return i % 2 == 0\n}) // []int{2, 4}\n\nFilterLT([]int{1, 2, 3, 4}, 3)        // []int{1, 2}\nFilterLTE([]int{1, 2, 3, 4}, 3)       // []int{1, 2, 3}\nFilterGT([]int{1, 2, 3, 4}, 3)        // []int{4}\nFilterGTE([]int{1, 2, 3, 4}, 3)       // []int{3, 4}\nFilterRange([]int{1, 2, -2, 4}, 0, 3) // []int{1, 2}\nFilterNE([]int{1, 2, 3, 4}, 3)        // []int{1, 2, 4}\nFilterIN([]int{1, 2, 3, 4}, 3, 2, 7)  // []int{2, 3}\nFilterNIN([]int{1, 2, 3, 4}, 3, 2, 7) // []int{1, 4}\nFilterLIKE([]string{\"*Abc*\", \"*abc*\", \"abc*\", \"*abc\"}, \"Abc\")  // []string{\"*Abc*\"}\nFilterILIKE([]string{\"*Abc*\", \"*abc*\", \"abc*\", \"*abc\"}, \"Abc\") // []string{\"*Abc*\", \"*abc*\", \"abc*\", \"*abc\"}\n```\n\n### Slice iteration\n---\n\n#### ForEach / ForEachReverse\n\nIterates over slice content.\n\n```go\nForEach([]int{1, 2, 3}, func (i, v int) {\n    fmt.Printf(\"%d \", v)\n}) // prints 1 2 3\n\nForEachReverse([]int{1, 2, 3}, func (i, v int) {\n    fmt.Printf(\"%d \", v)\n}) // prints 3 2 1\n\n// ForEachPtr can be faster if you iterate over a slice of big structs\nForEachPtr([]BigStruct{...}, func (i, v *BigStruct) { ... })\nForEachPtrReverse([]BigStruct{...}, func (i, v *BigStruct) { ... })\n```\n\n#### Iter / IterReverse\n\nIterates over one or multiple slices with ability to stop.\n\n```go\nIter(func (i, v int) bool {\n    fmt.Printf(\"%d \", v)\n    return i \u003c 3\n}, []int{1, 2, 3}, []int{4, 5}) // prints 1 2 3 4\n\nIterReverse(func (i, v int) bool {\n    fmt.Printf(\"%d \", v)\n    return true\n}, []int{1, 2, 3}, []int{4, 5}) // prints 5 4 3 2 1\n\n// IterPtr can be faster if you iterate over a slice of big structs\nIterPtr(func (i, v *BigStruct) bool { ... }, []BigStruct{...})\nIterPtrReverse(func (i, v *BigStruct) bool { ... }, []BigStruct{...})\n```\n\n### Slice uniqueness\n---\n\n#### IsUnique / IsUniqueBy\n\nReturns true if a slice contains unique values.\n\n```go\nIsUnique([]int{1, 2, 3}) // true\nIsUnique([]int{1, 2, 1}) // false\n\n// Use IsUniqueBy for custom function\nIsUniqueBy([]string{\"one\", \"ONE\"}, strings.ToLower) // false\n```\n\n#### FindUniques / FindUniquesBy\n\nFinds all unique elements in the given slice. The order of elements in the result is the same\nas they appear in the input.\n\n```go\nFindUniques([]int{1, 2, 3, 2})  // []int{1, 3}\nFindUniques([]int{1, 2, 2, 1})  // []int{}\n\n// FindUniquesBy supports custom key function\nFindUniquesBy([]string{\"one\", \"ONE\", \"Two\"}, func (s string) string {\n    return strings.ToLower(s)\n}) // []string{\"Two\"}\n```\n\n#### FindDuplicates / FindDuplicatesBy\n\nFinds all elements which are duplicated in the given slice. The order of elements in the result is the same\nas they appear in the input.\n\n```go\nFindDuplicates([]int{1, 2, 3, 2})  // []int{2}\nFindDuplicates([]int{1, 2, 3})     // []int{}\n\n// FindDuplicatesBy supports custom key function\nFindDuplicatesBy([]string{\"one\", \"ONE\", \"Two\"}, func (s string) string {\n    return strings.ToLower(s)\n}) // []string{\"one\"}\n```\n\n#### ToSet / ToSetBy / ToSetByReverse\n\nCalculates unique values of a slice.\n\n```go\nToSet([]int{1, 2, 3, 1, 2})        // []int{1, 2, 3}\nToSet([]string{\"one\", \"2\", \"one\"}) // []string{\"one\", \"2\"}\n\n// Use ToSetBy for custom key function\nToSetBy([]string{\"one\", \"TWO\", \"two\", \"One\"}, strings.ToLower) // []string{\"one\", \"TWO\"}\n\n\n// Use ToSetByReverse for iterating items from the end of the list\nToSetByReverse([]string{\"one\", \"TWO\", \"two\", \"One\"}, strings.ToLower) // []string{\"One\", \"two\"}\n```\n\n### Slice transformation\n---\n\n#### MapSlice / MapSliceEx\n\nTransforms a slice to a slice.\n\n```go\nMapSlice([]string{\"1\", \"2 \", \" 3\"}, strings.TrimSpace)   // []string{\"1\", \"2\", \"3\"}\n\n// Use MapSliceEx to transform with error handling\nMapSliceEx([]string{\"1\",\"2\",\"3\"}, gofn.ParseInt[int])    // []int{1, 2, 3}\nMapSliceEx([]string{\"1\",\"x\",\"3\"}, gofn.ParseInt[int])    // strconv.ErrSyntax\nMapSliceEx([]string{\"1\",\"200\",\"3\"}, gofn.ParseInt[int8]) // strconv.ErrRange\n```\n\n#### MapSliceToMap / MapSliceToMapEx\n\nTransforms a slice to a map.\n\n```go\nMapSliceToMap([]int{1, 2, 3}, func (i int) (int, string) {\n    return i, fmt.Sprintf(\"%d\", i)\n}) // map[int]string{1: \"1\", 2: \"2\", 3: \"3\"}\n\n// Use MapSliceToMapEx to transform with error handling\nMapSliceToMapEx([]string{\"1\",\"300\",\"3\"}, func (s string) (string, int, bool) {\n    v, e := gofn.ParseInt[int8](s)\n    return s, v, e\n}) // strconv.ErrRange\n```\n\n#### MapSliceToMapKeys\n\nTransforms a slice to a map with using slice items as map keys.\n\n```go\nMapSliceToMapKeys([]int{1, 2, 3, 2}, \"x\")     // map[int]string{1: \"x\", 2: \"x\", 3: \"x\"}\nMapSliceToMapKeys([]int{1, 2, 1}, struct{}{}) // map[int]struct{}{1: struct{}{}, 2: struct{}{}}\n```\n\n### Slice algo\n---\n\n#### Compact\n\nCompacts a slice by removing zero elements.  Not change the source slice.\n\n```go\ns := Compact([]int{1, 0, 3}) // s == []int{1, 3}\n```\n\n#### Reverse / ReverseCopy\n\nReverses slice content.\n\n```go\nReverse([]int{1, 2, 3}) // []int{3, 2, 1}\n\ns1 := []int{1, 2, 3}\ns2 := ReverseCopy(s1)  // s1 == []int{1, 2, 3}, s2 == []int{3, 2, 1}\n```\n\n#### Drop\n\nReturns a new slice with dropping values in the specified list.\nNOTE: this function is just a call to `FilterNIN()`.\n\n```go\nDrop([]int{1, 2, 3, 4}, 3, 1) // []int{2, 4}\n```\n\n#### Shuffle\n\nShuffle items of a slice. Not change the source slice.\n\n```go\ns := Shuffle([]int{1, 2, 3}) // s is a new slice with random items of the input\n```\n\n#### Chunk / ChunkByPieces\n\nSplits slice content into chunks.\n\n```go\nChunk([]int{1, 2, 3, 4, 5}, 2)         // [][]int{[]int{1, 2}, []int{3, 4}, []int{5}}\nChunkByPieces([]int{1, 2, 3, 4, 5}, 2) // [][]int{[]int{1, 2, 3}, []int{4, 5}}\n```\n\n#### Union / UnionBy\n\nFinds all unique values from multiple slices.\n\n```go\nUnion([]int{1, 3, 2}, []int{1, 2, 2, 4}) // []int{1, 3, 2, 4}\n```\n\n#### Intersection / IntersectionBy\n\nFinds all unique shared values from multiple slices.\n\n```go\nIntersection([]int{1, 3, 2}, []int{1, 2, 2, 4}) // []int{1, 2}\n```\n\n#### Difference / DifferenceBy\n\nFinds all different values from 2 slices.\n\n```go\nleft, right := Difference([]int{1, 3, 2}, []int{2, 2, 4}) // left == []int{1, 3}, right == []int{4}\n```\n \n#### Reduce / ReduceEx\n\nReduces a slice to a value.\n\n```go\nReduce([]int{1, 2, 3}, func (accumulator int, currentValue int) int {\n    return accumulator + currentValue\n}) // 6\n\nReduceEx([]int{1, 2, 3}, func (accumulator int, currentValue int, i index) int {\n    return accumulator + currentValue\n}, 10) // 16\n```\n\n#### ReduceReverse / ReduceReverseEx\n\nReduces a slice to a value with iterating from the end.\n\n```go\nReduceReverse([]int{1, 2, 3}, func (accumulator int, currentValue int) int {\n    return accumulator + currentValue\n}) // 6\n\nReduceReverseEx([]int{1, 2, 3}, func (accumulator int, currentValue int, i index) int {\n    return accumulator + currentValue\n}, 10) // 16\n```\n\n#### Partition / PartitionN\n\nSplits a slice into multiple partitions.\n\n```go\n// Splits a slice into 2 partitions\np0, 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}\n\n// Splits a slice into 3 partitions\np := PartitionN([]int{1, 2, 3, 4, 5}, 3, func (v int, index int) int {return v%3})) // p == [[3], [1, 4], [2, 5]]\n```\n\n#### Flatten / Flatten3\n\nFlattens multi-dimension slice.\n\n```go\nFlatten([]int{1, 2, 3}, []int{4, 5})                    // []int{1, 2, 3, 4, 5}\nFlatten3([][]int{{1, 2}, {3, 4}, [][]int{{5, 6}, {7}}}) // []int{1, 2, 3, 4, 5, 6, 7}\n```\n\n#### Zip / Zip\\\u003cN\\\u003e\n\nCombines values from multiple slices by each position (`N` is from 3 to 5).\n\n```go\nZip([]int{1, 2, 3}, []int{4, 5})                              // []*Tuple2{{1, 4), {2, 5}}\nZip3([]int{1, 2, 3}, []string{\"4\", \"5\"}, []float32{6.0, 7.0}) // []*Tuple3{{1, \"4\", 6.0), {2, \"5\", 7.0}}\n```\n\n### Slice conversion\n---\n\n#### ToIfaceSlice\n\nConverts a slice of any type to a slice of interfaces.\n\n```go\nToIfaceSlice([]int{1, 2, 3})         // []any{1, 2, 3}\nToIfaceSlice([]string{\"foo\", \"bar\"}) // []any{\"foo\", \"bar\"}\n```\n\n#### ToStringSlice\n\nConverts a slice of string-approximate type to a slice of strings.\n\n```go\ntype XType string\nToStringSlice([]XType{XType(\"foo\"), XType(\"bar\")}) // []string{\"foo\", \"bar\"}\n```\n\n#### ToNumberSlice\n\nConverts a slice of number type to a slice of specified number type.\n\n```go\nToNumberSlice[int]([]int8{1, 2, 3})    // []int{1, 2, 3}\nToNumberSlice[float32]([]int{1, 2, 3}) // []float32{1.0, 2.0, 3.0}\n\ntype XType int\nToNumberSlice[int]([]XType{XType(1), XType(2)}) // []int{1, 2}\n```\n\n#### ToSlice\n\nCreates a slice for individual values.\n\n```go\nToSlice(1, 2, 3) // []int{1, 2, 3}\n```\n\n#### ToSliceSkippingNil / ToSliceSkippingZero\n\nCreates a slice for individual values with skipping nil or zero ones.\n\n```go\nv1 := 1\nv2 := 2\nToSliceSkippingNil(nil, \u0026v1, nil, \u0026v2) // []*int{\u0026v1, \u0026v2}\n\nToSliceSkippingZero(1, 2, 0, 3)        // []int{1, 2, 3}\nToSliceSkippingZero(\"\", \"a\", \"\")       // []string{\"a\"}\n```\n\n#### ToPtrSlice\n\nCreates a slice of pointers point to the given elements.\n\n```go\ns1 := []int{1, 2, 3}\ns2 := ToPtrSlice(s1) // []*int{\u0026s1[0], \u0026s1[1], \u0026s1[2]}\n```\n\n### Slice subset\n---\n\n#### ContainSlice\n\nReturns true if a slice contains another slice.\n\n```go\nContainSlice([]int{1, 2, 3, 4, 5}, []int{2, 3, 4}) // true\nContainSlice([]int{1, 2, 3, 4, 5}, []int{2, 4})    // false\n```\n\n#### IndexOfSlice\n\nFinds the first occurrence of a sub-slice in a slice.\n\n```go\nIndexOfSlice([]int{1, 2, 3, 4, 5}, []int{2, 3, 4}) // 1\nIndexOfSlice([]int{1, 2, 3, 4, 5}, []int{2, 4})    // -1\n```\n\n#### LastIndexOfSlice\n\nFinds the last occurrence of a sub-slice in a slice.\n\n```go\nLastIndexOfSlice([]int{1, 2, 3, 1, 2, 3, 4}, []int{1, 2, 3}) // 3\nLastIndexOfSlice([]int{1, 2, 3, 4, 5}, []int{2, 4})          // -1\n```\n\n#### SubSlice\n\nReturns sub slice of a slice in range [start, end). `end` param is exclusive. This function doesn't raise error.\nPassing negative numbers for `start` and `end` to get items from the end of the slice.\n\n```go\nSubSlice([]int{1, 2, 3}, 0, 2)   // []{1, 2}\nSubSlice([]int{1, 2, 3}, -1, -2) // []{3}\nSubSlice([]int{1, 2, 3}, -1, -3) // []{2, 3}\n```\n\n### Map \n---\n \n#### MapEqual / MapEqualBy\n\nReturns true if 2 maps equal.\n\n```go\nMapEqual(map[int]string{1: \"one\", 2: \"two\"}, map[int]string{2: \"two\", 1: \"one\"}) // true\nMapEqual(map[int]string{1: \"one\", 2: \"two\"}, map[int]string{1: \"one\", 2: \"TWO\"}) // false\n```\n\n#### MapContainKeys\n\nReturns true if a map contains all given keys.\n\n```go\nMapContainKeys(map[int]int{1: 11, 2: 22}, 1)    // true\nMapContainKeys(map[int]int{1: 11, 2: 22}, 1, 2) // true\nMapContainKeys(map[int]int{1: 11, 2: 22}, 1, 3) // false\n```\n\n#### MapContainValues\n\nReturns true if a map contains all given values.\n\n```go\nMapContainValues(map[int]int{1: 11, 2: 22}, 11)     // true\nMapContainValues(map[int]int{1: 11, 2: 22}, 11, 22) // true\nMapContainValues(map[int]int{1: 11, 2: 22}, 11, 33) // false\n```\n\n#### MapIter\n\nIterates over the entries of one or multiple maps.\n\n```go\nMapIter(func(k int, v string) { ... }, map[int]string{1: \"11\", 2: \"22\"}, map[int]string{3: \"33\", 4: \"44\"})\n```\n\n#### MapKeys\n\nGets all keys of a map.\n\n```go\nMapKeys(map[int]int{1: 11, 2: 22}) // []int{1, 2} (note: values may be in different order)\n```\n\n#### MapValues\n\nGets all values of a map.\n\n```go\nMapValues(map[int]int{1: 11, 2: 22, 3: 22}) // []int{11, 22, 22} (note: values may be in different order)\n```\n\n#### MapEntries\n\nGets all entries (key, value) of a map.\n\n```go\nMapEntries(map[int]int{1: 11, 2: 22}) // []*Tuple2[int,int]{{1,11}, {2,22}} (note: values may be in different order)\n```\n \n#### MapGet\n\nRetrieves map value for a key, returns the default value if not exist.\n\n```go\nMapGet(map[int]int{1: 11, 2: 22}, 1, 0) // 11 (found)\nMapGet(map[int]int{1: 11, 2: 22}, 3, 0) // 0 (not found)\n```\n\n#### MapPop\n\nRemoves entry from a map and returns the current value if found.\n\n```go\nMapPop(map[int]int{1: 11, 2: 22}, 1, 0) // 11 (found)\nMapPop(map[int]int{1: 11, 2: 22}, 3, 0) // 0 (not found)\n```\n\n#### MapSetDefault\n\nSets default value for a key and returns the current value.\n\n```go\nMapSetDefault(map[int]int{1: 11, 2: 22}, 1, 0) // 11 (no value added to the map)\nMapSetDefault(map[int]int{1: 11, 2: 22}, 3, 0) // 0 (entry [3, 0] is added to the map)\n```\n\n#### MapUpdate / MapUpdateExistingOnly / MapUpdateNewOnly\n\nUpdates a map content with another map.\n\n```go\ns := map[int]int{1: 11, 2: 22}\nMapUpdate(s, map[int]int{1: 111, 3: 33})             // s == map[int]int{1: 111, 2: 22, 3: 33}\nMapUpdateExistingOnly(s, map[int]int{2: 222, 3: 33}) // s == map[int]int{1: 11, 2: 222}\nMapUpdateNewOnly(s, map[int]int{2: 222, 3: 33})      // s == map[int]int{1: 11, 2: 22, 3: 33}\n```\n\n#### MapCopy\n\nCopies a map.\n\n```go\nMapCopy(map[int]int{1: 11, 2: 22, 3: 33}) // map[int]int{1: 11, 2: 22, 3: 33}\n```\n\n#### MapPick\n\nCopies map content for specified keys only.\n\n```go\nMapPick(map[int]int{1: 11, 2: 22, 3: 33}, 2, 3, 2) // map[int]int{2: 22, 3: 33}\n```\n\n#### MapOmit\n\nOmits keys from a map.\n\n```go\nm := map[int]int{1: 11, 2: 22, 3: 33}\nMapOmit(m, 2, 3, 4) // m == map[int]int{1: 11}\n```\n\n#### MapOmitCopy\n\nCopies map content with omitting specified keys.\n\n```go\nMapOmitCopy(map[int]int{1: 11, 2: 22, 3: 33}, 2, 3, 4) // map[int]int{1: 11}\n```\n\n#### MapReverse\n\nReturns a new map with exchanging keys and values of the given map.\n\n```go\nm, dupKeys := MapReverse(map[int]int{1: 11, 2: 22, 3: 33}) // m == map[int]int{11: 1, 22: 2, 33: 3} and dupKeys == []int{}\nm, dupKeys := MapReverse(map[int]int{1: 11, 2: 11, 3: 33}) // m == map[int]int{11: \u003c1 or 2\u003e, 33: 3} and dupKeys == []int{1, 2}\n```\n\n### Struct\n---\n\n#### StructToMap\n\nConverts struct contents to a map. This function is a shortcut to [rflutil.StructToMap](https://github.com/tiendc/go-rflutil#structtomap).\n\n#### ParseTag / ParseTagOf / ParseTagsOf\n\nParses struct tags. These functions are shortcuts to [rflutil.ParseTag](https://github.com/tiendc/go-rflutil#parsetag--parsetagof--parsetagsof).\n\n### String\n---\n\n#### RuneLength\n\nAlias of `utf8.RuneCountInString`.\n\n```go\nlen(\"café\")        // 5\nRuneLength(\"café\") // 4\n```\n\n#### RandString / RandStringEx\n\nGenerates a random string.\n\n```go\nRandString(10)                         // Generates a string of 10 characters from alphabets and digits\nRandStringEx(10, []rune(\"0123456789\")) // Generates a string of 10 characters from the specified ones\n```\n\n#### StringSplit / StringSplitN\n\nSplits a string with handling quotes by ignoring any separator within the quotes.\n\n```go\nStringSplit(\"ab cd \\\"12 34\\\"\", \" \", \"\\\"\")     // \"[]string{\"ab\", \"cd\", \"\\\"12 34\\\"\"}\nStringSplit(\"ab cd {12 34}\", \" \", \"{ }\")      // \"[]string{\"ab\", \"cd\", \"{12 34}\"}\nStringSplitN(\"ab cd {12 34}\", \" \", \"{ }\", 2)  // \"[]string{\"ab\", \"cd {12 34}\"}\n```\n\n#### StringJoin / StringJoinEx / StringJoinBy\n\nJoins a slice of any element type.\n\n```go\ns := StringJoin([]int{1,2,3}, \", \") // s == \"1, 2, 3\"\n\ntype Struct struct {\n    I int\n    S string\n}\ns := StringJoinBy([]Struct{{I:1, s:\"a\"}, {I:2, s:\"b\"}}, \", \", func (v Struct) string {\n    return fmt.Sprintf(\"%d:%s\", v.I, v.S)\n}) // s == \"1:a, 2:b\"\n```\n\n#### StringLexJoin / StringLexJoinEx\n\nJoins a slice of any element type in lexical manner.\n\n```go\nStringLexJoin([]int{1,2,3}, \", \", \" and \")              // return \"1, 2 and 3\"\n\n// Use a custom format string\nStringLexJoinEx([]int64{254, 255}, \", \", \" or \", \"%#x\") // returns \"0xfe or 0xff\"\n```\n\n#### StringWrap / StringUnwrap\n\nWraps/Unwraps a string with the given tokens.\n\n```go\nStringWrap(\"abc\", \"*\")            // \"*abc*\"\nStringWrapLR(\"abc\", \"[\", \"]\")     // \"[abc]\"\n\nStringUnwrap(\"*abc*\", \"*\")        // \"abc\"\nStringUnwrapLR(\"[abc]\", \"[\", \"]\") // \"abc\"\n```\n\n#### StringToUpper1stLetter / StringToLower1stLetter\n\nCapitalizes the first letter of a string.\n\n```go\nStringToUpper1stLetter(\"abc\")  // \"Abc\"\nStringToLower1stLetter(\"Abc\")  // \"abc\"\n```\n\n### Number\n---\n\n#### ParseInt / ParseUint / ParseFloat\n\nParses a number using **strconv.ParseInt** then converts the value to a specific type.\n\n```go\nParseInt[int16](\"111\")            // int16(111)\nParseInt[int8](\"128\")             // strconv.ErrRange\n\n// Return default value on failure\nParseIntDef(\"200\", 10)            // int(200)\nParseIntDef(\"200\", int8(10))      // int8(10)\n\n// Parse integer with specific base\nParseIntEx[int8](\"eeff1234\", 16)  // strconv.ErrRange\nParseIntEx[int](\"eeff1234\", 16)   // int value for \"eeff1234\"\n\n// Parse string containing commas\nParseInt[int](\"1,234,567\")        // strconv.ErrSyntax\nParseIntUngroup[int](\"1,234,567\") // int(1234567)\n```\n\n- **NOTE**: There are also **ParseUint** for unsigned integers and **ParseFloat** for floating numbers.\n\n#### FormatInt / FormatUint / FormatFloat\n\nFormats a number value.\n\n```go\nFormatInt(123)            // \"123\"\n\n// Format number with specific format string (use fmt.Sprintf)\nFormatIntEx(123, \"%05d\")  // \"00123\"\n\n// Format number with decimal grouping\nFormatIntGroup(1234567)   // \"1,234,567\"\n```\n\n- **NOTE**: There are also **FormatUint** for unsigned integers and **FormatFloat** for floating numbers.\n\n#### NumberFmtGroup\n\nGroups digits of a number. Input number is of type string.\n\n```go\nNumberFmtGroup(\"1234567\", '.', ',')         // \"1,234,567\"\nNumberFmtGroup(\"1234567\", ',', '.')         // \"1.234.567\"\nNumberFmtGroup(\"1234567.12345\", '.', ',')   // \"1,234,567.12345\"\n```\n\n#### ToSafeInt\\\u003cN\\\u003e / ToSafeUint\\\u003cN\\\u003e\n\nSafely casts an integer of any type to a specific type.\n\n```go\nv, err := ToSafeInt8(-129)                     // err is ErrOverflow\nv, err := ToSafeInt16(math.MaxUint16)          // err is ErrOverflow\nv, err := ToSafeUint32(math.MaxUint64)         // err is ErrOverflow\nv, err := ToSafeUint64(-1)                     // err is ErrOverflow\n\nv, err := ToSafeInt8(127)                      // err == nil, v == int8(127)\nv, err := ToSafeUint16(math.MaxInt16)          // err == nil, v == uint16(math.MaxInt16)\nv, err := ToSafeInt32(math.MaxInt32)           // err == nil, v == int32(math.MaxInt32)\nv, err := ToSafeUint64(uint64(math.MaxUint64)) // err == nil, v == uint64(math.MaxUint64)\n```\n\n### Concurrency\n---\n\n#### ExecTasks / ExecTasksEx\n\nExecutes tasks concurrently with ease. This function provides a convenient way for one of the most\npopular use case in practical.\n\n```go\n// In case you want to store the task results into a shared variable,\n// make sure you use enough synchronization\nvar task1Result any\nvar task2Result []any\n\n// Allow spending maximum 10s to finish all the tasks\nctx := context.WithTimeout(context.Background(), 10 * time.Second)\n\nerr := ExecTasks(ctx, 0 /* max concurrent tasks */,\n    // Task 1st:\n    func(ctx context.Context) (err error) {\n        task1Result, err = getDataFromDB()\n        return err\n    },\n    // Task 2nd:\n    func(ctx context.Context) (err error) {\n        for i:=0; i\u003c10; i++ {\n            if err := ctx.Err(); err != nil {\n                return err\n            }\n            task2Result = append(task2Result, \u003csome data\u003e)\n            return nil\n        }\n    },\n)\nif err != nil {\n    // one or more tasks failed\n}\n```\n\n#### ExecTaskFunc / ExecTaskFuncEx\n\nExecutes a task function on every target objects concurrently. This function is similar to `ExecTasks()`, but it\ntakes only one function and a list of target objects.\n\n```go\nvar mu sync.Mutex\nvar evens []int\nvar odds []any\n\ntaskFunc := func(ctx context.Context, v int) error {\n    mu.Lock()\n    if v%2 == 0 {\n        evens = append(evens, v)\n    } else {\n        odds = append(odds, v)\n    }\n    mu.Unlock()\n    return nil\n}\n\nerr := ExecTaskFunc(ctx, 0 /* max concurrent tasks */, taskFunc, 1, 2, 3, 4, 5)\nif err != nil {\n    // one or more tasks failed\n}\n\n// Result is: evens has [2, 4], odds has [1, 3, 5] (with undetermined order of items)\n```\n\n### Function\n---\n\n#### Bind\\\u003cN\\\u003eArg\\\u003cM\\\u003eRet\n\nFully binds a function with returning a function which requires no argument.\n\n```go\nfunc myCalc(a1 int, a2 string) error { ... }\nmyQuickCalc := Bind2Arg1Ret(myCalc, 100, \"hello\")\nerr := myQuickCalc()\n```\n\n#### Partial\\\u003cN\\\u003eArg\\\u003cM\\\u003eRet\n\nPartially binds the first argument of a function.\n\n```go\nfunc myCalc(a1 int, a2 string) error { ... }\nmyQuickCalc := Partial2Arg1Ret(myCalc, 100)\nerr := myQuickCalc(\"hello\")\n```\n\n### Randomization\n\n**NOTE**: Should not use these functions for crypto purpose.\n\n---\n\n#### RandChoice\n\nPicks up an item randomly from a list of items.\n\n```go\nval, valid := RandChoice(1, 2, 3) // valid == true and `val` is one of the input items\nval, valid := RandChoice[int]()   // valid == false and `val` is int(0)\n```\n\n#### RandChoiceMaker\n\nProvides a method to pick up items randomly from a list of items without duplication of choice.\n\n```go\nchoiceMaker := NewRandChoiceMaker([]int{1, 2, 3})\nfor choiceMaker.HasNext() {\n    randItem, _ := choiceMaker.Next()\n}\n// OR\nfor {\n    randItem, valid := choiceMaker.Next()\n    if !valid {\n        break\n    }\n}\n```\n\n#### RandToken / RandTokenAsHex\n\nGenerates random tokens using `crypto/rand` package. The output can be used for crypto purpose.\nThese functions should not be called concurrently.\n\n```go\nRandToken(numBytes)      // returns []byte of size numBytes\nRandTokenAsHex(numBytes) // returns a string of size numBytes*2\n```\n\n### Error handling\n---\n\n#### ErrWrap / ErrWrapL\n\nConvenient functions to wrap a single error with a single message.\n\n```go\n// shortcut call of fmt.Errorf(\"%w: %s\")\ne := ErrWrap(err, \"failed to do something\")\n\n// shortcut call of fmt.Errorf(\"%s: %w\")\ne := ErrWrapL(\"failed to do something\", err)\n```\n\n#### ErrUnWrap\n\nUnwraps an error into a slice of errors. This function can unwrap an error created by `errors.Join()` and\n`fmt.Errorf(\u003cone or multiple errors passed here\u003e)`.\n\n```go\ne1 := errors.New(\"e1\")\ne2 := errors.New(\"e2\")\ne3 := errors.Join(e1, e2)\ne4 := fmt.Errorf(\"%w, %w\", e1, e2)\n\nerrs := ErrUnwrap(e1) // errs == []error{e1}\nerrs := ErrUnwrap(e3) // errs == []error{e1,e2}\nerrs := ErrUnwrap(e4) // errs == []error{e1,e2}\n```\n\n#### ErrUnwrapToRoot\n\nUnwraps an error until the deepest one.\n\n```go\ne1 := errors.New(\"e1\")\ne2 := fmt.Errorf(\"e2: %w\", e1)\ne3 := fmt.Errorf(\"e3: %w\", e2)\n\ne := ErrUnwrapToRoot(e3) // e == e1\ne := ErrUnwrapToRoot(e2) // e == e1\n```\n\n### Time\n---\n\n#### MinTime / MaxTime / MinMaxTime\n\nFinds minimum/maximum time value in a slice.\n\n```go\nt0 := time.Time{}\nt1 := time.Date(2000, time.December, 1, 0, 0, 0, 0, time.UTC)\nt2 := time.Date(2001, time.December, 1, 0, 0, 0, 0, time.UTC)\nMinTime(t0, t1, t2)     // t0\nMinTime(t1, t2)         // t1\nMaxTime(t0, t1, t2)     // t2\nMinMaxTime(t0, t1, t2)  // t0, t2\n```\n\n#### ExecDuration / ExecDurationN\n\nMeasures time executing a function.\n\n```go\nduration := ExecDuration(func() { // do something })\n// The given function returns a value\noutVal, duration := ExecDuration1(func() string { return \"hello\" })              // outVal == \"hello\"\n// The given function returns 2 values\noutVal1, err, duration := ExecDuration2(func() (int, error) { return 123, nil }) // outVal1 == 123, err == nil\n```\n\n#### ExecDelay\n\nExecutes a function after a time duration. This function is just an alias of `time.AfterFunc`.\n\n```go\ntimer := ExecDelay(3*time.Second, func() {\n    // do something after waiting 3 seconds\n})\n```\n\n### Math \n---\n\n#### All\n\nReturns `true` if all given values are evaluated `true`.\n\n```go\nAll(1, \"1\", 0.5) // true\nAll(1, \"1\", 0.0) // false\nAll(1, \"\", -1)   // false\nAll()            // true\n```\n\n#### Any\n\nReturns `true` if any of the given values is evaluated `true`.\n\n```go\nAny(1, \"\", 0.5)  // true\nAny(1, \"1\", 0.0) // true\nAny(0, \"\", 0.0)  // false\nAny()            // false\n```\n\n#### Abs\n\nCalculates absolute value of an integer.\n\n```go\nAbs(-123)          // int64(123)\nAbs(123)           // int64(123)\nAbs(math.MinInt64) // math.MinInt64 (special case)\n```\n \n#### Clamp\n\nClamps a value within a range (lower and upper bounds are inclusive).\n\n```go\nClamp(3, 10, 20)  // 10\nClamp(30, 10, 20) // 20\nClamp(15, 10, 20) // 15\n```\n\n#### Min / Max / MinMax\n\nFinds minimum/maximum value in a slice.\n\n```go\nMin(1, 2, 3, -1)             // -1\nMinIn([]int{1, 2, 3, -1})    // -1\nMinInBy([]string{\"a\", \"B\"}, func(a, b int) bool {\n    return strings.ToLower(a) \u003c strings.ToLower(b)\n}) // \"a\"\n\nMax(1, 2, 3, -1)             // 3\nMaxIn([]int{1, 2, 3, -1})    // 3\nMaxInBy([]string{\"a\", \"B\"}, func(a, b int) bool {\n    return strings.ToLower(a) \u003c strings.ToLower(b)\n}) // \"B\"\n\nMinMax(1, 2, 3, -1)          // -1, 3\n```\n\n#### Sum / SumAs\n\nCalculates `sum` value of slice elements.\n\n```go\nSum([]int{1, 2, 3})            // 6\nSumAs[int]([]int8{50, 60, 70}) // 180 (Sum() will fail as the result overflows int8)\n```\n\n#### Product / ProductAs\n\nCalculates `product` value of slice elements.\n\n```go\nProduct([]int{1, 2, 3})         // 6\nProductAs[int]([]int8{5, 6, 7}) // 210 (Product() will fail as the result overflows int8)\n```\n\n### Utility\n---\n\n#### FirstNonEmpty\n\nReturns the first non-empty value in the given arguments if found, otherwise returns the zero value.\nThis function uses `reflection`. You can connsider using `Coalesce` for generic types.\n\nValues considered \"non-empty\" are:\n  - not empty values (0, empty string, false, nil, ...)\n  - not empty containers (slice, array, map, channel)\n  - not pointers that point to zero/empty values\n\n```go\nFirstNonEmpty(0, -1, 2, 3)                          // -1\nFirstNonEmpty(\"\", \" \", \"b\")                         // \" \"\nFirstNonEmpty([]int{}, nil, []int{1}, []int{2, 3})  // []int{1}\nFirstNonEmpty([]int{}, nil, \u0026[]int{}, []int{2, 3})  // []int{2, 3}\nFirstNonEmpty[any](nil, 0, 0.0, \"\", struct{}{})     // nil (the first zero value)\n```\n\n#### Coalesce\n\nReturns the first non-zero argument. Input type must be comparable.\n\n```go\nCoalesce(0, -1, 2)         // -1\nCoalesce(\"\", \" \", \"s\")     // \" \"\nCoalesce[*int](ptr1, ptr2) // the first non-nil pointer\n```\n\n#### If\n\nA convenient function works like C ternary operator.\n\n**WARNING**: This function may cause the program to crash on misuses due to both passing\nexpressions are evaluated regardless of the condition.\nFor example: `firstItem := If(len(slice) \u003e 0, slice[0], defaultVal)` will crash if `slice` is empty as\nthe expression `slice[0]` is always evaluated. Use it at your own risk.\n\n```go\nval := If(x \u003e 100, val1, val2) // If x \u003e 100, val == val1, otherwise val == val2\n```\n\n#### Must\\\u003cN\\\u003e\n\nMust\\\u003cN\\\u003e ( N is from 1 to 6) functions accept a number of arguments with the last one is of `error` type.\nMust\\\u003cN\\\u003e functions return the first N-1 arguments if the error is `nil`, otherwise they panic.\n\n```go\nfunc CalculateAmount() (int, error) {}\namount := Must(CalculateAmount()) // panic on error, otherwise returns the amount\n\nfunc CalculateData() (int, string, float64, error) {}\nv1, v2, v3 := Must4(CalculateData()) // panic on error, otherwise returns the 3 first values\n```\n\n#### ToPtr\n\nReturns a pointer to the input argument.\n\n```go\nfunc aFunc(ptr *int) {}\n\n// Use ToPtr to pass a value inline\naFunc(ToPtr(10))\n```\n\n#### PtrValueOrEmpty\n\nReturns the value pointed by a pointer or an empty value if it's nil.\n\n```go\nPtrValueOrEmpty(ToPtr(3))   // 3\nPtrValueOrEmpty[int](nil)   // 0\nPtrValueOrEmpty[*int](nil)  // nil\n```\n\n#### Head\n\nTakes the first argument.\n\n```go\nHead(1, \"2\", 1.0, 3)   // 1\n```\n\n#### Tail\n\nTakes the last argument.\n\n```go\nTail[string](true, \"2\", 1.0, \"3\") // \"3\"\n``` \n\n## Benchmarks\n\n#### Equal vs ContentEqual vs reflect.DeepEqual\n___\n\n```\nBenchmark_Slice_Equal/StructSlice/Equal\nBenchmark_Slice_Equal/StructSlice/Equal-8           510845715           2.047 ns/op\nBenchmark_Slice_Equal/StructSlice/ContentEqual\nBenchmark_Slice_Equal/StructSlice/ContentEqual-8    583167950           2.061 ns/op\nBenchmark_Slice_Equal/StructSlice/DeepEqual\nBenchmark_Slice_Equal/StructSlice/DeepEqual-8       15403771            79.19 ns/op\n\nBenchmark_Slice_Equal/IntSlice/Equal\nBenchmark_Slice_Equal/IntSlice/Equal-8              589706185           2.087 ns/op\nBenchmark_Slice_Equal/IntSlice/ContentEqual\nBenchmark_Slice_Equal/IntSlice/ContentEqual-8       523120755           2.194 ns/op\nBenchmark_Slice_Equal/IntSlice/DeepEqual\nBenchmark_Slice_Equal/IntSlice/DeepEqual-8          15243183            77.93 ns/op\n```\n\n## Contributing\n\n- You are welcome to make pull requests for new functions and bug fixes.\n\n## License\n\n- [MIT License](LICENSE)\n\n[doc-img]: https://pkg.go.dev/badge/github.com/tiendc/gofn\n[doc]: https://pkg.go.dev/github.com/tiendc/gofn\n[gover-img]: https://img.shields.io/badge/Go-%3E%3D%201.18-blue\n[gover]: https://img.shields.io/badge/Go-%3E%3D%201.18-blue\n[ci-img]: https://github.com/tiendc/gofn/actions/workflows/go.yml/badge.svg\n[ci]: https://github.com/tiendc/gofn/actions/workflows/go.yml\n[cov-img]: https://codecov.io/gh/tiendc/gofn/branch/master/graph/badge.svg\n[cov]: https://codecov.io/gh/tiendc/gofn\n[rpt-img]: https://goreportcard.com/badge/github.com/tiendc/gofn\n[rpt]: https://goreportcard.com/report/github.com/tiendc/gofn\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftiendc%2Fgofn","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftiendc%2Fgofn","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftiendc%2Fgofn/lists"}