├── .github └── workflows │ ├── code.yml │ ├── doc.yml │ └── test.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── README.tmpl.md ├── array.go ├── array_test.go ├── cmd ├── generate.go └── generate_test.go ├── fp.go ├── fp_test.go ├── go.mod ├── map.go ├── map_test.go ├── math.go ├── math_test.go ├── test_test.go └── type.go /.github/workflows/code.yml: -------------------------------------------------------------------------------- 1 | name: Code 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | branches: ["main"] 8 | 9 | jobs: 10 | analysis: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - name: Set up Go 16 | uses: actions/setup-go@v4 17 | with: 18 | go-version: "1.18" 19 | 20 | - name: Revive Action 21 | uses: morphy2k/revive-action@v2.5.2 22 | 23 | - name: Check formatting 24 | run: test -z $(gofmt -l .) || (gofmt -l . && exit 1) 25 | 26 | - name: Spelling Check 27 | uses: reviewdog/action-misspell@v1.13.1 28 | 29 | - name: golangci-lint 30 | uses: golangci/golangci-lint-action@v3 31 | with: 32 | version: v1.54 33 | args: --timeout=30m 34 | -------------------------------------------------------------------------------- /.github/workflows/doc.yml: -------------------------------------------------------------------------------- 1 | name: Document 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | branches: ["main"] 8 | 9 | jobs: 10 | document: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - name: Copy README.md 16 | run: cp README.md README.md.bak 17 | 18 | - name: Generate README.md 19 | run: make doc 20 | 21 | - name: Compare README.md 22 | run: | 23 | diff README.md README.md.bak 24 | if [ $? -ne 0 ]; then 25 | echo "README.md is not generated from README.tmpl.md" 26 | exit 1 27 | else 28 | echo "README.md is generated from README.tmpl.md" 29 | fi 30 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | branches: ["main"] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - name: Set up Go 16 | uses: actions/setup-go@v4 17 | with: 18 | go-version: "1.18" 19 | 20 | - name: Test 21 | run: make test TEST_FLAGS="-race -coverprofile=coverage.txt -covermode=atomic" 22 | 23 | - name: Upload coverage reports to Codecov 24 | uses: codecov/codecov-action@v3 25 | env: 26 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 27 | with: 28 | file: ./coverage.txt 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 su chen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: doc test 2 | 3 | doc: 4 | env GFNCWD=`pwd` go run ./cmd/generate.go 5 | 6 | test: 7 | go test -race -v ./... ${TEST_FLAGS} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gfn 2 | 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/suchen-sci/gfn?style=flat-square)](https://goreportcard.com/report/github.com/suchen-sci/gfn) 4 | [![Coverage](https://codecov.io/gh/suchen-sci/gfn/branch/main/graph/badge.svg)](https://app.codecov.io/gh/suchen-sci/gfn/tree/main) 5 | [![Tests](https://github.com/suchen-sci/gfn/actions/workflows/test.yml/badge.svg)](https://github.com/suchen-sci/gfn/actions/workflows/test.yml) 6 | [![Releases](https://img.shields.io/github/release/suchen-sci/gfn/all.svg?style=flat-square)](https://github.com/suchen-sci/gfn/releases) 7 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/suchen-sci/gfn/blob/main/LICENSE) 8 | 9 | `gfn` is a Golang library that leverages generics to provide various methods, including common functional programming techniques such as `Map`, `Reduce`, and `Filter`, along with other utilities like `Contains`, `Keys`, etc. The idea of this library is very simple, it aims to port as many small utilities from other languages to `Go` as possible. The implementation is highly influenced by`Python`, `Ruby`, `JavaScript` and `Lodash`. 10 | 11 | 1. No `reflect`. 12 | 2. No third-party packages. 13 | 3. Time complexity of `O(n)`. 14 | 15 | - [Documentation](#documentation) 16 | - [Contributing](#contributing) 17 | - [License](#license) 18 | 19 | ## Documentation 20 | 21 | - [Installation](#installation) 22 | - [Usage](#usage) 23 | - [Type](#type) 24 | - [Functional](#functional) 25 | - [gfn.Filter](#gfnfilter) 26 | - [gfn.FilterKV](#gfnfilterkv) 27 | - [gfn.Map](#gfnmap) 28 | - [gfn.Reduce](#gfnreduce) 29 | - [gfn.ReduceKV](#gfnreducekv) 30 | - [Math](#math) 31 | - [gfn.Abs](#gfnabs) 32 | - [gfn.DivMod](#gfndivmod) 33 | - [gfn.Max](#gfnmax) 34 | - [gfn.MaxBy](#gfnmaxby) 35 | - [gfn.Mean](#gfnmean) 36 | - [gfn.MeanBy](#gfnmeanby) 37 | - [gfn.Min](#gfnmin) 38 | - [gfn.MinBy](#gfnminby) 39 | - [gfn.MinMax](#gfnminmax) 40 | - [gfn.MinMaxBy](#gfnminmaxby) 41 | - [gfn.Mode](#gfnmode) 42 | - [gfn.ModeBy](#gfnmodeby) 43 | - [gfn.Sum](#gfnsum) 44 | - [gfn.SumBy](#gfnsumby) 45 | - [Array](#array) 46 | - [gfn.All](#gfnall) 47 | - [gfn.Any](#gfnany) 48 | - [gfn.Chunk](#gfnchunk) 49 | - [gfn.Concat](#gfnconcat) 50 | - [gfn.Contains](#gfncontains) 51 | - [gfn.Copy](#gfncopy) 52 | - [gfn.Count](#gfncount) 53 | - [gfn.CountBy](#gfncountby) 54 | - [gfn.Counter](#gfncounter) 55 | - [gfn.CounterBy](#gfncounterby) 56 | - [gfn.Difference](#gfndifference) 57 | - [gfn.DifferenceBy](#gfndifferenceby) 58 | - [gfn.Equal](#gfnequal) 59 | - [gfn.EqualBy](#gfnequalby) 60 | - [gfn.Fill](#gfnfill) 61 | - [gfn.Find](#gfnfind) 62 | - [gfn.FindLast](#gfnfindlast) 63 | - [gfn.ForEach](#gfnforeach) 64 | - [gfn.GroupBy](#gfngroupby) 65 | - [gfn.IndexOf](#gfnindexof) 66 | - [gfn.Intersection](#gfnintersection) 67 | - [gfn.IntersectionBy](#gfnintersectionby) 68 | - [gfn.IsSorted](#gfnissorted) 69 | - [gfn.IsSortedBy](#gfnissortedby) 70 | - [gfn.LastIndexOf](#gfnlastindexof) 71 | - [gfn.Range](#gfnrange) 72 | - [gfn.RangeBy](#gfnrangeby) 73 | - [gfn.Remove](#gfnremove) 74 | - [gfn.Repeat](#gfnrepeat) 75 | - [gfn.Reverse](#gfnreverse) 76 | - [gfn.Sample](#gfnsample) 77 | - [gfn.Shuffle](#gfnshuffle) 78 | - [gfn.ToSet](#gfntoset) 79 | - [gfn.Union](#gfnunion) 80 | - [gfn.UnionBy](#gfnunionby) 81 | - [gfn.Uniq](#gfnuniq) 82 | - [gfn.UniqBy](#gfnuniqby) 83 | - [gfn.Unzip](#gfnunzip) 84 | - [gfn.Zip](#gfnzip) 85 | - [Map](#map) 86 | - [gfn.Clear](#gfnclear) 87 | - [gfn.Clone](#gfnclone) 88 | - [gfn.DeleteBy](#gfndeleteby) 89 | - [gfn.DifferentKeys](#gfndifferentkeys) 90 | - [gfn.EqualKV](#gfnequalkv) 91 | - [gfn.EqualKVBy](#gfnequalkvby) 92 | - [gfn.ForEachKV](#gfnforeachkv) 93 | - [gfn.GetOrDefault](#gfngetordefault) 94 | - [gfn.IntersectKeys](#gfnintersectkeys) 95 | - [gfn.Invert](#gfninvert) 96 | - [gfn.IsDisjoint](#gfnisdisjoint) 97 | - [gfn.Items](#gfnitems) 98 | - [gfn.Keys](#gfnkeys) 99 | - [gfn.Select](#gfnselect) 100 | - [gfn.ToKV](#gfntokv) 101 | - [gfn.Update](#gfnupdate) 102 | - [gfn.Values](#gfnvalues) 103 | 104 | 105 | 106 | ## Installation 107 | ``` 108 | go get github.com/suchen-sci/gfn 109 | ``` 110 | 111 | ## Usage 112 | ``` 113 | import "github.com/suchen-sci/gfn" 114 | ``` 115 | 116 | ## Type 117 | 118 | ```go 119 | /* 120 | byte: alias for uint8 121 | rune: alias for int32 122 | time.Duration: alias for int64 123 | ... 124 | */ 125 | 126 | type Int interface { 127 | ~int | ~int8 | ~int16 | ~int32 | ~int64 128 | } 129 | 130 | type Uint interface { 131 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr 132 | } 133 | 134 | type Float interface { 135 | ~float32 | ~float64 136 | } 137 | 138 | type Complex interface { 139 | ~complex64 | ~complex128 140 | } 141 | 142 | type Pair[T, U any] struct { 143 | First T 144 | Second U 145 | } 146 | ``` 147 | 148 | 149 | ## Functional 150 | 151 | 152 | ### gfn.Filter 153 | ```go 154 | func Filter[T any](array []T, filter func(T) bool) []T 155 | ``` 156 | Filter returns a new array containing elements of the original array that satisfy the provided function. 157 | 158 | #### Example: 159 | ```go 160 | array := []int{1, 2, 3, 4, 5, 6} 161 | gfn.Filter(array, func(i int) bool { return i%2 == 0 }) 162 | // []int{2, 4, 6} 163 | ``` 164 | [back to top](#gfn) 165 | 166 | 167 | ### gfn.FilterKV 168 | ```go 169 | func FilterKV[K comparable, V any](m map[K]V, fn func(K, V) bool) map[K]V 170 | ``` 171 | FilterKV returns a new map containing elements of the original map that satisfy the provided function. 172 | 173 | #### Example: 174 | ```go 175 | m := map[int]string{1: "a", 2: "b", 3: "c"} 176 | gfn.FilterKV(m, func(k int, v string) bool { 177 | return k == 1 || v == "c" 178 | }) 179 | // map[int]string{1: "a", 3: "c"} 180 | ``` 181 | [back to top](#gfn) 182 | 183 | 184 | ### gfn.Map 185 | ```go 186 | func Map[T any, R any](array []T, mapper func(T) R) []R 187 | ``` 188 | Map returns a new array with the results of calling the mapper function on each element. No MapKV because I don't know what to return, an array or a map? Instead, please use ForEachKV. 189 | 190 | #### Example: 191 | ```go 192 | gfn.Map([]int{1, 2, 3}, func(i int) string { return i+1 }) 193 | // []int{2, 3, 4} 194 | 195 | gfn.Map([]int{1, 2, 3}, func(i int) string { 196 | return strconv.Itoa(i) 197 | }) 198 | // []string{"1", "2", "3"} 199 | ``` 200 | [back to top](#gfn) 201 | 202 | 203 | ### gfn.Reduce 204 | ```go 205 | func Reduce[T any, R any](array []T, init R, fn func(R, T) R) R 206 | ``` 207 | Reduce executes a reducer function on each element of the array, resulting in a single output value. 208 | 209 | #### Example: 210 | ```go 211 | gfn.Reduce([]int{1, 2, 3}, 0, func(a, b int) int { 212 | return a + b 213 | }) 214 | // 6 215 | ``` 216 | [back to top](#gfn) 217 | 218 | 219 | ### gfn.ReduceKV 220 | ```go 221 | func ReduceKV[K comparable, V any, R any](m map[K]V, init R, fn func(R, K, V) R) R 222 | ``` 223 | ReduceKV executes a reducer function on each element of the map, resulting in a single output value. 224 | 225 | #### Example: 226 | ```go 227 | m := map[string]int{"a": 1, "b": 2, "c": 3} 228 | total := gfn.ReduceKV(m, 0, func(value int, k string, v int) int { 229 | return value + v 230 | }) 231 | // 6 232 | ``` 233 | [back to top](#gfn) 234 | 235 | 236 | 237 | 238 | ## Math 239 | 240 | 241 | ### gfn.Abs 242 | ```go 243 | func Abs[T Int | Float](x T) T 244 | ``` 245 | Abs returns the absolute value of x. 246 | 247 | #### Example: 248 | ```go 249 | gfn.Abs(-1) // 1 250 | gfn.Abs(-100.99) // 100.99 251 | ``` 252 | [back to top](#gfn) 253 | 254 | 255 | ### gfn.DivMod 256 | ```go 257 | func DivMod[T Int | Uint](a, b T) (T, T) 258 | ``` 259 | DivMod returns quotient and remainder of a/b. 260 | 261 | #### Example: 262 | ```go 263 | gfn.DivMod(10, 3) // (3, 1) 264 | ``` 265 | [back to top](#gfn) 266 | 267 | 268 | ### gfn.Max 269 | ```go 270 | func Max[T Int | Uint | Float | ~string](array ...T) T 271 | ``` 272 | Max returns the maximum value in the array. For float64 arrays, NaN values are skipped. 273 | 274 | #### Example: 275 | ```go 276 | gfn.Max([]int16{1, 5, 9, 10}...) // 10 277 | gfn.Max("ab", "cd", "e") // "e" 278 | 279 | gfn.Max(1.1, math.NaN(), 2.2) // 2.2 280 | gfn.Max([]float64{math.NaN(), math.NaN(), math.NaN()}...) // NaN 281 | ``` 282 | [back to top](#gfn) 283 | 284 | 285 | ### gfn.MaxBy 286 | ```go 287 | func MaxBy[T any, U Int | Uint | Float | ~string](array []T, fn func(T) U) T 288 | ``` 289 | MaxBy returns the maximum value in the array, using the given function to transform values. 290 | 291 | #### Example: 292 | ```go 293 | type Product struct { 294 | name string 295 | amount int 296 | } 297 | products := []Product{ 298 | {"apple", 10}, 299 | {"banana", 20}, 300 | {"orange", 30}, 301 | } 302 | p := gfn.MaxBy(products, func(p Product) int { 303 | return p.amount 304 | }) // {"orange", 30} 305 | ``` 306 | [back to top](#gfn) 307 | 308 | 309 | ### gfn.Mean 310 | ```go 311 | func Mean[T Int | Uint | Float](array ...T) float64 312 | ``` 313 | Mean returns the mean of all values in the array. 314 | 315 | #### Example: 316 | ```go 317 | gfn.Mean(1, 2, 3) // 2.0 318 | gfn.Mean([]int{1, 2, 3, 4}...) // 2.5 319 | ``` 320 | [back to top](#gfn) 321 | 322 | 323 | ### gfn.MeanBy 324 | ```go 325 | func MeanBy[T any, U Int | Uint | Float](array []T, fn func(T) U) float64 326 | ``` 327 | MeanBy returns the mean of all values in the array after applying fn to each value. 328 | 329 | #### Example: 330 | ```go 331 | type Product struct { 332 | name string 333 | cost float64 334 | } 335 | products := []Product{ 336 | {"apple", 1.5}, 337 | {"banana", 2.5}, 338 | {"orange", 3.5}, 339 | {"lemon", 4.5}, 340 | } 341 | gfn.MeanBy(products, func(p Product) float64 { 342 | return p.cost 343 | }) // 3.0 344 | ``` 345 | [back to top](#gfn) 346 | 347 | 348 | ### gfn.Min 349 | ```go 350 | func Min[T Int | Uint | Float | ~string](array ...T) T 351 | ``` 352 | Min returns the minimum value in the array. For float64 arrays, NaN values are skipped. 353 | 354 | #### Example: 355 | ```go 356 | gfn.Min(1.1, 2.2, 3.3) // 1.1 357 | gfn.Min([]int16{1, 5, 9, 10}...) // 1 358 | 359 | gfn.Min(1, -1, 10) // -1 360 | gfn.Min([]float64{1.1, math.Inf(-1), math.NaN()}...) // math.Inf(-1) 361 | ``` 362 | [back to top](#gfn) 363 | 364 | 365 | ### gfn.MinBy 366 | ```go 367 | func MinBy[T any, U Int | Uint | Float | ~string](array []T, fn func(T) U) T 368 | ``` 369 | MinBy returns the minimum value in the array, using the given function to transform values. 370 | 371 | #### Example: 372 | ```go 373 | type Product struct { 374 | name string 375 | amount int 376 | } 377 | products := []Product{ 378 | {"apple", 10}, 379 | {"banana", 20}, 380 | {"orange", 30}, 381 | } 382 | p := gfn.MinBy(products, func(p Product) int { 383 | return p.amount 384 | }) // {"apple", 10} 385 | ``` 386 | [back to top](#gfn) 387 | 388 | 389 | ### gfn.MinMax 390 | ```go 391 | func MinMax[T Int | Uint | Float | ~string](array ...T) (T, T) 392 | ``` 393 | MinMax returns the minimum and maximum value in the array. For float64 arrays, please use MinMaxFloat64. 394 | 395 | #### Example: 396 | ```go 397 | gfn.MinMax(1, 5, 9, 10) // 1, 10 398 | 399 | gfn.MinMax(math.NaN(), 1.85, 2.2) // 1.85, 2.2 400 | gfn.MinMax(math.NaN(), math.NaN(), math.NaN()) // NaN, NaN 401 | ``` 402 | [back to top](#gfn) 403 | 404 | 405 | ### gfn.MinMaxBy 406 | ```go 407 | func MinMaxBy[T any, U Int | Uint | Float | ~string](array []T, fn func(T) U) (T, T) 408 | ``` 409 | MinMaxBy returns the minimum and maximum value in the array, using the given function to transform values. 410 | 411 | #### Example: 412 | ```go 413 | type Product struct { 414 | name string 415 | amount int 416 | } 417 | products := []Product{ 418 | {"banana", 20}, 419 | {"orange", 30}, 420 | {"apple", 10}, 421 | {"grape", 50}, 422 | {"lemon", 40}, 423 | } 424 | gfn.MinMaxBy(products, func(p Product) int { 425 | return p.amount 426 | }) // {"apple", 10}, {"grape", 50} 427 | ``` 428 | [back to top](#gfn) 429 | 430 | 431 | ### gfn.Mode 432 | ```go 433 | func Mode[T comparable](array []T) T 434 | ``` 435 | Mode returns the most frequent value in the array. 436 | 437 | #### Example: 438 | ```go 439 | gfn.Mode([]int{1, 1, 5, 5, 5, 2, 2})) // 5 440 | ``` 441 | [back to top](#gfn) 442 | 443 | 444 | ### gfn.ModeBy 445 | ```go 446 | func ModeBy[T any, U comparable](array []T, fn func(T) U) T 447 | ``` 448 | ModeBy returns the most frequent value in the array, using the given function to transform values. 449 | 450 | #### Example: 451 | ```go 452 | type Product struct { 453 | name string 454 | amount int 455 | } 456 | products := []Product{ 457 | {"banana", 20}, 458 | {"banana", 20}, 459 | {"apple", 10}, 460 | } 461 | gfn.ModeBy(products, func(p Product) int { 462 | return p.amount 463 | }) // {"banana", 20} 464 | ``` 465 | [back to top](#gfn) 466 | 467 | 468 | ### gfn.Sum 469 | ```go 470 | func Sum[T Int | Uint | Float | ~string | Complex](array ...T) T 471 | ``` 472 | Sum returns the sum of all values in the array. Be careful when using this function for float64 arrays with NaN and Inf values. Sum([math.NaN(), 0.5]) produces math.NaN(). Sum(math.Inf(1), math.Inf(-1)) produces math.NaN() too. 473 | 474 | #### Example: 475 | ```go 476 | gfn.Sum([]int{1, 5, 9, 10}...) // 25 477 | gfn.Sum(1.1, 2.2, 3.3) // 6.6 478 | gfn.Sum("ab", "cd", "e") // "abcde" 479 | ``` 480 | [back to top](#gfn) 481 | 482 | 483 | ### gfn.SumBy 484 | ```go 485 | func SumBy[T any, U Int | Uint | Float | ~string](array []T, fn func(T) U) U 486 | ``` 487 | SumBy returns the sum of all values in the array after applying fn to each value. 488 | 489 | #### Example: 490 | ```go 491 | type Product struct { 492 | name string 493 | amount int 494 | } 495 | products := []Product{ 496 | {"apple", 10}, 497 | {"banana", 20}, 498 | {"orange", 30}, 499 | } 500 | gfn.SumBy(products, func(p Product) int { 501 | return p.amount 502 | }) // 60 503 | ``` 504 | [back to top](#gfn) 505 | 506 | 507 | 508 | 509 | ## Array 510 | 511 | 512 | ### gfn.All 513 | ```go 514 | func All[T any](array []T, fn func(T) bool) bool 515 | ``` 516 | All returns true if all elements in an array pass a given test. 517 | 518 | #### Example: 519 | ```go 520 | gfn.All([]int{1, 2, 3, 4}, func(i int) bool { 521 | return i > 0 522 | } 523 | // true 524 | ``` 525 | [back to top](#gfn) 526 | 527 | 528 | ### gfn.Any 529 | ```go 530 | func Any[T any](array []T, fn func(T) bool) bool 531 | ``` 532 | Any returns true if at least one element in an array passes a given test. 533 | 534 | #### Example: 535 | ```go 536 | gfn.Any([]int{1, 2, 3, 4}, func(i int) bool { 537 | return i > 3 538 | } 539 | // true 540 | ``` 541 | [back to top](#gfn) 542 | 543 | 544 | ### gfn.Chunk 545 | ```go 546 | func Chunk[T any](array []T, size int) [][]T 547 | ``` 548 | Chunk splits an array into chunks of given size. 549 | 550 | #### Example: 551 | ```go 552 | gfn.Chunk([]int{1, 2, 3, 4, 5}, 2) // [][]int{{1, 2}, {3, 4}, {5}} 553 | ``` 554 | [back to top](#gfn) 555 | 556 | 557 | ### gfn.Concat 558 | ```go 559 | func Concat[T any](arrays ...[]T) []T 560 | ``` 561 | Concat returns a new array that is the result of joining two or more arrays. 562 | 563 | #### Example: 564 | ```go 565 | gfn.Concat([]int{1, 2}, []int{3, 4}) // []int{1, 2, 3, 4} 566 | ``` 567 | [back to top](#gfn) 568 | 569 | 570 | ### gfn.Contains 571 | ```go 572 | func Contains[T comparable](array []T, value T) bool 573 | ``` 574 | Contains returns true if the array contains the value. 575 | 576 | #### Example: 577 | ```go 578 | gfn.Contains([]int{1, 2, 3}, 2) // true 579 | gfn.Contains([]string{"a", "b", "c"}, "b") // true 580 | gfn.Contains([]time.Duration{time.Second}, time.Second) // true 581 | ``` 582 | [back to top](#gfn) 583 | 584 | 585 | ### gfn.Copy 586 | ```go 587 | func Copy[T any](array []T) []T 588 | ``` 589 | Copy returns a new array that is a shallow copy of the original array. 590 | 591 | #### Example: 592 | ```go 593 | gfn.Copy([]int{1, 2, 3}) // []int{1, 2, 3} 594 | 595 | array := []int{1, 2, 3, 4, 5, 6} 596 | gfn.Copy(array[2:]) 597 | // []int{3, 4, 5, 6} 598 | ``` 599 | [back to top](#gfn) 600 | 601 | 602 | ### gfn.Count 603 | ```go 604 | func Count[T comparable](array []T, value T) int 605 | ``` 606 | Count returns the number of occurrences of a value in an array. 607 | 608 | #### Example: 609 | ```go 610 | gfn.Count([]int{1, 2, 2, 2, 5, 6}, 2) // 3 611 | ``` 612 | [back to top](#gfn) 613 | 614 | 615 | ### gfn.CountBy 616 | ```go 617 | func CountBy[T any](array []T, fn func(T) bool) int 618 | ``` 619 | CountBy returns the number of elements in an array that satisfy a predicate. 620 | 621 | #### Example: 622 | ```go 623 | type Employee struct { 624 | name string 625 | department string 626 | } 627 | employees := []Employee{ 628 | {"Alice", "Accounting"}, 629 | {"Cindy", "Engineering"}, 630 | {"Dave", "Engineering"}, 631 | {"Eve", "Engineering"}, 632 | } 633 | gfn.CountBy(employees, func(e Employee) bool { 634 | return e.department == "Engineering" 635 | }) // 3 636 | ``` 637 | [back to top](#gfn) 638 | 639 | 640 | ### gfn.Counter 641 | ```go 642 | func Counter[T comparable](array []T) map[T]int 643 | ``` 644 | Counter returns a map of values and their counts. 645 | 646 | #### Example: 647 | ```go 648 | gfn.Counter([]int{1, 2, 2, 2, 2}) // map[int]int{1: 1, 2: 4} 649 | ``` 650 | [back to top](#gfn) 651 | 652 | 653 | ### gfn.CounterBy 654 | ```go 655 | func CounterBy[T any, U comparable](array []T, fn func(T) U) map[U]int 656 | ``` 657 | CounterBy returns a map of values and their counts. The values are calculated by the given function. 658 | 659 | #### Example: 660 | ```go 661 | type Employee struct { 662 | name string 663 | department string 664 | } 665 | employees := []Employee{ 666 | {"Alice", "Accounting"}, 667 | {"Dave", "Engineering"}, 668 | {"Eve", "Engineering"}, 669 | } 670 | gfn.CounterBy(employees, func(e Employee) string { 671 | return e.department 672 | }) // map[string]int{"Accounting": 1, "Engineering": 2} 673 | ``` 674 | [back to top](#gfn) 675 | 676 | 677 | ### gfn.Difference 678 | ```go 679 | func Difference[T comparable](array []T, others ...[]T) []T 680 | ``` 681 | Difference returns a new array that is a copy of the original array, removing all occurrences of any item that also appear in others. The order is preserved from the original array. 682 | 683 | #### Example: 684 | ```go 685 | gfn.Difference([]int{1, 2, 3, 4}, []int{2, 4}) // []int{1, 3} 686 | ``` 687 | [back to top](#gfn) 688 | 689 | 690 | ### gfn.DifferenceBy 691 | ```go 692 | func DifferenceBy[T any, U comparable](fn func(T) U, array []T, others ...[]T) []T 693 | ``` 694 | DifferenceBy returns a new array that is a copy of the original array, removing all occurrences of any item that also appear in others. The occurrences are determined by applying a function to each element. 695 | 696 | #### Example: 697 | ```go 698 | type Data struct { 699 | value int 700 | } 701 | data1 := []Data{{1}, {3}, {2}, {4}, {5}, {2}} 702 | data2 := []Data{{3}, {4}, {5}} 703 | gfn.DifferenceBy(func(d Data) int { return d.value }, data1, data2) 704 | // []Data{{1}, {2}, {2}} 705 | ``` 706 | [back to top](#gfn) 707 | 708 | 709 | ### gfn.Equal 710 | ```go 711 | func Equal[T comparable](a, b []T) bool 712 | ``` 713 | Equal returns true if two arrays are equal. Two arrays are considered equal if both are nil, or if their lengths are equal and their elements are equal. Elements are compared using == operator. 714 | 715 | #### Example: 716 | ```go 717 | gfn.Equal([]int{1, 2, 3}, []int{1, 2, 3}) // true 718 | gfn.Equal([]string{"a", "c", "b"}, []string{"a", "b", "c"}) // false 719 | ``` 720 | [back to top](#gfn) 721 | 722 | 723 | ### gfn.EqualBy 724 | ```go 725 | func EqualBy[T1, T2 any](a []T1, b []T2, fn func(T1, T2) bool) bool 726 | ``` 727 | EqualBy returns true if two arrays are equal by comparing their elements using the given function. 728 | 729 | #### Example: 730 | ```go 731 | a := []int{1, 2, 3, 4, 5} 732 | b := []rune{'a', 'b', 'c', 'd', 'e'} 733 | gfn.EqualBy(a, b, func(aa int, bb rune) bool { 734 | return (aa - 1) == int(bb-'a') 735 | }) // true 736 | ``` 737 | [back to top](#gfn) 738 | 739 | 740 | ### gfn.Fill 741 | ```go 742 | func Fill[T any](array []T, value T) 743 | ``` 744 | Fill sets all elements of an array to a given value. You can control the start and end index by using the slice. 745 | 746 | #### Example: 747 | ```go 748 | array := make([]bool, 5) 749 | gfn.Fill(array, true) 750 | // []bool{true, true, true, true, true} 751 | 752 | // you can control the start and end index by using the slice 753 | array2 := make([]int, 5) 754 | gfn.Fill(array2[2:], 100) 755 | // []int{0, 0, 100, 100, 100} 756 | ``` 757 | [back to top](#gfn) 758 | 759 | 760 | ### gfn.Find 761 | ```go 762 | func Find[T any](array []T, fn func(T) bool) (T, int) 763 | ``` 764 | Find returns the first element in an array that passes a given test and corresponding index. Index of -1 is returned if no element passes the test. 765 | 766 | #### Example: 767 | ```go 768 | value, index := gfn.Find([]string{"a", "ab", "abc"}, func(s string) bool { 769 | return len(s) > 1 770 | }) 771 | // "ab", 1 772 | ``` 773 | [back to top](#gfn) 774 | 775 | 776 | ### gfn.FindLast 777 | ```go 778 | func FindLast[T any](array []T, fn func(T) bool) (T, int) 779 | ``` 780 | FindLast returns the last element in an array that passes a given test and corresponding index. Index of -1 is returned if no element passes the test. 781 | 782 | #### Example: 783 | ```go 784 | value, index := gfn.FindLast([]string{"a", "ab", "abc"}, func(s string) bool { 785 | return len(s) > 1 786 | }) 787 | // "abc", 2 788 | ``` 789 | [back to top](#gfn) 790 | 791 | 792 | ### gfn.ForEach 793 | ```go 794 | func ForEach[T any](array []T, fn func(value T)) 795 | ``` 796 | ForEach executes a provided function once for each array element. 797 | 798 | #### Example: 799 | ```go 800 | sum := 0 801 | gfn.ForEach([]int{1, 2, 3}, func(i int) { 802 | sum += i 803 | }) 804 | // sum == 6 805 | ``` 806 | [back to top](#gfn) 807 | 808 | 809 | ### gfn.GroupBy 810 | ```go 811 | func GroupBy[T any, K comparable](array []T, groupFn func(T) K) map[K][]T 812 | ``` 813 | GroupBy generate a map of arrays by grouping the elements of an array according to a given function. 814 | 815 | #### Example: 816 | ```go 817 | array := []int{1, 2, 3, 4, 5, 6, 7, 8} 818 | groups := gfn.GroupBy(array, func(i int) string { 819 | if i%2 == 0 { 820 | return "even" 821 | } 822 | return "odd" 823 | }) 824 | // map[string][]int{ 825 | // "even": []int{2, 4, 6, 8}, 826 | // "odd": []int{1, 3, 5, 7}, 827 | // } 828 | ``` 829 | [back to top](#gfn) 830 | 831 | 832 | ### gfn.IndexOf 833 | ```go 834 | func IndexOf[T comparable](array []T, value T) int 835 | ``` 836 | IndexOf returns the index of the first occurrence of a value in an array, or -1 if not found. 837 | 838 | #### Example: 839 | ```go 840 | gfn.IndexOf([]int{1, 2, 3, 4}, 3) // 2 841 | gfn.IndexOf([]int{1, 2, 3, 4}, 5) // -1 842 | ``` 843 | [back to top](#gfn) 844 | 845 | 846 | ### gfn.Intersection 847 | ```go 848 | func Intersection[T comparable](arrays ...[]T) []T 849 | ``` 850 | Intersection returns a new array that is the intersection of two or more arrays. 851 | 852 | #### Example: 853 | ```go 854 | arr1 := []int{1, 2, 3, 4, 5} 855 | arr2 := []int{2, 3, 4, 5, 6} 856 | arr3 := []int{5, 4, 3, 2} 857 | arr4 := []int{2, 3} 858 | gfn.Intersection(arr1, arr2, arr3, arr4) // []int{2, 3} 859 | ``` 860 | [back to top](#gfn) 861 | 862 | 863 | ### gfn.IntersectionBy 864 | ```go 865 | func IntersectionBy[T any, U comparable](fn func(T) U, arrays ...[]T) []T 866 | ``` 867 | IntersectionBy returns a new array that is the intersection of two or more arrays, where intersection is determined by a given function. 868 | 869 | #### Example: 870 | ```go 871 | type Data struct { 872 | value int 873 | } 874 | data1 := []Data{{1}, {3}, {2}, {4}, {5}} 875 | data2 := []Data{{2}, {3}} 876 | gfn.IntersectionBy(func(d Data) int { return d.value }, data1, data2) 877 | // []Data{{3}, {2}} 878 | ``` 879 | [back to top](#gfn) 880 | 881 | 882 | ### gfn.IsSorted 883 | ```go 884 | func IsSorted[T Int | Uint | Float | ~string](array []T) bool 885 | ``` 886 | IsSorted returns true if the array is sorted in ascending order. 887 | 888 | #### Example: 889 | ```go 890 | gfn.IsSorted([]int{1, 2, 3, 4}) // true 891 | ``` 892 | [back to top](#gfn) 893 | 894 | 895 | ### gfn.IsSortedBy 896 | ```go 897 | func IsSortedBy[T any](array []T, order func(a1, a2 T) bool) bool 898 | ``` 899 | IsSortedBy returns true if the array is sorted in the given order. The order function should return true if a1 is ok to be placed before a2. 900 | 901 | #### Example: 902 | ```go 903 | gfn.IsSortedBy([]int{2, 2, 1, 1, -1, -1}, func(a, b int) bool { return a >= b }) 904 | // true 905 | ``` 906 | [back to top](#gfn) 907 | 908 | 909 | ### gfn.LastIndexOf 910 | ```go 911 | func LastIndexOf[T comparable](array []T, value T) int 912 | ``` 913 | LastIndexOf returns the index of the last occurrence of a value in an array, or -1 if not found. 914 | 915 | #### Example: 916 | ```go 917 | gfn.LastIndexOf([]int{3, 3, 3, 4}, 3) // 2 918 | gfn.LastIndexOf([]int{1, 2, 3, 4}, 5) // -1 919 | ``` 920 | [back to top](#gfn) 921 | 922 | 923 | ### gfn.Range 924 | ```go 925 | func Range[T Int | Uint](start, end T) []T 926 | ``` 927 | Range function returns a sequence of numbers, starting from start, and increments by 1, until end is reached (not included). 928 | 929 | #### Example: 930 | ```go 931 | gfn.Range(0, 7) // []int{0, 1, 2, 3, 4, 5, 6} 932 | gfn.Range(3, 8) // []int{3, 4, 3, 6, 7} 933 | gfn.Range(-10, -5) // []int{-10, -9, -8, -7, -6} 934 | ``` 935 | [back to top](#gfn) 936 | 937 | 938 | ### gfn.RangeBy 939 | ```go 940 | func RangeBy[T Int | Uint](start, end, step T) []T 941 | ``` 942 | RangeBy function returns a sequence of numbers, starting from start, and increments/decrements by step, until end is reached (not included). Zero step panics. 943 | 944 | #### Example: 945 | ```go 946 | gfn.RangeBy(0, 7, 1) // []int{0, 1, 2, 3, 4, 5, 6} 947 | gfn.RangeBy(0, 8, 2) // []int{0, 2, 4, 6} 948 | gfn.RangeBy(10, 0, -2) // []int{10, 8, 6, 4, 2} 949 | ``` 950 | [back to top](#gfn) 951 | 952 | 953 | ### gfn.Remove 954 | ```go 955 | func Remove[T comparable](array []T, values ...T) []T 956 | ``` 957 | Remove removes all elements from an array that equal to given values. 958 | 959 | #### Example: 960 | ```go 961 | gfn.Remove([]int{1, 2, 3, 4, 2, 3, 2, 3}, 2, 3) // []int{1, 4} 962 | ``` 963 | [back to top](#gfn) 964 | 965 | 966 | ### gfn.Repeat 967 | ```go 968 | func Repeat[T any](array []T, repeat int) []T 969 | ``` 970 | Repeat returns a new array that is the result of repeating an array a given number of times. 971 | 972 | #### Example: 973 | ```go 974 | gfn.Repeat([]int{1, 2, 3}, 3) // []int{1, 2, 3, 1, 2, 3, 1, 2, 3} 975 | ``` 976 | [back to top](#gfn) 977 | 978 | 979 | ### gfn.Reverse 980 | ```go 981 | func Reverse[T any](array []T) 982 | ``` 983 | Reverse reverses an array in place. 984 | 985 | #### Example: 986 | ```go 987 | array := []int{1, 2, 3, 4} 988 | gfn.Reverse(array) 989 | // []int{4, 3, 2, 1} 990 | ``` 991 | [back to top](#gfn) 992 | 993 | 994 | ### gfn.Sample 995 | ```go 996 | func Sample[T any](array []T, n int) []T 997 | ``` 998 | Sample returns a random sample of n elements from an array. Every position in the array are at most selected once. n should be less or equal to len(array). 999 | 1000 | #### Example: 1001 | ```go 1002 | gfn.Sample([]int{1, 2, 3, 4, 5}, 3) // []int{3, 1, 5} or other random choices. 1003 | ``` 1004 | [back to top](#gfn) 1005 | 1006 | 1007 | ### gfn.Shuffle 1008 | ```go 1009 | func Shuffle[T any](array []T) 1010 | ``` 1011 | Shuffle randomizes the order of elements by using Fisher–Yates algorithm 1012 | 1013 | #### Example: 1014 | ```go 1015 | array := []int{1, 2, 3, 4} 1016 | gfn.Shuffle(array) 1017 | // array: []int{2, 1, 4, 3} or other random order 1018 | ``` 1019 | [back to top](#gfn) 1020 | 1021 | 1022 | ### gfn.ToSet 1023 | ```go 1024 | func ToSet[T comparable](array []T) map[T]struct{} 1025 | ``` 1026 | ToSet converts an array to a set. 1027 | 1028 | #### Example: 1029 | ```go 1030 | gfn.ToSet([]int{0, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5}) 1031 | // map[int]struct{}{0: {}, 1: {}, 2: {}, 3: {}, 4: {}, 5: {}} 1032 | ``` 1033 | [back to top](#gfn) 1034 | 1035 | 1036 | ### gfn.Union 1037 | ```go 1038 | func Union[T comparable](arrays ...[]T) []T 1039 | ``` 1040 | Union returns an array with all duplicates removed from multiple arrays. 1041 | 1042 | #### Example: 1043 | ```go 1044 | gfn.Union([]int{1, 2, 3}, []int{2, 3, 4}, []int{3, 4, 5}) 1045 | // []int{1, 2, 3, 4, 5} 1046 | ``` 1047 | [back to top](#gfn) 1048 | 1049 | 1050 | ### gfn.UnionBy 1051 | ```go 1052 | func UnionBy[T any, U comparable](fn func(T) U, arrays ...[]T) []T 1053 | ``` 1054 | UnionBy returns an array with all duplicates removed from multiple arrays by applying a function to each element. 1055 | 1056 | #### Example: 1057 | ```go 1058 | type Employee struct { 1059 | name string 1060 | department string 1061 | } 1062 | group1 := []Employee{ 1063 | {"Alice", "Accounting"}, 1064 | {"Bob", "Accounting"}, 1065 | {"Cindy", "Engineering"}, 1066 | } 1067 | group2 := []Employee{ 1068 | {"Alice", "Accounting"}, 1069 | {"Cindy", "Engineering"}, 1070 | {"Dave", "Engineering"}, 1071 | {"Eve", "Engineering"}, 1072 | } 1073 | gfn.UnionBy(func(e Employee) string { return e.name }, group1, group2) 1074 | // []Employee{ 1075 | // {"Alice", "Accounting"}, 1076 | // {"Bob", "Accounting"}, 1077 | // {"Cindy", "Engineering"}, 1078 | // {"Dave", "Engineering"}, 1079 | // {"Eve", "Engineering"}, 1080 | // } 1081 | ``` 1082 | [back to top](#gfn) 1083 | 1084 | 1085 | ### gfn.Uniq 1086 | ```go 1087 | func Uniq[T comparable](array []T) []T 1088 | ``` 1089 | Uniq returns an array with all duplicates removed. 1090 | 1091 | #### Example: 1092 | ```go 1093 | gfn.Uniq([]int{1, 2, 2, 3, 3, 3, 4, 4, 4, 4}) // []int{1, 2, 3, 4} 1094 | ``` 1095 | [back to top](#gfn) 1096 | 1097 | 1098 | ### gfn.UniqBy 1099 | ```go 1100 | func UniqBy[T any, U comparable](array []T, fn func(T) U) []T 1101 | ``` 1102 | UniqBy returns an array with all duplicates removed by applying a function to each element. 1103 | 1104 | #### Example: 1105 | ```go 1106 | type Employee struct { 1107 | name string 1108 | department string 1109 | } 1110 | employees := []Employee{ 1111 | {"Alice", "Accounting"}, 1112 | {"Bob", "Accounting"}, 1113 | {"Cindy", "Engineering"}, 1114 | {"Dave", "Engineering"}, 1115 | } 1116 | gfn.UniqBy(employees, func(e Employee) string { 1117 | return e.department 1118 | }) 1119 | // []Employee{{"Alice", "Accounting"}, {"Cindy", "Engineering"}} 1120 | ``` 1121 | [back to top](#gfn) 1122 | 1123 | 1124 | ### gfn.Unzip 1125 | ```go 1126 | func Unzip[T, U any](n int, unzipFn func(i int) (T, U)) ([]T, []U) 1127 | ``` 1128 | Unzip returns two arrays built from the elements of a sequence of pairs. 1129 | 1130 | #### Example: 1131 | ```go 1132 | pairs := []gfn.Pair[int, string]{ 1133 | {First: 1, Second: "a"}, 1134 | {First: 2, Second: "b"}, 1135 | {First: 3, Second: "c"}, 1136 | } 1137 | gfn.Unzip(len(pairs), func(i int) (int, string) { 1138 | return pairs[i].First, pairs[i].Second 1139 | }) 1140 | // ([]int{1, 2, 3}, []string{"a", "b", "c"}) 1141 | ``` 1142 | [back to top](#gfn) 1143 | 1144 | 1145 | ### gfn.Zip 1146 | ```go 1147 | func Zip[T, U any](a []T, b []U) []Pair[T, U] 1148 | ``` 1149 | Zip returns a sequence of pairs built from the elements of two arrays. 1150 | 1151 | #### Example: 1152 | ```go 1153 | gfn.Zip([]int{1, 2, 3}, []string{"a", "b", "c"}) 1154 | // []gfn.Pair[int, string]{ 1155 | // {First: 1, Second: "a"}, 1156 | // {First: 2, Second: "b"}, 1157 | // {First: 3, Second: "c"} 1158 | // } 1159 | ``` 1160 | [back to top](#gfn) 1161 | 1162 | 1163 | 1164 | 1165 | ## Map 1166 | 1167 | 1168 | ### gfn.Clear 1169 | ```go 1170 | func Clear[K comparable, V any](m map[K]V) 1171 | ``` 1172 | Clear removes all keys from a map. 1173 | 1174 | #### Example: 1175 | ```go 1176 | m := map[int]string{1: "a", 2: "b", 3: "c"} 1177 | gfn.Clear(m) 1178 | // m is now an empty map 1179 | ``` 1180 | [back to top](#gfn) 1181 | 1182 | 1183 | ### gfn.Clone 1184 | ```go 1185 | func Clone[K comparable, V any](m map[K]V) map[K]V 1186 | ``` 1187 | Clone returns a shallow copy of a map. 1188 | 1189 | #### Example: 1190 | ```go 1191 | m := map[int]string{1: "a", 2: "b", 3: "c"} 1192 | m2 := gfn.Clone(m) 1193 | // m2 is a copy of m 1194 | ``` 1195 | [back to top](#gfn) 1196 | 1197 | 1198 | ### gfn.DeleteBy 1199 | ```go 1200 | func DeleteBy[K comparable, V any](m map[K]V, deleteFn func(K, V) bool) 1201 | ``` 1202 | DeleteBy deletes keys from a map if the predicate function returns true. 1203 | 1204 | #### Example: 1205 | ```go 1206 | m := map[int]string{1: "a", 2: "b", 3: "c"} 1207 | gfn.DeleteBy(m, func(k int, v string) bool { 1208 | return k == 1 || v == "c" 1209 | }) 1210 | // map[int]string{2: "b"} 1211 | ``` 1212 | [back to top](#gfn) 1213 | 1214 | 1215 | ### gfn.DifferentKeys 1216 | ```go 1217 | func DifferentKeys[K comparable, V any](ms ...map[K]V) []K 1218 | ``` 1219 | DifferentKeys returns a slice of keys that are in the first map but not in the others, only keys in the map are considered, not values. It usually used to find the difference between two or more sets. 1220 | 1221 | #### Example: 1222 | ```go 1223 | m1 := map[int]string{1: "a", 2: "b", 3: "c", 4: "d"} 1224 | m2 := map[int]string{1: "a", 2: "b"} 1225 | m3 := map[int]string{2: "b", 3: "c"} 1226 | gfn.DifferentKeys(m1, m2, m3) // []int{4} 1227 | ``` 1228 | [back to top](#gfn) 1229 | 1230 | 1231 | ### gfn.EqualKV 1232 | ```go 1233 | func EqualKV[K, V comparable](a map[K]V, b map[K]V) bool 1234 | ``` 1235 | EqualKV returns true if two maps/sets are equal. 1236 | 1237 | #### Example: 1238 | ```go 1239 | map1 := map[int]struct{}{1: {}, 2: {}, 3: {}} 1240 | map2 := map[int]struct{}{1: {}, 2: {}, 3: {}} 1241 | gfn.EqualKV(map1, map2) // true 1242 | ``` 1243 | [back to top](#gfn) 1244 | 1245 | 1246 | ### gfn.EqualKVBy 1247 | ```go 1248 | func EqualKVBy[K comparable, V1, V2 any](a map[K]V1, b map[K]V2, fn func(K, V1, V2) bool) bool 1249 | ``` 1250 | EqualKVBy returns true if two maps/sets are equal by a custom function. 1251 | 1252 | #### Example: 1253 | ```go 1254 | m1 := map[int]string{1: "a", 2: "b", 3: "c"} 1255 | m2 := map[int]string{1: "e", 2: "f", 3: "g"} 1256 | gfn.EqualKVBy(m1, m2, func(k int, a, b string) bool { 1257 | return len(a) == len(b) 1258 | }) // true 1259 | ``` 1260 | [back to top](#gfn) 1261 | 1262 | 1263 | ### gfn.ForEachKV 1264 | ```go 1265 | func ForEachKV[K comparable, V any](m map[K]V, fn func(K, V)) 1266 | ``` 1267 | ForEachKV iterates over a map and calls a function for each key/value pair. 1268 | 1269 | #### Example: 1270 | ```go 1271 | m := map[int]string{1: "a", 2: "b", 3: "c"} 1272 | array := make([]int, 0, len(m)) 1273 | gfn.ForEachKV(m, func(k int, v string) { 1274 | array = append(array, k) 1275 | } 1276 | // array is []int{1, 2, 3} or other order 1277 | 1278 | m := map[int]string{1: "a", 2: "b", 3: "c"} 1279 | invert := map[string]int{} 1280 | gfn.ForEachKV(m, func(k int, v string) { 1281 | invert[v] = k 1282 | } 1283 | // invert is map[string]int{"a": 1, "b": 2, "c": 3} 1284 | ``` 1285 | [back to top](#gfn) 1286 | 1287 | 1288 | ### gfn.GetOrDefault 1289 | ```go 1290 | func GetOrDefault[K comparable, V any](m map[K]V, key K, defaultValue V) V 1291 | ``` 1292 | GetOrDefault returns the value for a key if it exists, otherwise it returns the default value. 1293 | 1294 | #### Example: 1295 | ```go 1296 | m := map[int]string{1: "a", 2: "b", 3: "c"} 1297 | gfn.GetOrDefault(m, 1, "d") // "a" 1298 | gfn.GetOrDefault(m, 4, "d") // "d" 1299 | ``` 1300 | [back to top](#gfn) 1301 | 1302 | 1303 | ### gfn.IntersectKeys 1304 | ```go 1305 | func IntersectKeys[K comparable, V any](ms ...map[K]V) []K 1306 | ``` 1307 | IntersectKeys returns a slice of keys that are in all maps. It usually used to find the intersection of two or more sets. 1308 | 1309 | #### Example: 1310 | ```go 1311 | m1 := map[int]string{1: "a", 2: "b", 3: "c", 4: "d"} 1312 | m2 := map[int]string{1: "a", 2: "b"} 1313 | m3 := map[int]string{2: "b", 3: "c", 4: "d"} 1314 | gfn.IntersectKeys(m1, m2, m3) // []int{2} 1315 | ``` 1316 | [back to top](#gfn) 1317 | 1318 | 1319 | ### gfn.Invert 1320 | ```go 1321 | func Invert[K, V comparable](m map[K]V) map[V]K 1322 | ``` 1323 | Invert returns a map with keys and values swapped. 1324 | 1325 | #### Example: 1326 | ```go 1327 | m := map[string]string{ 1328 | "Array": "array.go", 1329 | "Map": "map.go", 1330 | "Set": "set.go", 1331 | "Math": "math.go", 1332 | } 1333 | 1334 | gfn.Invert(m) 1335 | // map[string]string{ 1336 | // "array.go": "Array", 1337 | // "map.go": "Map", 1338 | // "set.go": "Set", 1339 | // "math.go": "Math", 1340 | // } 1341 | ``` 1342 | [back to top](#gfn) 1343 | 1344 | 1345 | ### gfn.IsDisjoint 1346 | ```go 1347 | func IsDisjoint[K comparable, V1 any, V2 any](m1 map[K]V1, m2 map[K]V2) bool 1348 | ``` 1349 | IsDisjoint returns true if the maps have no keys in common. It usually used to check if two sets are disjoint. 1350 | 1351 | #### Example: 1352 | ```go 1353 | m1 := map[int]string{1: "a", 2: "b", 3: "c"} 1354 | m2 := map[int]int{4: 4, 5: 5, 6: 6} 1355 | IsDisjoint(m1, m2) // true 1356 | 1357 | m3 := map[int]struct{}{1: {}, 2: {}, 3: {}} 1358 | m4 := map[int]struct{}{4: {}, 5: {}, 6: {}} 1359 | gfn.IsDisjoint(m3, m4) // true 1360 | ``` 1361 | [back to top](#gfn) 1362 | 1363 | 1364 | ### gfn.Items 1365 | ```go 1366 | func Items[K comparable, V any](m map[K]V) []Pair[K, V] 1367 | ``` 1368 | Items returns a slice of pairs of keys and values. 1369 | 1370 | #### Example: 1371 | ```go 1372 | m := map[int]string{1: "a", 2: "b", 3: "c"} 1373 | 1374 | gfn.Items(m) 1375 | // []gfn.Pair[int, string]{ 1376 | // {1, "a"}, 1377 | // {2, "b"}, 1378 | // {3, "c"}, 1379 | // } 1380 | ``` 1381 | [back to top](#gfn) 1382 | 1383 | 1384 | ### gfn.Keys 1385 | ```go 1386 | func Keys[K comparable, V any](m map[K]V) []K 1387 | ``` 1388 | Keys returns the keys of a map. 1389 | 1390 | #### Example: 1391 | ```go 1392 | gfn.Keys(map[int]string{1: "a", 2: "b", 3: "c"}) 1393 | // []int{1, 2, 3} or []int{3, 2, 1} or []int{2, 1, 3} etc. 1394 | ``` 1395 | [back to top](#gfn) 1396 | 1397 | 1398 | ### gfn.Select 1399 | ```go 1400 | func Select[K comparable, V any](m map[K]V, fn func(K, V) bool) map[K]V 1401 | ``` 1402 | Select returns a map with keys and values that satisfy the predicate function. 1403 | 1404 | #### Example: 1405 | ```go 1406 | m := map[int]string{1: "a", 2: "b", 3: "c"} 1407 | gfn.Select(m, func(k int, v string) bool { 1408 | return k == 1 || v == "c" 1409 | }) 1410 | // map[int]string{1: "a", 3: "c"} 1411 | ``` 1412 | [back to top](#gfn) 1413 | 1414 | 1415 | ### gfn.ToKV 1416 | ```go 1417 | func ToKV[K comparable, V any](n int, fn func(int) (K, V)) map[K]V 1418 | ``` 1419 | ToKV converts a slice to a map using a function to generate the keys and values. 1420 | 1421 | #### Example: 1422 | ```go 1423 | gfn.ToKV(3, func(i int) (int, string) { 1424 | return i, strconv.Itoa(i) 1425 | }) 1426 | // map[int]string{0: "0", 1: "1", 2: "2"} 1427 | ``` 1428 | [back to top](#gfn) 1429 | 1430 | 1431 | ### gfn.Update 1432 | ```go 1433 | func Update[K comparable, V any](m map[K]V, other ...map[K]V) 1434 | ``` 1435 | Update updates a map with the keys and values from other maps. 1436 | 1437 | #### Example: 1438 | ```go 1439 | // use Update to do union of maps 1440 | m1 := map[int]string{1: "a", 2: "b", 3: "c"} 1441 | m2 := map[int]string{4: "d", 5: "e"} 1442 | union := map[int]string{} 1443 | gfn.Update(union, m1, m2) 1444 | // union: map[int]string{1: "a", 2: "b", 3: "c", 4: "d", 5: "e"} 1445 | 1446 | m1 := map[int]string{1: "a", 2: "b", 3: "c"} 1447 | m2 := map[int]string{1: "d", 2: "e"} 1448 | m3 := map[int]string{1: "f"} 1449 | gfn.Update(m1, m2, m3) 1450 | // map[int]string{1: "f", 2: "e", 3: "c"} 1451 | ``` 1452 | [back to top](#gfn) 1453 | 1454 | 1455 | ### gfn.Values 1456 | ```go 1457 | func Values[K comparable, V any](m map[K]V) []V 1458 | ``` 1459 | Values returns the values of a map. 1460 | 1461 | #### Example: 1462 | ```go 1463 | gfn.Values(map[int]string{1: "a", 2: "b", 3: "c"}) 1464 | // []string{"a", "b", "c"} or []string{"c", "b", "a"} or []string{"b", "a", "c"} etc. 1465 | ``` 1466 | [back to top](#gfn) 1467 | 1468 | 1469 | 1470 | 1471 | 1472 | ## Contributing 1473 | 1474 | Format: 1475 | ```go 1476 | /* @example MyFunc 1477 | // your examples here 1478 | MyFunc(...) 1479 | // output: ... 1480 | */ 1481 | 1482 | // MyFunc is ... 1483 | func MyFunc(args ...) (return values...) { 1484 | // your code here. 1485 | } 1486 | ``` 1487 | 1488 | then run following command to update `README.md` (generated from `README.tmpl.md`). 1489 | 1490 | ```bash 1491 | make doc 1492 | ``` 1493 | 1494 | [back to top](#gfn) 1495 | 1496 | ## License 1497 | `gfn` is under the MIT license. See the [LICENSE](https://github.com/suchen-sci/gfn/blob/main/LICENSE) file for details. 1498 | 1499 | 1500 | [back to top](#gfn) 1501 | -------------------------------------------------------------------------------- /README.tmpl.md: -------------------------------------------------------------------------------- 1 | # gfn 2 | 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/suchen-sci/gfn?style=flat-square)](https://goreportcard.com/report/github.com/suchen-sci/gfn) 4 | [![Coverage](https://codecov.io/gh/suchen-sci/gfn/branch/main/graph/badge.svg)](https://app.codecov.io/gh/suchen-sci/gfn/tree/main) 5 | [![Tests](https://github.com/suchen-sci/gfn/actions/workflows/test.yml/badge.svg)](https://github.com/suchen-sci/gfn/actions/workflows/test.yml) 6 | [![Releases](https://img.shields.io/github/release/suchen-sci/gfn/all.svg?style=flat-square)](https://github.com/suchen-sci/gfn/releases) 7 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/suchen-sci/gfn/blob/main/LICENSE) 8 | 9 | `gfn` is a Golang library that leverages generics to provide various methods, including common functional programming techniques such as `Map`, `Reduce`, and `Filter`, along with other utilities like `Contains`, `Keys`, etc. The idea of this library is very simple, it aims to port as many small utilities from other languages to `Go` as possible. The implementation is highly influenced by`Python`, `Ruby`, `JavaScript` and `Lodash`. 10 | 11 | 1. No `reflect`. 12 | 2. No third-party packages. 13 | 3. Time complexity of `O(n)`. 14 | 15 | - [Documentation](#documentation) 16 | - [Contributing](#contributing) 17 | - [License](#license) 18 | 19 | ## Documentation 20 | 21 | - [Installation](#installation) 22 | - [Usage](#usage) 23 | - [Type](#type) 24 | {{ TOC }} 25 | 26 | 27 | ## Installation 28 | ``` 29 | go get github.com/suchen-sci/gfn 30 | ``` 31 | 32 | ## Usage 33 | ``` 34 | import "github.com/suchen-sci/gfn" 35 | ``` 36 | 37 | ## Type 38 | 39 | ```go 40 | /* 41 | byte: alias for uint8 42 | rune: alias for int32 43 | time.Duration: alias for int64 44 | ... 45 | */ 46 | 47 | type Int interface { 48 | ~int | ~int8 | ~int16 | ~int32 | ~int64 49 | } 50 | 51 | type Uint interface { 52 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr 53 | } 54 | 55 | type Float interface { 56 | ~float32 | ~float64 57 | } 58 | 59 | type Complex interface { 60 | ~complex64 | ~complex128 61 | } 62 | 63 | type Pair[T, U any] struct { 64 | First T 65 | Second U 66 | } 67 | ``` 68 | 69 | {{ CONTENT }} 70 | 71 | ## Contributing 72 | 73 | Format: 74 | ```go 75 | /* @example MyFunc 76 | // your examples here 77 | MyFunc(...) 78 | // output: ... 79 | */ 80 | 81 | // MyFunc is ... 82 | func MyFunc(args ...) (return values...) { 83 | // your code here. 84 | } 85 | ``` 86 | 87 | then run following command to update `README.md` (generated from `README.tmpl.md`). 88 | 89 | ```bash 90 | make doc 91 | ``` 92 | 93 | [back to top](#gfn) 94 | 95 | ## License 96 | `gfn` is under the MIT license. See the [LICENSE](https://github.com/suchen-sci/gfn/blob/main/LICENSE) file for details. 97 | 98 | 99 | [back to top](#gfn) 100 | -------------------------------------------------------------------------------- /array.go: -------------------------------------------------------------------------------- 1 | // Package gfn is a Golang library that leverages generics to provide various methods. 2 | package gfn 3 | 4 | import "math/rand" 5 | 6 | /* @example Contains 7 | gfn.Contains([]int{1, 2, 3}, 2) // true 8 | gfn.Contains([]string{"a", "b", "c"}, "b") // true 9 | gfn.Contains([]time.Duration{time.Second}, time.Second) // true 10 | */ 11 | 12 | // Contains returns true if the array contains the value. 13 | func Contains[T comparable](array []T, value T) bool { 14 | for _, v := range array { 15 | if v == value { 16 | return true 17 | } 18 | } 19 | return false 20 | } 21 | 22 | /* @example Range 23 | gfn.Range(0, 7) // []int{0, 1, 2, 3, 4, 5, 6} 24 | gfn.Range(3, 8) // []int{3, 4, 3, 6, 7} 25 | gfn.Range(-10, -5) // []int{-10, -9, -8, -7, -6} 26 | */ 27 | 28 | // Range function returns a sequence of numbers, starting from start, 29 | // and increments by 1, until end is reached (not included). 30 | func Range[T Int | Uint](start, end T) []T { 31 | if start >= end { 32 | return []T{} 33 | } 34 | 35 | res := make([]T, end-start) 36 | for i := 0; i < len(res); i++ { 37 | res[i] = start + T(i) 38 | } 39 | return res 40 | } 41 | 42 | /* @example RangeBy 43 | gfn.RangeBy(0, 7, 1) // []int{0, 1, 2, 3, 4, 5, 6} 44 | gfn.RangeBy(0, 8, 2) // []int{0, 2, 4, 6} 45 | gfn.RangeBy(10, 0, -2) // []int{10, 8, 6, 4, 2} 46 | */ 47 | 48 | // RangeBy function returns a sequence of numbers, starting from start, 49 | // and increments/decrements by step, until end is reached (not included). 50 | // Zero step panics. 51 | func RangeBy[T Int | Uint](start, end, step T) []T { 52 | if step == 0 { 53 | panic("step must not be zero") 54 | } 55 | 56 | if start < end && step > 0 { 57 | res := make([]T, 0, (end-start)/step) 58 | for i := start; i < end; i += step { 59 | res = append(res, i) 60 | } 61 | return res 62 | } 63 | if start > end && step < 0 { 64 | res := make([]T, 0, (end-start)/step) 65 | for i := start; i > end; i += step { 66 | res = append(res, i) 67 | } 68 | return res 69 | } 70 | return []T{} 71 | } 72 | 73 | /* @example Shuffle 74 | array := []int{1, 2, 3, 4} 75 | gfn.Shuffle(array) 76 | // array: []int{2, 1, 4, 3} or other random order 77 | */ 78 | 79 | // Shuffle randomizes the order of elements by using Fisher–Yates algorithm 80 | func Shuffle[T any](array []T) { 81 | for i := range array { 82 | j := rand.Intn(i + 1) 83 | array[i], array[j] = array[j], array[i] 84 | } 85 | } 86 | 87 | /* @example Equal 88 | gfn.Equal([]int{1, 2, 3}, []int{1, 2, 3}) // true 89 | gfn.Equal([]string{"a", "c", "b"}, []string{"a", "b", "c"}) // false 90 | */ 91 | 92 | // Equal returns true if two arrays are equal. Two arrays are considered equal 93 | // if both are nil, or if their lengths are equal and their elements are equal. 94 | // Elements are compared using == operator. 95 | func Equal[T comparable](a, b []T) bool { 96 | if len(a) != len(b) { 97 | return false 98 | } 99 | for i, aa := range a { 100 | if aa != b[i] { 101 | return false 102 | } 103 | } 104 | return true 105 | } 106 | 107 | /* @example EqualBy 108 | a := []int{1, 2, 3, 4, 5} 109 | b := []rune{'a', 'b', 'c', 'd', 'e'} 110 | gfn.EqualBy(a, b, func(aa int, bb rune) bool { 111 | return (aa - 1) == int(bb-'a') 112 | }) // true 113 | */ 114 | 115 | // EqualBy returns true if two arrays are equal by comparing their elements 116 | // using the given function. 117 | func EqualBy[T1, T2 any](a []T1, b []T2, fn func(T1, T2) bool) bool { 118 | if len(a) != len(b) { 119 | return false 120 | } 121 | for i, aa := range a { 122 | if !fn(aa, b[i]) { 123 | return false 124 | } 125 | } 126 | return true 127 | } 128 | 129 | /* @example ToSet 130 | gfn.ToSet([]int{0, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5}) 131 | // map[int]struct{}{0: {}, 1: {}, 2: {}, 3: {}, 4: {}, 5: {}} 132 | */ 133 | 134 | // ToSet converts an array to a set. 135 | func ToSet[T comparable](array []T) map[T]struct{} { 136 | res := make(map[T]struct{}) 137 | for _, v := range array { 138 | res[v] = struct{}{} 139 | } 140 | return res 141 | } 142 | 143 | /* @example IsSortedBy 144 | gfn.IsSortedBy([]int{2, 2, 1, 1, -1, -1}, func(a, b int) bool { return a >= b }) 145 | // true 146 | */ 147 | 148 | // IsSortedBy returns true if the array is sorted in the given order. 149 | // The order function should return true if a1 is ok to be placed before a2. 150 | func IsSortedBy[T any](array []T, order func(a1, a2 T) bool) bool { 151 | for i := 0; i < len(array)-1; i++ { 152 | if !order(array[i], array[i+1]) { 153 | return false 154 | } 155 | } 156 | return true 157 | } 158 | 159 | /* @example IsSorted 160 | gfn.IsSorted([]int{1, 2, 3, 4}) // true 161 | */ 162 | 163 | // IsSorted returns true if the array is sorted in ascending order. 164 | func IsSorted[T Int | Uint | Float | ~string](array []T) bool { 165 | for i := 0; i < len(array)-1; i++ { 166 | if array[i] > array[i+1] { 167 | return false 168 | } 169 | } 170 | return true 171 | } 172 | 173 | /* @example Counter 174 | gfn.Counter([]int{1, 2, 2, 2, 2}) // map[int]int{1: 1, 2: 4} 175 | */ 176 | 177 | // Counter returns a map of values and their counts. 178 | func Counter[T comparable](array []T) map[T]int { 179 | res := make(map[T]int) 180 | for _, v := range array { 181 | res[v]++ 182 | } 183 | return res 184 | } 185 | 186 | /* @example CounterBy 187 | type Employee struct { 188 | name string 189 | department string 190 | } 191 | employees := []Employee{ 192 | {"Alice", "Accounting"}, 193 | {"Dave", "Engineering"}, 194 | {"Eve", "Engineering"}, 195 | } 196 | gfn.CounterBy(employees, func(e Employee) string { 197 | return e.department 198 | }) // map[string]int{"Accounting": 1, "Engineering": 2} 199 | */ 200 | 201 | // CounterBy returns a map of values and their counts. The values are 202 | // calculated by the given function. 203 | func CounterBy[T any, U comparable](array []T, fn func(T) U) map[U]int { 204 | res := make(map[U]int) 205 | for _, v := range array { 206 | res[fn(v)]++ 207 | } 208 | return res 209 | } 210 | 211 | /* @example Zip 212 | gfn.Zip([]int{1, 2, 3}, []string{"a", "b", "c"}) 213 | // []gfn.Pair[int, string]{ 214 | // {First: 1, Second: "a"}, 215 | // {First: 2, Second: "b"}, 216 | // {First: 3, Second: "c"} 217 | // } 218 | */ 219 | 220 | // Zip returns a sequence of pairs built from the elements of two arrays. 221 | func Zip[T, U any](a []T, b []U) []Pair[T, U] { 222 | l := Min(len(a), len(b)) 223 | res := make([]Pair[T, U], l) 224 | for i := 0; i < l; i++ { 225 | res[i] = Pair[T, U]{a[i], b[i]} 226 | } 227 | return res 228 | } 229 | 230 | /* @example Unzip 231 | pairs := []gfn.Pair[int, string]{ 232 | {First: 1, Second: "a"}, 233 | {First: 2, Second: "b"}, 234 | {First: 3, Second: "c"}, 235 | } 236 | gfn.Unzip(len(pairs), func(i int) (int, string) { 237 | return pairs[i].First, pairs[i].Second 238 | }) 239 | // ([]int{1, 2, 3}, []string{"a", "b", "c"}) 240 | */ 241 | 242 | // Unzip returns two arrays built from the elements of a sequence of pairs. 243 | func Unzip[T, U any](n int, unzipFn func(i int) (T, U)) ([]T, []U) { 244 | if n < 0 { 245 | panic("negative length") 246 | } 247 | a := make([]T, n) 248 | b := make([]U, n) 249 | for i := 0; i < n; i++ { 250 | a[i], b[i] = unzipFn(i) 251 | } 252 | return a, b 253 | } 254 | 255 | /* @example Sample 256 | gfn.Sample([]int{1, 2, 3, 4, 5}, 3) // []int{3, 1, 5} or other random choices. 257 | */ 258 | 259 | // Sample returns a random sample of n elements from an array. Every position in 260 | // the array are at most selected once. n should be less or equal to len(array). 261 | func Sample[T any](array []T, n int) []T { 262 | if n < 0 { 263 | panic("negative length") 264 | } 265 | if n > len(array) { 266 | panic("sample size larger than array length") 267 | } 268 | indexes := Range(0, n) 269 | Shuffle(indexes) 270 | res := make([]T, n) 271 | for i := 0; i < n; i++ { 272 | res[i] = array[indexes[i]] 273 | } 274 | return res 275 | } 276 | 277 | /* @example Uniq 278 | gfn.Uniq([]int{1, 2, 2, 3, 3, 3, 4, 4, 4, 4}) // []int{1, 2, 3, 4} 279 | */ 280 | 281 | // Uniq returns an array with all duplicates removed. 282 | func Uniq[T comparable](array []T) []T { 283 | res := []T{} 284 | seen := make(map[T]struct{}) 285 | for _, v := range array { 286 | if _, ok := seen[v]; !ok { 287 | res = append(res, v) 288 | seen[v] = struct{}{} 289 | } 290 | } 291 | return res 292 | } 293 | 294 | /* @example UniqBy 295 | type Employee struct { 296 | name string 297 | department string 298 | } 299 | employees := []Employee{ 300 | {"Alice", "Accounting"}, 301 | {"Bob", "Accounting"}, 302 | {"Cindy", "Engineering"}, 303 | {"Dave", "Engineering"}, 304 | } 305 | gfn.UniqBy(employees, func(e Employee) string { 306 | return e.department 307 | }) 308 | // []Employee{{"Alice", "Accounting"}, {"Cindy", "Engineering"}} 309 | */ 310 | 311 | // UniqBy returns an array with all duplicates removed by applying a function to each element. 312 | func UniqBy[T any, U comparable](array []T, fn func(T) U) []T { 313 | res := []T{} 314 | seen := make(map[U]struct{}) 315 | for _, v := range array { 316 | value := fn(v) 317 | if _, ok := seen[value]; !ok { 318 | res = append(res, v) 319 | seen[value] = struct{}{} 320 | } 321 | } 322 | return res 323 | } 324 | 325 | /* @example Union 326 | gfn.Union([]int{1, 2, 3}, []int{2, 3, 4}, []int{3, 4, 5}) 327 | // []int{1, 2, 3, 4, 5} 328 | */ 329 | 330 | // Union returns an array with all duplicates removed from multiple arrays. 331 | func Union[T comparable](arrays ...[]T) []T { 332 | res := []T{} 333 | seen := make(map[T]struct{}) 334 | for _, array := range arrays { 335 | for _, v := range array { 336 | if _, ok := seen[v]; !ok { 337 | res = append(res, v) 338 | seen[v] = struct{}{} 339 | } 340 | } 341 | } 342 | return res 343 | } 344 | 345 | /* @example UnionBy 346 | type Employee struct { 347 | name string 348 | department string 349 | } 350 | group1 := []Employee{ 351 | {"Alice", "Accounting"}, 352 | {"Bob", "Accounting"}, 353 | {"Cindy", "Engineering"}, 354 | } 355 | group2 := []Employee{ 356 | {"Alice", "Accounting"}, 357 | {"Cindy", "Engineering"}, 358 | {"Dave", "Engineering"}, 359 | {"Eve", "Engineering"}, 360 | } 361 | gfn.UnionBy(func(e Employee) string { return e.name }, group1, group2) 362 | // []Employee{ 363 | // {"Alice", "Accounting"}, 364 | // {"Bob", "Accounting"}, 365 | // {"Cindy", "Engineering"}, 366 | // {"Dave", "Engineering"}, 367 | // {"Eve", "Engineering"}, 368 | // } 369 | */ 370 | 371 | // UnionBy returns an array with all duplicates removed from multiple arrays 372 | // by applying a function to each element. 373 | func UnionBy[T any, U comparable](fn func(T) U, arrays ...[]T) []T { 374 | res := []T{} 375 | seen := make(map[U]struct{}) 376 | for _, array := range arrays { 377 | for _, v := range array { 378 | value := fn(v) 379 | if _, ok := seen[value]; !ok { 380 | res = append(res, v) 381 | seen[value] = struct{}{} 382 | } 383 | } 384 | } 385 | return res 386 | } 387 | 388 | /* @example Copy 389 | gfn.Copy([]int{1, 2, 3}) // []int{1, 2, 3} 390 | 391 | array := []int{1, 2, 3, 4, 5, 6} 392 | gfn.Copy(array[2:]) 393 | // []int{3, 4, 5, 6} 394 | */ 395 | 396 | // Copy returns a new array that is a shallow copy of the original array. 397 | func Copy[T any](array []T) []T { 398 | res := make([]T, len(array)) 399 | copy(res, array) 400 | return res 401 | } 402 | 403 | /* @example Difference 404 | gfn.Difference([]int{1, 2, 3, 4}, []int{2, 4}) // []int{1, 3} 405 | */ 406 | 407 | // Difference returns a new array that is a copy of the original array, 408 | // removing all occurrences of any item that also appear in others. 409 | // The order is preserved from the original array. 410 | func Difference[T comparable](array []T, others ...[]T) []T { 411 | res := Copy(array) 412 | for _, other := range others { 413 | m := ToSet(other) 414 | res = Filter(res, func(v T) bool { 415 | _, ok := m[v] 416 | return !ok 417 | }) 418 | } 419 | return res 420 | } 421 | 422 | /* @example DifferenceBy 423 | type Data struct { 424 | value int 425 | } 426 | data1 := []Data{{1}, {3}, {2}, {4}, {5}, {2}} 427 | data2 := []Data{{3}, {4}, {5}} 428 | gfn.DifferenceBy(func(d Data) int { return d.value }, data1, data2) 429 | // []Data{{1}, {2}, {2}} 430 | */ 431 | 432 | // DifferenceBy returns a new array that is a copy of the original array, 433 | // removing all occurrences of any item that also appear in others. The occurrences 434 | // are determined by applying a function to each element. 435 | func DifferenceBy[T any, U comparable](fn func(T) U, array []T, others ...[]T) []T { 436 | res := make([]Pair[U, T], len(array)) 437 | for i, v := range array { 438 | res[i] = Pair[U, T]{fn(v), v} 439 | } 440 | for _, other := range others { 441 | seen := map[U]struct{}{} 442 | for _, v := range other { 443 | seen[fn(v)] = struct{}{} 444 | } 445 | res = Filter(res, func(p Pair[U, T]) bool { 446 | _, ok := seen[p.First] 447 | return !ok 448 | }) 449 | } 450 | return Map(res, func(p Pair[U, T]) T { 451 | return p.Second 452 | }) 453 | } 454 | 455 | /* @example Fill 456 | array := make([]bool, 5) 457 | gfn.Fill(array, true) 458 | // []bool{true, true, true, true, true} 459 | 460 | // you can control the start and end index by using the slice 461 | array2 := make([]int, 5) 462 | gfn.Fill(array2[2:], 100) 463 | // []int{0, 0, 100, 100, 100} 464 | */ 465 | 466 | // Fill sets all elements of an array to a given value. 467 | // You can control the start and end index by using the slice. 468 | func Fill[T any](array []T, value T) { 469 | for i := range array { 470 | array[i] = value 471 | } 472 | } 473 | 474 | /* @example Count 475 | gfn.Count([]int{1, 2, 2, 2, 5, 6}, 2) // 3 476 | */ 477 | 478 | // Count returns the number of occurrences of a value in an array. 479 | func Count[T comparable](array []T, value T) int { 480 | res := 0 481 | for _, v := range array { 482 | if v == value { 483 | res++ 484 | } 485 | } 486 | return res 487 | } 488 | 489 | /* @example CountBy 490 | type Employee struct { 491 | name string 492 | department string 493 | } 494 | employees := []Employee{ 495 | {"Alice", "Accounting"}, 496 | {"Cindy", "Engineering"}, 497 | {"Dave", "Engineering"}, 498 | {"Eve", "Engineering"}, 499 | } 500 | gfn.CountBy(employees, func(e Employee) bool { 501 | return e.department == "Engineering" 502 | }) // 3 503 | */ 504 | 505 | // CountBy returns the number of elements in an array that satisfy a predicate. 506 | func CountBy[T any](array []T, fn func(T) bool) int { 507 | res := 0 508 | for _, v := range array { 509 | if fn(v) { 510 | res++ 511 | } 512 | } 513 | return res 514 | } 515 | 516 | /* @example GroupBy 517 | array := []int{1, 2, 3, 4, 5, 6, 7, 8} 518 | groups := gfn.GroupBy(array, func(i int) string { 519 | if i%2 == 0 { 520 | return "even" 521 | } 522 | return "odd" 523 | }) 524 | // map[string][]int{ 525 | // "even": []int{2, 4, 6, 8}, 526 | // "odd": []int{1, 3, 5, 7}, 527 | // } 528 | */ 529 | 530 | // GroupBy generate a map of arrays by grouping the elements of an array 531 | // according to a given function. 532 | func GroupBy[T any, K comparable](array []T, groupFn func(T) K) map[K][]T { 533 | res := make(map[K][]T) 534 | for _, v := range array { 535 | k := groupFn(v) 536 | res[k] = append(res[k], v) 537 | } 538 | return res 539 | } 540 | 541 | /* @example IndexOf 542 | gfn.IndexOf([]int{1, 2, 3, 4}, 3) // 2 543 | gfn.IndexOf([]int{1, 2, 3, 4}, 5) // -1 544 | */ 545 | 546 | // IndexOf returns the index of the first occurrence of a value in an array, 547 | // or -1 if not found. 548 | func IndexOf[T comparable](array []T, value T) int { 549 | for i, v := range array { 550 | if v == value { 551 | return i 552 | } 553 | } 554 | return -1 555 | } 556 | 557 | /* @example LastIndexOf 558 | gfn.LastIndexOf([]int{3, 3, 3, 4}, 3) // 2 559 | gfn.LastIndexOf([]int{1, 2, 3, 4}, 5) // -1 560 | */ 561 | 562 | // LastIndexOf returns the index of the last occurrence of a value in an array, 563 | // or -1 if not found. 564 | func LastIndexOf[T comparable](array []T, value T) int { 565 | for i := len(array) - 1; i >= 0; i-- { 566 | if array[i] == value { 567 | return i 568 | } 569 | } 570 | return -1 571 | } 572 | 573 | /* @example Reverse 574 | array := []int{1, 2, 3, 4} 575 | gfn.Reverse(array) 576 | // []int{4, 3, 2, 1} 577 | */ 578 | 579 | // Reverse reverses an array in place. 580 | func Reverse[T any](array []T) { 581 | for i, j := 0, len(array)-1; i < j; i, j = i+1, j-1 { 582 | array[i], array[j] = array[j], array[i] 583 | } 584 | } 585 | 586 | /* @example All 587 | gfn.All([]int{1, 2, 3, 4}, func(i int) bool { 588 | return i > 0 589 | } 590 | // true 591 | */ 592 | 593 | // All returns true if all elements in an array pass a given test. 594 | func All[T any](array []T, fn func(T) bool) bool { 595 | for _, v := range array { 596 | if !fn(v) { 597 | return false 598 | } 599 | } 600 | return true 601 | } 602 | 603 | /* @example Any 604 | gfn.Any([]int{1, 2, 3, 4}, func(i int) bool { 605 | return i > 3 606 | } 607 | // true 608 | */ 609 | 610 | // Any returns true if at least one element in an array passes a given test. 611 | func Any[T any](array []T, fn func(T) bool) bool { 612 | for _, v := range array { 613 | if fn(v) { 614 | return true 615 | } 616 | } 617 | return false 618 | } 619 | 620 | /* @example Concat 621 | gfn.Concat([]int{1, 2}, []int{3, 4}) // []int{1, 2, 3, 4} 622 | */ 623 | 624 | // Concat returns a new array that is the result of joining two or more arrays. 625 | func Concat[T any](arrays ...[]T) []T { 626 | var res []T 627 | for _, array := range arrays { 628 | res = append(res, array...) 629 | } 630 | return res 631 | } 632 | 633 | /* @example Find 634 | value, index := gfn.Find([]string{"a", "ab", "abc"}, func(s string) bool { 635 | return len(s) > 1 636 | }) 637 | // "ab", 1 638 | */ 639 | 640 | // Find returns the first element in an array that passes a given test and corresponding index. 641 | // Index of -1 is returned if no element passes the test. 642 | func Find[T any](array []T, fn func(T) bool) (T, int) { 643 | for i, v := range array { 644 | if fn(v) { 645 | return v, i 646 | } 647 | } 648 | var res T 649 | return res, -1 650 | } 651 | 652 | /* @example FindLast 653 | value, index := gfn.FindLast([]string{"a", "ab", "abc"}, func(s string) bool { 654 | return len(s) > 1 655 | }) 656 | // "abc", 2 657 | */ 658 | 659 | // FindLast returns the last element in an array that passes a given test and corresponding index. 660 | // Index of -1 is returned if no element passes the test. 661 | func FindLast[T any](array []T, fn func(T) bool) (T, int) { 662 | for i := len(array) - 1; i >= 0; i-- { 663 | if fn(array[i]) { 664 | return array[i], i 665 | } 666 | } 667 | var res T 668 | return res, -1 669 | } 670 | 671 | /* @example Remove 672 | gfn.Remove([]int{1, 2, 3, 4, 2, 3, 2, 3}, 2, 3) // []int{1, 4} 673 | */ 674 | 675 | // Remove removes all elements from an array that equal to given values. 676 | func Remove[T comparable](array []T, values ...T) []T { 677 | res := []T{} 678 | valueSet := ToSet(values) 679 | for _, v := range array { 680 | _, ok := valueSet[v] 681 | if !ok { 682 | res = append(res, v) 683 | } 684 | } 685 | return res 686 | } 687 | 688 | /* @example Intersection 689 | arr1 := []int{1, 2, 3, 4, 5} 690 | arr2 := []int{2, 3, 4, 5, 6} 691 | arr3 := []int{5, 4, 3, 2} 692 | arr4 := []int{2, 3} 693 | gfn.Intersection(arr1, arr2, arr3, arr4) // []int{2, 3} 694 | */ 695 | 696 | // Intersection returns a new array that is the intersection of two or more arrays. 697 | func Intersection[T comparable](arrays ...[]T) []T { 698 | if len(arrays) <= 1 { 699 | panic("requires at least 2 arrays") 700 | } 701 | 702 | res := Uniq(arrays[0]) 703 | for _, arr := range arrays[1:] { 704 | set := ToSet(arr) 705 | res = Filter(res, func(v T) bool { 706 | _, ok := set[v] 707 | return ok 708 | }) 709 | } 710 | return res 711 | } 712 | 713 | /* @example IntersectionBy 714 | type Data struct { 715 | value int 716 | } 717 | data1 := []Data{{1}, {3}, {2}, {4}, {5}} 718 | data2 := []Data{{2}, {3}} 719 | gfn.IntersectionBy(func(d Data) int { return d.value }, data1, data2) 720 | // []Data{{3}, {2}} 721 | */ 722 | 723 | // IntersectionBy returns a new array that is the intersection of two or more arrays, 724 | // where intersection is determined by a given function. 725 | func IntersectionBy[T any, U comparable](fn func(T) U, arrays ...[]T) []T { 726 | if len(arrays) <= 1 { 727 | panic("requires at least 2 arrays") 728 | } 729 | // make unique pair of array[0] 730 | var res []Pair[U, T] 731 | seen := map[U]struct{}{} 732 | for _, v := range arrays[0] { 733 | key := fn(v) 734 | if _, ok := seen[key]; !ok { 735 | res = append(res, Pair[U, T]{key, v}) 736 | seen[key] = struct{}{} 737 | } 738 | } 739 | // filter by seen 740 | for _, arr := range arrays[1:] { 741 | seen = map[U]struct{}{} 742 | for _, v := range arr { 743 | seen[fn(v)] = struct{}{} 744 | } 745 | res = Filter(res, func(p Pair[U, T]) bool { 746 | _, ok := seen[p.First] 747 | return ok 748 | }) 749 | } 750 | return Map(res, func(p Pair[U, T]) T { 751 | return p.Second 752 | }) 753 | } 754 | 755 | /* @example Repeat 756 | gfn.Repeat([]int{1, 2, 3}, 3) // []int{1, 2, 3, 1, 2, 3, 1, 2, 3} 757 | */ 758 | 759 | // Repeat returns a new array that is the result of repeating an array 760 | // a given number of times. 761 | func Repeat[T any](array []T, repeat int) []T { 762 | if repeat < 0 { 763 | panic("repeat must be greater or equal to 0") 764 | } 765 | if repeat == 0 { 766 | return []T{} 767 | } 768 | if repeat == 1 { 769 | return Copy(array) 770 | } 771 | 772 | res := make([]T, len(array)*repeat) 773 | for i := 0; i < repeat; i++ { 774 | copy(res[i*len(array):], array) 775 | } 776 | return res 777 | } 778 | 779 | /* @example ForEach 780 | sum := 0 781 | gfn.ForEach([]int{1, 2, 3}, func(i int) { 782 | sum += i 783 | }) 784 | // sum == 6 785 | */ 786 | 787 | // ForEach executes a provided function once for each array element. 788 | func ForEach[T any](array []T, fn func(value T)) { 789 | for _, v := range array { 790 | fn(v) 791 | } 792 | } 793 | 794 | /* @example Chunk 795 | gfn.Chunk([]int{1, 2, 3, 4, 5}, 2) // [][]int{{1, 2}, {3, 4}, {5}} 796 | */ 797 | 798 | // Chunk splits an array into chunks of given size. 799 | func Chunk[T any](array []T, size int) [][]T { 800 | if size <= 0 { 801 | panic("size must be greater than 0") 802 | } 803 | 804 | var res [][]T 805 | for i := 0; i < len(array); i += size { 806 | end := i + size 807 | if end > len(array) { 808 | end = len(array) 809 | } 810 | res = append(res, array[i:end]) 811 | } 812 | return res 813 | } 814 | -------------------------------------------------------------------------------- /array_test.go: -------------------------------------------------------------------------------- 1 | package gfn_test 2 | 3 | import ( 4 | "math/rand" 5 | "sort" 6 | "strconv" 7 | "testing" 8 | "time" 9 | 10 | . "github.com/suchen-sci/gfn" 11 | ) 12 | 13 | func TestContains(t *testing.T) { 14 | AssertTrue(t, Contains([]int{1, 2, 3}, 2)) 15 | AssertFalse(t, Contains([]int{1, 2, 3}, 4)) 16 | 17 | AssertTrue(t, Contains([]string{"a", "b", "c"}, "b")) 18 | AssertFalse(t, Contains([]string{"a", "b", "c"}, "d")) 19 | 20 | AssertTrue(t, Contains([]bool{true, false, true}, true)) 21 | AssertFalse(t, Contains([]bool{true, true, true}, false)) 22 | 23 | AssertTrue(t, Contains([]float64{1.1, 2.2, 3.3}, 2.2)) 24 | AssertFalse(t, Contains([]float64{1.1, 2.2, 3.3}, 4.4)) 25 | 26 | type data struct { 27 | data int 28 | } 29 | AssertTrue(t, Contains([]data{{data: 1}, {data: 2}}, data{data: 2})) 30 | AssertFalse(t, Contains([]data{{data: 1}, {data: 2}}, data{data: 3})) 31 | 32 | dataA := &data{data: 1} 33 | dataB := &data{data: 1} 34 | AssertTrue(t, Contains([]*data{dataA, dataB}, dataA)) 35 | AssertFalse(t, Contains([]*data{dataA, dataB}, &data{data: 1})) 36 | 37 | AssertTrue(t, Contains([]time.Duration{time.Second, 2 * time.Second}, time.Second)) 38 | AssertFalse(t, Contains([]time.Duration{time.Second, 2 * time.Second}, 3*time.Second)) 39 | 40 | type Number int 41 | type Numbers []Number 42 | AssertTrue(t, Contains(Numbers{1, 2, 3}, Number(2))) 43 | } 44 | 45 | func TestRange(t *testing.T) { 46 | AssertSliceEqual(t, []int{0, 1, 2, 3, 4, 5, 6}, Range(0, 7)) 47 | AssertSliceEqual(t, []int{5, 6, 7, 8, 9}, Range(5, 10)) 48 | AssertSliceEqual(t, []int{}, Range(0, 0)) 49 | AssertSliceEqual(t, []int{}, Range(10, 0)) 50 | AssertSliceEqual(t, []int{-3, -2, -1, 0, 1}, Range(-3, 2)) 51 | 52 | AssertSliceEqual(t, []uint{0, 1, 2, 3, 4, 5, 6}, Range[uint](0, 7)) 53 | AssertSliceEqual(t, []uint{5, 6, 7, 8, 9}, Range[uint](5, 10)) 54 | AssertSliceEqual(t, []uint{}, Range[uint](0, 0)) 55 | AssertSliceEqual(t, []uint{}, Range[uint](10, 0)) 56 | } 57 | 58 | func TestRangeBy(t *testing.T) { 59 | AssertSliceEqual(t, []int{0, 1, 2, 3, 4, 5, 6}, RangeBy(0, 7, 1)) 60 | AssertSliceEqual(t, []int{0, 3, 6, 9, 12, 15}, RangeBy(0, 17, 3)) 61 | AssertSliceEqual(t, []int{}, RangeBy(0, 5, -2)) 62 | AssertSliceEqual(t, []int{-10, -8, -6, -4, -2, 0}, RangeBy(-10, 1, 2)) 63 | AssertSliceEqual(t, []int{}, RangeBy(0, 0, 1)) 64 | AssertSliceEqual(t, []int{0, 2, 4, 6}, RangeBy(0, 8, 2)) 65 | AssertSliceEqual(t, []int{10, 8, 6, 4, 2}, RangeBy(10, 0, -2)) 66 | 67 | AssertSliceEqual(t, []int{13, 11, 9, 7}, RangeBy(13, 5, -2)) 68 | AssertSliceEqual(t, []int{29, 24, 19, 14, 9, 4}, RangeBy(29, 0, -5)) 69 | AssertSliceEqual(t, []int{}, RangeBy(5, 0, 2)) 70 | AssertSliceEqual(t, []int{1, -1, -3, -5, -7, -9}, RangeBy(1, -10, -2)) 71 | AssertSliceEqual(t, []int{}, RangeBy(0, 0, -1)) 72 | 73 | AssertSliceEqual(t, []uint{0, 1, 2, 3, 4, 5, 6}, RangeBy[uint](0, 7, 1)) 74 | AssertSliceEqual(t, []uint{0, 3, 6, 9, 12, 15}, RangeBy[uint](0, 17, 3)) 75 | AssertSliceEqual(t, []uint{}, RangeBy[uint](0, 0, 1)) 76 | AssertSliceEqual(t, []uint{}, RangeBy[uint](5, 0, 2)) 77 | 78 | AssertSliceEqual(t, []int64{}, RangeBy[int64](0, 5, -2)) 79 | AssertSliceEqual(t, []int64{-10, -8, -6, -4, -2, 0}, RangeBy[int64](-10, 1, 2)) 80 | AssertSliceEqual(t, []int64{13, 11, 9, 7}, RangeBy[int64](13, 5, -2)) 81 | AssertSliceEqual(t, []int64{29, 24, 19, 14, 9, 4}, RangeBy[int64](29, 0, -5)) 82 | AssertSliceEqual(t, []int64{1, -1, -3, -5, -7, -9}, RangeBy[int64](1, -10, -2)) 83 | AssertSliceEqual(t, []int64{}, RangeBy[int64](0, 0, -1)) 84 | 85 | AssertPanics(t, func() { 86 | RangeBy(0, 10, 0) 87 | }) 88 | } 89 | 90 | func TestShuffle(t *testing.T) { 91 | array := Range(0, 200000) 92 | Shuffle(array) 93 | AssertFalse(t, IsSorted(array)) 94 | AssertFalse(t, Equal(Range(0, 200000), array)) 95 | 96 | sort.Ints(array) 97 | AssertTrue(t, IsSorted(array)) 98 | AssertSliceEqual(t, Range(0, 200000), array) 99 | } 100 | 101 | func TestEqual(t *testing.T) { 102 | AssertTrue(t, Equal([]int{1, 2, 3}, []int{1, 2, 3})) 103 | AssertFalse(t, Equal([]int{1, 3, 2}, []int{1, 2, 3})) 104 | 105 | AssertTrue(t, Equal([]string{"a", "b", "c"}, []string{"a", "b", "c"})) 106 | AssertFalse(t, Equal([]string{"a", "c", "b"}, []string{"a", "b", "c"})) 107 | AssertFalse(t, Equal([]string{"a", "c", "b"}, []string{"a", "b"})) 108 | } 109 | 110 | func TestToSet(t *testing.T) { 111 | set := ToSet([]int{0, 1, 2, 3, 4, 5}) 112 | AssertEqual(t, 6, len(set)) 113 | for i := 0; i < 6; i++ { 114 | _, ok := set[i] 115 | AssertTrue(t, ok, strconv.Itoa(i)) 116 | } 117 | 118 | set = ToSet([]int{0, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5}) 119 | AssertEqual(t, 6, len(set)) 120 | for i := 0; i < 6; i++ { 121 | _, ok := set[i] 122 | AssertTrue(t, ok, strconv.Itoa(i)) 123 | } 124 | 125 | expected := map[int]struct{}{0: {}, 1: {}, 2: {}, 3: {}, 4: {}, 5: {}} 126 | AssertMapEqual(t, expected, set) 127 | } 128 | 129 | func TestIsSortedBy(t *testing.T) { 130 | AssertTrue(t, IsSortedBy([]int{}, func(a, b int) bool { return a < b })) 131 | AssertTrue(t, IsSortedBy([]int{1, 2, 3, 4, 5, 6, 7}, func(a, b int) bool { return a <= b })) 132 | AssertTrue(t, IsSortedBy([]int{1, 1, 1, 2, 2, 2, 2}, func(a, b int) bool { return a <= b })) 133 | AssertTrue(t, IsSortedBy([]int{2, 2, 2, 1, 1, 1, -1, -1}, func(a, b int) bool { return a >= b })) 134 | 135 | AssertFalse(t, IsSortedBy([]int{1, 2, 10, 4, 5, 6, 7}, func(a, b int) bool { return a <= b })) 136 | AssertFalse(t, IsSortedBy([]int{1, 1, 1, 100, 2, 2, 2}, func(a, b int) bool { return a <= b })) 137 | AssertFalse(t, IsSortedBy([]int{2, 2, -10, 1, 1, 1, -1, -1}, func(a, b int) bool { return a >= b })) 138 | } 139 | 140 | func TestDistribution(t *testing.T) { 141 | // check empty array 142 | AssertMapEqual(t, map[int]int{}, Counter([]int{})) 143 | 144 | // check array with many elements 145 | { 146 | array := make([]int, 100000) 147 | for i := 0; i < 100000; i++ { 148 | array[i] = i 149 | } 150 | rand.Shuffle(len(array), func(i, j int) { 151 | array[i], array[j] = array[j], array[i] 152 | }) 153 | distr := Counter(array) 154 | for i := 0; i < 100000; i++ { 155 | AssertEqual(t, 1, distr[i]) 156 | } 157 | } 158 | 159 | // check distribution 160 | AssertMapEqual(t, map[int]int{1: 1, 2: 1, 3: 1, 4: 1}, Counter([]int{1, 2, 3, 4})) 161 | AssertMapEqual(t, map[int]int{1: 1, 2: 2, 3: 1, 4: 1}, Counter([]int{1, 2, 3, 4, 2})) 162 | AssertMapEqual(t, map[int]int{1: 1, 2: 4}, Counter([]int{1, 2, 2, 2, 2})) 163 | } 164 | 165 | func TestIsSorted(t *testing.T) { 166 | AssertTrue(t, IsSorted([]int{})) 167 | AssertTrue(t, IsSorted([]int{1, 2, 3})) 168 | AssertTrue(t, IsSorted([]int{1, 1, 1, 1, 1, 1})) 169 | AssertTrue(t, IsSorted([]int{1, 2, 2, 3, 3, 3})) 170 | 171 | AssertFalse(t, IsSorted([]int{1, 23, 2})) 172 | AssertFalse(t, IsSorted([]int{1, 23, 99, 1, 100, 2})) 173 | } 174 | 175 | func TestZip(t *testing.T) { 176 | AssertSliceEqual(t, []Pair[int, int]{}, Zip([]int{}, []int{})) 177 | AssertSliceEqual(t, []Pair[int, string]{{1, "a"}, {2, "b"}, {3, "c"}}, Zip([]int{1, 2, 3}, []string{"a", "b", "c"})) 178 | AssertSliceEqual(t, []Pair[int, string]{{1, "a"}, {2, "b"}}, Zip([]int{1, 2}, []string{"a", "b", "c"})) 179 | } 180 | 181 | func TestUnzip(t *testing.T) { 182 | pairs := []Pair[int, string]{ 183 | {First: 1, Second: "a"}, 184 | {First: 2, Second: "b"}, 185 | {First: 3, Second: "c"}, 186 | } 187 | a, b := Unzip(len(pairs), func(i int) (int, string) { 188 | return pairs[i].First, pairs[i].Second 189 | }) 190 | AssertSliceEqual(t, []int{1, 2, 3}, a) 191 | AssertSliceEqual(t, []string{"a", "b", "c"}, b) 192 | 193 | AssertPanics(t, func() { 194 | Unzip(-1, func(i int) (int, string) { 195 | return 0, "" 196 | }) 197 | }) 198 | } 199 | 200 | func TestSample(t *testing.T) { 201 | for i := 0; i < 100; i++ { 202 | array := Range(i, i+100) 203 | samples := Sample(array, 90) 204 | AssertEqual(t, 90, len(samples)) 205 | m := map[int]struct{}{} 206 | for _, sample := range samples { 207 | _, ok := m[sample] 208 | AssertFalse(t, ok) 209 | m[sample] = struct{}{} 210 | AssertTrue(t, Contains(array, sample)) 211 | } 212 | } 213 | 214 | AssertPanics(t, func() { 215 | Sample([]int{}, 1) 216 | }) 217 | 218 | AssertPanics(t, func() { 219 | Sample([]int{1}, -1) 220 | }) 221 | } 222 | 223 | func TestUniq(t *testing.T) { 224 | for i := 0; i < 100; i++ { 225 | array := []int{} 226 | start := rand.Intn(100) 227 | for j := start; j < start+100; j++ { 228 | array = append(array, j, j, j) 229 | } 230 | Shuffle(array) 231 | array = Uniq(array) 232 | sort.Ints(array) 233 | AssertEqual(t, 100, len(array)) 234 | AssertSliceEqual(t, Range(start, start+100), array) 235 | } 236 | } 237 | 238 | func TestUnion(t *testing.T) { 239 | a1 := []int{1, 2, 2, 3, 3, 4, 5} 240 | a2 := []int{2, 3, 3, 4, 4, 5, 6} 241 | a3 := []int{3, 4, 4, 5, 5, 6, 7} 242 | AssertSliceEqual(t, []int{1, 2, 3, 4, 5, 6, 7}, Union(a1, a2, a3)) 243 | } 244 | 245 | func TestCopy(t *testing.T) { 246 | a := []int{1, 2, 3, 4, 5} 247 | b := Copy(a) 248 | AssertSliceEqual(t, a, b) 249 | a[0] = 100 250 | AssertEqual(t, 1, b[0]) 251 | 252 | c := []int{1, 2, 3, 4, 5} 253 | d := Copy(c[2:]) 254 | AssertSliceEqual(t, []int{3, 4, 5}, d) 255 | } 256 | 257 | func TestDiff(t *testing.T) { 258 | a := []int{1, 2, 3, 4, 5, 6, 7} 259 | b := []int{2, 4, 6} 260 | c := []int{1, 3, 5, 7} 261 | AssertSliceEqual(t, c, Difference(a, b)) 262 | 263 | for i := 0; i < 100; i++ { 264 | a := Range(i, i+200) 265 | b := Range(i+50, i+150) 266 | c := Range(i+150, i+200) 267 | Shuffle(a) 268 | Shuffle(b) 269 | Shuffle(c) 270 | 271 | d := Difference(a, b, c) 272 | sort.Ints(d) 273 | AssertSliceEqual(t, Range(i, i+50), d) 274 | } 275 | } 276 | 277 | func TestFill(t *testing.T) { 278 | array := make([]bool, 5) 279 | AssertSliceEqual(t, []bool{false, false, false, false, false}, array) 280 | Fill(array, true) 281 | AssertSliceEqual(t, []bool{true, true, true, true, true}, array) 282 | 283 | array2 := make([]int, 5) 284 | Fill(array2[2:], 100) 285 | AssertSliceEqual(t, []int{0, 0, 100, 100, 100}, array2) 286 | } 287 | 288 | func TestCount(t *testing.T) { 289 | array := Range(0, 200) 290 | Shuffle(array) 291 | for i := 0; i < 200; i++ { 292 | AssertEqual(t, 1, Count(array, i)) 293 | } 294 | 295 | array2 := []int{1, 1, 1, 2, 1, 4, 1} 296 | AssertEqual(t, 5, Count(array2, 1)) 297 | } 298 | 299 | func TestGroupBy(t *testing.T) { 300 | array := []int{1, 2, 3, 4, 5, 6, 7, 8} 301 | groups := GroupBy(array, func(i int) string { 302 | if i%2 == 0 { 303 | return "even" 304 | } 305 | return "odd" 306 | }) 307 | AssertEqual(t, 2, len(groups)) 308 | AssertSliceEqual(t, []int{2, 4, 6, 8}, groups["even"]) 309 | AssertSliceEqual(t, []int{1, 3, 5, 7}, groups["odd"]) 310 | } 311 | 312 | func TestIndexOf(t *testing.T) { 313 | AssertEqual(t, -1, IndexOf([]int{}, 1)) 314 | AssertEqual(t, -1, IndexOf([]int{1, 2, 3}, 4)) 315 | AssertEqual(t, 2, IndexOf([]int{1, 2, 3}, 3)) 316 | AssertEqual(t, 2, IndexOf([]int{1, 2, 3, 3, 3}, 3)) 317 | } 318 | 319 | func TestLastIndexOf(t *testing.T) { 320 | AssertEqual(t, -1, LastIndexOf([]int{}, 1)) 321 | AssertEqual(t, -1, LastIndexOf([]int{1, 2, 3}, 4)) 322 | AssertEqual(t, 2, LastIndexOf([]int{3, 3, 3}, 3)) 323 | AssertEqual(t, 0, LastIndexOf([]int{3, 2, 2, 2, 2}, 3)) 324 | } 325 | 326 | func TestReverse(t *testing.T) { 327 | array := []int{1, 2, 3, 4, 5} 328 | Reverse(array) 329 | AssertSliceEqual(t, []int{5, 4, 3, 2, 1}, array) 330 | 331 | array2 := []int{1, 2, 3, 4} 332 | Reverse(array2) 333 | AssertSliceEqual(t, []int{4, 3, 2, 1}, array2) 334 | } 335 | 336 | func TestAll(t *testing.T) { 337 | AssertTrue(t, All([]int{1, 2, 3, 4, 5}, func(i int) bool { 338 | return i > 0 339 | })) 340 | AssertFalse(t, All([]int{1, 2, 3, 4, 5}, func(i int) bool { 341 | return i > 1 342 | })) 343 | } 344 | 345 | func TestAny(t *testing.T) { 346 | AssertTrue(t, Any([]int{1, 2, 3, 4, 5}, func(i int) bool { 347 | return i > 4 348 | })) 349 | AssertFalse(t, Any([]int{1, 2, 3, 4, 5}, func(i int) bool { 350 | return i > 5 351 | })) 352 | } 353 | 354 | func TestConcat(t *testing.T) { 355 | AssertSliceEqual(t, []int{1, 2, 3, 4, 5}, Concat([]int{1, 2}, []int{3, 4, 5})) 356 | AssertSliceEqual(t, []int{1, 2, 3, 4, 5}, Concat([]int{1, 2}, []int{3}, []int{4, 5})) 357 | AssertSliceEqual(t, []int{1, 2, 3, 4, 5}, Concat([]int{1, 2}, []int{3}, []int{4}, []int{5})) 358 | AssertSliceEqual(t, []int{1, 2, 3, 4, 5}, Concat([]int{1, 2}, []int{}, []int{3}, []int{4}, []int{}, []int{5})) 359 | } 360 | 361 | func TestFind(t *testing.T) { 362 | { 363 | value, index := Find([]string{}, func(s string) bool { 364 | return len(s) != 0 365 | }) 366 | AssertEqual(t, -1, index) 367 | AssertEqual(t, "", value) 368 | } 369 | { 370 | value, index := Find([]string{"a", "ab", "abc"}, func(s string) bool { 371 | return len(s) > 1 372 | }) 373 | AssertEqual(t, 1, index) 374 | AssertEqual(t, "ab", value) 375 | } 376 | } 377 | 378 | func TestFindLast(t *testing.T) { 379 | { 380 | value, index := FindLast([]string{}, func(s string) bool { 381 | return len(s) != 0 382 | }) 383 | AssertEqual(t, -1, index) 384 | AssertEqual(t, "", value) 385 | } 386 | { 387 | value, index := FindLast([]string{"a", "ab", "abc"}, func(s string) bool { 388 | return len(s) > 1 389 | }) 390 | AssertEqual(t, 2, index) 391 | AssertEqual(t, "abc", value) 392 | } 393 | } 394 | 395 | func TestRemove(t *testing.T) { 396 | { 397 | array := []int{1, 2, 3, 4, 2, 4} 398 | AssertSliceEqual(t, []int{1, 3, 4, 4}, Remove(array, 2)) 399 | } 400 | { 401 | array := []int{1, 2, 3, 4, 2, 4} 402 | AssertSliceEqual(t, []int{1, 3}, Remove(array, 2, 4)) 403 | } 404 | } 405 | 406 | func TestIntersection(t *testing.T) { 407 | { 408 | arr1 := []int{1, 2, 3, 4, 5} 409 | arr2 := []int{2, 3, 4, 5, 6} 410 | arr3 := []int{5, 4, 3, 2} 411 | arr4 := []int{2, 3} 412 | AssertSliceEqual(t, []int{2, 3}, Intersection(arr1, arr2, arr3, arr4)) 413 | } 414 | { 415 | arr1 := []int{1, 2, 3, 4, 5, 4, 3, 2, 1} 416 | arr2 := []int{2, 3, 4, 5, 6, 2, 3, 2, 3} 417 | arr3 := []int{5, 4, 3, 2, 2, 3} 418 | arr4 := []int{2, 3, 2, 3, 2, 3} 419 | AssertSliceEqual(t, []int{2, 3}, Intersection(arr1, arr2, arr3, arr4)) 420 | } 421 | 422 | AssertPanics(t, func() { 423 | Intersection[int]() 424 | }) 425 | AssertPanics(t, func() { 426 | Intersection([]int{1, 2, 3}) 427 | }) 428 | } 429 | 430 | func TestRepeat(t *testing.T) { 431 | AssertSliceEqual(t, []int{5, 5, 5}, Repeat([]int{5}, 3)) 432 | AssertSliceEqual(t, []int{5, 3, 1, 5, 3, 1, 5, 3, 1}, Repeat([]int{5, 3, 1}, 3)) 433 | AssertSliceEqual(t, []int{}, Repeat([]int{5, 3, 1}, 0)) 434 | AssertSliceEqual(t, []int{5, 3, 1}, Repeat([]int{5, 3, 1}, 1)) 435 | AssertSliceEqual(t, []int{}, Repeat[int](nil, 1)) 436 | 437 | AssertPanics(t, func() { 438 | Repeat[int](nil, -1) 439 | }) 440 | } 441 | 442 | func TestForEach(t *testing.T) { 443 | sum := 0 444 | ForEach([]int{1, 2, 3}, func(i int) { 445 | sum += i 446 | }) 447 | AssertEqual(t, 6, sum) 448 | } 449 | 450 | func TestCountBy(t *testing.T) { 451 | type Employee struct { 452 | name string 453 | department string 454 | } 455 | employees := []Employee{ 456 | {"Alice", "Accounting"}, 457 | {"Bob", "Accounting"}, 458 | {"Cindy", "Engineering"}, 459 | {"Dave", "Engineering"}, 460 | {"Eve", "Engineering"}, 461 | } 462 | AssertEqual(t, 3, CountBy(employees, func(e Employee) bool { 463 | return e.department == "Engineering" 464 | })) 465 | } 466 | 467 | func TestDistributionBy(t *testing.T) { 468 | type Employee struct { 469 | name string 470 | department string 471 | } 472 | employees := []Employee{ 473 | {"Alice", "Accounting"}, 474 | {"Bob", "Accounting"}, 475 | {"Cindy", "Engineering"}, 476 | {"Dave", "Engineering"}, 477 | {"Eve", "Engineering"}, 478 | } 479 | dist := CounterBy(employees, func(e Employee) string { 480 | return e.department 481 | }) 482 | AssertMapEqual(t, map[string]int{"Accounting": 2, "Engineering": 3}, dist) 483 | } 484 | 485 | func TestUniqBy(t *testing.T) { 486 | type Employee struct { 487 | name string 488 | department string 489 | } 490 | employees := []Employee{ 491 | {"Alice", "Accounting"}, 492 | {"Bob", "Accounting"}, 493 | {"Cindy", "Engineering"}, 494 | {"Dave", "Engineering"}, 495 | {"Eve", "Engineering"}, 496 | } 497 | uniq := UniqBy(employees, func(e Employee) string { 498 | return e.department 499 | }) 500 | expected := []Employee{ 501 | {"Alice", "Accounting"}, 502 | {"Cindy", "Engineering"}, 503 | } 504 | AssertSliceEqual(t, expected, uniq) 505 | } 506 | 507 | func TestUnionBy(t *testing.T) { 508 | type Employee struct { 509 | name string 510 | department string 511 | } 512 | group1 := []Employee{ 513 | {"Alice", "Accounting"}, 514 | {"Bob", "Accounting"}, 515 | {"Cindy", "Engineering"}, 516 | } 517 | group2 := []Employee{ 518 | {"Alice", "Accounting"}, 519 | {"Cindy", "Engineering"}, 520 | {"Dave", "Engineering"}, 521 | {"Eve", "Engineering"}, 522 | } 523 | union := UnionBy(func(e Employee) string { return e.name }, group1, group2) 524 | expected := []Employee{ 525 | {"Alice", "Accounting"}, 526 | {"Bob", "Accounting"}, 527 | {"Cindy", "Engineering"}, 528 | {"Dave", "Engineering"}, 529 | {"Eve", "Engineering"}, 530 | } 531 | AssertSliceEqual(t, expected, union) 532 | } 533 | 534 | func TestIntersectionBy(t *testing.T) { 535 | type Data struct { 536 | value int 537 | } 538 | data1 := []Data{{1}, {3}, {2}, {4}, {5}} 539 | data2 := []Data{{2}, {3}, {4}, {5}, {6}} 540 | data3 := []Data{{5}, {4}, {3}, {2}} 541 | data4 := []Data{{2}, {3}} 542 | intersect := IntersectionBy(func(d Data) int { return d.value }, data1, data2, data3, data4) 543 | expected := []Data{{3}, {2}} 544 | AssertSliceEqual(t, expected, intersect) 545 | 546 | AssertPanics(t, func() { 547 | IntersectionBy(func(d Data) int { return d.value }, data1) 548 | }) 549 | } 550 | 551 | func TestDiffBy(t *testing.T) { 552 | type Data struct { 553 | value int 554 | } 555 | data1 := []Data{{1}, {3}, {2}, {4}, {5}, {2}} 556 | data2 := []Data{{3}, {4}, {5}, {6}} 557 | data3 := []Data{{5}, {4}, {3}} 558 | data4 := []Data{{3}, {4}} 559 | intersect := DifferenceBy(func(d Data) int { return d.value }, data1, data2, data3, data4) 560 | expected := []Data{{1}, {2}, {2}} 561 | AssertSliceEqual(t, expected, intersect) 562 | } 563 | 564 | func TestChunk(t *testing.T) { 565 | { 566 | arr := []int{1, 2, 3, 4, 5, 6, 7, 8} 567 | chunks := Chunk(arr, 1) 568 | expected := [][]int{{1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}} 569 | for i := range chunks { 570 | AssertSliceEqual(t, expected[i], chunks[i], strconv.Itoa(i)) 571 | } 572 | } 573 | { 574 | arr := []int{1, 2, 3, 4, 5, 6, 7, 8} 575 | chunks := Chunk(arr, 4) 576 | expected := [][]int{{1, 2, 3, 4}, {5, 6, 7, 8}} 577 | for i := range chunks { 578 | AssertSliceEqual(t, expected[i], chunks[i], strconv.Itoa(i)) 579 | } 580 | } 581 | { 582 | arr := []int{1, 2, 3, 4, 5, 6, 7, 8} 583 | chunks := Chunk(arr, 3) 584 | expected := [][]int{{1, 2, 3}, {4, 5, 6}, {7, 8}} 585 | for i := range chunks { 586 | AssertSliceEqual(t, expected[i], chunks[i], strconv.Itoa(i)) 587 | } 588 | } 589 | 590 | AssertPanics(t, func() { 591 | Chunk([]int{1, 2, 3}, 0) 592 | }) 593 | } 594 | 595 | func TestEqualBy(t *testing.T) { 596 | { 597 | a := []int{1, 2, 3, 4, 5} 598 | b := []rune{'a', 'b', 'c', 'd', 'e'} 599 | AssertTrue(t, EqualBy(a, b, func(aa int, bb rune) bool { 600 | return (aa - 1) == int(bb-'a') 601 | })) 602 | } 603 | { 604 | a := []int{1, 2, 3, 4, 5} 605 | b := []rune{'a', 'b', 'c', 'd', 'f'} 606 | AssertFalse(t, EqualBy(a, b, func(aa int, bb rune) bool { 607 | return (aa - 1) == int(bb-'a') 608 | })) 609 | } 610 | { 611 | a := []int{1, 2, 3} 612 | b := []rune{'a', 'b', 'c', 'd', 'f'} 613 | AssertFalse(t, EqualBy(a, b, func(aa int, bb rune) bool { 614 | return (aa - 1) == int(bb-'a') 615 | })) 616 | } 617 | } 618 | -------------------------------------------------------------------------------- /cmd/generate.go: -------------------------------------------------------------------------------- 1 | // Package main is used to generate README.md 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "sort" 9 | "strings" 10 | "text/template" 11 | ) 12 | 13 | // See Contributing part in README.md 14 | 15 | var categories = [][2]string{ 16 | {"Functional", "fp.go"}, 17 | {"Math", "math.go"}, 18 | {"Array", "array.go"}, 19 | {"Map", "map.go"}, 20 | } 21 | 22 | const readmeTemplateFile = "README.tmpl.md" 23 | const readmeFile = "README.md" 24 | 25 | func main() { 26 | wd := os.Getenv("GFNCWD") 27 | if wd == "" { 28 | panic("GFNCWD is not set") 29 | } 30 | 31 | toc := "" 32 | content := "" 33 | 34 | for i := 0; i < len(categories); i++ { 35 | cat, err := processCategory(categories[i][0], filepath.Join(wd, categories[i][1])) 36 | if err != nil { 37 | panic(fmt.Errorf("process category %s %s failed, %s", categories[i][0], categories[i][1], err.Error())) 38 | } 39 | toc += cat.toc() 40 | content += cat.content() 41 | } 42 | 43 | readmeTmpl, err := os.ReadFile(filepath.Join(wd, readmeTemplateFile)) 44 | if err != nil { 45 | panic(err) 46 | } 47 | readmeStr := strings.Replace(strings.Replace(string(readmeTmpl), "{{ TOC }}", toc, 1), "{{ CONTENT }}", content, 1) 48 | err = os.WriteFile(filepath.Join(wd, readmeFile), []byte(readmeStr), 0644) 49 | if err != nil { 50 | panic(err) 51 | } 52 | } 53 | 54 | const tocTemplate = ` 55 | - [{{ .Name }}](#{{ .Name | toLower }}) 56 | {{ range .Fns }} - [gfn.{{ .Title }}](#gfn{{ .TOC | toLower }}) 57 | {{ end }} 58 | ` 59 | 60 | const contentTemplate = ` 61 | ## {{ .Name }} 62 | {{ if .Fns }} 63 | {{ range .Fns }} 64 | ### gfn.{{ .Title }} 65 | ;;;go 66 | {{ .Signature }} 67 | ;;; 68 | {{ .Comment }} 69 | {{ if .Example }} 70 | #### Example: 71 | ;;;go 72 | {{ .Example }} 73 | ;;; 74 | [back to top](#gfn) 75 | {{ end }} 76 | {{ end }} 77 | {{ end }} 78 | ` 79 | 80 | type category struct { 81 | Name string 82 | Fns []function 83 | } 84 | 85 | func (c *category) toc() string { 86 | sb := strings.Builder{} 87 | funcs := template.FuncMap{ 88 | "toLower": strings.ToLower, 89 | } 90 | tmlp := template.Must(template.New("toc").Funcs(funcs).Parse(tocTemplate)) 91 | if err := tmlp.Execute(&sb, c); err != nil { 92 | panic(err) 93 | } 94 | return strings.TrimSpace(sb.String()) + "\n" 95 | } 96 | 97 | func (c *category) content() string { 98 | sb := strings.Builder{} 99 | tmpl := template.Must(template.New("function").Parse(strings.ReplaceAll(contentTemplate, ";;;", "```"))) 100 | if err := tmpl.Execute(&sb, c); err != nil { 101 | panic(err) 102 | } 103 | return sb.String() 104 | } 105 | 106 | type fnState int 107 | 108 | const ( 109 | stateStart fnState = iota 110 | stateComment 111 | stateFinish 112 | stateAbort 113 | ) 114 | 115 | type function struct { 116 | Name string 117 | Signature string 118 | Comment string 119 | Example string 120 | state fnState 121 | deprecated bool 122 | } 123 | 124 | func (f *function) Title() string { 125 | if f.deprecated { 126 | return f.Name + " (Deprecated)" 127 | } 128 | return f.Name 129 | } 130 | 131 | func (f *function) TOC() string { 132 | if f.deprecated { 133 | return f.Name + "-deprecated" 134 | } 135 | return f.Name 136 | } 137 | 138 | func (f *function) addComment(line string) { 139 | if f.state == stateStart { 140 | f.state = stateComment 141 | line = strings.TrimPrefix(line, "//") 142 | line = strings.TrimSpace(line) 143 | words := strings.SplitN(line, " ", 3) 144 | if words[0] == "Deprecated:" { 145 | f.deprecated = true 146 | f.Name = words[1] 147 | } else { 148 | f.Name = words[0] 149 | } 150 | f.Comment = line 151 | 152 | } else if f.state == stateComment { 153 | line = strings.TrimPrefix(line, "//") 154 | line = strings.TrimSpace(line) 155 | f.Comment += " " + line 156 | 157 | } 158 | } 159 | 160 | func (f *function) addSignature(line string) { 161 | line = strings.TrimSpace(line) 162 | nameFirstChar := string(strings.TrimPrefix(line, "func ")[0]) 163 | if nameFirstChar != strings.ToUpper(nameFirstChar) { 164 | f.state = stateAbort 165 | return 166 | } 167 | line = strings.TrimRight(line, "{") 168 | f.Signature = line 169 | f.state = stateFinish 170 | } 171 | 172 | func (f *function) finish() bool { 173 | return f.state == stateFinish 174 | } 175 | 176 | func (f *function) abort() bool { 177 | return f.state == stateAbort 178 | } 179 | 180 | func processCategory(name, filePath string) (*category, error) { 181 | lines, err := readFile(filePath) 182 | if err != nil { 183 | return nil, err 184 | } 185 | cat := category{} 186 | cat.Name = name 187 | 188 | multiLineComments := map[string][]string{} 189 | 190 | fn := function{} 191 | i := 0 192 | for i < len(lines) { 193 | line := lines[i] 194 | if strings.HasPrefix(line, "//") { 195 | fn.addComment(line) 196 | 197 | } else if strings.HasPrefix(line, "func") { 198 | fn.addSignature(line) 199 | 200 | } else if strings.HasPrefix(line, "/* @example") { 201 | line = strings.TrimPrefix(line, "/* @example") 202 | name := strings.TrimSpace(line) 203 | comments := []string{} 204 | for j := i + 1; j < len(lines); j++ { 205 | if strings.HasPrefix(lines[j], "*/") || strings.HasPrefix(lines[j], " */") { 206 | i = j + 1 207 | break 208 | } 209 | comments = append(comments, strings.ReplaceAll(lines[j], "\t", " ")) 210 | } 211 | multiLineComments[name] = comments 212 | } else { 213 | fn = function{} 214 | } 215 | 216 | if fn.finish() { 217 | cat.Fns = append(cat.Fns, fn) 218 | fn = function{} 219 | } else if fn.abort() { 220 | fn = function{} 221 | } 222 | i++ 223 | } 224 | 225 | sort.Slice(cat.Fns, func(i, j int) bool { 226 | return cat.Fns[i].Name < cat.Fns[j].Name 227 | }) 228 | 229 | for i := range cat.Fns { 230 | fn = cat.Fns[i] 231 | if comments, ok := multiLineComments[fn.Name]; ok { 232 | if fn.Example == "" { 233 | fn.Example = strings.Join(comments, "\n") 234 | } else { 235 | fn.Example = strings.Join(comments, "\n") + "\n\n" + fn.Example 236 | } 237 | cat.Fns[i] = fn 238 | } 239 | } 240 | return &cat, nil 241 | } 242 | 243 | func readFile(filePath string) ([]string, error) { 244 | data, err := os.ReadFile(filePath) 245 | if err != nil { 246 | return nil, err 247 | } 248 | return strings.Split(string(data), "\n"), nil 249 | } 250 | -------------------------------------------------------------------------------- /cmd/generate_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | func TestProcessCategory(t *testing.T) { 11 | fileData := ` 12 | 13 | /* @example F1 14 | this is multiline comments for F1. 15 | */ 16 | 17 | 18 | // F1 is f1. 19 | func F1(a int) int { 20 | return a 21 | } 22 | 23 | // skipFn is a function that should be skipped. 24 | func skipFn(a int) int { 25 | return a 26 | } 27 | 28 | /* @example F2 29 | this is multiline comments for F2. 30 | */ 31 | 32 | // Deprecated: F2 is f2. 33 | func F2(a int) int { 34 | return a 35 | } 36 | ` 37 | dir, err := os.MkdirTemp("", "test-generate") 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | defer os.RemoveAll(dir) 42 | 43 | filePath := filepath.Join(dir, "test.go") 44 | if err := os.WriteFile(filePath, []byte(fileData), 0644); err != nil { 45 | t.Fatal(err) 46 | } 47 | categories = [][2]string{{"Test", "test.go"}} 48 | cat, err := processCategory(categories[0][0], filePath) 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | 53 | toc := `- [Test](#test) 54 | - [gfn.F1](#gfnf1) 55 | - [gfn.F2 (Deprecated)](#gfnf2-deprecated) 56 | ` 57 | if cat.toc() != toc { 58 | t.Fatalf("toc not match, expect: %s, got: %s", toc, cat.toc()) 59 | } 60 | 61 | content := ` ## Test 62 | 63 | 64 | ### gfn.F1 65 | ;;;go 66 | func F1(a int) int 67 | ;;; 68 | F1 is f1. 69 | 70 | #### Example: 71 | ;;;go 72 | this is multiline comments for F1. 73 | ;;; 74 | [back to top](#gfn) 75 | 76 | 77 | ### gfn.F2 (Deprecated) 78 | ;;;go 79 | func F2(a int) int 80 | ;;; 81 | Deprecated: F2 is f2. 82 | 83 | #### Example: 84 | ;;;go 85 | this is multiline comments for F2. 86 | ;;; 87 | [back to top](#gfn) 88 | ` 89 | expected := strings.TrimSpace(strings.ReplaceAll(content, ";;;", "```")) 90 | got := strings.TrimSpace(cat.content()) 91 | if expected != got { 92 | t.Fatalf("content not match, expect: %s, got: %s", expected, got) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /fp.go: -------------------------------------------------------------------------------- 1 | package gfn 2 | 3 | /* @example Map 4 | gfn.Map([]int{1, 2, 3}, func(i int) string { return i+1 }) 5 | // []int{2, 3, 4} 6 | 7 | gfn.Map([]int{1, 2, 3}, func(i int) string { 8 | return strconv.Itoa(i) 9 | }) 10 | // []string{"1", "2", "3"} 11 | */ 12 | 13 | // Map returns a new array with the results of calling the mapper function on each element. 14 | // No MapKV because I don't know what to return, an array or a map? Instead, please use ForEachKV. 15 | func Map[T any, R any](array []T, mapper func(T) R) []R { 16 | result := make([]R, len(array)) 17 | for i, v := range array { 18 | result[i] = mapper(v) 19 | } 20 | return result 21 | } 22 | 23 | /* @example Filter 24 | array := []int{1, 2, 3, 4, 5, 6} 25 | gfn.Filter(array, func(i int) bool { return i%2 == 0 }) 26 | // []int{2, 4, 6} 27 | */ 28 | 29 | // Filter returns a new array containing elements of the original array 30 | // that satisfy the provided function. 31 | func Filter[T any](array []T, filter func(T) bool) []T { 32 | result := make([]T, 0) 33 | for _, v := range array { 34 | if filter(v) { 35 | result = append(result, v) 36 | } 37 | } 38 | return result 39 | } 40 | 41 | /* @example FilterKV 42 | m := map[int]string{1: "a", 2: "b", 3: "c"} 43 | gfn.FilterKV(m, func(k int, v string) bool { 44 | return k == 1 || v == "c" 45 | }) 46 | // map[int]string{1: "a", 3: "c"} 47 | */ 48 | 49 | // FilterKV returns a new map containing elements of the original map 50 | // that satisfy the provided function. 51 | func FilterKV[K comparable, V any](m map[K]V, fn func(K, V) bool) map[K]V { 52 | return Select(m, fn) 53 | } 54 | 55 | /* @example Reduce 56 | gfn.Reduce([]int{1, 2, 3}, 0, func(a, b int) int { 57 | return a + b 58 | }) 59 | // 6 60 | */ 61 | 62 | // Reduce executes a reducer function on each element of the array, 63 | // resulting in a single output value. 64 | func Reduce[T any, R any](array []T, init R, fn func(R, T) R) R { 65 | result := init 66 | for _, v := range array { 67 | result = fn(result, v) 68 | } 69 | return result 70 | } 71 | 72 | /* @example ReduceKV 73 | m := map[string]int{"a": 1, "b": 2, "c": 3} 74 | total := gfn.ReduceKV(m, 0, func(value int, k string, v int) int { 75 | return value + v 76 | }) 77 | // 6 78 | */ 79 | 80 | // ReduceKV executes a reducer function on each element of the map, 81 | // resulting in a single output value. 82 | func ReduceKV[K comparable, V any, R any](m map[K]V, init R, fn func(R, K, V) R) R { 83 | result := init 84 | for k, v := range m { 85 | result = fn(result, k, v) 86 | } 87 | return result 88 | } 89 | -------------------------------------------------------------------------------- /fp_test.go: -------------------------------------------------------------------------------- 1 | package gfn_test 2 | 3 | import ( 4 | "strconv" 5 | "testing" 6 | 7 | . "github.com/suchen-sci/gfn" 8 | ) 9 | 10 | func TestMap(t *testing.T) { 11 | { 12 | // int to string 13 | actual := Map([]int{1, 2, 3}, func(i int) string { 14 | return strconv.Itoa(i) 15 | }) 16 | AssertSliceEqual(t, []string{"1", "2", "3"}, actual) 17 | } 18 | { 19 | // string to struct 20 | type data struct { 21 | data string 22 | } 23 | actual := Map([]string{"a", "b", "c"}, func(s string) data { 24 | return data{data: s} 25 | }) 26 | AssertSliceEqual(t, []data{{data: "a"}, {data: "b"}, {data: "c"}}, actual) 27 | } 28 | { 29 | // string to string 30 | actual := Map([]string{"a", "b", "c"}, func(s string) string { 31 | return s + s 32 | }) 33 | AssertSliceEqual(t, []string{"aa", "bb", "cc"}, actual) 34 | } 35 | } 36 | 37 | func TestFilter(t *testing.T) { 38 | AssertSliceEqual(t, []int{2, 4, 6}, Filter([]int{1, 2, 3, 4, 5, 6}, func(i int) bool { 39 | return i%2 == 0 40 | })) 41 | 42 | AssertSliceEqual(t, []string{"abc", "def"}, Filter([]string{"abc", "def", "abcdef"}, func(s string) bool { 43 | return len(s) <= 3 44 | })) 45 | 46 | { 47 | type data struct { 48 | data int 49 | } 50 | expected := []data{{data: 2}, {data: 4}, {data: 6}} 51 | inputData := []data{{data: 1}, {data: 2}, {data: 3}, {data: 4}, {data: 5}, {data: 6}} 52 | AssertSliceEqual(t, expected, Filter(inputData, func(d data) bool { 53 | return d.data%2 == 0 54 | })) 55 | } 56 | } 57 | 58 | func TestReduce(t *testing.T) { 59 | AssertEqual(t, 6, Reduce([]int{1, 2, 3}, 0, func(a, b int) int { 60 | return a + b 61 | })) 62 | } 63 | 64 | func TestFilterKV(t *testing.T) { 65 | m := map[int]string{1: "a", 2: "b", 3: "c"} 66 | m = FilterKV(m, func(k int, v string) bool { 67 | return k == 1 || v == "c" 68 | }) 69 | AssertMapEqual(t, map[int]string{1: "a", 3: "c"}, m) 70 | } 71 | 72 | func TestReduceKV(t *testing.T) { 73 | m := map[string]int{"a": 1, "b": 2, "c": 3} 74 | total := ReduceKV(m, 0, func(value int, k string, v int) int { 75 | return value + v 76 | }) 77 | AssertEqual(t, 6, total) 78 | } 79 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/suchen-sci/gfn 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /map.go: -------------------------------------------------------------------------------- 1 | package gfn 2 | 3 | /* @example EqualKV 4 | map1 := map[int]struct{}{1: {}, 2: {}, 3: {}} 5 | map2 := map[int]struct{}{1: {}, 2: {}, 3: {}} 6 | gfn.EqualKV(map1, map2) // true 7 | */ 8 | 9 | // EqualKV returns true if two maps/sets are equal. 10 | func EqualKV[K, V comparable](a map[K]V, b map[K]V) bool { 11 | if len(a) != len(b) { 12 | return false 13 | } 14 | 15 | for k, v := range a { 16 | if v2, ok := b[k]; !ok || v != v2 { 17 | return false 18 | } 19 | } 20 | return true 21 | } 22 | 23 | /* @example EqualKVBy 24 | m1 := map[int]string{1: "a", 2: "b", 3: "c"} 25 | m2 := map[int]string{1: "e", 2: "f", 3: "g"} 26 | gfn.EqualKVBy(m1, m2, func(k int, a, b string) bool { 27 | return len(a) == len(b) 28 | }) // true 29 | */ 30 | 31 | // EqualKVBy returns true if two maps/sets are equal by a custom function. 32 | func EqualKVBy[K comparable, V1, V2 any](a map[K]V1, b map[K]V2, fn func(K, V1, V2) bool) bool { 33 | if len(a) != len(b) { 34 | return false 35 | } 36 | 37 | for k, v := range a { 38 | if v2, ok := b[k]; !ok || !fn(k, v, v2) { 39 | return false 40 | } 41 | } 42 | return true 43 | } 44 | 45 | /* @example Keys 46 | gfn.Keys(map[int]string{1: "a", 2: "b", 3: "c"}) 47 | // []int{1, 2, 3} or []int{3, 2, 1} or []int{2, 1, 3} etc. 48 | */ 49 | 50 | // Keys returns the keys of a map. 51 | func Keys[K comparable, V any](m map[K]V) []K { 52 | keys := make([]K, len(m)) 53 | i := 0 54 | for k := range m { 55 | keys[i] = k 56 | i++ 57 | } 58 | return keys 59 | } 60 | 61 | /* @example Values 62 | gfn.Values(map[int]string{1: "a", 2: "b", 3: "c"}) 63 | // []string{"a", "b", "c"} or []string{"c", "b", "a"} or []string{"b", "a", "c"} etc. 64 | */ 65 | 66 | // Values returns the values of a map. 67 | func Values[K comparable, V any](m map[K]V) []V { 68 | values := make([]V, len(m)) 69 | i := 0 70 | for _, v := range m { 71 | values[i] = v 72 | i++ 73 | } 74 | return values 75 | } 76 | 77 | /* @example Invert 78 | m := map[string]string{ 79 | "Array": "array.go", 80 | "Map": "map.go", 81 | "Set": "set.go", 82 | "Math": "math.go", 83 | } 84 | 85 | gfn.Invert(m) 86 | // map[string]string{ 87 | // "array.go": "Array", 88 | // "map.go": "Map", 89 | // "set.go": "Set", 90 | // "math.go": "Math", 91 | // } 92 | */ 93 | 94 | // Invert returns a map with keys and values swapped. 95 | func Invert[K, V comparable](m map[K]V) map[V]K { 96 | res := make(map[V]K) 97 | for k, v := range m { 98 | res[v] = k 99 | } 100 | return res 101 | } 102 | 103 | /* @example Clear 104 | m := map[int]string{1: "a", 2: "b", 3: "c"} 105 | gfn.Clear(m) 106 | // m is now an empty map 107 | */ 108 | 109 | // Clear removes all keys from a map. 110 | func Clear[K comparable, V any](m map[K]V) { 111 | for k := range m { 112 | delete(m, k) 113 | } 114 | } 115 | 116 | /* @example Items 117 | m := map[int]string{1: "a", 2: "b", 3: "c"} 118 | 119 | gfn.Items(m) 120 | // []gfn.Pair[int, string]{ 121 | // {1, "a"}, 122 | // {2, "b"}, 123 | // {3, "c"}, 124 | // } 125 | */ 126 | 127 | // Items returns a slice of pairs of keys and values. 128 | func Items[K comparable, V any](m map[K]V) []Pair[K, V] { 129 | items := make([]Pair[K, V], len(m)) 130 | i := 0 131 | for k, v := range m { 132 | items[i] = Pair[K, V]{k, v} 133 | i++ 134 | } 135 | return items 136 | } 137 | 138 | /* @example Update 139 | // use Update to do union of maps 140 | m1 := map[int]string{1: "a", 2: "b", 3: "c"} 141 | m2 := map[int]string{4: "d", 5: "e"} 142 | union := map[int]string{} 143 | gfn.Update(union, m1, m2) 144 | // union: map[int]string{1: "a", 2: "b", 3: "c", 4: "d", 5: "e"} 145 | 146 | m1 := map[int]string{1: "a", 2: "b", 3: "c"} 147 | m2 := map[int]string{1: "d", 2: "e"} 148 | m3 := map[int]string{1: "f"} 149 | gfn.Update(m1, m2, m3) 150 | // map[int]string{1: "f", 2: "e", 3: "c"} 151 | */ 152 | 153 | // Update updates a map with the keys and values from other maps. 154 | func Update[K comparable, V any](m map[K]V, other ...map[K]V) { 155 | for _, o := range other { 156 | for k, v := range o { 157 | m[k] = v 158 | } 159 | } 160 | } 161 | 162 | /* @example Clone 163 | m := map[int]string{1: "a", 2: "b", 3: "c"} 164 | m2 := gfn.Clone(m) 165 | // m2 is a copy of m 166 | */ 167 | 168 | // Clone returns a shallow copy of a map. 169 | func Clone[K comparable, V any](m map[K]V) map[K]V { 170 | res := make(map[K]V, len(m)) 171 | for k, v := range m { 172 | res[k] = v 173 | } 174 | return res 175 | } 176 | 177 | /* @example DeleteBy 178 | m := map[int]string{1: "a", 2: "b", 3: "c"} 179 | gfn.DeleteBy(m, func(k int, v string) bool { 180 | return k == 1 || v == "c" 181 | }) 182 | // map[int]string{2: "b"} 183 | */ 184 | 185 | // DeleteBy deletes keys from a map if the predicate function returns true. 186 | func DeleteBy[K comparable, V any](m map[K]V, deleteFn func(K, V) bool) { 187 | for k, v := range m { 188 | if deleteFn(k, v) { 189 | delete(m, k) 190 | } 191 | } 192 | } 193 | 194 | /* @example Select 195 | m := map[int]string{1: "a", 2: "b", 3: "c"} 196 | gfn.Select(m, func(k int, v string) bool { 197 | return k == 1 || v == "c" 198 | }) 199 | // map[int]string{1: "a", 3: "c"} 200 | */ 201 | 202 | // Select returns a map with keys and values that satisfy the predicate function. 203 | func Select[K comparable, V any](m map[K]V, fn func(K, V) bool) map[K]V { 204 | res := make(map[K]V) 205 | for k, v := range m { 206 | if fn(k, v) { 207 | res[k] = v 208 | } 209 | } 210 | return res 211 | } 212 | 213 | /* @example IsDisjoint 214 | m1 := map[int]string{1: "a", 2: "b", 3: "c"} 215 | m2 := map[int]int{4: 4, 5: 5, 6: 6} 216 | IsDisjoint(m1, m2) // true 217 | 218 | m3 := map[int]struct{}{1: {}, 2: {}, 3: {}} 219 | m4 := map[int]struct{}{4: {}, 5: {}, 6: {}} 220 | gfn.IsDisjoint(m3, m4) // true 221 | */ 222 | 223 | // IsDisjoint returns true if the maps have no keys in common. It usually 224 | // used to check if two sets are disjoint. 225 | func IsDisjoint[K comparable, V1 any, V2 any](m1 map[K]V1, m2 map[K]V2) bool { 226 | if len(m1) != len(m2) { 227 | return false 228 | } 229 | for k1 := range m1 { 230 | if _, ok := m2[k1]; ok { 231 | return false 232 | } 233 | } 234 | return true 235 | } 236 | 237 | /* @example IntersectKeys 238 | m1 := map[int]string{1: "a", 2: "b", 3: "c", 4: "d"} 239 | m2 := map[int]string{1: "a", 2: "b"} 240 | m3 := map[int]string{2: "b", 3: "c", 4: "d"} 241 | gfn.IntersectKeys(m1, m2, m3) // []int{2} 242 | */ 243 | 244 | // IntersectKeys returns a slice of keys that are in all maps. It usually 245 | // used to find the intersection of two or more sets. 246 | func IntersectKeys[K comparable, V any](ms ...map[K]V) []K { 247 | if len(ms) == 0 { 248 | return nil 249 | } 250 | if len(ms) == 1 { 251 | return Keys(ms[0]) 252 | } 253 | 254 | res := Keys(ms[0]) 255 | for _, m := range ms[1:] { 256 | res = Filter(res, func(k K) bool { 257 | _, ok := m[k] 258 | return ok 259 | }) 260 | } 261 | return res 262 | } 263 | 264 | /* @example DifferentKeys 265 | m1 := map[int]string{1: "a", 2: "b", 3: "c", 4: "d"} 266 | m2 := map[int]string{1: "a", 2: "b"} 267 | m3 := map[int]string{2: "b", 3: "c"} 268 | gfn.DifferentKeys(m1, m2, m3) // []int{4} 269 | */ 270 | 271 | // DifferentKeys returns a slice of keys that are in the first map but not in the others, 272 | // only keys in the map are considered, not values. It usually used to find the 273 | // difference between two or more sets. 274 | func DifferentKeys[K comparable, V any](ms ...map[K]V) []K { 275 | if len(ms) == 0 { 276 | return nil 277 | } 278 | if len(ms) == 1 { 279 | return Keys(ms[0]) 280 | } 281 | 282 | res := Keys(ms[0]) 283 | for _, m := range ms[1:] { 284 | res = Filter(res, func(k K) bool { 285 | _, ok := m[k] 286 | return !ok 287 | }) 288 | } 289 | return res 290 | } 291 | 292 | /* @example GetOrDefault 293 | m := map[int]string{1: "a", 2: "b", 3: "c"} 294 | gfn.GetOrDefault(m, 1, "d") // "a" 295 | gfn.GetOrDefault(m, 4, "d") // "d" 296 | */ 297 | 298 | // GetOrDefault returns the value for a key if it exists, otherwise it returns the default value. 299 | func GetOrDefault[K comparable, V any](m map[K]V, key K, defaultValue V) V { 300 | value, ok := m[key] 301 | if ok { 302 | return value 303 | } 304 | return defaultValue 305 | } 306 | 307 | /* @example ForEachKV 308 | m := map[int]string{1: "a", 2: "b", 3: "c"} 309 | array := make([]int, 0, len(m)) 310 | gfn.ForEachKV(m, func(k int, v string) { 311 | array = append(array, k) 312 | } 313 | // array is []int{1, 2, 3} or other order 314 | 315 | m := map[int]string{1: "a", 2: "b", 3: "c"} 316 | invert := map[string]int{} 317 | gfn.ForEachKV(m, func(k int, v string) { 318 | invert[v] = k 319 | } 320 | // invert is map[string]int{"a": 1, "b": 2, "c": 3} 321 | */ 322 | 323 | // ForEachKV iterates over a map and calls a function for each key/value pair. 324 | func ForEachKV[K comparable, V any](m map[K]V, fn func(K, V)) { 325 | for k, v := range m { 326 | fn(k, v) 327 | } 328 | } 329 | 330 | /* @example ToKV 331 | gfn.ToKV(3, func(i int) (int, string) { 332 | return i, strconv.Itoa(i) 333 | }) 334 | // map[int]string{0: "0", 1: "1", 2: "2"} 335 | */ 336 | 337 | // ToKV converts a slice to a map using a function to generate the keys and values. 338 | func ToKV[K comparable, V any](n int, fn func(int) (K, V)) map[K]V { 339 | if n < 0 { 340 | panic("n must be greater than or equal to zero") 341 | } 342 | m := make(map[K]V, n) 343 | for i := 0; i < n; i++ { 344 | k, v := fn(i) 345 | m[k] = v 346 | } 347 | return m 348 | } 349 | -------------------------------------------------------------------------------- /map_test.go: -------------------------------------------------------------------------------- 1 | package gfn_test 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "strconv" 7 | "testing" 8 | 9 | . "github.com/suchen-sci/gfn" 10 | ) 11 | 12 | func TestEqualKV(t *testing.T) { 13 | { 14 | type Number int 15 | type Numbers map[Number]struct{} 16 | map1 := Numbers{} 17 | map2 := Numbers{} 18 | for i := 0; i < 100; i++ { 19 | map1[Number(i)] = struct{}{} 20 | map2[Number(i)] = struct{}{} 21 | } 22 | AssertTrue(t, EqualKV(map1, map2)) 23 | 24 | map2[100] = struct{}{} 25 | AssertFalse(t, EqualKV(map1, map2)) 26 | } 27 | 28 | { 29 | map1 := map[int]string{} 30 | map2 := map[int]string{} 31 | for i := 0; i < 100; i++ { 32 | map1[i] = strconv.Itoa(i) 33 | map2[i] = strconv.Itoa(i) 34 | } 35 | AssertTrue(t, EqualKV(map1, map2)) 36 | 37 | map2[0] = "999" 38 | AssertFalse(t, EqualKV(map1, map2)) 39 | } 40 | } 41 | 42 | func TestKeys(t *testing.T) { 43 | { 44 | keys := Keys(map[int]string{1: "a", 2: "b", 3: "c"}) 45 | sort.Ints(keys) 46 | AssertSliceEqual(t, []int{1, 2, 3}, keys) 47 | } 48 | 49 | { 50 | type Number int 51 | expected := []Number{1, 2, 3} 52 | keys := Keys(map[Number]string{1: "a", 2: "b", 3: "c"}) 53 | sort.Slice(keys, func(i, j int) bool { 54 | return keys[i] < keys[j] 55 | }) 56 | AssertSliceEqual(t, expected, keys) 57 | } 58 | } 59 | 60 | func TestValues(t *testing.T) { 61 | values := Values(map[int]string{1: "a", 2: "b", 3: "c"}) 62 | sort.Strings(values) 63 | AssertSliceEqual(t, []string{"a", "b", "c"}, values) 64 | } 65 | 66 | func TestInvert(t *testing.T) { 67 | { 68 | m := map[string]string{ 69 | "Array": "array.go", 70 | "Map": "map.go", 71 | "Set": "set.go", 72 | "Math": "math.go", 73 | } 74 | expected := map[string]string{ 75 | "array.go": "Array", 76 | "map.go": "Map", 77 | "set.go": "Set", 78 | "math.go": "Math", 79 | } 80 | AssertMapEqual(t, expected, Invert(m)) 81 | } 82 | 83 | { 84 | m := map[string]int{ 85 | "a": 1, 86 | "b": 2, 87 | "c": 3, 88 | } 89 | expected := map[int]string{ 90 | 1: "a", 91 | 2: "b", 92 | 3: "c", 93 | } 94 | AssertMapEqual(t, expected, Invert(m)) 95 | } 96 | } 97 | 98 | func TestClear(t *testing.T) { 99 | m := map[int]float32{} 100 | for i := 0; i < 1000; i++ { 101 | m[i] = float32(i) 102 | } 103 | Clear(m) 104 | AssertEqual(t, 0, len(m)) 105 | } 106 | 107 | func TestItems(t *testing.T) { 108 | items := Items(map[int]string{1: "a", 2: "b", 3: "c"}) 109 | sort.Slice(items, func(i, j int) bool { 110 | return items[i].First < items[j].First 111 | }) 112 | expected := []Pair[int, string]{ 113 | {1, "a"}, 114 | {2, "b"}, 115 | {3, "c"}, 116 | } 117 | AssertSliceEqual(t, expected, items) 118 | } 119 | 120 | func TestUpdate(t *testing.T) { 121 | m1 := map[int]string{1: "a", 2: "b", 3: "c"} 122 | m2 := map[int]string{1: "d", 2: "e"} 123 | m3 := map[int]string{1: "f"} 124 | expected := map[int]string{1: "f", 2: "e", 3: "c"} 125 | Update(m1, m2, m3) 126 | AssertMapEqual(t, expected, m1) 127 | } 128 | 129 | func TestClone(t *testing.T) { 130 | m := map[int]string{1: "a", 2: "b", 3: "c"} 131 | clone := Clone(m) 132 | AssertMapEqual(t, m, clone) 133 | m[1] = "d" 134 | AssertFalse(t, EqualKV(m, clone)) 135 | } 136 | 137 | func TestDeleteBy(t *testing.T) { 138 | m := map[int]string{1: "a", 2: "b", 3: "c"} 139 | DeleteBy(m, func(k int, v string) bool { 140 | return k == 1 || v == "c" 141 | }) 142 | AssertMapEqual(t, map[int]string{2: "b"}, m) 143 | } 144 | 145 | func TestSelect(t *testing.T) { 146 | m := map[int]string{1: "a", 2: "b", 3: "c"} 147 | rejected := Select(m, func(k int, v string) bool { 148 | return k == 1 || v == "c" 149 | }) 150 | AssertMapEqual(t, map[int]string{1: "a", 3: "c"}, rejected) 151 | } 152 | 153 | func TestIsDisjoint(t *testing.T) { 154 | { 155 | m1 := map[int]string{1: "a", 2: "b", 3: "c"} 156 | m2 := map[int]int{4: 4, 5: 5, 6: 6} 157 | AssertTrue(t, IsDisjoint(m1, m2)) 158 | } 159 | { 160 | m1 := map[int]string{1: "a", 2: "b", 3: "c"} 161 | m2 := map[int]string{1: "a", 2: "b"} 162 | AssertFalse(t, IsDisjoint(m1, m2)) 163 | } 164 | { 165 | m1 := map[int]struct{}{1: {}, 2: {}, 3: {}} 166 | m2 := map[int]struct{}{4: {}, 5: {}, 6: {}} 167 | AssertTrue(t, IsDisjoint(m1, m2)) 168 | } 169 | { 170 | m1 := map[int]struct{}{1: {}, 2: {}, 3: {}} 171 | m2 := map[int]struct{}{1: {}, 2: {}, 3: {}} 172 | AssertFalse(t, IsDisjoint(m1, m2)) 173 | } 174 | } 175 | 176 | func TestIntersectKeys(t *testing.T) { 177 | m1 := map[int]string{1: "a", 2: "b", 3: "c", 4: "d"} 178 | m2 := map[int]string{1: "a", 2: "b"} 179 | m3 := map[int]string{2: "b", 3: "c", 4: "d"} 180 | keys := IntersectKeys[int, string](m1, m2, m3) 181 | AssertSliceEqual(t, []int{2}, keys) 182 | 183 | AssertEqual(t, 0, len(IntersectKeys[int, string]())) 184 | AssertEqual(t, 4, len(IntersectKeys(m1))) 185 | } 186 | 187 | func TestDifferentKeys(t *testing.T) { 188 | m1 := map[int]string{1: "a", 2: "b", 3: "c", 4: "d"} 189 | m2 := map[int]string{1: "a", 2: "b"} 190 | m3 := map[int]string{2: "b", 3: "c"} 191 | keys := DifferentKeys(m1, m2, m3) 192 | AssertSliceEqual(t, []int{4}, keys) 193 | 194 | AssertEqual(t, 0, len(DifferentKeys[int, string]())) 195 | AssertEqual(t, 4, len(DifferentKeys(m1))) 196 | } 197 | 198 | func TestGetOrDefault(t *testing.T) { 199 | m := map[int]string{1: "a", 2: "b", 3: "c"} 200 | AssertEqual(t, "a", GetOrDefault(m, 1, "d")) 201 | AssertEqual(t, "d", GetOrDefault(m, 4, "d")) 202 | } 203 | 204 | func TestEqualKVBy(t *testing.T) { 205 | { 206 | m1 := map[int]string{1: "a", 2: "b", 3: "c"} 207 | m2 := map[int]string{1: "e", 2: "f", 3: "g"} 208 | AssertTrue(t, EqualKVBy(m1, m2, func(k int, a, b string) bool { 209 | return len(a) == len(b) 210 | })) 211 | } 212 | { 213 | m1 := map[int]string{1: "a", 2: "b", 3: "c"} 214 | m2 := map[int]string{1: "e", 2: "f", 3: "g"} 215 | AssertFalse(t, EqualKVBy(m1, m2, func(k int, a, b string) bool { 216 | return a == b 217 | })) 218 | } 219 | { 220 | m1 := map[int]string{1: "a"} 221 | m2 := map[int]string{1: "e", 2: "f", 3: "g"} 222 | AssertFalse(t, EqualKVBy(m1, m2, func(k int, a, b string) bool { 223 | return a == b 224 | })) 225 | } 226 | } 227 | 228 | func TestForEachKV(t *testing.T) { 229 | arr := []string{} 230 | m := map[int]string{1: "a", 2: "b", 3: "c"} 231 | ForEachKV(m, func(k int, v string) { 232 | arr = append(arr, fmt.Sprintf("%d:%s", k, v)) 233 | }) 234 | sort.Strings(arr) 235 | AssertSliceEqual(t, []string{"1:a", "2:b", "3:c"}, arr) 236 | } 237 | 238 | func TestToKV(t *testing.T) { 239 | m := ToKV(3, func(i int) (int, string) { 240 | return i, strconv.Itoa(i) 241 | }) 242 | AssertMapEqual(t, map[int]string{0: "0", 1: "1", 2: "2"}, m) 243 | 244 | AssertPanics(t, func() { 245 | ToKV(-1, func(i int) (int, string) { 246 | return i, strconv.Itoa(i) 247 | }) 248 | }) 249 | } 250 | -------------------------------------------------------------------------------- /math.go: -------------------------------------------------------------------------------- 1 | package gfn 2 | 3 | /* @example Max 4 | gfn.Max([]int16{1, 5, 9, 10}...) // 10 5 | gfn.Max("ab", "cd", "e") // "e" 6 | 7 | gfn.Max(1.1, math.NaN(), 2.2) // 2.2 8 | gfn.Max([]float64{math.NaN(), math.NaN(), math.NaN()}...) // NaN 9 | */ 10 | 11 | // Max returns the maximum value in the array. For float64 arrays, NaN values are skipped. 12 | func Max[T Int | Uint | Float | ~string](array ...T) T { 13 | if len(array) == 0 { 14 | panic("array is empty") 15 | } 16 | 17 | res := array[0] 18 | for _, v := range array { 19 | if isNaN(v) { 20 | continue 21 | } 22 | if isNaN(res) || v > res { 23 | res = v 24 | } 25 | } 26 | return res 27 | } 28 | 29 | // isNaN reports whether input is an IEEE 754 "not-a-number" value. 30 | func isNaN[T Int | Uint | Float | ~string](x T) bool { 31 | // IEEE 754 says that only NaNs satisfy x != x. 32 | return x != x 33 | } 34 | 35 | /* @example MaxBy 36 | type Product struct { 37 | name string 38 | amount int 39 | } 40 | products := []Product{ 41 | {"apple", 10}, 42 | {"banana", 20}, 43 | {"orange", 30}, 44 | } 45 | p := gfn.MaxBy(products, func(p Product) int { 46 | return p.amount 47 | }) // {"orange", 30} 48 | */ 49 | 50 | // MaxBy returns the maximum value in the array, using the given function to transform values. 51 | func MaxBy[T any, U Int | Uint | Float | ~string](array []T, fn func(T) U) T { 52 | if len(array) == 0 { 53 | panic("array is empty") 54 | } 55 | 56 | res := array[0] 57 | value := fn(res) 58 | for _, v := range array[1:] { 59 | current := fn(v) 60 | if current > value { 61 | res = v 62 | value = current 63 | } 64 | } 65 | return res 66 | } 67 | 68 | /* @example Min 69 | gfn.Min(1.1, 2.2, 3.3) // 1.1 70 | gfn.Min([]int16{1, 5, 9, 10}...) // 1 71 | 72 | gfn.Min(1, -1, 10) // -1 73 | gfn.Min([]float64{1.1, math.Inf(-1), math.NaN()}...) // math.Inf(-1) 74 | */ 75 | 76 | // Min returns the minimum value in the array. For float64 arrays, NaN values are skipped. 77 | func Min[T Int | Uint | Float | ~string](array ...T) T { 78 | if len(array) == 0 { 79 | panic("array is empty") 80 | } 81 | 82 | res := array[0] 83 | for _, v := range array { 84 | if isNaN(v) { 85 | continue 86 | } 87 | if isNaN(res) || v < res { 88 | res = v 89 | } 90 | } 91 | return res 92 | } 93 | 94 | /* @example MinBy 95 | type Product struct { 96 | name string 97 | amount int 98 | } 99 | products := []Product{ 100 | {"apple", 10}, 101 | {"banana", 20}, 102 | {"orange", 30}, 103 | } 104 | p := gfn.MinBy(products, func(p Product) int { 105 | return p.amount 106 | }) // {"apple", 10} 107 | */ 108 | 109 | // MinBy returns the minimum value in the array, using the given function to transform values. 110 | func MinBy[T any, U Int | Uint | Float | ~string](array []T, fn func(T) U) T { 111 | if len(array) == 0 { 112 | panic("array is empty") 113 | } 114 | 115 | res := array[0] 116 | value := fn(res) 117 | for _, v := range array[1:] { 118 | current := fn(v) 119 | if current < value { 120 | res = v 121 | value = current 122 | } 123 | } 124 | return res 125 | } 126 | 127 | /* @example Sum 128 | gfn.Sum([]int{1, 5, 9, 10}...) // 25 129 | gfn.Sum(1.1, 2.2, 3.3) // 6.6 130 | gfn.Sum("ab", "cd", "e") // "abcde" 131 | */ 132 | 133 | // Sum returns the sum of all values in the array. 134 | // Be careful when using this function for float64 arrays with NaN and Inf values. 135 | // Sum([math.NaN(), 0.5]) produces math.NaN(). Sum(math.Inf(1), math.Inf(-1)) produces math.NaN() too. 136 | func Sum[T Int | Uint | Float | ~string | Complex](array ...T) T { 137 | if len(array) == 0 { 138 | panic("array is empty") 139 | } 140 | 141 | res := array[0] 142 | for i, v := range array { 143 | if i > 0 { 144 | res += v 145 | } 146 | } 147 | return res 148 | } 149 | 150 | /* @example SumBy 151 | type Product struct { 152 | name string 153 | amount int 154 | } 155 | products := []Product{ 156 | {"apple", 10}, 157 | {"banana", 20}, 158 | {"orange", 30}, 159 | } 160 | gfn.SumBy(products, func(p Product) int { 161 | return p.amount 162 | }) // 60 163 | */ 164 | 165 | // SumBy returns the sum of all values in the array after applying fn to each value. 166 | func SumBy[T any, U Int | Uint | Float | ~string](array []T, fn func(T) U) U { 167 | if len(array) == 0 { 168 | panic("array is empty") 169 | } 170 | res := fn(array[0]) 171 | for _, v := range array[1:] { 172 | res += fn(v) 173 | } 174 | return res 175 | } 176 | 177 | /* @example Abs 178 | gfn.Abs(-1) // 1 179 | gfn.Abs(-100.99) // 100.99 180 | */ 181 | 182 | // Abs returns the absolute value of x. 183 | func Abs[T Int | Float](x T) T { 184 | if x < 0 { 185 | return -x 186 | } 187 | return x 188 | } 189 | 190 | /* @example DivMod 191 | gfn.DivMod(10, 3) // (3, 1) 192 | */ 193 | 194 | // DivMod returns quotient and remainder of a/b. 195 | func DivMod[T Int | Uint](a, b T) (T, T) { 196 | return a / b, a % b 197 | } 198 | 199 | /* @example Mean 200 | gfn.Mean(1, 2, 3) // 2.0 201 | gfn.Mean([]int{1, 2, 3, 4}...) // 2.5 202 | */ 203 | 204 | // Mean returns the mean of all values in the array. 205 | func Mean[T Int | Uint | Float](array ...T) float64 { 206 | if len(array) == 0 { 207 | panic("array is empty") 208 | } 209 | 210 | sum := 0.0 211 | for _, v := range array { 212 | sum += float64(v) 213 | } 214 | return sum / float64(len(array)) 215 | } 216 | 217 | /* @example MeanBy 218 | type Product struct { 219 | name string 220 | cost float64 221 | } 222 | products := []Product{ 223 | {"apple", 1.5}, 224 | {"banana", 2.5}, 225 | {"orange", 3.5}, 226 | {"lemon", 4.5}, 227 | } 228 | gfn.MeanBy(products, func(p Product) float64 { 229 | return p.cost 230 | }) // 3.0 231 | */ 232 | 233 | // MeanBy returns the mean of all values in the array after applying fn to each value. 234 | func MeanBy[T any, U Int | Uint | Float](array []T, fn func(T) U) float64 { 235 | if len(array) == 0 { 236 | panic("array is empty") 237 | } 238 | 239 | sum := 0.0 240 | for _, v := range array { 241 | sum += float64(fn(v)) 242 | } 243 | return sum / float64(len(array)) 244 | } 245 | 246 | /* @example MinMax 247 | gfn.MinMax(1, 5, 9, 10) // 1, 10 248 | 249 | gfn.MinMax(math.NaN(), 1.85, 2.2) // 1.85, 2.2 250 | gfn.MinMax(math.NaN(), math.NaN(), math.NaN()) // NaN, NaN 251 | */ 252 | 253 | // MinMax returns the minimum and maximum value in the array. For float64 arrays, please use MinMaxFloat64. 254 | func MinMax[T Int | Uint | Float | ~string](array ...T) (T, T) { 255 | if len(array) == 0 { 256 | panic("array is empty") 257 | } 258 | 259 | minimum := array[0] 260 | maximum := array[0] 261 | for _, v := range array { 262 | if isNaN(v) { 263 | continue 264 | } 265 | if isNaN(minimum) || v < minimum { 266 | minimum = v 267 | } 268 | if isNaN(maximum) || v > maximum { 269 | maximum = v 270 | } 271 | } 272 | return minimum, maximum 273 | } 274 | 275 | /* @example MinMaxBy 276 | type Product struct { 277 | name string 278 | amount int 279 | } 280 | products := []Product{ 281 | {"banana", 20}, 282 | {"orange", 30}, 283 | {"apple", 10}, 284 | {"grape", 50}, 285 | {"lemon", 40}, 286 | } 287 | gfn.MinMaxBy(products, func(p Product) int { 288 | return p.amount 289 | }) // {"apple", 10}, {"grape", 50} 290 | */ 291 | 292 | // MinMaxBy returns the minimum and maximum value in the array, using the given function to transform values. 293 | func MinMaxBy[T any, U Int | Uint | Float | ~string](array []T, fn func(T) U) (T, T) { 294 | if len(array) == 0 { 295 | panic("array is empty") 296 | } 297 | 298 | minimum := array[0] 299 | minValue := fn(minimum) 300 | maximum := minimum 301 | maxValue := minValue 302 | for _, v := range array[1:] { 303 | current := fn(v) 304 | if current < minValue { 305 | minimum = v 306 | minValue = current 307 | } 308 | if current > maxValue { 309 | maximum = v 310 | maxValue = current 311 | } 312 | } 313 | return minimum, maximum 314 | } 315 | 316 | /* @example Mode 317 | gfn.Mode([]int{1, 1, 5, 5, 5, 2, 2})) // 5 318 | */ 319 | 320 | // Mode returns the most frequent value in the array. 321 | func Mode[T comparable](array []T) T { 322 | if len(array) == 0 { 323 | panic("array is empty") 324 | } 325 | 326 | value := array[0] 327 | count := 1 328 | seen := make(map[T]int) 329 | 330 | for _, v := range array { 331 | seen[v]++ 332 | if seen[v] > count { 333 | value = v 334 | count = seen[v] 335 | } 336 | } 337 | return value 338 | } 339 | 340 | /* @example ModeBy 341 | type Product struct { 342 | name string 343 | amount int 344 | } 345 | products := []Product{ 346 | {"banana", 20}, 347 | {"banana", 20}, 348 | {"apple", 10}, 349 | } 350 | gfn.ModeBy(products, func(p Product) int { 351 | return p.amount 352 | }) // {"banana", 20} 353 | */ 354 | 355 | // ModeBy returns the most frequent value in the array, using the given function to transform values. 356 | func ModeBy[T any, U comparable](array []T, fn func(T) U) T { 357 | if len(array) == 0 { 358 | panic("array is empty") 359 | } 360 | 361 | value := array[0] 362 | count := 1 363 | seen := make(map[U]int) 364 | 365 | for _, v := range array { 366 | current := fn(v) 367 | seen[current]++ 368 | if seen[current] > count { 369 | value = v 370 | count = seen[current] 371 | } 372 | } 373 | return value 374 | } 375 | -------------------------------------------------------------------------------- /math_test.go: -------------------------------------------------------------------------------- 1 | package gfn_test 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "math/rand" 7 | "testing" 8 | 9 | . "github.com/suchen-sci/gfn" 10 | ) 11 | 12 | func TestMax(t *testing.T) { 13 | // ints 14 | AssertEqual(t, 3, Max(1, 2, 3), "int") 15 | AssertEqual(t, int8(4), Max(int8(4), int8(2), int8(3)), "int8") 16 | AssertEqual(t, int16(4), Max(int16(4), int16(2), int16(3)), "int16") 17 | AssertEqual(t, int32(4), Max(int32(4), int32(2), int32(3)), "int32") 18 | AssertEqual(t, int64(4), Max(int64(4), int64(2), int64(3)), "int64") 19 | 20 | // uints 21 | AssertEqual(t, uint(3), Max([]uint{1, 2, 3}...), "uint") 22 | AssertEqual(t, uint8(3), Max([]uint8{1, 2, 3}...), "uint8") 23 | AssertEqual(t, uint16(3), Max([]uint16{1, 2, 3}...), "uint16") 24 | AssertEqual(t, uint32(3), Max([]uint32{1, 2, 3}...), "uint32") 25 | AssertEqual(t, uint64(3), Max([]uint64{1, 2, 3}...), "uint64") 26 | AssertEqual(t, uintptr(3), Max([]uintptr{1, 2, 3}...), "uintptr") 27 | 28 | // float32 29 | AssertEqual(t, float32(3.3), Max([]float32{1.1, 2.2, 3.3}...), "float32") 30 | 31 | // string 32 | AssertEqual(t, "cd", Max([]string{"abc", "bd", "cd"}...), "string") 33 | 34 | // check ~int 35 | type MyInt int 36 | AssertEqual(t, MyInt(3), Max([]MyInt{1, 2, 3}...), "MyInt") 37 | 38 | AssertEqual(t, 2.2, Max(math.NaN(), 1, 2.2)) 39 | AssertEqual(t, 2.8, Max(1, -1, math.NaN(), 1, 2.8)) 40 | AssertEqual(t, math.Inf(1), Max(1, -1, math.NaN(), 1, math.Inf(1))) 41 | AssertEqual(t, math.Inf(1), Max(1, -1, 1, math.Inf(1))) 42 | AssertEqual(t, 1.9, Max(1.9, -1., 1.)) 43 | AssertTrue(t, math.IsNaN(Max(math.NaN(), math.NaN(), math.NaN()))) 44 | 45 | // check empty array 46 | AssertPanics(t, func() { 47 | Max([]int{}...) 48 | }) 49 | 50 | // check array with many elements 51 | { 52 | array := make([]int, 100000) 53 | for i := 0; i < 100000; i++ { 54 | array[i] = i 55 | } 56 | rand.Shuffle(len(array), func(i, j int) { 57 | array[i], array[j] = array[j], array[i] 58 | }) 59 | AssertEqual(t, 99999, Max(array...)) 60 | } 61 | } 62 | 63 | func TestMin(t *testing.T) { 64 | // ints 65 | AssertEqual(t, 1, Min(1, 2, 3), "int") 66 | AssertEqual(t, int8(2), Min(int8(4), int8(2), int8(3)), "int8") 67 | AssertEqual(t, int16(2), Min(int16(4), int16(2), int16(3)), "int16") 68 | AssertEqual(t, int32(2), Min(int32(4), int32(2), int32(3)), "int32") 69 | AssertEqual(t, int64(2), Min(int64(4), int64(2), int64(3)), "int64") 70 | 71 | // uints 72 | AssertEqual(t, uint(1), Min([]uint{1, 2, 3}...), "uint") 73 | AssertEqual(t, uint8(1), Min([]uint8{1, 2, 3}...), "uint8") 74 | AssertEqual(t, uint16(1), Min([]uint16{1, 2, 3}...), "uint16") 75 | AssertEqual(t, uint32(1), Min([]uint32{1, 2, 3}...), "uint32") 76 | AssertEqual(t, uint64(1), Min([]uint64{1, 2, 3}...), "uint64") 77 | AssertEqual(t, uintptr(1), Min([]uintptr{1, 2, 3}...), "uintptr") 78 | 79 | // float32 80 | AssertEqual(t, float32(1.1), Min([]float32{1.1, 2.2, 3.3}...), "float32") 81 | 82 | // string 83 | AssertEqual(t, "abc", Min([]string{"abc", "bd", "cd"}...), "string") 84 | 85 | // check ~int 86 | type MyInt int 87 | AssertEqual(t, MyInt(1), Min([]MyInt{1, 2, 3}...), "MyInt") 88 | 89 | // float64 with NaN 90 | AssertEqual(t, 1.85, Min(math.NaN(), 1.85, 2.2)) 91 | AssertEqual(t, -1, Min(1, -1, math.NaN(), 1, 2.8)) 92 | AssertEqual(t, math.Inf(-1), Min(1, -1, math.NaN(), 1, math.Inf(-1))) 93 | AssertEqual(t, -1, Min(1, -1, 1, math.Inf(1))) 94 | AssertEqual(t, -1, Min(1.9, -1., 1.)) 95 | AssertTrue(t, math.IsNaN(Min(math.NaN(), math.NaN(), math.NaN()))) 96 | 97 | // check empty array 98 | AssertPanics(t, func() { 99 | Min([]int{}...) 100 | }) 101 | 102 | // check array with many elements 103 | { 104 | array := make([]int, 100000) 105 | for i := 0; i < 100000; i++ { 106 | array[i] = i 107 | } 108 | rand.Shuffle(len(array), func(i, j int) { 109 | array[i], array[j] = array[j], array[i] 110 | }) 111 | AssertEqual(t, 0, Min(array...)) 112 | } 113 | } 114 | 115 | func TestSum(t *testing.T) { 116 | // ints 117 | AssertEqual(t, 10, Sum(1, 2, 3, 4), "int") 118 | AssertEqual(t, int8(10), Sum([]int8{1, 2, 3, 4}...), "int8") 119 | AssertEqual(t, int16(10), Sum([]int16{1, 2, 3, 4}...), "int16") 120 | AssertEqual(t, int32(10), Sum([]int32{1, 2, 3, 4}...), "int32") 121 | AssertEqual(t, int64(10), Sum([]int64{1, 2, 3, 4}...), "int64") 122 | 123 | // uints 124 | AssertEqual(t, uint(10), Sum([]uint{1, 2, 3, 4}...), "uint") 125 | AssertEqual(t, uint8(10), Sum([]uint8{1, 2, 3, 4}...), "uint8") 126 | AssertEqual(t, uint16(10), Sum([]uint16{1, 2, 3, 4}...), "uint16") 127 | AssertEqual(t, uint32(10), Sum([]uint32{1, 2, 3, 4}...), "uint32") 128 | AssertEqual(t, uint64(10), Sum([]uint64{1, 2, 3, 4}...), "uint64") 129 | AssertEqual(t, uintptr(10), Sum([]uintptr{1, 2, 3, 4}...), "uintptr") 130 | 131 | // floats 132 | AssertFloatEqual(t, float32(10.1), Sum([]float32{1.1, 2.2, 3.3, 3.5}...), "float32") 133 | AssertFloatEqual(t, 10.1, Sum([]float64{1.1, 2.2, 3.3, 3.5}...), "float64") 134 | 135 | // string 136 | AssertEqual(t, "abcde", Sum([]string{"ab", "cd", "e"}...), "string") 137 | 138 | // complex 139 | AssertEqual(t, complex64(10+10i), Sum([]complex64{1 + 1i, 2 + 2i, 3 + 3i, 4 + 4i}...), "complex64") 140 | AssertEqual(t, complex128(10+10i), Sum([]complex128{1 + 1i, 2 + 2i, 3 + 3i, 4 + 4i}...), "complex128") 141 | 142 | // check ~int 143 | type MyInt int 144 | AssertEqual(t, MyInt(10), Sum([]MyInt{1, 2, 3, 4}...), "~int") 145 | 146 | // check empty array 147 | AssertPanics(t, func() { 148 | Sum([]int{}...) 149 | }) 150 | 151 | // check array with many elements 152 | { 153 | array := make([]int, 100000) 154 | res := 0 155 | for i := 0; i < 100000; i++ { 156 | array[i] = i 157 | res += i 158 | } 159 | rand.Shuffle(len(array), func(i, j int) { 160 | array[i], array[j] = array[j], array[i] 161 | }) 162 | AssertEqual(t, res, Sum(array...)) 163 | } 164 | 165 | // check float64 NaN 166 | AssertTrue(t, math.IsNaN(Sum([]float64{1, 2, 3, math.NaN()}...))) 167 | AssertTrue(t, math.IsNaN(Sum([]float64{math.NaN(), 2, 3}...))) 168 | AssertTrue(t, math.IsNaN(Sum([]float64{math.Inf(1), math.Inf(-1), math.Inf(1)}...))) 169 | } 170 | 171 | func TestAbs(t *testing.T) { 172 | AssertEqual(t, 1., Abs(1.)) 173 | AssertEqual(t, 1., Abs(-1.)) 174 | AssertEqual(t, math.Inf(1), Abs(math.Inf(1))) 175 | AssertEqual(t, math.Inf(1), Abs(math.Inf(-1))) 176 | AssertTrue(t, math.IsNaN(Abs(math.NaN()))) 177 | AssertTrue(t, math.IsNaN(Abs(-math.NaN()))) 178 | 179 | AssertEqual(t, 1, Abs(-1)) 180 | AssertEqual(t, 1, Abs(1)) 181 | AssertEqual(t, int64(100), Abs(int64(-100))) 182 | AssertEqual(t, int64(100), Abs(int64(100))) 183 | } 184 | 185 | func TestDivMod(t *testing.T) { 186 | for i := 0; i < 1000; i++ { 187 | a := rand.Intn(10000) + 1 188 | b := rand.Intn(10000) + 1 189 | if rand.Intn(1000) < 250 { 190 | a = -a 191 | } 192 | if rand.Intn(1000) < 250 { 193 | b = -b 194 | } 195 | div, mod := DivMod(a, b) 196 | AssertEqual(t, a/b, div, fmt.Sprintf("%d/%d", a, b)) 197 | AssertEqual(t, a%b, mod, fmt.Sprintf("%d/%d", a, b)) 198 | } 199 | 200 | testCases := []struct { 201 | a, b, div, mod int 202 | }{ 203 | {1, 1, 1, 0}, 204 | {1, 2, 0, 1}, 205 | {-10, 3, -3, -1}, 206 | {-11, 3, -3, -2}, 207 | {-12, 3, -4, 0}, 208 | {-13, 3, -4, -1}, 209 | {139, 3, 46, 1}, 210 | } 211 | 212 | for _, tc := range testCases { 213 | div, mod := DivMod(tc.a, tc.b) 214 | AssertEqual(t, tc.div, div, fmt.Sprintf("%d/%d", tc.a, tc.b)) 215 | AssertEqual(t, tc.mod, mod, fmt.Sprintf("%d/%d", tc.a, tc.b)) 216 | } 217 | 218 | AssertPanics(t, func() { 219 | DivMod(1, 0) 220 | }) 221 | } 222 | 223 | func TestSumBy(t *testing.T) { 224 | type Product struct { 225 | name string 226 | amount int 227 | } 228 | products := []Product{ 229 | {"apple", 10}, 230 | {"banana", 20}, 231 | {"orange", 30}, 232 | } 233 | AssertEqual(t, 60, SumBy(products, func(p Product) int { 234 | return p.amount 235 | })) 236 | 237 | AssertPanics(t, func() { 238 | SumBy([]int{}, func(i int) int { 239 | return i 240 | }) 241 | }) 242 | } 243 | 244 | func TestMaxBy(t *testing.T) { 245 | type Product struct { 246 | name string 247 | amount int 248 | } 249 | products := []Product{ 250 | {"apple", 10}, 251 | {"banana", 20}, 252 | {"orange", 30}, 253 | } 254 | p := MaxBy(products, func(p Product) int { 255 | return p.amount 256 | }) 257 | AssertEqual(t, "orange", p.name) 258 | 259 | AssertPanics(t, func() { 260 | MaxBy([]int{}, func(i int) int { 261 | return i 262 | }) 263 | }) 264 | } 265 | 266 | func TestMinBy(t *testing.T) { 267 | type Product struct { 268 | name string 269 | amount int 270 | } 271 | products := []Product{ 272 | {"banana", 20}, 273 | {"apple", 10}, 274 | {"orange", 30}, 275 | } 276 | p := MinBy(products, func(p Product) int { 277 | return p.amount 278 | }) 279 | AssertEqual(t, "apple", p.name) 280 | 281 | AssertPanics(t, func() { 282 | MinBy([]int{}, func(i int) int { 283 | return i 284 | }) 285 | }) 286 | } 287 | 288 | func TestMean(t *testing.T) { 289 | AssertFloatEqual(t, 2.5, Mean([]int{1, 2, 3, 4}...), "int") 290 | AssertFloatEqual(t, 3.0, Mean([]float64{1.5, 2.5, 3.5, 4.5}...), "int") 291 | 292 | AssertPanics(t, func() { 293 | Mean([]int{}...) 294 | }) 295 | } 296 | 297 | func TestMeanBy(t *testing.T) { 298 | type Product struct { 299 | name string 300 | cost float64 301 | } 302 | products := []Product{ 303 | {"apple", 1.5}, 304 | {"banana", 2.5}, 305 | {"orange", 3.5}, 306 | {"lemon", 4.5}, 307 | } 308 | AssertFloatEqual(t, 3.0, MeanBy(products, func(p Product) float64 { 309 | return p.cost 310 | })) 311 | 312 | AssertPanics(t, func() { 313 | MeanBy([]int{}, func(i int) int { 314 | return i 315 | }) 316 | }) 317 | } 318 | 319 | func TestMinMax(t *testing.T) { 320 | { 321 | minVal, maxVal := MinMax(1, 2, 3, 4) 322 | AssertEqual(t, 1, minVal) 323 | AssertEqual(t, 4, maxVal) 324 | } 325 | 326 | // check empty array 327 | AssertPanics(t, func() { 328 | MinMax([]int{}...) 329 | }) 330 | 331 | // check array with many elements 332 | { 333 | array := make([]int, 100000) 334 | for i := 0; i < 100000; i++ { 335 | array[i] = i 336 | } 337 | rand.Shuffle(len(array), func(i, j int) { 338 | array[i], array[j] = array[j], array[i] 339 | }) 340 | minVal, maxVal := MinMax(array...) 341 | AssertEqual(t, 99999, maxVal) 342 | AssertEqual(t, 0, minVal) 343 | } 344 | 345 | // check float64 with NaN values 346 | { 347 | var minVal, maxVal float64 348 | minVal, maxVal = MinMax(math.NaN(), 1.85, 2.2) 349 | AssertEqual(t, 1.85, minVal) 350 | AssertEqual(t, 2.2, maxVal) 351 | 352 | minVal, maxVal = MinMax(1, -1, math.NaN(), 1, 2.8) 353 | AssertEqual(t, -1, minVal) 354 | AssertEqual(t, 2.8, maxVal) 355 | 356 | minVal, maxVal = MinMax(1, -1, math.NaN(), 1, math.Inf(-1)) 357 | AssertEqual(t, math.Inf(-1), minVal) 358 | AssertEqual(t, 1, maxVal) 359 | 360 | minVal, maxVal = MinMax(1, -1, 1, math.Inf(1)) 361 | AssertEqual(t, -1, minVal) 362 | AssertEqual(t, math.Inf(1), maxVal) 363 | 364 | minVal, maxVal = MinMax(math.NaN(), math.NaN(), math.NaN()) 365 | AssertTrue(t, math.IsNaN(minVal)) 366 | AssertTrue(t, math.IsNaN(maxVal)) 367 | } 368 | } 369 | 370 | func TestMinMaxBy(t *testing.T) { 371 | type Product struct { 372 | name string 373 | amount int 374 | } 375 | products := []Product{ 376 | {"banana", 20}, 377 | {"orange", 30}, 378 | {"apple", 10}, 379 | {"grape", 50}, 380 | {"lemon", 40}, 381 | } 382 | minP, maxP := MinMaxBy(products, func(p Product) int { 383 | return p.amount 384 | }) 385 | AssertEqual(t, "apple", minP.name) 386 | AssertEqual(t, "grape", maxP.name) 387 | 388 | AssertPanics(t, func() { 389 | MinMaxBy([]int{}, func(i int) int { 390 | return i 391 | }) 392 | }) 393 | } 394 | 395 | func TestMode(t *testing.T) { 396 | AssertEqual(t, 5, Mode([]int{1, 1, 1, 5, 5, 5, 5, 2, 2, 2})) 397 | AssertEqual(t, 1, Mode([]int{1, 1, 1, 1, 5, 5, 5, 5, 2, 2, 2})) 398 | 399 | AssertPanics(t, func() { 400 | Mode([]int{}) 401 | }) 402 | } 403 | 404 | func TestModeBy(t *testing.T) { 405 | type Product struct { 406 | name string 407 | amount int 408 | } 409 | products := []Product{ 410 | {"banana", 20}, 411 | {"banana", 20}, 412 | {"apple", 10}, 413 | {"grape", 50}, 414 | {"lemon", 40}, 415 | } 416 | AssertEqual(t, "banana", ModeBy(products, func(p Product) int { 417 | return p.amount 418 | }).name) 419 | 420 | AssertPanics(t, func() { 421 | ModeBy([]int{}, func(i int) int { 422 | return i 423 | }) 424 | }) 425 | } 426 | -------------------------------------------------------------------------------- /test_test.go: -------------------------------------------------------------------------------- 1 | package gfn_test 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "runtime/debug" 7 | "strings" 8 | "testing" 9 | 10 | . "github.com/suchen-sci/gfn" 11 | ) 12 | 13 | func fail(t *testing.T, failMsg string, tags ...string) { 14 | t.Helper() 15 | if len(tags) == 0 { 16 | t.Error(failMsg) 17 | } else { 18 | t.Errorf("%s, tags: %s", failMsg, strings.Join(tags, ", ")) 19 | } 20 | } 21 | 22 | func AssertEqual[T comparable](t *testing.T, expected T, actual T, tags ...string) { 23 | t.Helper() 24 | if expected != actual { 25 | fail(t, fmt.Sprintf("expected: %v, actual: %v", expected, actual), tags...) 26 | } 27 | } 28 | 29 | func AssertFloatEqual[T Float](t *testing.T, expected T, actual T, tags ...string) { 30 | t.Helper() 31 | if math.Abs(float64(expected-actual)) > 0.0001 { 32 | fail(t, fmt.Sprintf("expected: %v, actual: %v", expected, actual), tags...) 33 | } 34 | } 35 | 36 | func AssertSliceEqual[T comparable](t *testing.T, expected []T, actual []T, tags ...string) { 37 | t.Helper() 38 | if expected == nil || actual == nil { 39 | if expected == nil && actual == nil { 40 | return 41 | } 42 | fail(t, fmt.Sprintf("expected: %v, actual: %v", expected, actual), tags...) 43 | return 44 | } 45 | 46 | if len(expected) != len(actual) { 47 | fail(t, fmt.Sprintf("expected: %v, actual: %v", expected, actual), tags...) 48 | return 49 | } 50 | 51 | for i := 0; i < len(expected); i++ { 52 | if expected[i] != actual[i] { 53 | fail(t, fmt.Sprintf("expected: %v, actual: %v", expected, actual), tags...) 54 | return 55 | } 56 | } 57 | } 58 | 59 | func AssertMapEqual[T comparable, V comparable](t *testing.T, expected map[T]V, actual map[T]V, tags ...string) { 60 | t.Helper() 61 | if expected == nil || actual == nil { 62 | if expected == nil && actual == nil { 63 | return 64 | } 65 | fail(t, fmt.Sprintf("expected: %v, actual: %v", expected, actual), tags...) 66 | return 67 | } 68 | 69 | if len(expected) != len(actual) { 70 | fail(t, fmt.Sprintf("expected: %v, actual: %v", expected, actual), tags...) 71 | return 72 | } 73 | 74 | for k, v := range expected { 75 | if actualV, ok := actual[k]; !ok || v != actualV { 76 | fail(t, fmt.Sprintf("expected: %v, actual: %v", expected, actual), tags...) 77 | return 78 | } 79 | } 80 | } 81 | 82 | func AssertTrue(t *testing.T, actual bool, tags ...string) { 83 | t.Helper() 84 | if !actual { 85 | fail(t, "expected: true, actual: false", tags...) 86 | } 87 | } 88 | 89 | func AssertFalse(t *testing.T, actual bool, tags ...string) { 90 | t.Helper() 91 | if actual { 92 | fail(t, "expected: false, actual: true", tags...) 93 | } 94 | } 95 | 96 | func AssertPanics(t *testing.T, fn func(), tags ...string) { 97 | t.Helper() 98 | defer func() { 99 | t.Helper() 100 | if r := recover(); r == nil { 101 | fail(t, "expected: panic, actual: not panic", tags...) 102 | } 103 | }() 104 | fn() 105 | } 106 | 107 | func AssertNotPanics(t *testing.T, fn func(), tags ...string) { 108 | t.Helper() 109 | defer func() { 110 | t.Helper() 111 | if r := recover(); r != nil { 112 | stack := string(debug.Stack()) 113 | fail(t, fmt.Sprintf("expected: not panic, actual: %v, %v", r, stack), tags...) 114 | } 115 | }() 116 | fn() 117 | } 118 | -------------------------------------------------------------------------------- /type.go: -------------------------------------------------------------------------------- 1 | package gfn 2 | 3 | /* 4 | Other basic types: 5 | - bool 6 | - string 7 | - byte, alias for uint8 8 | - rune, alias for int32 9 | */ 10 | 11 | // Int contains signed integer types. 12 | type Int interface { 13 | ~int | ~int8 | ~int16 | ~int32 | ~int64 14 | } 15 | 16 | // Uint contains unsigned integer types. 17 | type Uint interface { 18 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr 19 | } 20 | 21 | // Float contains floating-point types. 22 | type Float interface { 23 | ~float32 | ~float64 24 | } 25 | 26 | // Complex contains complex types. 27 | type Complex interface { 28 | ~complex64 | ~complex128 29 | } 30 | 31 | // Pair is a generic pair of values. 32 | type Pair[T, U any] struct { 33 | First T 34 | Second U 35 | } 36 | --------------------------------------------------------------------------------