├── .gitignore ├── LICENSE ├── README.md ├── README_zh-CN.md ├── comparator.go ├── converter.go ├── functional.go ├── go.mod ├── go.sum ├── helpers.go ├── map.go ├── number.go ├── slice.go ├── tests ├── converter_test.go ├── functional_test.go ├── helpers_test.go ├── map_test.go ├── number_test.go ├── slice_test.go └── types.go └── types └── sortable.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | .idea 4 | .vscode 5 | node_modules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 sxyazi 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-collection 2 | 3 | English | [简体中文](README_zh-CN.md) 4 | 5 | `go-collection` provides developers with a convenient set of functions for working with common slices, maps, and arrays data. These functions are based on the generic types of Go 1.18, which makes it easier to use them without annoying type assertions. In addition to using these functions directly, it also supports method chaining. 6 | 7 | ```go 8 | collect.Reduce(collect.Filter(collect.Map([]int{1, 2, 3}, fn), fn), fn) 9 | ``` 10 | 11 | Equivalent to: 12 | 13 | ```go 14 | collect.UseSlice([]int{1, 2, 3}).Map(fn).Filter(fn).Reduce(fn) 15 | ``` 16 | 17 | ## Installation 18 | 19 | ```shell 20 | go get -u github.com/sxyazi/go-collection 21 | ``` 22 | 23 | Then import it 24 | 25 | ```go 26 | import collect "github.com/sxyazi/go-collection" 27 | ``` 28 | 29 | ## API 30 | 31 | Its API is very simple and if you have used other similar packages, you should be able to get started with it in a few minutes. **For convenience, they are described below in function form**. 32 | 33 | ### Slice 34 | 35 | The corresponding chained function is `collect.UseSlice()` 36 | 37 | - `Len` gets the length of the slice 38 | 39 |
40 | Examples 41 | 42 | ```go 43 | d1 := []int{1, 2, 3} 44 | collect.Len(d1) // 3 45 | 46 | d2 := []string{"a", "b", "c"} 47 | collect.Len(d2) // 3 48 | ``` 49 | 50 |
51 | 52 | - `Each` iterates over each element in the slice 53 | 54 |
55 | Examples 56 | 57 | ```go 58 | d := []float64{1, 2, 3} 59 | collect.Each(d, func(value float64, index int) { 60 | fmt.Println(index, value) 61 | }) 62 | ``` 63 | 64 |
65 | 66 | - `Empty` checks if the slice is empty 67 | 68 |
69 | Examples 70 | 71 | ```go 72 | var d []int 73 | collect.Empty(d) // true 74 | ``` 75 | 76 |
77 | 78 | - `Same` checks if the contents of two slices are the same 79 | 80 |
81 | Examples 82 | 83 | ```go 84 | d1 := []int{1, 2, 3} 85 | d2 := []int{1, 2, 3} 86 | collect.Same(d1, d2) // true 87 | 88 | d3 := [][]int{{1, 2, 3}, {4, 5, 6}} 89 | d4 := [][]int{{1, 2, 3}, {4, 5, 6}} 90 | collect.Same(d3, d4) // true 91 | ``` 92 | 93 |
94 | 95 | - `First` gets the first element of the slice 96 | 97 |
98 | Examples 99 | 100 | ```go 101 | d1 := []int{1, 2, 3} 102 | value, ok := collect.First(d1) // 1, true 103 | 104 | var d2 []int 105 | value, ok = collect.First(d2) // 0, false 106 | ``` 107 | 108 |
109 | 110 | - `Last` gets the last element of the slice 111 | 112 |
113 | Examples 114 | 115 | ```go 116 | d1 := []int{1, 2, 3} 117 | value, ok := collect.Last(d1) // 3, true 118 | 119 | var d2 []int 120 | value, ok = collect.Last(d2) // 0, false 121 | ``` 122 | 123 |
124 | 125 | - `Index` gets the index of the specified element in the slice, and returns -1 if it does not exist. 126 | 127 |
128 | Examples 129 | 130 | ```go 131 | d1 := []int{1, 2, 3} 132 | collect.Index(d1, 2) // 1 133 | 134 | s1 := []string{"a", "b", "c"} 135 | s2 := []string{"d", "e", "f"} 136 | collect.Index([][]string{s1, s2}, s2) // 1 137 | ``` 138 | 139 |
140 | 141 | - `Contains` checks if the slice contains the specified element 142 | 143 |
144 | Examples 145 | 146 | ```go 147 | d1 := []int{1, 2, 3} 148 | collect.Contains(d1, 2) // true 149 | 150 | s1 := []string{"a", "b", "c"} 151 | s2 := []string{"d", "e", "f"} 152 | collect.Contains([][]string{s1, s2}, s2) // true 153 | ``` 154 | 155 |
156 | 157 | - `Diff` computes the difference set of two slices 158 | 159 |
160 | Examples 161 | 162 | ```go 163 | d := []int{1, 2, 3} 164 | collect.Diff(d, []int{2, 3}) // []int{1} 165 | ``` 166 | 167 |
168 | 169 | - `Filter` filters the elements in the slice 170 | 171 |
172 | Examples 173 | 174 | ```go 175 | collect.Filter([]int{1, 2, 3, 4, 5}, func(value, index int) bool { 176 | return value % 2 == 0 177 | }) // []int{2, 4} 178 | ``` 179 | 180 |
181 | 182 | - `Map` iterates over and sets the value of the elements in the slice 183 | 184 |
185 | Examples 186 | 187 | ```go 188 | collect.Map([]int{1, 2, 3}, func(value, index int) int { 189 | return value * 2 190 | }) // []int{2, 4, 6} 191 | ``` 192 | 193 |
194 | 195 | - `Unique` removes duplicate elements in the slice 196 | 197 |
198 | Examples 199 | 200 | ```go 201 | d := []int{1, 2, 3, 3, 4} 202 | collect.Unique(d) // []int{1, 2, 3, 4} 203 | ``` 204 | 205 |
206 | 207 | - `Duplicates` gets the duplicate elements in the slice 208 | 209 |
210 | Examples 211 | 212 | ```go 213 | d := []string{"a", "b", "a", "c"} 214 | collect.Duplicates(d) // map[int]string{2: "a"} 215 | ``` 216 | 217 |
218 | 219 | - `Merge` merges the current slice with other slices 220 | 221 |
222 | Examples 223 | 224 | ```go 225 | d1 := []int{1, 2} 226 | d2 := []int{3, 4} 227 | d3 := []int{5, 6} 228 | 229 | collect.Merge(d1, d2) // []int{1, 2, 3, 4} 230 | collect.Merge(d1, d2, d3) // []int{1, 2, 3, 4, 5, 6} 231 | ``` 232 | 233 |
234 | 235 | - `Random` gets an element of the slice at random 236 | 237 |
238 | Examples 239 | 240 | ```go 241 | d := []int{1, 2} 242 | value, ok := collect.Random(d) // 1 or 2, true 243 | 244 | d := []int{} 245 | value, ok := collect.Random(d) // 0, false 246 | ``` 247 | 248 |
249 | 250 | - `Reverse` reverses the elements in a slice 251 | 252 |
253 | Examples 254 | 255 | ```go 256 | d := []int{1, 2} 257 | collect.Reverse(d) // []int{2, 1} 258 | ``` 259 | 260 |
261 | 262 | - `Shuffle` randomly shuffles the elements in a slice 263 | 264 |
265 | Examples 266 | 267 | ```go 268 | d := []int{1, 2} 269 | collect.Shuffle(d) // []int{1, 2} or []int{2, 1} 270 | ``` 271 | 272 |
273 | 274 | - `Slice` takes a segment from a slice 275 | 276 |
277 | Examples 278 | 279 | Function signature: `Slice(items T, offset int)` 280 | 281 | ```go 282 | d := []int{1, 2, 3, 4, 5} 283 | collect.Slice(d, 2) // []int{3, 4, 5} 284 | collect.Slice(d, -1) // []int{5} 285 | collect.Slice(d, -2) // []int{4, 5} 286 | ``` 287 | 288 | Function signature: `Slice(items T, offset, length int)` 289 | 290 | ```go 291 | d := []int{1, 2, 3, 4, 5} 292 | collect.Slice(d, 0, 2) // []int{1, 2} 293 | collect.Slice(d, 2, 3) // []int{3, 4, 5} 294 | collect.Slice(d, 3, -2) // []int{3, 4} 295 | collect.Slice(d, -4, 3) // []int{2, 3, 4} 296 | ``` 297 | 298 |
299 | 300 | - `Split` splits a slice into multiple slices by the specified amount 301 | 302 |
303 | Examples 304 | 305 | ```go 306 | d := []int{1, 2, 3, 4, 5} 307 | collect.Split(d, 2) // [][]int{{1, 2}, {3, 4}, {5}} 308 | ``` 309 | 310 |
311 | 312 | - `Splice` removes a segment from the slice 313 | 314 |
315 | Examples 316 | 317 | Function signature: `Splice(items T, offset int)` 318 | 319 | ```go 320 | d := []int{1, 2, 3, 4, 5} 321 | collect.Splice(&d, 2) // []int{3, 4, 5} 322 | d // []int{1, 2} 323 | ``` 324 | 325 | Function signature: `Splice(items T, offset, length int)` 326 | 327 | ```go 328 | d := []int{1, 2, 3, 4, 5} 329 | collect.Splice(&d, 2, 2) // []int{3, 4} 330 | d // []int{1, 2, 5} 331 | ``` 332 | 333 | Function signature: `Splice(items T, offset, length int, replacements ...T|E)` 334 | 335 | ```go 336 | d1 := []int{1, 2, 3, 4} 337 | collect.Splice(&d1, 1, 2, []int{22, 33}) // []int{2, 3} 338 | d1 // []int{1, 22, 33, 4} 339 | 340 | d2 := []int{1, 2, 3, 4} 341 | collect.Splice(&d2, 1, 2, 22, 33) // []int{2, 3} 342 | d2 // []int{1, 22, 33, 4} 343 | 344 | d3 := []int{1, 2, 3, 4} 345 | collect.Splice(&d3, 1, 2, []int{22}, 33, []int{55}) // []int{2, 3} 346 | d3 // []int{1, 22, 33, 55, 4} 347 | ``` 348 | 349 | It is worth noting that this method also supports the use of negative numbers as arguments, and its behavior is the same as that of `Slice`, which is not repeated here due to space constraints. 350 | 351 |
352 | 353 | - `Reduce` reduces the collection to a single value, and the parameters of each iteration are the results of the previous iteration 354 | 355 |
356 | Examples 357 | 358 | ```go 359 | collect.Reduce([]int{1, 2, 3}, 100, func(carry, value, key int) int { 360 | return carry + value 361 | }) // 106 362 | ``` 363 | 364 |
365 | 366 | - `Pop` removes and returns the last element of the collection 367 | 368 |
369 | Examples 370 | 371 | ```go 372 | d := []int{1, 2} 373 | v, ok := collect.Pop(&d) // 2, true 374 | d // []int{1} 375 | 376 | c := collect.UseSlice([]int{1, 2}) 377 | v, ok := c.Pop() // 2, true 378 | c.All() // []int{1} 379 | ``` 380 | 381 |
382 | 383 | - `Push` appends an element to the end of a collection 384 | 385 |
386 | Examples 387 | 388 | ```go 389 | d := []int{1, 2} 390 | Push(&d, 3) 391 | d // []int{1, 2, 3} 392 | 393 | collect.UseSlice([]int{1, 2}).Push(3).All() // []int{1, 2, 3} 394 | ``` 395 | 396 |
397 | 398 | - `Where` filters the collection by the specified rules 399 | 400 |
401 | Examples 402 | 403 | Function signature: `Where(items T, target any)` 404 | 405 | ```go 406 | collect.Where([]int{1, 2, 3}, 2) // []int{2} 407 | ``` 408 | 409 | Function signature: `Where(items T, operator string, target any)` 410 | 411 | ```go 412 | d := []int{1, 2, 3, 4} 413 | collect.Where(d, "=", 2) // []int{2} 414 | collect.Where(d, "!=", 2) // []int{1, 3, 4} 415 | collect.Where(d, ">", 2) // []int{3, 4} 416 | collect.Where(d, ">=", 2) // []int{2, 3, 4} 417 | collect.Where(d, "<", 3) // []int{1, 2} 418 | collect.Where(d, "<=", 3) // []int{1, 2, 3} 419 | ``` 420 | 421 | Function signature: `Where(items T, key any, target any)` 422 | 423 | ```go 424 | d := []User{{ID: 1, Name: "Hugo"}, {ID: 2, Name: "Lisa"}, {ID: 3, Name: "Iris"}, {ID: 4, Name: "Lisa"}} 425 | collect.Where(d, "Name", "Lisa") // []User{{2 Lisa} {4 Lisa}} 426 | ``` 427 | 428 | Function signature: `Where(items T, key any, operator string, target any)` 429 | 430 | ```go 431 | d := []User{{ID: 1, Name: "Hugo"}, {ID: 2, Name: "Lisa"}, {ID: 3, Name: "Iris"}, {ID: 4, Name: "Lisa"}} 432 | collect.Where(d, "Name", "!=", "Lisa") // []User{{1 Hugo} {3 Iris}} 433 | ``` 434 | 435 |
436 | 437 | - `WhereIn` removes elements from the collection that do not exist in the specified slice 438 | 439 |
440 | Examples 441 | 442 | Function signature: `WhereIn(items T, targets []any)` 443 | 444 | ```go 445 | d := []int{1, 2, 3, 4} 446 | collect.WhereIn(d, []int{2, 3}) // []int{2, 3} 447 | ``` 448 | 449 | Function signature: `WhereIn(items T, key any, targets []any)` 450 | 451 | ```go 452 | d := []User{{ID: 1, Name: "Hugo"}, {ID: 2, Name: "Lisa"}, {ID: 3, Name: "Iris"}, {ID: 4, Name: "Lisa"}} 453 | collect.WhereIn(d, "Name", []string{"Hugo", "Iris"}) // []User{{1 Hugo} {3 Iris}} 454 | ``` 455 | 456 |
457 | 458 | - `WhereNotIn` removes elements from the collection that exist in the specified slice 459 | 460 |
461 | Examples 462 | 463 | Function signature: `WhereNotIn(items T, targets []any)` 464 | 465 | ```go 466 | d := []int{1, 2, 3, 4} 467 | collect.WhereNotIn(d, []int{2, 3}) // []int{1, 4} 468 | ``` 469 | 470 | Function signature: `WhereNotIn(items T, key any, targets []any)` 471 | 472 | ```go 473 | d := []User{{ID: 1, Name: "Hugo"}, {ID: 2, Name: "Lisa"}, {ID: 3, Name: "Iris"}, {ID: 4, Name: "Lisa"}} 474 | collect.WhereNotIn(d, "Name", []string{"Lisa", "Iris"}) // []User{{1 Hugo}} 475 | ``` 476 | 477 |
478 | 479 | ### Array 480 | 481 | Exactly the same as [slice](#Slice), you just pass in the array converted to a slice: 482 | 483 | ```go 484 | arr := [3]int{1, 2, 3} 485 | 486 | collect.Len(arr[:]) 487 | // or 488 | collect.UseSlice(arr[:]).Len() 489 | ``` 490 | 491 | ### Map 492 | 493 | The corresponding chained function is `collect.UseMap()` 494 | 495 | - `Len` gets the number of elements in the map 496 | 497 |
498 | Examples 499 | 500 | ```go 501 | d1 := map[string]int{"a": 1, "b": 2, "c": 3} 502 | collect.Len(d1) // 3 503 | ``` 504 | 505 |
506 | 507 | - `Empty` checks if the map is empty 508 | 509 |
510 | Examples 511 | 512 | ```go 513 | var d map[string]int 514 | collect.Empty(d) // true 515 | ``` 516 | 517 |
518 | 519 | - `Only` gets the elements of the map with the specified keys 520 | 521 |
522 | Examples 523 | 524 | ```go 525 | d := map[string]int{"a": 1, "b": 2, "c": 3} 526 | collect.Only(d, "a") // map[string]int{"a": 1} 527 | collect.Only(d, "a", "b") // map[string]int{"a": 1, "b": 2} 528 | ``` 529 | 530 |
531 | 532 | - `Except` gets the elements of the map with the specified keys removed 533 | 534 |
535 | Examples 536 | 537 | ```go 538 | d := map[string]int{"a": 1, "b": 2, "c": 3} 539 | collect.Except(d, "a") // map[string]int{"b": 2, "c": 3} 540 | collect.Except(d, "a", "b") // map[string]int{"c": 3} 541 | ``` 542 | 543 |
544 | 545 | - `Keys` gets all the keys in the map 546 | 547 |
548 | Examples 549 | 550 | ```go 551 | d := map[string]int{"a": 1, "b": 2, "c": 3} 552 | collect.Keys(d) // []string{"a", "b", "c"} 553 | ``` 554 | 555 |
556 | 557 | - `DiffKeys` compares with the given collection and returns the key/value pairs in the given collection that do not exist in the original collection 558 | 559 |
560 | Examples 561 | 562 | ```go 563 | d1 := map[string]int{"a": 1, "b": 2, "c": 3} 564 | d2 := map[string]int{"b": 22, "c": 33} 565 | 566 | collect.DiffKeys(d1, d2) // map[string]int{"a": 1} 567 | ``` 568 | 569 |
570 | 571 | - `Has` checks if the map contains the specified key 572 | 573 |
574 | Examples 575 | 576 | ```go 577 | d := map[string]int{"a": 1} 578 | collect.Has(d, "a") // true 579 | ``` 580 | 581 |
582 | 583 | - `Get` gets the value of the specified key in the map 584 | 585 |
586 | Examples 587 | 588 | ```go 589 | d := map[string]int{"a": 1} 590 | 591 | value, ok := collect.Get(d, "a") // 1, true 592 | value, ok := collect.Get(d, "b") // 0, false 593 | ``` 594 | 595 |
596 | 597 | - `Put` sets the value of the specified key in the map 598 | 599 |
600 | Examples 601 | 602 | ```go 603 | d := map[string]int{"a": 1} 604 | collect.Put(d, "b", 2) // map[string]int{"a": 1, "b": 2} 605 | ``` 606 | 607 |
608 | 609 | - `Pull` removes the specified key from the collection and returns its value 610 | 611 |
612 | Examples 613 | 614 | ```go 615 | d := map[string]int{"a": 1, "b": 2} 616 | v, ok := collect.Pull(d, "b") // 2, true 617 | d // map[string]int{"a": 1} 618 | ``` 619 | 620 |
621 | 622 | - `Merge` merges the current map with other maps 623 | 624 |
625 | Examples 626 | 627 | ```go 628 | d1 := map[string]int{"a": 1, "b": 2} 629 | d2 := map[string]int{"b": 22} 630 | d3 := map[string]int{"b": 222, "c": 3} 631 | 632 | collect.MapMerge(d1, d2) // map[string]int{"a": 1, "b": 22} 633 | collect.UseMap(d1).Merge(d2).All() // Equal to the above 634 | 635 | collect.MapMerge(d1, d2, d3) // map[string]int{"a": 1, "b": 222, "c": 3} 636 | collect.UseMap(d1).Merge(d2, d3).All() // Equal to the above 637 | ``` 638 | 639 |
640 | 641 | - `Union` unites the current map with other maps, and the items in the original map are given priority 642 | 643 |
644 | Examples 645 | 646 | ```go 647 | d1 := map[string]int{"a": 1, "b": 2} 648 | d2 := map[string]int{"b": 22, "c": 3} 649 | collect.Union(d1, d2) // map[string]int{"a": 1, "b": 2, "c": 3} 650 | ``` 651 | 652 |
653 | 654 | ### Number slice 655 | 656 | The corresponding chained function is `collect.UseNumber()`,which is a subset of [slice](#Slice) and includes, in addition to all the methods of slice, the additional: 657 | 658 | - `Sum` calculates the sum 659 | 660 |
661 | Examples 662 | 663 | ```go 664 | collect.Sum([]float64{1, 3.14}) // 4.14 665 | ``` 666 | 667 |
668 | 669 | - `Min` calculates the minimum value 670 | 671 |
672 | Examples 673 | 674 | ```go 675 | collect.Min([]int{0, 1, -3}) // -3 676 | ``` 677 | 678 |
679 | 680 | - `Max` calculates the maximum value 681 | 682 |
683 | Examples 684 | 685 | ```go 686 | collect.Max([]int{0, 1, -3}) // 1 687 | ``` 688 | 689 |
690 | 691 | - `Sort` sorts the numbers in the collection in ascending order 692 | 693 |
694 | Examples 695 | 696 | ```go 697 | collect.Sort([]float64{1, -4, 0, -4.3}) // []float64{-4.3, -4, 0, 1} 698 | ``` 699 | 700 |
701 | 702 | - `SortDesc` sorts the numbers in the collection in descending order 703 | 704 |
705 | Examples 706 | 707 | ```go 708 | collect.SortDesc([]float64{1, -4, 0, -4.3}) // []float64{1, 0, -4, -4.3} 709 | ``` 710 | 711 |
712 | 713 | - `Avg` calculates the average 714 | 715 |
716 | Examples 717 | 718 | ```go 719 | collect.Avg([]int{1, 2, 3, 4}) // 2.5 720 | ``` 721 | 722 |
723 | 724 | - `Median` calculates the median 725 | 726 |
727 | Examples 728 | 729 | ```go 730 | collect.Median([]int{1, 2, 3, 4}) // 2.5 731 | ``` 732 | 733 |
734 | 735 | ### Standalone functions 736 | 737 | Due to Golang's support for generics, it is [not possible to define generic types in methods](https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#no-parameterized-methods), so only their function implementations (which do not support chain calls) are listed below: 738 | 739 | - `AnyGet` gets value of arbitrary types (slices, maps, arrays, structures, and pointers to these) in a non-strict form 740 | 741 |
742 | Examples 743 | 744 | ```go 745 | m := map[string]int{"a": 1, "b": 2} 746 | collect.AnyGet[int](m, "b") // 2 747 | 748 | u := &User{"Email": "user@example.com"} 749 | collect.AnyGet[string](u, "Email") // user@example.com 750 | 751 | s := [][]int{{1, 2}, {3, 4}} 752 | collect.AnyGet[[]int](s, 1) // []{3, 4} 753 | ``` 754 | 755 |
756 | 757 | - `Pluck` retrieves all values for a given key. supports all values supported by `AnyGet` 758 | 759 |
760 | Examples 761 | 762 | ```go 763 | d := []User{{ID: 33, Name: "Lucy"}, {ID: 193, Name: "Peter"}} 764 | collect.Pluck[int](d, "ID") // int[]{33, 193} 765 | ``` 766 | 767 |
768 | 769 | - `MapPluck` retrieves all values of a given key, only maps are supported 770 | 771 |
772 | Examples 773 | 774 | ```go 775 | d := []map[string]int{{"ID": 33, "Score": 10}, {"ID": 193, "Score": 6}} 776 | collect.MapPluck(d, "ID") // int[]{33, 193} 777 | ``` 778 | 779 |
780 | 781 | - `KeyBy` retrieves a collection with the value of the given key as the identifier (if there are duplicate keys, only the last one will be kept). Supports all values supported by `AnyGet` 782 | 783 |
784 | Examples 785 | 786 | ```go 787 | d := []User{{ID: 33, Name: "Lucy"}, {ID: 193, Name: "Peter"}, {ID: 194, Name: "Peter"}} 788 | collect.KeyBy[string](d, "Name") // map[Lucy:{33 Lucy} Peter:{194 Peter}] 789 | ``` 790 | 791 |
792 | 793 | - `MapKeyBy` retrieves the collection with the value of the given key as the identifier (if there are duplicate keys, only the last one will be kept), only maps are supported 794 | 795 |
796 | Examples 797 | 798 | ```go 799 | d := []map[string]int{{"ID": 33, "Score": 6}, {"ID": 193, "Score": 10}, {"ID": 194, "Score": 10}} 800 | collect.MapKeyBy(d, "Score") // map[6:map[ID:33 Score:6] 10:map[ID:194 Score:10]] 801 | ``` 802 | 803 |
804 | 805 | - `GroupBy` groups the items in a collection using the value of the given key as the identifier. Supports all values supported by `AnyGet` 806 | 807 |
808 | Examples 809 | 810 | ```go 811 | d := []User{{ID: 33, Name: "Lucy"}, {ID: 193, Name: "Peter"}, {ID: 194, Name: "Peter"}} 812 | collect.GroupBy[string](d, "Name") // map[Lucy:[{33 Lucy}] Peter:[{193 Peter} {194 Peter}]] 813 | ``` 814 | 815 |
816 | 817 | - `MapGroupBy` groups items in a collection using the value of the given key as the identifier, only maps are supported 818 | 819 |
820 | Examples 821 | 822 | ```go 823 | d := []map[string]int{{"ID": 33, "Score": 6}, {"ID": 193, "Score": 10}, {"ID": 194, "Score": 10}} 824 | collect.MapGroupBy(d, "Score") // map[6:[map[ID:33 Score:6]] 10:[map[ID:193 Score:10] map[ID:194 Score:10]]] 825 | ``` 826 | 827 |
828 | 829 | - `Count` counts the number of occurrences of each element in the slice 830 | 831 |
832 | Examples 833 | 834 | ```go 835 | d := []bool{true, true, false} 836 | collect.Count(d) // map[bool]int{true: 2, false: 1} 837 | ``` 838 | 839 |
840 | 841 | - `Times` creates a new collection of slice by calling the callback with specified number of times 842 | 843 |
844 | Examples 845 | 846 | ```go 847 | collect.Times(3, func(number int) float64 { 848 | return float64(number) * 3.14 849 | }) // *SliceCollection{[]float64{3.14, 6.28, 9.42}} 850 | ``` 851 | 852 |
853 | 854 | - `SortBy` calls a callback for each element and performs an ascending sort by the return value of the callback 855 | 856 |
857 | Examples 858 | 859 | ```go 860 | collect.SortBy([]int{2, 1, 3}, func(item, index int) string { 861 | return strconv.Itoa(item) 862 | }) // *SliceCollection{[]int{1, 2, 3}} 863 | ``` 864 | 865 |
866 | 867 | - `SortByDesc` calls a callback for each element and performs a descending sort by the return value of the callback 868 | 869 |
870 | Examples 871 | 872 | ```go 873 | collect.SortByDesc([]int{2, 1, 3}, func(item, index int) string { 874 | return strconv.Itoa(item) 875 | }) // *SliceCollection{[]int{3, 2, 1}} 876 | ``` 877 | 878 |
879 | 880 | ## License 881 | 882 | go-collection is [MIT licensed](LICENSE). 883 | -------------------------------------------------------------------------------- /README_zh-CN.md: -------------------------------------------------------------------------------- 1 | # go-collection 2 | 3 | [English](README.md) | 简体中文 4 | 5 | `go-collection` 向开发者提供了一组便利的函数,用于处理常见的切片、映射、数组数据。这些函数基于 Go 1.18 中的泛型实现,这让在使用时更加方便,而无需烦人的类型断言。除了直接使用这些函数外,它同样支持链式调用。 6 | 7 | ```go 8 | collect.Reduce(collect.Filter(collect.Map([]int{1, 2, 3}, fn), fn), fn) 9 | ``` 10 | 11 | 等价于: 12 | 13 | ```go 14 | collect.UseSlice([]int{1, 2, 3}).Map(fn).Filter(fn).Reduce(fn) 15 | ``` 16 | 17 | ## 安装 18 | 19 | ```shell 20 | go get -u github.com/sxyazi/go-collection 21 | ``` 22 | 23 | 然后导入它 24 | 25 | ```go 26 | import collect "github.com/sxyazi/go-collection" 27 | ``` 28 | 29 | ## API 30 | 31 | 它的 API 非常简单,如果您用过其它类似的包,应该可以在几分钟内上手它。**为了方便,下面以函数的形式介绍它们**。 32 | 33 | ### 切片 34 | 35 | 对应的链式函数为 `collect.UseSlice()` 36 | 37 | - Len:获取切片的长度 38 | 39 |
40 | 例子 41 | 42 | ```go 43 | d1 := []int{1, 2, 3} 44 | collect.Len(d1) // 3 45 | 46 | d2 := []string{"a", "b", "c"} 47 | collect.Len(d2) // 3 48 | ``` 49 | 50 |
51 | 52 | - Each:遍历切片中的每个元素 53 | 54 |
55 | 例子 56 | 57 | ```go 58 | d := []float64{1, 2, 3} 59 | collect.Each(d, func(value float64, index int) { 60 | fmt.Println(index, value) 61 | }) 62 | ``` 63 | 64 |
65 | 66 | - Empty:检查切片是否为空 67 | 68 |
69 | 例子 70 | 71 | ```go 72 | var d []int 73 | collect.Empty(d) // true 74 | ``` 75 | 76 |
77 | 78 | - Same:检查两个切片的内容是否相同 79 | 80 |
81 | 例子 82 | 83 | ```go 84 | d1 := []int{1, 2, 3} 85 | d2 := []int{1, 2, 3} 86 | collect.Same(d1, d2) // true 87 | 88 | d3 := [][]int{{1, 2, 3}, {4, 5, 6}} 89 | d4 := [][]int{{1, 2, 3}, {4, 5, 6}} 90 | collect.Same(d3, d4) // true 91 | ``` 92 | 93 |
94 | 95 | - First:获取切片的第一个元素 96 | 97 |
98 | 例子 99 | 100 | ```go 101 | d1 := []int{1, 2, 3} 102 | value, ok := collect.First(d1) // 1, true 103 | 104 | var d2 []int 105 | value, ok = collect.First(d2) // 0, false 106 | ``` 107 | 108 |
109 | 110 | - Last:获取切片的最后一个元素 111 | 112 |
113 | 例子 114 | 115 | ```go 116 | d1 := []int{1, 2, 3} 117 | value, ok := collect.Last(d1) // 3, true 118 | 119 | var d2 []int 120 | value, ok = collect.Last(d2) // 0, false 121 | ``` 122 | 123 |
124 | 125 | - Index:获取指定元素在切片中的索引,如果不存在返回 -1 126 | 127 |
128 | 例子 129 | 130 | ```go 131 | d1 := []int{1, 2, 3} 132 | collect.Index(d1, 2) // 1 133 | 134 | s1 := []string{"a", "b", "c"} 135 | s2 := []string{"d", "e", "f"} 136 | collect.Index([][]string{s1, s2}, s2) // 1 137 | ``` 138 | 139 |
140 | 141 | - Contains:检查切片中是否包含指定元素 142 | 143 |
144 | 例子 145 | 146 | ```go 147 | d1 := []int{1, 2, 3} 148 | collect.Contains(d1, 2) // true 149 | 150 | s1 := []string{"a", "b", "c"} 151 | s2 := []string{"d", "e", "f"} 152 | collect.Contains([][]string{s1, s2}, s2) // true 153 | ``` 154 | 155 |
156 | 157 | - Diff:计算两个切片的差集 158 | 159 |
160 | 例子 161 | 162 | ```go 163 | d := []int{1, 2, 3} 164 | collect.Diff(d, []int{2, 3}) // []int{1} 165 | ``` 166 | 167 |
168 | 169 | - Filter:过滤切片中的元素 170 | 171 |
172 | 例子 173 | 174 | ```go 175 | collect.Filter([]int{1, 2, 3, 4, 5}, func(value, index int) bool { 176 | return value % 2 == 0 177 | }) // []int{2, 4} 178 | ``` 179 | 180 |
181 | 182 | - Map:遍历并设置切片中元素的值 183 | 184 |
185 | 例子 186 | 187 | ```go 188 | collect.Map([]int{1, 2, 3}, func(value, index int) int { 189 | return value * 2 190 | }) // []int{2, 4, 6} 191 | ``` 192 | 193 |
194 | 195 | - Unique:去除切片中重复的元素 196 | 197 |
198 | 例子 199 | 200 | ```go 201 | d := []int{1, 2, 3, 3, 4} 202 | collect.Unique(d) // []int{1, 2, 3, 4} 203 | ``` 204 | 205 |
206 | 207 | - Duplicates:获取切片中的重复元素 208 | 209 |
210 | 例子 211 | 212 | ```go 213 | d := []string{"a", "b", "a", "c"} 214 | collect.Duplicates() // map[int]string{2: "a"} 215 | ``` 216 | 217 |
218 | 219 | - Merge:将当前切片与其它切片合并 220 | 221 |
222 | 例子 223 | 224 | ```go 225 | d1 := []int{1, 2} 226 | d2 := []int{3, 4} 227 | d3 := []int{5, 6} 228 | 229 | collect.Merge(d1, d2) // []int{1, 2, 3, 4} 230 | collect.Merge(d1, d2, d3) // []int{1, 2, 3, 4, 5, 6} 231 | ``` 232 | 233 |
234 | 235 | - Random:随机获取切片中的一个元素 236 | 237 |
238 | 例子 239 | 240 | ```go 241 | d := []int{1, 2} 242 | value, ok := collect.Random(d) // 1 or 2, true 243 | 244 | d := []int{} 245 | value, ok := collect.Random(d) // 0, false 246 | ``` 247 | 248 |
249 | 250 | - Reverse:反转切片中的元素 251 | 252 |
253 | 例子 254 | 255 | ```go 256 | d := []int{1, 2} 257 | collect.Reverse(d) // []int{2, 1} 258 | ``` 259 | 260 |
261 | 262 | - Shuffle:随机打乱切片中的元素 263 | 264 |
265 | 例子 266 | 267 | ```go 268 | d := []int{1, 2} 269 | collect.Shuffle(d) // []int{1, 2} or []int{2, 1} 270 | ``` 271 | 272 |
273 | 274 | - Slice:从切片中截取一段 275 | 276 |
277 | 例子 278 | 279 | 函数签名:`Slice(items T, offset int)` 280 | 281 | ```go 282 | d := []int{1, 2, 3, 4, 5} 283 | collect.Slice(d, 2) // []int{3, 4, 5} 284 | collect.Slice(d, -1) // []int{5} 285 | collect.Slice(d, -2) // []int{4, 5} 286 | ``` 287 | 288 | 函数签名:`Slice(items T, offset, length int)` 289 | 290 | ```go 291 | d := []int{1, 2, 3, 4, 5} 292 | collect.Slice(d, 0, 2) // []int{1, 2} 293 | collect.Slice(d, 2, 3) // []int{3, 4, 5} 294 | collect.Slice(d, 3, -2) // []int{3, 4} 295 | collect.Slice(d, -4, 3) // []int{2, 3, 4} 296 | ``` 297 | 298 |
299 | 300 | - Split:按照指定的数量将切片分割为多个 301 | 302 |
303 | 例子 304 | 305 | ```go 306 | d := []int{1, 2, 3, 4, 5} 307 | collect.Split(d, 2) // [][]int{{1, 2}, {3, 4}, {5}} 308 | ``` 309 | 310 |
311 | 312 | - Splice:从切片中删除一段 313 | 314 |
315 | 例子 316 | 317 | 函数签名:`Splice(items T, offset int)` 318 | 319 | ```go 320 | d := []int{1, 2, 3, 4, 5} 321 | collect.Splice(&d, 2) // []int{3, 4, 5} 322 | d // []int{1, 2} 323 | ``` 324 | 325 | 函数签名:`Splice(items T, offset, length int)` 326 | 327 | ```go 328 | d := []int{1, 2, 3, 4, 5} 329 | collect.Splice(&d, 2, 2) // []int{3, 4} 330 | d // []int{1, 2, 5} 331 | ``` 332 | 333 | 函数签名:`Splice(items T, offset, length int, replacements ...T|E)` 334 | 335 | ```go 336 | d1 := []int{1, 2, 3, 4} 337 | collect.Splice(&d1, 1, 2, []int{22, 33}) // []int{2, 3} 338 | d1 // []int{1, 22, 33, 4} 339 | 340 | d2 := []int{1, 2, 3, 4} 341 | collect.Splice(&d2, 1, 2, 22, 33) // []int{2, 3} 342 | d2 // []int{1, 22, 33, 4} 343 | 344 | d3 := []int{1, 2, 3, 4} 345 | collect.Splice(&d3, 1, 2, []int{22}, 33, []int{55}) // []int{2, 3} 346 | d3 // []int{1, 22, 33, 55, 4} 347 | ``` 348 | 349 | 值得注意的是,该方法同样支持使用负数作为参数,其行为与 `Slice` 一致,受于篇幅限制这里不再赘述。 350 | 351 |
352 | 353 | - Reduce:将集合减少到一个单一的值,每轮迭代的参数为上一轮迭代的结果 354 | 355 |
356 | 例子 357 | 358 | ```go 359 | collect.Reduce([]int{1, 2, 3}, 100, func(carry, value, key int) int { 360 | return carry + value 361 | }) // 106 362 | ``` 363 | 364 |
365 | 366 | - Pop:移除并返回集合中的最后一个元素 367 | 368 |
369 | 例子 370 | 371 | ```go 372 | d := []int{1, 2} 373 | v, ok := collect.Pop(&d) // 2, true 374 | d // []int{1} 375 | 376 | c := collect.UseSlice([]int{1, 2}) 377 | v, ok := c.Pop() // 2, true 378 | c.All() // []int{1} 379 | ``` 380 | 381 |
382 | 383 | - Push:将一个元素追加到集合的末端 384 | 385 |
386 | 例子 387 | 388 | ```go 389 | d := []int{1, 2} 390 | Push(&d, 3) 391 | d // []int{1, 2, 3} 392 | 393 | collect.UseSlice([]int{1, 2}).Push(3).All() // []int{1, 2, 3} 394 | ``` 395 | 396 |
397 | 398 | - Where:通过指定的条件过滤集合 399 | 400 |
401 | 例子 402 | 403 | 函数签名:`Where(items T, target any)` 404 | 405 | ```go 406 | collect.Where([]int{1, 2, 3}, 2) // []int{2} 407 | ``` 408 | 409 | 函数签名:`Where(items T, operator string, target any)` 410 | 411 | ```go 412 | d := []int{1, 2, 3, 4} 413 | collect.Where(d, "=", 2) // []int{2} 414 | collect.Where(d, "!=", 2) // []int{1, 3, 4} 415 | collect.Where(d, ">", 2) // []int{3, 4} 416 | collect.Where(d, ">=", 2) // []int{2, 3, 4} 417 | collect.Where(d, "<", 3) // []int{1, 2} 418 | collect.Where(d, "<=", 3) // []int{1, 2, 3} 419 | ``` 420 | 421 | 函数签名:`Where(items T, key any, target any)` 422 | 423 | ```go 424 | d := []User{{ID: 1, Name: "Hugo"}, {ID: 2, Name: "Lisa"}, {ID: 3, Name: "Iris"}, {ID: 4, Name: "Lisa"}} 425 | collect.Where(d, "Name", "Lisa") // []User{{2 Lisa} {4 Lisa}} 426 | ``` 427 | 428 | 函数签名:`Where(items T, key any, operator string, target any)` 429 | 430 | ```go 431 | d := []User{{ID: 1, Name: "Hugo"}, {ID: 2, Name: "Lisa"}, {ID: 3, Name: "Iris"}, {ID: 4, Name: "Lisa"}} 432 | collect.Where(d, "Name", "!=", "Lisa") // []User{{1 Hugo} {3 Iris}} 433 | ``` 434 | 435 |
436 | 437 | - WhereIn:移除集合中不存在于指定切片中的元素 438 | 439 |
440 | 例子 441 | 442 | 函数签名:`WhereIn(items T, targets []any)` 443 | 444 | ```go 445 | d := []int{1, 2, 3, 4} 446 | collect.WhereIn(d, []int{2, 3}) // []int{2, 3} 447 | ``` 448 | 449 | 函数签名:`WhereIn(items T, key any, targets []any)` 450 | 451 | ```go 452 | d := []User{{ID: 1, Name: "Hugo"}, {ID: 2, Name: "Lisa"}, {ID: 3, Name: "Iris"}, {ID: 4, Name: "Lisa"}} 453 | collect.WhereIn(d, "Name", []string{"Hugo", "Iris"}) // []User{{1 Hugo} {3 Iris}} 454 | ``` 455 | 456 |
457 | 458 | - WhereNotIn:移除集合中存在于指定切片中的元素 459 | 460 |
461 | 例子 462 | 463 | 函数签名:`WhereNotIn(items T, targets []any)` 464 | 465 | ```go 466 | d := []int{1, 2, 3, 4} 467 | collect.WhereNotIn(d, []int{2, 3}) // []int{1, 4} 468 | ``` 469 | 470 | 函数签名:`WhereNotIn(items T, key any, targets []any)` 471 | 472 | ```go 473 | d := []User{{ID: 1, Name: "Hugo"}, {ID: 2, Name: "Lisa"}, {ID: 3, Name: "Iris"}, {ID: 4, Name: "Lisa"}} 474 | collect.WhereNotIn(d, "Name", []string{"Lisa", "Iris"}) // []User{{1 Hugo}} 475 | ``` 476 | 477 |
478 | 479 | ### 数组 480 | 481 | 与 [切片](#切片) 完全一致,您只需将数组转换为切片传入: 482 | 483 | ```go 484 | arr := [3]int{1, 2, 3} 485 | 486 | collect.Len(arr[:]) 487 | // or 488 | collect.UseSlice(arr[:]).Len() 489 | ``` 490 | 491 | ### 映射 492 | 493 | 对应的链式函数为 `collect.UseMap()` 494 | 495 | - Len:获取映射的元素个数 496 | 497 |
498 | 例子 499 | 500 | ```go 501 | d1 := map[string]int{"a": 1, "b": 2, "c": 3} 502 | collect.Len(d1) // 3 503 | ``` 504 | 505 |
506 | 507 | - Empty:检查映射是否为空 508 | 509 |
510 | 例子 511 | 512 | ```go 513 | var d map[string]int 514 | collect.Empty(d) // true 515 | ``` 516 | 517 |
518 | 519 | - Only:获取映射中指定键的元素 520 | 521 |
522 | 例子 523 | 524 | ```go 525 | d := map[string]int{"a": 1, "b": 2, "c": 3} 526 | collect.Only(d, "a") // map[string]int{"a": 1} 527 | collect.Only(d, "a", "b") // map[string]int{"a": 1, "b": 2} 528 | ``` 529 | 530 |
531 | 532 | - Except:获取映射中除去指定键的元素 533 | 534 |
535 | 例子 536 | 537 | ```go 538 | d := map[string]int{"a": 1, "b": 2, "c": 3} 539 | collect.Except(d, "a") // map[string]int{"b": 2, "c": 3} 540 | collect.Except(d, "a", "b") // map[string]int{"c": 3} 541 | ``` 542 | 543 |
544 | 545 | - Keys:获取映射中所有的键 546 | 547 |
548 | 例子 549 | 550 | ```go 551 | d := map[string]int{"a": 1, "b": 2, "c": 3} 552 | collect.Keys(d) // []string{"a", "b", "c"} 553 | ``` 554 | 555 |
556 | 557 | - DiffKeys:与给定的集合比较,返回给定集合中不存在于原始集合的键/值对 558 | 559 |
560 | 例子 561 | 562 | ```go 563 | d1 := map[string]int{"a": 1, "b": 2, "c": 3} 564 | d2 := map[string]int{"b": 22, "c": 33} 565 | 566 | collect.DiffKeys(d1, d2) // map[string]int{"a": 1} 567 | ``` 568 | 569 |
570 | 571 | - Has:检查映射中是否包含指定键 572 | 573 |
574 | 例子 575 | 576 | ```go 577 | d := map[string]int{"a": 1} 578 | collect.Has(d, "a") // true 579 | ``` 580 | 581 |
582 | 583 | - Get:获取映射中指定键的值 584 | 585 |
586 | 例子 587 | 588 | ```go 589 | d := map[string]int{"a": 1} 590 | 591 | value, ok := collect.Get(d, "a") // 1, true 592 | value, ok := collect.Get(d, "b") // 0, false 593 | ``` 594 | 595 |
596 | 597 | - Put:设置映射中指定键的值 598 | 599 |
600 | 例子 601 | 602 | ```go 603 | d := map[string]int{"a": 1} 604 | collect.Put(d, "b", 2) 605 | d // map[string]int{"a": 1, "b": 2} 606 | ``` 607 | 608 |
609 | 610 | - Pull:从集合中删除指定键,并返回其值 611 | 612 |
613 | 例子 614 | 615 | ```go 616 | d := map[string]int{"a": 1, "b": 2} 617 | v, ok := collect.Pull(d, "b") // 2, true 618 | d // map[string]int{"a": 1} 619 | ``` 620 | 621 |
622 | 623 | - Merge:将当前映射与其它映射合并 624 | 625 |
626 | 例子 627 | 628 | ```go 629 | d1 := map[string]int{"a": 1, "b": 2} 630 | d2 := map[string]int{"b": 22} 631 | d3 := map[string]int{"b": 222, "c": 3} 632 | 633 | collect.MapMerge(d1, d2) // map[string]int{"a": 1, "b": 22} 634 | collect.UseMap(d1).Merge(d2).All() // Equivalent to the above 635 | 636 | collect.MapMerge(d1, d2, d3) // map[string]int{"a": 1, "b": 222, "c": 3} 637 | collect.UseMap(d1).Merge(d2, d3).All() // Equivalent to the above 638 | ``` 639 | 640 |
641 | 642 | - Union:将当前映射与其它映射联合,原映射中的项目会被优先考虑 643 | 644 |
645 | 例子 646 | 647 | ```go 648 | d1 := map[string]int{"a": 1, "b": 2} 649 | d2 := map[string]int{"b": 22, "c": 3} 650 | collect.Union(d1, d2) // map[string]int{"a": 1, "b": 2, "c": 3} 651 | ``` 652 | 653 |
654 | 655 | ### 数字切片 656 | 657 | 对应的链式函数为 `collect.UseNumber()`,它是 [切片](#切片) 的子集,除切片的所有方法外,还额外包括: 658 | 659 | - Sum:求和 660 | 661 |
662 | 例子 663 | 664 | ```go 665 | collect.Sum([]float64{1, 3.14}) // 4.14 666 | ``` 667 | 668 |
669 | 670 | - Min:求最小值 671 | 672 |
673 | 例子 674 | 675 | ```go 676 | collect.Min([]int{0, 1, -3}) // -3 677 | ``` 678 | 679 |
680 | 681 | - Max:求最大值 682 | 683 |
684 | 例子 685 | 686 | ```go 687 | collect.Max([]int{0, 1, -3}) // 1 688 | ``` 689 | 690 |
691 | 692 | - Sort:对集合中的数字按升序排序 693 | 694 |
695 | 例子 696 | 697 | ```go 698 | collect.Sort([]float64{1, -4, 0, -4.3}) // []float64{-4.3, -4, 0, 1} 699 | ``` 700 | 701 |
702 | 703 | - SortDesc:对集合中的数字按降序排序 704 | 705 |
706 | 例子 707 | 708 | ```go 709 | collect.SortDesc([]float64{1, -4, 0, -4.3}) // []float64{1, 0, -4, -4.3} 710 | ``` 711 | 712 |
713 | 714 | - Avg:求平均数 715 | 716 |
717 | 例子 718 | 719 | ```go 720 | collect.Avg([]int{1, 2, 3, 4}) // 2.5 721 | ``` 722 | 723 |
724 | 725 | - Median:求中位数 726 | 727 |
728 | 例子 729 | 730 | ```go 731 | collect.Median([]int{1, 2, 3, 4}) // 2.5 732 | ``` 733 | 734 |
735 | 736 | ### 独立函数 737 | 738 | 受限于 [Golang 泛型](https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#no-parameterized-methods) 的支持,无法在方法中定义泛型类型,因此以下列出的这些只有其函数实现(不支持链式调用): 739 | 740 | - AnyGet:以一种非严格的形式获取任意类型(切片、映射、数组、结构体,以及这些的指针)中的值 741 | 742 |
743 | 例子 744 | 745 | ```go 746 | m := map[string]int{"a": 1, "b": 2} 747 | collect.AnyGet[int](m, "b") // 2 748 | 749 | u := &User{"Email": "user@example.com"} 750 | collect.AnyGet[string](u, "Email") // user@example.com 751 | 752 | s := [][]int{{1, 2}, {3, 4}} 753 | collect.AnyGet[[]int](s, 1) // []{3, 4} 754 | ``` 755 | 756 |
757 | 758 | - Pluck:检索给定键的所有值,支持 `AnyGet` 支持的所有值 759 | 760 |
761 | 例子 762 | 763 | ```go 764 | d := []User{{ID: 33, Name: "Lucy"}, {ID: 193, Name: "Peter"}} 765 | collect.Pluck[int](d, "ID") // int[]{33, 193} 766 | ``` 767 | 768 |
769 | 770 | - MapPluck:检索给定键的所有值,只支持映射 771 | 772 |
773 | 例子 774 | 775 | ```go 776 | d := []map[string]int{{"ID": 33, "Score": 10}, {"ID": 193, "Score": 6}} 777 | collect.MapPluck(d, "ID") // int[]{33, 193} 778 | ``` 779 | 780 |
781 | 782 | - KeyBy:以给定键的值为标识检索集合(若存在重复的键,则只有最后一个会被保留)。支持 `AnyGet` 支持的所有值 783 | 784 |
785 | 例子 786 | 787 | ```go 788 | d := []User{{ID: 33, Name: "Lucy"}, {ID: 193, Name: "Peter"}, {ID: 194, Name: "Peter"}} 789 | collect.KeyBy[string](d, "Name") // map[Lucy:{33 Lucy} Peter:{194 Peter}] 790 | ``` 791 | 792 |
793 | 794 | - MapKeyBy:以给定键的值为标识检索集合(若存在重复的键,则只有最后一个会被保留),只支持映射 795 | 796 |
797 | 例子 798 | 799 | ```go 800 | d := []map[string]int{{"ID": 33, "Score": 6}, {"ID": 193, "Score": 10}, {"ID": 194, "Score": 10}} 801 | collect.MapKeyBy(d, "Score") // map[6:map[ID:33 Score:6] 10:map[ID:194 Score:10]] 802 | ``` 803 | 804 |
805 | 806 | - GroupBy:以给定键的值为标识,对集合中的项目分组。支持 `AnyGet` 支持的所有值 807 | 808 |
809 | 例子 810 | 811 | ```go 812 | d := []User{{ID: 33, Name: "Lucy"}, {ID: 193, Name: "Peter"}, {ID: 194, Name: "Peter"}} 813 | collect.GroupBy[string](d, "Name") // map[Lucy:[{33 Lucy}] Peter:[{193 Peter} {194 Peter}]] 814 | ``` 815 | 816 |
817 | 818 | - MapGroupBy:以给定键的值为标识,对集合中的项目分组,只支持映射 819 | 820 |
821 | 例子 822 | 823 | ```go 824 | d := []map[string]int{{"ID": 33, "Score": 6}, {"ID": 193, "Score": 10}, {"ID": 194, "Score": 10}} 825 | collect.MapGroupBy(d, "Score") // map[6:[map[ID:33 Score:6]] 10:[map[ID:193 Score:10] map[ID:194 Score:10]]] 826 | ``` 827 | 828 |
829 | 830 | - Count:统计切片中每个元素出现的次数 831 | 832 |
833 | 例子 834 | 835 | ```go 836 | d := []bool{true, true, false} 837 | collect.Count(d) // map[bool]int{true: 2, false: 1} 838 | ``` 839 | 840 |
841 | 842 | - Times:通过调用指定次数的回调,创建一个新的切片集合 843 | 844 |
845 | 例子 846 | 847 | ```go 848 | collect.Times(3, func(number int) float64 { 849 | return float64(number) * 3.14 850 | }) // *SliceCollection{[]float64{3.14, 6.28, 9.42}} 851 | ``` 852 | 853 |
854 | 855 | - SortBy:为每个元素调用回调函数,并按回调函数的返回值执行升序排序 856 | 857 |
858 | 例子 859 | 860 | ```go 861 | collect.SortBy([]int{2, 1, 3}, func(item, index int) string { 862 | return strconv.Itoa(item) 863 | }) // *SliceCollection{[]int{1, 2, 3}} 864 | ``` 865 | 866 |
867 | 868 | - SortByDesc:为每个元素调用回调函数,并按回调函数的返回值执行降序排序 869 | 870 |
871 | 例子 872 | 873 | ```go 874 | collect.SortByDesc([]int{2, 1, 3}, func(item, index int) string { 875 | return strconv.Itoa(item) 876 | }) // *SliceCollection{[]int{3, 2, 1}} 877 | ``` 878 | 879 |
880 | 881 | ## 许可 882 | 883 | go-collection is [MIT licensed](LICENSE). 884 | -------------------------------------------------------------------------------- /comparator.go: -------------------------------------------------------------------------------- 1 | package collect 2 | 3 | import ( 4 | "golang.org/x/exp/constraints" 5 | "math" 6 | "reflect" 7 | ) 8 | 9 | func IsNumber(v any) bool { 10 | switch v.(type) { 11 | case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64: 12 | return true 13 | default: 14 | return false 15 | } 16 | } 17 | 18 | func NumberCompare[T constraints.Integer | constraints.Float](a T, operator string, b T) bool { 19 | switch operator { 20 | case "=", "!=": 21 | var eq bool 22 | switch av := any(a).(type) { 23 | case float64: 24 | if math.IsNaN(av) && math.IsNaN(any(b).(float64)) { 25 | eq = true 26 | } else { 27 | eq = math.Abs(av-any(b).(float64)) <= 1e-9 28 | } 29 | case float32: 30 | eq = math.Abs(float64(av)-float64(any(b).(float32))) <= 1e-9 31 | default: 32 | eq = a == b 33 | } 34 | 35 | if operator == "=" { 36 | return eq 37 | } else { 38 | return !eq 39 | } 40 | case "<": 41 | return a < b 42 | case "<=": 43 | return a <= b 44 | case ">": 45 | return a > b 46 | case ">=": 47 | return a >= b 48 | } 49 | return false 50 | } 51 | 52 | func AnyNumberCompare(a any, operator string, b any) bool { 53 | if a == nil || b == nil { 54 | return false 55 | } else if !IsNumber(a) { 56 | return false 57 | } 58 | 59 | ar, br := reflect.ValueOf(a), reflect.ValueOf(b) 60 | switch true { 61 | case ar.CanInt(): 62 | if !br.CanInt() { 63 | return operator != "=" 64 | } 65 | return NumberCompare(ar.Int(), operator, br.Int()) 66 | case ar.CanUint(): 67 | if !br.CanUint() { 68 | return operator != "=" 69 | } 70 | return NumberCompare(ar.Uint(), operator, br.Uint()) 71 | case ar.CanFloat(): 72 | if !br.CanFloat() { 73 | return operator != "=" 74 | } 75 | return NumberCompare(ar.Float(), operator, br.Float()) 76 | } 77 | 78 | return false 79 | } 80 | 81 | func Compare(a any, operator string, b any) bool { 82 | if a == nil && b == nil { 83 | return operator == "=" 84 | } else if a == nil || b == nil { 85 | return operator != "=" 86 | } 87 | 88 | if IsNumber(a) || IsNumber(b) { 89 | return AnyNumberCompare(a, operator, b) 90 | } else if operator != "=" && operator != "!=" { 91 | return false 92 | } 93 | 94 | ar, br := reflect.TypeOf(a), reflect.TypeOf(b) 95 | ak, bk := ar.Kind(), br.Kind() 96 | if ak != bk { 97 | return operator == "!=" 98 | } 99 | 100 | if ak != reflect.Slice && ak != reflect.Map && ak != reflect.Func { 101 | switch operator { 102 | case "=": 103 | return a == b 104 | case "!=": 105 | return a != b 106 | } 107 | } 108 | 109 | p := reflect.ValueOf(a).UnsafePointer() 110 | switch operator { 111 | case "=": 112 | return p == reflect.ValueOf(b).UnsafePointer() 113 | case "!=": 114 | return p != reflect.ValueOf(b).UnsafePointer() 115 | } 116 | 117 | return false 118 | } 119 | 120 | type ComparisonSet struct { 121 | LooseNumber bool 122 | z map[any]map[reflect.Kind]struct{} 123 | } 124 | 125 | func (c *ComparisonSet) Normalize(v reflect.Value) (reflect.Kind, any) { 126 | kind := v.Kind() 127 | if kind == reflect.Slice || kind == reflect.Func || kind == reflect.Map { 128 | return kind, v.UnsafePointer() 129 | } 130 | 131 | if c.LooseNumber { 132 | switch true { 133 | case v.CanInt(): 134 | return reflect.Int64, v.Int() 135 | case v.CanUint(): 136 | return reflect.Uint64, v.Uint() 137 | case v.CanFloat(): 138 | return reflect.Float64, v.Float() 139 | } 140 | } 141 | 142 | return kind, v.Interface() 143 | } 144 | 145 | func (c *ComparisonSet) Add(v any) { 146 | kind, value := c.Normalize(reflect.ValueOf(v)) 147 | if _, ok := c.z[value]; !ok { 148 | c.z[value] = make(map[reflect.Kind]struct{}) 149 | } 150 | 151 | c.z[value][kind] = struct{}{} 152 | } 153 | 154 | func (c *ComparisonSet) Has(v any) bool { 155 | kind, value := c.Normalize(reflect.ValueOf(v)) 156 | if m, ok := c.z[value]; ok { 157 | _, ok := m[kind] 158 | return ok 159 | } 160 | 161 | return false 162 | } 163 | 164 | func NewComparisonSet(looseNumber bool) *ComparisonSet { 165 | return &ComparisonSet{looseNumber, make(map[any]map[reflect.Kind]struct{})} 166 | } 167 | -------------------------------------------------------------------------------- /converter.go: -------------------------------------------------------------------------------- 1 | package collect 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "golang.org/x/exp/constraints" 7 | "math" 8 | "strconv" 9 | ) 10 | 11 | func StringToNumber[T constraints.Integer | constraints.Float](s string) (result T, _ error) { 12 | switch any(result).(type) { 13 | // Signed 14 | case int: 15 | n, err := strconv.Atoi(s) 16 | return T(n), err 17 | case int8: 18 | n, err := strconv.ParseInt(s, 10, 8) 19 | return T(n), err 20 | case int16: 21 | n, err := strconv.ParseInt(s, 10, 16) 22 | return T(n), err 23 | case int32: 24 | n, err := strconv.ParseInt(s, 10, 32) 25 | return T(n), err 26 | case int64: 27 | n, err := strconv.ParseInt(s, 10, 64) 28 | return T(n), err 29 | 30 | // Unsigned 31 | case uint: 32 | n, err := strconv.ParseUint(s, 10, 0) 33 | return T(n), err 34 | case uint8: 35 | n, err := strconv.ParseUint(s, 10, 8) 36 | return T(n), err 37 | case uint16: 38 | n, err := strconv.ParseUint(s, 10, 16) 39 | return T(n), err 40 | case uint32: 41 | n, err := strconv.ParseUint(s, 10, 32) 42 | return T(n), err 43 | case uint64: 44 | n, err := strconv.ParseUint(s, 10, 64) 45 | return T(n), err 46 | case uintptr: 47 | return result, errors.New("conversion failed") 48 | 49 | // Float 50 | case float32: 51 | n, err := strconv.ParseFloat(s, 32) 52 | return T(n), err 53 | case float64: 54 | n, err := strconv.ParseFloat(s, 64) 55 | return T(n), err 56 | } 57 | 58 | return 59 | } 60 | 61 | func OffsetToIndex(actual, offset int, args ...int) (int, int) { 62 | length := actual 63 | if len(args) >= 1 { 64 | length = args[0] 65 | } 66 | if actual == 0 || (offset == 0 && length < 0) { 67 | return 0, 0 68 | } 69 | 70 | // negative offset 71 | if offset < 0 { 72 | offset += actual 73 | } 74 | if offset >= actual || offset < 0 { 75 | return 0, 0 76 | } else if length == 0 { 77 | return offset, offset 78 | } 79 | 80 | // negative length 81 | if length < 0 { 82 | if offset+length < 0 { 83 | offset, length = 0, offset+1 84 | } else { 85 | offset, length = offset+length+1, -length 86 | } 87 | } 88 | 89 | length = int(math.Min(float64(length), float64(actual-offset))) 90 | return offset, offset + length 91 | } 92 | 93 | func NumberFrom[N constraints.Integer | constraints.Float, T ~[]E, E any](c *SliceCollection[T, E]) *NumberCollection[[]N, N] { 94 | if c.Empty() { 95 | return &NumberCollection[[]N, N]{} 96 | } 97 | 98 | z := c.All() 99 | items := make([]N, len(z), cap(z)) 100 | for key, item := range z { 101 | switch v := (interface{})(item).(type) { 102 | case string: 103 | items[key], _ = StringToNumber[N](v) 104 | default: 105 | items[key], _ = StringToNumber[N](fmt.Sprintf("%d", v)) 106 | } 107 | } 108 | 109 | return UseNumber[[]N, N](items) 110 | } 111 | -------------------------------------------------------------------------------- /functional.go: -------------------------------------------------------------------------------- 1 | package collect 2 | 3 | import ( 4 | "github.com/sxyazi/go-collection/types" 5 | "golang.org/x/exp/constraints" 6 | "math" 7 | "math/rand" 8 | "reflect" 9 | "sort" 10 | "time" 11 | ) 12 | 13 | /** 14 | * Any slice 15 | */ 16 | 17 | func Each[T ~[]E, E any](items T, callback func(value E, index int)) { 18 | for index, value := range items { 19 | callback(value, index) 20 | } 21 | } 22 | 23 | func Same[T ~[]E, E any](items, target T) bool { 24 | if len(items) != len(target) { 25 | return false 26 | } else if len(items) == 0 { 27 | return true 28 | } 29 | 30 | kind := reflect.TypeOf(items).Elem().Kind() 31 | if kind == reflect.Slice { 32 | return reflect.DeepEqual(items, target) 33 | } 34 | 35 | for index, item := range items { 36 | if Compare(item, "!=", target[index]) { 37 | return false 38 | } 39 | } 40 | return true 41 | } 42 | 43 | func First[T ~[]E, E any](items T) (E, bool) { 44 | var value E 45 | if len(items) == 0 { 46 | return value, false 47 | } 48 | 49 | value = items[0] 50 | return value, true 51 | } 52 | 53 | func Last[T ~[]E, E any](items T) (E, bool) { 54 | var value E 55 | if len(items) == 0 { 56 | return value, false 57 | } 58 | 59 | value = items[len(items)-1] 60 | return value, true 61 | } 62 | 63 | func Index[T ~[]E, E any](items T, target E) int { 64 | if len(items) == 0 { 65 | return -1 66 | } 67 | 68 | for index, item := range items { 69 | if Compare(item, "=", target) { 70 | return index 71 | } 72 | } 73 | 74 | return -1 75 | } 76 | 77 | func Contains[T ~[]E, E any](items T, item E) bool { 78 | return Index(items, item) != -1 79 | } 80 | 81 | func Diff[T ~[]E, E any](items, target T) T { 82 | var different T 83 | for _, item := range items { 84 | if Index(target, item) == -1 { 85 | different = append(different, item) 86 | } 87 | } 88 | 89 | return different 90 | } 91 | 92 | func Filter[T ~[]E, E any](items T, callback func(value E, index int) bool) T { 93 | var filtered T 94 | for index, item := range items { 95 | if callback(item, index) { 96 | filtered = append(filtered, item) 97 | } 98 | } 99 | 100 | return filtered 101 | } 102 | 103 | func Map[T ~[]E, E any](items T, callback func(value E, index int) E) T { 104 | mapped := make(T, len(items), cap(items)) 105 | for index, item := range items { 106 | mapped[index] = callback(item, index) 107 | } 108 | 109 | return mapped 110 | } 111 | 112 | func Unique[T ~[]E, E any](items T) T { 113 | if len(items) == 0 { 114 | return items 115 | } 116 | 117 | c := NewComparisonSet(true) 118 | return Filter(items, func(value E, _ int) bool { 119 | if !c.Has(value) { 120 | c.Add(value) 121 | return true 122 | } 123 | return false 124 | }) 125 | } 126 | 127 | func Duplicates[T ~[]E, E any](items T) map[int]E { 128 | m := make(map[int]E) 129 | if len(items) == 0 { 130 | return m 131 | } 132 | 133 | c := NewComparisonSet(true) 134 | for index, item := range items { 135 | if c.Has(item) { 136 | m[index] = item 137 | } else { 138 | c.Add(item) 139 | } 140 | } 141 | return m 142 | } 143 | 144 | func Merge[T ~[]E, E any](items T, targets ...T) T { 145 | for _, target := range targets { 146 | items = append(items, target...) 147 | } 148 | return items 149 | } 150 | 151 | func Random[T ~[]E, E any](items T) (E, bool) { 152 | if len(items) == 0 { 153 | var zero E 154 | return zero, false 155 | } 156 | 157 | rand.Seed(time.Now().UnixNano()) 158 | return items[rand.Intn(len(items))], true 159 | } 160 | 161 | func Reverse[T ~[]E, E any](items T) T { 162 | for i, j := 0, len(items)-1; i < j; i, j = i+1, j-1 { 163 | items[i], items[j] = items[j], items[i] 164 | } 165 | return items 166 | } 167 | 168 | func Shuffle[T ~[]E, E any](items T) T { 169 | rand.Seed(time.Now().UnixNano()) 170 | rand.Shuffle(len(items), func(i, j int) { items[i], items[j] = items[j], items[i] }) 171 | return items 172 | } 173 | 174 | func Slice[T ~[]E, E any](items T, offset int, args ...int) T { 175 | start, end := OffsetToIndex(len(items), offset, args...) 176 | return items[start:end] 177 | } 178 | 179 | func Split[T ~[]E, E any](items T, amount int) []T { 180 | split := make([]T, int(math.Ceil(float64(len(items))/float64(amount)))) 181 | for i, item := range items { 182 | split[i/amount] = append(split[i/amount], item) 183 | } 184 | 185 | return split 186 | } 187 | 188 | func Splice[T ~[]E, E any](items *T, offset int, args ...any) T { 189 | length := len(*items) 190 | if len(args) >= 1 { 191 | length = args[0].(int) 192 | } 193 | 194 | start, end := OffsetToIndex(len(*items), offset, length) 195 | slice := make(T, end-start) 196 | copy(slice, (*items)[start:end]) 197 | 198 | if len(args) < 2 { 199 | *items = append((*items)[:start], (*items)[end:]...) 200 | return slice 201 | } 202 | 203 | var reps T 204 | for _, rep := range args[1:] { 205 | switch v := rep.(type) { 206 | case E: 207 | reps = append(reps, v) 208 | case T: 209 | reps = append(reps, v...) 210 | default: 211 | panic("replacement type error") 212 | } 213 | } 214 | 215 | reps = append(reps, (*items)[end:]...) 216 | *items = append((*items)[:start], reps...) 217 | return slice 218 | } 219 | 220 | func Reduce[T ~[]E, E any](items T, initial E, callback func(carry E, value E, key int) E) E { 221 | for key, value := range items { 222 | initial = callback(initial, value, key) 223 | } 224 | 225 | return initial 226 | } 227 | 228 | func Pop[T ~[]E, E any](items *T) (E, bool) { 229 | l := len(*items) 230 | if l == 0 { 231 | var zero E 232 | return zero, false 233 | } 234 | 235 | value := (*items)[l-1] 236 | *items = append((*items)[:l-1], (*items)[l:]...) 237 | return value, true 238 | } 239 | 240 | func Push[T ~[]E, E any](items *T, item E) T { 241 | *items = append(*items, item) 242 | return *items 243 | } 244 | 245 | func Where[T ~[]E, E any](items T, args ...any) T { 246 | if len(args) < 1 { 247 | return items 248 | } 249 | 250 | // Where(target any) 251 | if len(args) == 1 { 252 | return Filter(items, func(value E, _ int) bool { 253 | return Compare(value, "=", args[0]) 254 | }) 255 | } 256 | 257 | var operator string 258 | var key any = nil 259 | var target any 260 | 261 | // Where(key any, operator string, target any) 262 | if len(args) >= 3 { 263 | key = args[0] 264 | operator = args[1].(string) 265 | target = args[2] 266 | } else { 267 | // Where(operator string, target any) | Where(key any, target any) 268 | switch v := args[0].(type) { 269 | case string: 270 | if Contains([]string{"=", "!=", ">", "<", ">=", "<="}, v) { 271 | operator = v 272 | target = args[1] 273 | } else { 274 | key = v 275 | operator = "=" 276 | target = args[1] 277 | } 278 | default: 279 | key = args[0] 280 | operator = "=" 281 | target = args[1] 282 | } 283 | } 284 | 285 | return Filter[T, E](items, func(value E, _ int) bool { 286 | if key == nil { 287 | return Compare(value, operator, target) 288 | } else if c, err := AnyGet[any](value, key); err == nil { 289 | return Compare(c, operator, target) 290 | } 291 | 292 | return false 293 | }) 294 | } 295 | 296 | func whereIn[T ~[]E, E any](operator string, items T, args ...any) T { 297 | if len(items) == 0 || len(args) == 0 { 298 | return items 299 | } 300 | 301 | var key any = nil 302 | var targets reflect.Value 303 | if len(args) == 1 { 304 | // WhereIn(targets []any) 305 | targets = reflect.ValueOf(args[0]) 306 | } else { 307 | // WhereIn(key any, targets []any) 308 | key = args[0] 309 | targets = reflect.ValueOf(args[1]) 310 | } 311 | 312 | if (targets.Kind() != reflect.Slice && targets.Kind() != reflect.Array) || targets.Len() == 0 { 313 | if operator == "=" { 314 | return make(T, 0) 315 | } else { 316 | return items 317 | } 318 | } 319 | 320 | c := NewComparisonSet(true) 321 | for i := 0; i < targets.Len(); i++ { 322 | c.Add(targets.Index(i).Interface()) 323 | } 324 | 325 | return Filter(items, func(value E, _ int) bool { 326 | if key == nil { 327 | if c.Has(value) { 328 | return operator == "=" 329 | } 330 | } else if v, err := AnyGet[any](value, key); err == nil { 331 | if c.Has(v) { 332 | return operator == "=" 333 | } 334 | } 335 | return operator != "=" 336 | }) 337 | } 338 | 339 | func WhereIn[T ~[]E, E any](items T, args ...any) T { 340 | return whereIn[T, E]("=", items, args...) 341 | } 342 | 343 | func WhereNotIn[T ~[]E, E any](items T, args ...any) T { 344 | return whereIn[T, E]("!=", items, args...) 345 | } 346 | 347 | /** 348 | * Number slice 349 | */ 350 | 351 | func Sum[T ~[]E, E constraints.Integer | constraints.Float](items T) (total E) { 352 | for _, value := range items { 353 | total += value 354 | } 355 | return 356 | } 357 | 358 | func Min[T ~[]E, E constraints.Integer | constraints.Float](items T) E { 359 | if len(items) == 0 { 360 | return 0 361 | } 362 | 363 | min := items[0] 364 | for _, value := range items { 365 | if min > value { 366 | min = value 367 | } 368 | } 369 | 370 | return min 371 | } 372 | 373 | func Max[T ~[]E, E constraints.Integer | constraints.Float](items T) E { 374 | if len(items) == 0 { 375 | return 0 376 | } 377 | 378 | max := items[0] 379 | for _, value := range items { 380 | if max < value { 381 | max = value 382 | } 383 | } 384 | 385 | return max 386 | } 387 | 388 | func Sort[T ~[]E, E constraints.Ordered](items T) T { 389 | sort.Sort(&types.SortableSlice[T, E]{items, false}) 390 | return items 391 | } 392 | 393 | func SortDesc[T ~[]E, E constraints.Ordered](items T) T { 394 | sort.Sort(&types.SortableSlice[T, E]{items, true}) 395 | return items 396 | } 397 | 398 | func Avg[T ~[]E, E constraints.Integer | constraints.Float](items T) float64 { 399 | if len(items) == 0 { 400 | return 0 401 | } 402 | 403 | return float64(Sum[T, E](items)) / float64(len(items)) 404 | } 405 | 406 | func Median[T ~[]E, E constraints.Integer | constraints.Float](items T) float64 { 407 | if len(items) == 0 { 408 | return 0 409 | } 410 | 411 | replica := make(T, len(items)) 412 | copy(replica, items) 413 | Sort[T, E](replica) 414 | 415 | half := len(replica) / 2 416 | if len(replica)%2 != 0 { 417 | return float64(replica[half]) 418 | } 419 | 420 | return float64(replica[half-1]+replica[half]) / 2 421 | } 422 | 423 | /** 424 | * Map 425 | */ 426 | 427 | func Only[T ~map[K]V, K comparable, V any](items T, keys ...K) T { 428 | m := make(T) 429 | for _, key := range keys { 430 | m[key] = items[key] 431 | } 432 | 433 | return m 434 | } 435 | 436 | func Except[T ~map[K]V, K comparable, V any](items T, keys ...K) T { 437 | keysMap := map[K]struct{}{} 438 | for _, key := range keys { 439 | keysMap[key] = struct{}{} 440 | } 441 | 442 | m := make(T) 443 | for key, value := range items { 444 | if _, ok := keysMap[key]; !ok { 445 | m[key] = value 446 | } 447 | } 448 | return m 449 | } 450 | 451 | func Keys[T ~map[K]V, K comparable, V any](items T) (keys []K) { 452 | for key := range items { 453 | keys = append(keys, key) 454 | } 455 | return 456 | } 457 | 458 | func DiffKeys[T ~map[K]V, K comparable, V any](items T, target T) T { 459 | m := make(T) 460 | for key := range items { 461 | if _, ok := target[key]; !ok { 462 | m[key] = items[key] 463 | } 464 | } 465 | 466 | return m 467 | } 468 | 469 | func Has[T ~map[K]V, K comparable, V any](items T, key K) bool { 470 | if _, ok := items[key]; ok { 471 | return true 472 | } else { 473 | return false 474 | } 475 | } 476 | 477 | func Get[T ~map[K]V, K comparable, V any](items T, key K) (value V, _ bool) { 478 | if !Has[T, K, V](items, key) { 479 | return 480 | } 481 | 482 | return items[key], true 483 | } 484 | 485 | func Put[T ~map[K]V, K comparable, V any](items T, key K, value V) T { 486 | items[key] = value 487 | return items 488 | } 489 | 490 | func Pull[T ~map[K]V, K comparable, V any](items T, key K) (value V, _ bool) { 491 | if v, ok := items[key]; ok { 492 | delete(items, key) 493 | return v, true 494 | } 495 | 496 | return 497 | } 498 | 499 | func MapSame[T ~map[K]V, K comparable, V any](items, target T) bool { 500 | if len(items) != len(target) { 501 | return false 502 | } else if len(items) == 0 { 503 | return true 504 | } 505 | 506 | kind := reflect.TypeOf(items).Elem().Kind() 507 | if kind == reflect.Slice { 508 | return reflect.DeepEqual(items, target) 509 | } 510 | 511 | for index, item := range items { 512 | if tv, ok := target[index]; !ok { 513 | return false 514 | } else if Compare(item, "!=", tv) { 515 | return false 516 | } 517 | } 518 | 519 | return true 520 | } 521 | 522 | func MapMerge[T ~map[K]V, K comparable, V any](items T, targets ...T) T { 523 | for _, target := range targets { 524 | for key, value := range target { 525 | items[key] = value 526 | } 527 | } 528 | return items 529 | } 530 | 531 | func Union[T ~map[K]V, K comparable, V any](items T, target T) T { 532 | for key, value := range target { 533 | if _, ok := items[key]; !ok { 534 | items[key] = value 535 | } 536 | } 537 | return items 538 | } 539 | 540 | /** 541 | * Standalone 542 | */ 543 | 544 | func Len(v any) int { 545 | if v == nil { 546 | return -1 547 | } 548 | 549 | switch reflect.TypeOf(v).Kind() { 550 | case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String: 551 | return reflect.ValueOf(v).Len() 552 | default: 553 | return -1 554 | } 555 | } 556 | 557 | func Empty(v any) bool { 558 | return Len(v) == 0 559 | } 560 | 561 | func Count[T ~[]E, E comparable](items T) map[E]int { 562 | times := make(map[E]int) 563 | for _, item := range items { 564 | times[item]++ 565 | } 566 | 567 | return times 568 | } 569 | 570 | func Times[T []E, E any](number int, callback func(number int) E) *SliceCollection[T, E] { 571 | items := make(T, number) 572 | for i := 0; i < number; i++ { 573 | items[i] = callback(i + 1) 574 | } 575 | 576 | return UseSlice[T, E](items) 577 | } 578 | 579 | func sortBy[T ~[]E, E any, C func(item E, index int) R, R constraints.Ordered](items T, desc bool, callback C) *SliceCollection[T, E] { 580 | structs := make([]*types.SortableStruct[R], len(items)) 581 | for index, item := range items { 582 | structs[index] = &types.SortableStruct[R]{callback(item, index), index} 583 | } 584 | 585 | replica := make(T, len(items)) 586 | copy(replica, items) 587 | 588 | sort.Sort(&types.SortableStructs[[]R, R]{structs, desc}) 589 | for index, s := range structs { 590 | items[index] = replica[s.Attached.(int)] 591 | } 592 | 593 | return UseSlice[T, E](items) 594 | } 595 | 596 | func SortBy[T ~[]E, E any, C func(item E, index int) R, R constraints.Ordered](items T, callback C) *SliceCollection[T, E] { 597 | return sortBy[T, E, C, R](items, false, callback) 598 | } 599 | 600 | func SortByDesc[T ~[]E, E any, C func(item E, index int) R, R constraints.Ordered](items T, callback C) *SliceCollection[T, E] { 601 | return sortBy[T, E, C, R](items, true, callback) 602 | } 603 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/sxyazi/go-collection 2 | 3 | go 1.18 4 | 5 | require golang.org/x/exp v0.0.0-20220205015713-f5f519d967d6 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/exp v0.0.0-20220205015713-f5f519d967d6 h1:OoObEk2yqD/yCsmLmK7ZBXY4C5t6FLoHFQH1/+VpMgU= 2 | golang.org/x/exp v0.0.0-20220205015713-f5f519d967d6/go.mod h1:lRnflEfy7nRvpQCcpkwaSP1nkrSyjkyFNcqXKfSXLMc= 3 | -------------------------------------------------------------------------------- /helpers.go: -------------------------------------------------------------------------------- 1 | package collect 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | "strconv" 8 | ) 9 | 10 | func AnyGet[V, K any](item any, key K) (zero V, _ error) { 11 | var result any 12 | ref := reflect.ValueOf(item) 13 | 14 | switch ref.Kind() { 15 | case reflect.Map: 16 | if r := ref.MapIndex(reflect.ValueOf(key)); r.IsValid() { 17 | result = r.Interface() 18 | } else { 19 | return zero, errors.New("invalid map index") 20 | } 21 | case reflect.Array, reflect.Slice: 22 | if index, err := strconv.Atoi(fmt.Sprintf("%v", key)); err != nil { 23 | return zero, err 24 | } else { 25 | if index < 0 || index >= ref.Len() { 26 | return zero, errors.New("index overflow") 27 | } 28 | 29 | result = ref.Index(index).Interface() 30 | } 31 | case reflect.Struct: 32 | if r := ref.FieldByName(fmt.Sprintf("%v", key)); r.IsValid() { 33 | result = r.Interface() 34 | } else { 35 | return zero, errors.New("invalid struct field") 36 | } 37 | case reflect.Pointer: 38 | return AnyGet[V, K](ref.Elem().Interface(), key) 39 | default: 40 | return zero, errors.New("failed to get") 41 | } 42 | 43 | switch result.(type) { 44 | case V: 45 | return result.(V), nil 46 | default: 47 | return zero, errors.New("type mismatch") 48 | } 49 | } 50 | 51 | func Pluck[V, K, I any](items []I, key K) []V { 52 | var zero V 53 | plucked := make([]V, len(items), cap(items)) 54 | 55 | for i, item := range items { 56 | if v, err := AnyGet[V](item, key); err == nil { 57 | plucked[i] = v 58 | } else { 59 | plucked[i] = zero 60 | } 61 | } 62 | 63 | return plucked 64 | } 65 | 66 | func MapPluck[K comparable, V any](items []map[K]V, key K) []V { 67 | var zero V 68 | plucked := make([]V, len(items), cap(items)) 69 | 70 | for i, item := range items { 71 | if v, ok := item[key]; ok { 72 | plucked[i] = v 73 | } else { 74 | plucked[i] = zero 75 | } 76 | } 77 | 78 | return plucked 79 | } 80 | 81 | func KeyBy[V comparable, K, I any](items []I, key K) map[V]I { 82 | result := make(map[V]I) 83 | for _, item := range items { 84 | if v, err := AnyGet[V](item, key); err == nil { 85 | result[v] = item 86 | } 87 | } 88 | return result 89 | } 90 | 91 | func MapKeyBy[K, V comparable](items []map[K]V, key K) map[V]map[K]V { 92 | result := make(map[V]map[K]V) 93 | for _, item := range items { 94 | result[item[key]] = item 95 | } 96 | return result 97 | } 98 | 99 | func GroupBy[V comparable, K, I any](items []I, key K) map[V][]I { 100 | result := make(map[V][]I) 101 | for _, item := range items { 102 | if v, err := AnyGet[V](item, key); err == nil { 103 | result[v] = append(result[v], item) 104 | } 105 | } 106 | return result 107 | } 108 | 109 | func MapGroupBy[K, V comparable](items []map[K]V, key K) map[V][]map[K]V { 110 | result := make(map[V][]map[K]V) 111 | for _, item := range items { 112 | result[item[key]] = append(result[item[key]], item) 113 | } 114 | return result 115 | } 116 | -------------------------------------------------------------------------------- /map.go: -------------------------------------------------------------------------------- 1 | package collect 2 | 3 | import "fmt" 4 | 5 | type MapCollection[T ~map[K]V, K comparable, V any] struct { 6 | z T 7 | } 8 | 9 | func UseMap[T ~map[K]V, K comparable, V any](items T) *MapCollection[T, K, V] { 10 | return &MapCollection[T, K, V]{items} 11 | } 12 | 13 | func (m *MapCollection[T, K, V]) All() T { 14 | return m.z 15 | } 16 | 17 | func (m *MapCollection[T, K, V]) New(items T) *MapCollection[T, K, V] { 18 | return &MapCollection[T, K, V]{items} 19 | } 20 | 21 | func (m *MapCollection[T, K, V]) Len() int { 22 | return len(m.z) 23 | } 24 | 25 | func (m *MapCollection[T, K, V]) Empty() bool { 26 | return len(m.z) == 0 27 | } 28 | 29 | func (m *MapCollection[T, K, V]) Print() *MapCollection[T, K, V] { 30 | fmt.Println(m.z) 31 | return m 32 | } 33 | 34 | func (m *MapCollection[T, K, V]) Only(keys ...K) *MapCollection[T, K, V] { 35 | m.z = Only[T, K, V](m.z, keys...) 36 | return m 37 | } 38 | 39 | func (m *MapCollection[T, K, V]) Except(keys ...K) *MapCollection[T, K, V] { 40 | m.z = Except[T, K, V](m.z, keys...) 41 | return m 42 | } 43 | 44 | func (m *MapCollection[T, K, V]) Keys() []K { 45 | return Keys[T, K, V](m.z) 46 | } 47 | 48 | func (m *MapCollection[T, K, V]) DiffKeys(target T) *MapCollection[T, K, V] { 49 | m.z = DiffKeys[T, K, V](m.z, target) 50 | return m 51 | } 52 | 53 | func (m *MapCollection[T, K, V]) Has(key K) bool { 54 | return Has[T, K, V](m.z, key) 55 | } 56 | 57 | func (m *MapCollection[T, K, V]) Get(key K) (value V, _ bool) { 58 | return Get[T, K, V](m.z, key) 59 | } 60 | 61 | func (m *MapCollection[T, K, V]) Put(key K, value V) *MapCollection[T, K, V] { 62 | Put[T, K, V](m.z, key, value) 63 | return m 64 | } 65 | 66 | func (m *MapCollection[T, K, V]) Pull(key K) (V, bool) { 67 | return Pull[T, K, V](m.z, key) 68 | } 69 | 70 | func (m *MapCollection[T, K, V]) Same(target T) bool { 71 | return MapSame[T, K, V](m.z, target) 72 | } 73 | 74 | func (m *MapCollection[T, K, V]) Merge(targets ...T) *MapCollection[T, K, V] { 75 | m.z = MapMerge[T, K, V](m.z, targets...) 76 | return m 77 | } 78 | 79 | func (m *MapCollection[T, K, V]) Union(target T) *MapCollection[T, K, V] { 80 | return m.New(Union[T, K, V](m.z, target)) 81 | } 82 | -------------------------------------------------------------------------------- /number.go: -------------------------------------------------------------------------------- 1 | package collect 2 | 3 | import ( 4 | "golang.org/x/exp/constraints" 5 | ) 6 | 7 | type NumberCollection[T ~[]E, E constraints.Integer | constraints.Float] struct { 8 | *SliceCollection[T, E] 9 | } 10 | 11 | func UseNumber[T ~[]E, E constraints.Integer | constraints.Float](items T) *NumberCollection[T, E] { 12 | return &NumberCollection[T, E]{UseSlice[T, E](items)} 13 | } 14 | 15 | func (n *NumberCollection[T, E]) Sum() (total E) { 16 | return Sum[T, E](n.All()) 17 | } 18 | 19 | func (n *NumberCollection[T, E]) Min() E { 20 | return Min[T, E](n.All()) 21 | } 22 | 23 | func (n *NumberCollection[T, E]) Max() E { 24 | return Max[T, E](n.All()) 25 | } 26 | 27 | func (n *NumberCollection[T, E]) Sort() *NumberCollection[T, E] { 28 | n.z = Sort[T, E](n.All()) 29 | return n 30 | } 31 | 32 | func (n *NumberCollection[T, E]) SortDesc() *NumberCollection[T, E] { 33 | n.z = SortDesc[T, E](n.All()) 34 | return n 35 | } 36 | 37 | func (n *NumberCollection[T, E]) Avg() float64 { 38 | return Avg[T, E](n.All()) 39 | } 40 | 41 | func (n *NumberCollection[T, E]) Median() float64 { 42 | return Median[T, E](n.All()) 43 | } 44 | -------------------------------------------------------------------------------- /slice.go: -------------------------------------------------------------------------------- 1 | package collect 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type SliceCollection[T ~[]E, E any] struct { 8 | z T 9 | } 10 | 11 | func UseSlice[T ~[]E, E any](items T) *SliceCollection[T, E] { 12 | return &SliceCollection[T, E]{items} 13 | } 14 | 15 | func (s *SliceCollection[T, E]) All() T { 16 | return s.z 17 | } 18 | 19 | func (s *SliceCollection[T, E]) New(items T) *SliceCollection[T, E] { 20 | return &SliceCollection[T, E]{items} 21 | } 22 | 23 | func (s *SliceCollection[T, E]) Len() int { 24 | return len(s.z) 25 | } 26 | 27 | func (s *SliceCollection[T, E]) Empty() bool { 28 | return len(s.z) == 0 29 | } 30 | 31 | func (s *SliceCollection[T, E]) Print() *SliceCollection[T, E] { 32 | fmt.Println(s.z) 33 | return s 34 | } 35 | 36 | func (s *SliceCollection[T, E]) Each(callback func(value E, index int)) *SliceCollection[T, E] { 37 | Each[T, E](s.z, callback) 38 | return s 39 | } 40 | 41 | func (s *SliceCollection[T, E]) Same(target T) bool { 42 | return Same[T, E](s.z, target) 43 | } 44 | 45 | func (s *SliceCollection[T, E]) First() (E, bool) { 46 | return First[T, E](s.z) 47 | } 48 | 49 | func (s *SliceCollection[T, E]) Last() (E, bool) { 50 | return Last[T, E](s.z) 51 | } 52 | 53 | func (s *SliceCollection[T, E]) Index(value E) int { 54 | return Index(s.z, value) 55 | } 56 | 57 | func (s *SliceCollection[T, E]) Contains(value E) bool { 58 | return Contains(s.z, value) 59 | } 60 | 61 | func (s *SliceCollection[T, E]) Diff(target T) *SliceCollection[T, E] { 62 | s.z = Diff[T, E](s.z, target) 63 | return s 64 | } 65 | 66 | func (s *SliceCollection[T, E]) Filter(callback func(value E, index int) bool) *SliceCollection[T, E] { 67 | s.z = Filter(s.z, callback) 68 | return s 69 | } 70 | 71 | func (s *SliceCollection[T, E]) Map(callback func(value E, index int) E) *SliceCollection[T, E] { 72 | s.z = Map(s.z, callback) 73 | return s 74 | } 75 | 76 | func (s *SliceCollection[T, E]) Unique() *SliceCollection[T, E] { 77 | s.z = Unique[T, E](s.z) 78 | return s 79 | } 80 | 81 | func (s *SliceCollection[T, E]) Duplicates() *MapCollection[map[int]E, int, E] { 82 | return UseMap[map[int]E, int, E](Duplicates[T, E](s.z)) 83 | } 84 | 85 | func (s *SliceCollection[T, E]) Merge(targets ...T) *SliceCollection[T, E] { 86 | s.z = Merge[T, E](s.z, targets...) 87 | return s 88 | } 89 | 90 | func (s *SliceCollection[T, E]) Random() (E, bool) { 91 | return Random[T, E](s.z) 92 | } 93 | 94 | func (s *SliceCollection[T, E]) Reverse() *SliceCollection[T, E] { 95 | s.z = Reverse[T, E](s.z) 96 | return s 97 | } 98 | 99 | func (s *SliceCollection[T, E]) Shuffle() *SliceCollection[T, E] { 100 | s.z = Shuffle[T, E](s.z) 101 | return s 102 | } 103 | 104 | func (s *SliceCollection[T, E]) Slice(offset int, length ...int) *SliceCollection[T, E] { 105 | s.z = Slice[T, E](s.z, offset, length...) 106 | return s 107 | } 108 | 109 | func (s *SliceCollection[T, E]) Split(amount int) []T { 110 | return Split[T, E](s.z, amount) 111 | } 112 | 113 | func (s *SliceCollection[T, E]) Splice(offset int, args ...any) *SliceCollection[T, E] { 114 | return s.New(Splice[T, E](&s.z, offset, args...)) 115 | } 116 | 117 | func (s *SliceCollection[T, E]) Reduce(initial E, callback func(carry E, value E, key int) E) E { 118 | return Reduce[T, E](s.z, initial, callback) 119 | } 120 | 121 | func (s *SliceCollection[T, E]) Pop() (E, bool) { 122 | return Pop[T, E](&s.z) 123 | } 124 | 125 | func (s *SliceCollection[T, E]) Push(item E) *SliceCollection[T, E] { 126 | Push[T, E](&s.z, item) 127 | return s 128 | } 129 | 130 | func (s *SliceCollection[T, E]) Where(args ...any) *SliceCollection[T, E] { 131 | s.z = Where[T, E](s.z, args...) 132 | return s 133 | } 134 | 135 | func (s *SliceCollection[T, E]) WhereIn(args ...any) *SliceCollection[T, E] { 136 | s.z = WhereIn[T, E](s.z, args...) 137 | return s 138 | } 139 | 140 | func (s *SliceCollection[T, E]) WhereNotIn(args ...any) *SliceCollection[T, E] { 141 | s.z = WhereNotIn[T, E](s.z, args...) 142 | return s 143 | } 144 | -------------------------------------------------------------------------------- /tests/converter_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | . "github.com/sxyazi/go-collection" 5 | "testing" 6 | ) 7 | 8 | // TODO 9 | func TestStringToNumber(t *testing.T) { 10 | 11 | } 12 | 13 | func TestNumberFrom(t *testing.T) { 14 | c1 := UseSlice([]string{"1", "2", "Hello", "3"}) 15 | if NumberFrom[float64](c1).Avg() != 1.5 { 16 | t.Fail() 17 | } 18 | 19 | c2 := UseSlice([]int32{392, 68, 27, 0}) 20 | if NumberFrom[uint](c2).Avg() != 121.75 { 21 | t.Fail() 22 | } 23 | 24 | c3 := UseSlice([]Foo{{}}) 25 | if NumberFrom[uint](c3).Sum() != 0 { 26 | t.Fail() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/functional_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | . "github.com/sxyazi/go-collection" 5 | "strconv" 6 | "testing" 7 | ) 8 | 9 | func TestFunctional_Len(t *testing.T) { 10 | if Len(nil) != -1 { 11 | t.Fail() 12 | } 13 | 14 | if Len([...]int{}) != 0 { 15 | t.Fail() 16 | } 17 | 18 | c := make(chan int, 10) 19 | c <- 1 20 | if Len(c) != 1 { 21 | t.Fail() 22 | } 23 | 24 | if Len(map[int]bool{1: true, 2: false}) != 2 { 25 | t.Fail() 26 | } 27 | 28 | if Len([]int{1, 2, 3}) != 3 { 29 | t.Fail() 30 | } 31 | 32 | if Len("Hello") != 5 { 33 | t.Fail() 34 | } 35 | 36 | if Len(struct{}{}) != -1 { 37 | t.Fail() 38 | } 39 | } 40 | 41 | func TestFunctional_Empty(t *testing.T) { 42 | if !Empty([]int{}) { 43 | t.Fail() 44 | } 45 | 46 | if Empty([]int{1, 2, 3}) { 47 | t.Fail() 48 | } 49 | } 50 | 51 | func TestFunctional_Count(t *testing.T) { 52 | m := Count([]int{1, 2, 2, 3}) 53 | if m[1] != 1 || m[2] != 2 || m[3] != 1 { 54 | t.Fail() 55 | } 56 | } 57 | 58 | func TestFunctional_Times(t *testing.T) { 59 | if !Times(3, func(number int) float64 { 60 | return float64(number) * 3.14 61 | }).Same([]float64{3.14, 6.28, 9.42}) { 62 | t.Fail() 63 | } 64 | } 65 | 66 | func TestFunctional_SortBy(t *testing.T) { 67 | if !SortBy([]int{2, 1, 3}, func(item, index int) string { 68 | return strconv.Itoa(item) 69 | }).Same([]int{1, 2, 3}) { 70 | t.Fail() 71 | } 72 | } 73 | 74 | func TestFunctional_SortByDesc(t *testing.T) { 75 | if !SortByDesc([]int{2, 1, 3}, func(item, index int) string { 76 | return strconv.Itoa(item) 77 | }).Same([]int{3, 2, 1}) { 78 | t.Fail() 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /tests/helpers_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "encoding/json" 5 | . "github.com/sxyazi/go-collection" 6 | "testing" 7 | ) 8 | 9 | func TestHelpers_AnyGet(t *testing.T) { 10 | // Nil 11 | if _, err := AnyGet[any](nil, ""); err == nil { 12 | t.Fail() 13 | } 14 | 15 | // Struct 16 | user := User{ID: 33, Name: "Lucy"} 17 | if v, err := AnyGet[string](user, "Name"); err != nil || v != "Lucy" { 18 | t.Fail() 19 | } 20 | if v, err := AnyGet[string](&user, "Name"); err != nil || v != "Lucy" { 21 | t.Fail() 22 | } 23 | if v, err := AnyGet[any](&user, "Name"); err != nil || v.(string) != "Lucy" { 24 | t.Fail() 25 | } 26 | 27 | // Slice 28 | users := []*User{&user} 29 | if _, err := AnyGet[any](users, 0); err != nil { 30 | t.Fail() 31 | } 32 | if v, err := AnyGet[*User](users, 0); err != nil || v != &user { 33 | t.Fail() 34 | } 35 | if v, err := AnyGet[*User](users, "0"); err != nil || v != &user { 36 | t.Fail() 37 | } 38 | if _, err := AnyGet[*User](users, 10); err == nil { 39 | t.Fail() 40 | } 41 | 42 | // Array 43 | if v, err := AnyGet[int]([]int{1, 2, 3}, 2); err != nil || v != 3 { 44 | t.Fail() 45 | } 46 | if v, err := AnyGet[int]([3]int{1, 2, 3}, 2); err != nil || v != 3 { 47 | t.Fail() 48 | } 49 | if v, err := AnyGet[int]([3]int{1, 2, 3}, "2"); err != nil || v != 3 { 50 | t.Fail() 51 | } 52 | if v, err := AnyGet[any]([3]int{1, 2, 3}, 2); err != nil || v.(int) != 3 { 53 | t.Fail() 54 | } 55 | 56 | // Interface 57 | var i any 58 | if _, err := AnyGet[any](i, ""); err == nil { 59 | t.Fail() 60 | } 61 | 62 | i = make(map[int]string) 63 | i.(map[int]string)[0] = "Hello" 64 | if _, err := AnyGet[string](i, 1); err == nil { 65 | t.Fail() 66 | } 67 | if v, err := AnyGet[string](i, 0); err != nil || v != "Hello" { 68 | t.Fail() 69 | } 70 | if v, err := AnyGet[any](i, 0); err != nil || v.(string) != "Hello" { 71 | t.Fail() 72 | } 73 | 74 | json.Unmarshal([]byte(`["World"]`), &i) 75 | if _, err := AnyGet[string](i, 1); err == nil { 76 | t.Fail() 77 | } 78 | if v, err := AnyGet[string](i, 0); err != nil || v != "World" { 79 | t.Fail() 80 | } 81 | if v, err := AnyGet[any](i, 0); err != nil || v.(string) != "World" { 82 | t.Fail() 83 | } 84 | } 85 | 86 | func TestHelpers_Pluck(t *testing.T) { 87 | ids := []uint{33, 193} 88 | users := []User{{ID: 33, Name: "Lucy"}, {ID: 193, Name: "Peter"}} 89 | 90 | if !UseSlice(Pluck[uint](users, "ID")).Same(ids) { 91 | t.Fail() 92 | } 93 | } 94 | 95 | func TestHelpers_MapPluck(t *testing.T) { 96 | ids := []uint{33, 193} 97 | s := []map[string]uint{{"ID": 33, "Score": 10}, {"ID": 193, "Score": 6}} 98 | 99 | if !UseSlice(MapPluck(s, "ID")).Same(ids) { 100 | t.Fail() 101 | } 102 | } 103 | 104 | func TestHelpers_KeyBy(t *testing.T) { 105 | users := []User{{ID: 33, Name: "Lucy"}, {ID: 193, Name: "Peter"}, {ID: 194, Name: "Peter"}} 106 | r := KeyBy[string](users, "Name") 107 | if len(r) != 2 { 108 | t.Fail() 109 | } 110 | if r["Lucy"].ID != 33 || r["Peter"].ID != 194 { 111 | t.Fail() 112 | } 113 | } 114 | 115 | func TestHelpers_MapKeyBy(t *testing.T) { 116 | m := []map[string]int{{"ID": 33, "Age": 40}, {"ID": 193, "Age": 25}, {"ID": 194, "Age": 25}} 117 | r := MapKeyBy(m, "Age") 118 | if len(r) != 2 { 119 | t.Fail() 120 | } 121 | if r[40]["ID"] != 33 || r[25]["ID"] != 194 { 122 | t.Fail() 123 | } 124 | } 125 | 126 | func TestHelpers_GroupBy(t *testing.T) { 127 | users := []User{{ID: 33, Name: "Lucy"}, {ID: 193, Name: "Peter"}, {ID: 194, Name: "Lacie"}} 128 | r2 := GroupBy[uint](users, "ID") 129 | if len(r2) != 3 || len(r2[33]) != 1 || len(r2[193]) != 1 || len(r2[194]) != 1 { 130 | t.Fail() 131 | } 132 | if r2[33][0].Name != "Lucy" || r2[193][0].Name != "Peter" || r2[194][0].Name != "Lacie" { 133 | t.Fail() 134 | } 135 | } 136 | 137 | func TestHelpers_MapGroupBy(t *testing.T) { 138 | m := []map[string]int{{"ID": 33, "Age": 40}, {"ID": 193, "Age": 25}, {"ID": 194, "Age": 25}} 139 | r := MapGroupBy(m, "Age") 140 | if len(r) != 2 || len(r[40]) != 1 || len(r[25]) != 2 { 141 | t.Fail() 142 | } 143 | if r[40][0]["ID"] != 33 || r[25][0]["ID"] != 193 || r[25][1]["ID"] != 194 { 144 | t.Fail() 145 | } 146 | } 147 | 148 | func TestHelper_IsNumber(t *testing.T) { 149 | var d1 int = 123 150 | if !IsNumber(d1) { 151 | t.Fail() 152 | } 153 | 154 | var d2 int32 = 123 155 | if !IsNumber(d2) { 156 | t.Fail() 157 | } 158 | 159 | var d3 float32 = 3.14 160 | if !IsNumber(d3) { 161 | t.Fail() 162 | } 163 | 164 | var d4 float64 = 2.71 165 | if !IsNumber(d4) { 166 | t.Fail() 167 | } 168 | 169 | d5 := true 170 | if IsNumber(d5) { 171 | t.Fail() 172 | } 173 | 174 | d6 := []int{1, 2, 3} 175 | if IsNumber(d6) { 176 | t.Fail() 177 | } 178 | } 179 | 180 | func TestHelper_NumberCompare(t *testing.T) { 181 | if !NumberCompare(10.3, "=", 10.3) { 182 | t.Fail() 183 | } 184 | if NumberCompare(10.3, "!=", 10.3) { 185 | t.Fail() 186 | } 187 | if !NumberCompare(10.3, ">", 4.7) { 188 | t.Fail() 189 | } 190 | if !NumberCompare(10.3, "<", 20.5) { 191 | t.Fail() 192 | } 193 | if !NumberCompare(10.3, ">=", 10.3) { 194 | t.Fail() 195 | } 196 | if !NumberCompare(10.3, "<=", 10.3) { 197 | t.Fail() 198 | } 199 | } 200 | 201 | func TestHelper_AnyNumberCompare(t *testing.T) { 202 | if !AnyNumberCompare(10.3, "=", 10.3) { 203 | t.Fail() 204 | } 205 | if AnyNumberCompare(10.3, "=", 10) { 206 | t.Fail() 207 | } 208 | if AnyNumberCompare(10, "=", 10.3) { 209 | t.Fail() 210 | } 211 | if !AnyNumberCompare(10.0, "!=", 10) { 212 | t.Fail() 213 | } 214 | if !AnyNumberCompare(10, "!=", 10.0) { 215 | t.Fail() 216 | } 217 | if AnyNumberCompare(10.1, "=", 10.0) { 218 | t.Fail() 219 | } 220 | 221 | if !AnyNumberCompare(10.3, ">", 4.7) { 222 | t.Fail() 223 | } 224 | if !AnyNumberCompare(10.3, "<", 20.5) { 225 | t.Fail() 226 | } 227 | if !AnyNumberCompare(10.3, ">=", 10.3) { 228 | t.Fail() 229 | } 230 | if !AnyNumberCompare(10.3, "<=", 10.3) { 231 | t.Fail() 232 | } 233 | 234 | } 235 | 236 | func TestHelpers_Compare(t *testing.T) { 237 | // Nil 238 | if !Compare(nil, "=", nil) { 239 | t.Fail() 240 | } 241 | if Compare(nil, "!=", nil) { 242 | t.Fail() 243 | } 244 | if Compare(nil, ">", nil) { 245 | t.Fail() 246 | } 247 | 248 | // Slice 249 | d1 := []int{1, 2, 3} 250 | if Compare(d1, "!=", d1) { 251 | t.Fail() 252 | } 253 | if Compare(d1, "=", []int{4, 5, 6}) { 254 | t.Fail() 255 | } 256 | 257 | // Array 258 | d2 := [...]int{1, 2, 3} 259 | if Compare(d2, "!=", d2) { 260 | t.Fail() 261 | } 262 | if Compare(d2, "=", []int{4, 5, 6}) { 263 | t.Fail() 264 | } 265 | if Compare(d2, "=", [...]int{4, 5, 6}) { 266 | t.Fail() 267 | } 268 | 269 | // Channel 270 | d3 := make(chan int) 271 | if Compare(d3, "!=", d3) { 272 | t.Fail() 273 | } 274 | if Compare(d3, "=", false) { 275 | t.Fail() 276 | } 277 | if Compare(d3, "=", make(chan int)) { 278 | t.Fail() 279 | } 280 | 281 | // Function 282 | d4 := func() {} 283 | if Compare(d4, "!=", d4) { 284 | t.Fail() 285 | } 286 | if Compare(d4, "=", false) { 287 | t.Fail() 288 | } 289 | if Compare(d4, "=", func() {}) { 290 | t.Fail() 291 | } 292 | 293 | // Interface 294 | d5 := interface{}(3.14) 295 | if Compare(d5, "!=", d5) { 296 | t.Fail() 297 | } 298 | if Compare(d5, "=", false) { 299 | t.Fail() 300 | } 301 | if Compare(d5, "!=", 3.14) { 302 | t.Fail() 303 | } 304 | 305 | // Map 306 | d6 := map[string]string{"name": "Lucy"} 307 | if Compare(d6, "!=", d6) { 308 | t.Fail() 309 | } 310 | if Compare(d6, "=", nil) { 311 | t.Fail() 312 | } 313 | if !Compare(d6, "!=", nil) { 314 | t.Fail() 315 | } 316 | 317 | // Struct 318 | d7 := struct{ Name string }{"Lucy"} 319 | if Compare(d7, "!=", d7) { 320 | t.Fail() 321 | } 322 | if Compare(d7, "=", nil) { 323 | t.Fail() 324 | } 325 | if !Compare(d7, "!=", nil) { 326 | t.Fail() 327 | } 328 | 329 | // Pointer 330 | d8 := &Foo{Bar: "abc"} 331 | if Compare(d8, "!=", d8) { 332 | t.Fail() 333 | } 334 | if Compare(d8, "=", &d8) { 335 | t.Fail() 336 | } 337 | if !Compare(d8, "!=", true) { 338 | t.Fail() 339 | } 340 | if Compare(d8, "=", nil) { 341 | t.Fail() 342 | } 343 | if !Compare(d8, "!=", nil) { 344 | t.Fail() 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /tests/map_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | . "github.com/sxyazi/go-collection" 5 | "testing" 6 | ) 7 | 8 | func TestMap_All(t *testing.T) { 9 | d := map[string]int{"foo": 1, "bar": 0} 10 | 11 | items := UseMap(d).All() 12 | if !UseMap(items).Same(d) { 13 | t.Fail() 14 | } 15 | } 16 | 17 | func TestMap_New(t *testing.T) { 18 | d1 := map[string]int{"foo": 1, "bar": 0} 19 | d2 := map[string]int{"foo": 1, "bar": 0} 20 | 21 | m1 := UseMap(d1) 22 | m2 := m1.New(d2) 23 | 24 | m2.Put("bar", 100) 25 | if !m1.Same(d1) || m2.Same(d1) { 26 | t.Fail() 27 | } 28 | } 29 | 30 | func TestMap_Len(t *testing.T) { 31 | d1 := map[string]int{"foo": 1, "bar": 0} 32 | if UseMap(d1).Len() != 2 { 33 | t.Fail() 34 | } 35 | 36 | d2 := map[string]int{} 37 | if UseMap(d2).Len() != 0 { 38 | t.Fail() 39 | } 40 | } 41 | 42 | func TestMap_Empty(t *testing.T) { 43 | d1 := map[string]int{"foo": 1, "bar": 0} 44 | if UseMap(d1).Empty() { 45 | t.Fail() 46 | } 47 | 48 | d2 := map[string]int{} 49 | if !UseMap(d2).Empty() { 50 | t.Fail() 51 | } 52 | } 53 | 54 | func TestMap_Only(t *testing.T) { 55 | d1 := map[string]int{"foo": 1, "bar": 0} 56 | if !UseMap(d1).Only("foo", "bar").Same(d1) { 57 | t.Fail() 58 | } 59 | if !UseMap(d1).Only("bar").Same(map[string]int{"bar": 0}) { 60 | t.Fail() 61 | } 62 | 63 | d2 := map[string]int{} 64 | if UseMap(d2).Only("foo", "bar").Same(d2) { 65 | t.Fail() 66 | } 67 | } 68 | 69 | func TestMap_Except(t *testing.T) { 70 | d1 := map[string]int{"foo": 1, "bar": 0} 71 | if !UseMap(d1).Except("foo", "bar").Empty() { 72 | t.Fail() 73 | } 74 | if !UseMap(d1).Except("bar").Same(map[string]int{"foo": 1}) { 75 | t.Fail() 76 | } 77 | 78 | d2 := map[string]int{} 79 | if !UseMap(d2).Except("foo", "bar").Same(d2) { 80 | t.Fail() 81 | } 82 | } 83 | 84 | func TestMap_Keys(t *testing.T) { 85 | d1 := map[string]int{"foo": 1, "bar": 0, "baz": 2} 86 | c := UseSlice(UseMap(d1).Keys()) 87 | if c.Len() != 3 || !c.Contains("foo") || !c.Contains("bar") || !c.Contains("baz") { 88 | t.Fail() 89 | } 90 | 91 | d2 := map[float64]int{} 92 | if !UseSlice(UseMap(d2).Keys()).Same([]float64{}) { 93 | t.Fail() 94 | } 95 | } 96 | 97 | func TestMap_DiffKeys(t *testing.T) { 98 | d1 := map[string]int{"foo": 1, "bar": 0, "baz": 2} 99 | d2 := map[string]int{"foo": 1, "bar": 0} 100 | if !UseMap(d1).DiffKeys(d2).Same(map[string]int{"baz": 2}) { 101 | t.Fail() 102 | } 103 | 104 | d3 := map[string]int{} 105 | if !UseMap(d1).DiffKeys(d3).Same(d1) { 106 | t.Fail() 107 | } 108 | } 109 | 110 | func TestMap_Has(t *testing.T) { 111 | d1 := map[string]int{"foo": 1, "bar": 0} 112 | if !UseMap(d1).Has("foo") { 113 | t.Fail() 114 | } 115 | if UseMap(d1).Has("baz") { 116 | t.Fail() 117 | } 118 | 119 | d2 := map[string]int{} 120 | if UseMap(d2).Has("foo") { 121 | t.Fail() 122 | } 123 | } 124 | 125 | func TestMap_Get(t *testing.T) { 126 | d1 := map[string]int{"foo": 1, "bar": 0} 127 | if v, ok := UseMap(d1).Get("foo"); !ok || v != 1 { 128 | t.Fail() 129 | } 130 | if v, ok := UseMap(d1).Get("baz"); ok || v != 0 { 131 | t.Fail() 132 | } 133 | 134 | d2 := map[string]int{} 135 | if v, ok := UseMap(d2).Get("foo"); ok || v != 0 { 136 | t.Fail() 137 | } 138 | } 139 | 140 | func TestMap_Put(t *testing.T) { 141 | d1 := map[string]int{"foo": 1} 142 | 143 | UseMap(d1).Put("bar", 20) 144 | if !UseMap(d1).Same(map[string]int{"foo": 1, "bar": 20}) { 145 | t.Fail() 146 | } 147 | 148 | // Functional test 149 | d2 := map[string]int{"foo": 1} 150 | Put(d2, "bar", 20) 151 | if !UseMap(d2).Same(map[string]int{"foo": 1, "bar": 20}) { 152 | t.Fail() 153 | } 154 | } 155 | 156 | func TestMap_Pull(t *testing.T) { 157 | d1 := map[string]int{"foo": 1, "bar": 2} 158 | c1 := UseMap(d1) 159 | if v, ok := c1.Pull("bar"); !ok || v != 2 { 160 | t.Fail() 161 | } 162 | if v, ok := c1.Pull("bar"); ok || v != 0 { 163 | t.Fail() 164 | } 165 | if !c1.Same(map[string]int{"foo": 1}) { 166 | t.Fail() 167 | } 168 | if !UseMap(d1).Same(map[string]int{"foo": 1}) { 169 | t.Fail() 170 | } 171 | 172 | // Functional test 173 | d2 := map[string]int{"foo": 1, "bar": 2} 174 | if v, ok := Pull(d2, "bar"); !ok || v != 2 { 175 | t.Fail() 176 | } 177 | if !UseMap(d2).Same(map[string]int{"foo": 1}) { 178 | t.Fail() 179 | } 180 | } 181 | 182 | func TestMap_Same(t *testing.T) { 183 | if !UseMap[map[int]int, int, int](nil).Same(nil) { 184 | t.Fail() 185 | } 186 | 187 | d1 := map[string]int{"foo": 1, "bar": 0} 188 | d2 := map[string]int{"foo": 1, "bar": 0} 189 | if !UseMap(d1).Same(d1) { 190 | t.Fail() 191 | } 192 | if !UseMap(d1).Same(d2) { 193 | t.Fail() 194 | } 195 | 196 | if !UseMap(map[bool]struct{}{}).Same(map[bool]struct{}{}) { 197 | t.Fail() 198 | } 199 | 200 | d3 := map[string]Foo{"foo": {Bar: "aaa"}, "bar": {Bar: "bbb"}} 201 | d4 := map[string]Foo{"foo": {Bar: "aaa"}, "bar": {Bar: "bbb"}} 202 | if !UseMap(d3).Same(d3) { 203 | t.Fail() 204 | } 205 | if !UseMap(d3).Same(d4) { 206 | t.Fail() 207 | } 208 | 209 | UseMap(d4).Put("bar", Foo{Bar: "ccc"}) 210 | if UseMap(d3).Same(d4) { 211 | t.Fail() 212 | } 213 | 214 | UseMap(d4).Put("bar", Foo{Bar: "bbb"}) 215 | if !UseMap(d3).Same(d4) { 216 | t.Fail() 217 | } 218 | } 219 | 220 | func TestMap_Merge(t *testing.T) { 221 | d1 := map[string]int{"a": 1, "b": 2, "c": 3} 222 | d2 := map[string]int{"c": 33} 223 | d3 := map[string]int{"c": 333, "d": 444} 224 | 225 | if !UseMap(d1).Merge().Same(d1) { 226 | t.Fail() 227 | } 228 | if !UseMap(d1).Merge(d2).Same(map[string]int{"a": 1, "b": 2, "c": 33}) { 229 | t.Fail() 230 | } 231 | if !UseMap(d1).Merge(d2, d3).Same(map[string]int{"a": 1, "b": 2, "c": 333, "d": 444}) { 232 | t.Fail() 233 | } 234 | } 235 | 236 | func TestMap_Union(t *testing.T) { 237 | d1 := map[string]int{"a": 1, "b": 2, "c": 3} 238 | d2 := map[string]int{"b": 22, "d": 44} 239 | 240 | if !UseMap(d1).Union(d2).Same(map[string]int{"a": 1, "b": 2, "c": 3, "d": 44}) { 241 | t.Fail() 242 | } 243 | 244 | f1 := Foo{Bar: "foo1"} 245 | f2 := Foo{Bar: "foo2"} 246 | f3 := Foo{Bar: "foo3"} 247 | 248 | d3 := map[Foo]int{f1: 1, f2: 2} 249 | d4 := map[Foo]int{f1: 11, f3: 33} 250 | if !UseMap(d3).Union(d4).Same(map[Foo]int{f1: 1, f2: 2, f3: 33}) { 251 | t.Fail() 252 | } 253 | 254 | d5 := map[*Foo]int{&f1: 1, &f2: 2} 255 | d6 := map[*Foo]int{&f1: 11, &f3: 33} 256 | if !UseMap(d5).Union(d6).Same(map[*Foo]int{&f1: 1, &f2: 2, &f3: 33}) { 257 | t.Fail() 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /tests/number_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | . "github.com/sxyazi/go-collection" 5 | "math" 6 | "testing" 7 | ) 8 | 9 | func TestNumber_Sum(t *testing.T) { 10 | d := []float64{0, 1.1, 2.2, 3.3, 4.4, 5.5} 11 | if UseNumber(d).Sum() != 16.5 { 12 | t.Fail() 13 | } 14 | } 15 | 16 | func TestNumber_Min(t *testing.T) { 17 | d := []float64{392, 17, 65, 0, 59, 33, -4} 18 | if UseNumber(d).Min() != -4 { 19 | t.Fail() 20 | } 21 | } 22 | 23 | func TestNumber_Max(t *testing.T) { 24 | d := []float64{392, 17, 65, 0, 59, 33, -4} 25 | if UseNumber(d).Max() != 392 { 26 | t.Fail() 27 | } 28 | } 29 | 30 | func TestNumber_Sort(t *testing.T) { 31 | d1 := []float64{0, 17.5, -4.01, 0.2, 59, 33, -4} 32 | if !UseNumber(d1).Sort().Same([]float64{-4.01, -4, 0, 0.2, 17.5, 33, 59}) { 33 | t.Fail() 34 | } 35 | 36 | d2 := []int{392, 17, 65, 0, 59, 33, -4} 37 | if !UseNumber(d2).Sort().Same([]int{-4, 0, 17, 33, 59, 65, 392}) { 38 | t.Fail() 39 | } 40 | 41 | d3 := []float64{0, math.NaN(), 17.5, math.NaN(), -4.01} 42 | if !UseNumber(d3).Sort().Same([]float64{math.NaN(), math.NaN(), -4.01, 0, 17.5}) { 43 | t.Fail() 44 | } 45 | } 46 | 47 | func TestNumber_SortDesc(t *testing.T) { 48 | d1 := []float64{0, 17.5, -4.01, 0.2, 59, 33, -4} 49 | if !UseNumber(d1).SortDesc().Same([]float64{59, 33, 17.5, 0.2, 0, -4, -4.01}) { 50 | t.Fail() 51 | } 52 | 53 | d2 := []int{392, 17, 65, 0, 59, 33, -4} 54 | if !UseNumber(d2).SortDesc().Same([]int{392, 65, 59, 33, 17, 0, -4}) { 55 | t.Fail() 56 | } 57 | 58 | d3 := []float64{0, math.NaN(), 17.5, math.NaN(), -4.01} 59 | if !UseNumber(d3).SortDesc().Same([]float64{17.5, 0, -4.01, math.NaN(), math.NaN()}) { 60 | t.Fail() 61 | } 62 | } 63 | 64 | func TestNumber_Avg(t *testing.T) { 65 | d := []float64{0, 1.1, 2.2, 3.3, 4.4, 5.5} 66 | if UseNumber(d).Avg() != 2.75 { 67 | t.Fail() 68 | } 69 | } 70 | 71 | func TestNumber_Median(t *testing.T) { 72 | if UseNumber([]int{1, 2, 3}).Median() != 2 { 73 | t.Fail() 74 | } 75 | if UseNumber([]int{1, 2, 3, 4}).Median() != 2.5 { 76 | t.Fail() 77 | } 78 | 79 | d1 := []float64{392, 17, 65.2, 0, 59, 33.33, -4} 80 | if UseNumber(d1).Median() != 33.33 { 81 | t.Fail() 82 | } 83 | 84 | d2 := []float64{392, 17, 65.2, 0, 33.33, -4} 85 | if UseNumber(d2).Median() != 25.165 { 86 | t.Fail() 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /tests/slice_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | . "github.com/sxyazi/go-collection" 5 | "math" 6 | "testing" 7 | ) 8 | 9 | func TestSlice_All(t *testing.T) { 10 | d := []int{1, 2, 3} 11 | 12 | items := UseSlice(d).All() 13 | if !UseSlice(items).Same(d) { 14 | t.Fail() 15 | } 16 | } 17 | 18 | func TestSlice_Len(t *testing.T) { 19 | var d1 []int 20 | if UseSlice(d1).Len() != 0 { 21 | t.Fail() 22 | } 23 | 24 | d2 := [3]int{1, 2, 3} 25 | if UseSlice(d2[:]).Len() != 3 { 26 | t.Fail() 27 | } 28 | } 29 | 30 | func TestSlice_Each(t *testing.T) { 31 | data := []float64{0, 2.71, 3.14} 32 | result := []float64{0, 0, 0} 33 | 34 | if !UseSlice(data).Each(func(value float64, index int) { 35 | result[index] = value 36 | }).Same(result) { 37 | t.Fail() 38 | } 39 | } 40 | 41 | func TestSlice_Empty(t *testing.T) { 42 | if !UseSlice([]int{}).Empty() { 43 | t.Fail() 44 | } 45 | 46 | if UseSlice([]float64{0, 2.71, 3.14}).Empty() { 47 | t.Fail() 48 | } 49 | } 50 | 51 | func TestSlice_Same(t *testing.T) { 52 | if !UseSlice[[]int, int](nil).Same(nil) { 53 | t.Fail() 54 | } 55 | 56 | if !UseSlice([]int{1, 2, 3}).Same([]int{1, 2, 3}) { 57 | t.Fail() 58 | } 59 | if UseSlice([]int{1, 2, 3}).Same([]int{1, 3}) { 60 | t.Fail() 61 | } 62 | if !UseSlice([]int{}).Same([]int{}) { 63 | t.Fail() 64 | } 65 | 66 | f1 := Foo{} 67 | f2 := Foo{} 68 | if !UseSlice([]Foo{f1, f2}).Same([]Foo{f2, f1}) { 69 | t.Fail() 70 | } 71 | if UseSlice([]*Foo{&f1, &f2}).Same([]*Foo{&f2, &f1}) { 72 | t.Fail() 73 | } 74 | 75 | s1 := []int{1, 2, 3} 76 | s2 := []int{1, 2, 3} 77 | s3 := []int{3, 2, 1} 78 | s4 := [][]int{s1, s2} 79 | if !UseSlice(s4).Same(s4) { 80 | t.Fail() 81 | } 82 | if !UseSlice(s4).Same([][]int{s2, s1}) { 83 | t.Fail() 84 | } 85 | if UseSlice(s4).Same([][]int{s1, s3}) { 86 | t.Fail() 87 | } 88 | 89 | if !UseSlice([]float64{math.NaN(), math.NaN()}).Same([]float64{math.NaN(), math.NaN()}) { 90 | t.Fail() 91 | } 92 | if UseSlice([]float64{math.NaN(), math.NaN()}).Same([]float64{0, math.NaN()}) { 93 | t.Fail() 94 | } 95 | } 96 | 97 | func TestSlice_First(t *testing.T) { 98 | data := []float64{32, 2.71, 3.14} 99 | 100 | if v, ok := UseSlice(data).First(); !ok || v != 32 { 101 | t.Fail() 102 | } 103 | } 104 | 105 | func TestSlice_Last(t *testing.T) { 106 | data := []float64{32, 2.71, 3.14} 107 | 108 | if v, ok := UseSlice(data).Last(); !ok || v != 3.14 { 109 | t.Fail() 110 | } 111 | } 112 | 113 | func TestSlice_Index(t *testing.T) { 114 | // Nil 115 | if v := UseSlice[[]int, int](nil).Index(1); v != -1 { 116 | t.Fail() 117 | } 118 | 119 | // Integer 120 | d1 := []int{1, 2, 3} 121 | if v := UseSlice(d1).Index(2); v != 1 { 122 | t.Fail() 123 | } 124 | if v := UseSlice(d1).Index(10); v != -1 { 125 | t.Fail() 126 | } 127 | 128 | // Float 129 | d2 := []float64{32, 2.71, 3.14} 130 | if v := UseSlice(d2).Index(2.71); v != 1 { 131 | t.Fail() 132 | } 133 | 134 | // String 135 | d3 := []string{"a", "b", "c"} 136 | if v := UseSlice(d3).Index("d"); v != -1 { 137 | t.Fail() 138 | } 139 | 140 | // Struct 141 | f1 := Foo{} 142 | f2 := Foo{Bar: "b"} 143 | d4 := []Foo{{Bar: "xx"}, f1, f2} 144 | if v := UseSlice(d4).Index(f2); v != 2 { 145 | t.Fail() 146 | } 147 | if v := UseSlice(d4).Index(Foo{}); v != 1 { 148 | t.Fail() 149 | } 150 | 151 | // Nested slice 152 | s1 := []int{1, 2, 3} 153 | s2 := []int{4, 5, 6} 154 | d5 := [][]int{s1, s2} 155 | if v := UseSlice(d5).Index(s2); v != 1 { 156 | t.Fail() 157 | } 158 | if v := UseSlice(d5).Index([]int{4, 5, 6}); v != -1 { 159 | t.Fail() 160 | } 161 | } 162 | 163 | func TestSlice_Contains(t *testing.T) { 164 | // Integer 165 | d1 := []int{1, 2, 3} 166 | if !UseSlice(d1).Contains(1) { 167 | t.Fail() 168 | } 169 | 170 | // Float 171 | d2 := []float64{32, 2.71, 3.14} 172 | if !UseSlice(d2).Contains(2.71) { 173 | t.Fail() 174 | } 175 | 176 | // String 177 | d3 := []string{"a", "b", "c"} 178 | if !UseSlice(d3).Contains("a") { 179 | t.Fail() 180 | } 181 | 182 | // Struct 183 | d4 := []Foo{{Bar: "xx"}, {Bar: "b"}, {Bar: "c"}} 184 | if !UseSlice(d4).Contains(Foo{Bar: "b"}) { 185 | t.Fail() 186 | } 187 | 188 | // Nested slice 189 | s1 := []int{1, 2, 3} 190 | s2 := []int{4, 5, 6} 191 | d5 := [][]int{s1, s2} 192 | if !UseSlice(d5).Contains(s1) { 193 | t.Fail() 194 | } 195 | if UseSlice(d5).Contains([]int{1, 2, 3}) { 196 | t.Fail() 197 | } 198 | } 199 | 200 | func TestSlice_Diff(t *testing.T) { 201 | d1 := []int{1, 2, 3, 4, 5} 202 | d2 := []int{2, 4, 6, 8} 203 | if !UseSlice(d1).Diff(d2).Same([]int{1, 3, 5}) { 204 | t.Fail() 205 | } 206 | } 207 | 208 | func TestSlice_Filter(t *testing.T) { 209 | d1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} 210 | if !UseSlice(d1).Filter(func(value int, index int) bool { 211 | return value%2 == 0 212 | }).Same([]int{2, 4, 6, 8}) { 213 | t.Fail() 214 | } 215 | } 216 | 217 | func TestSlice_Map(t *testing.T) { 218 | d1 := []float64{0, 2, 3, 4, 5} 219 | if !UseSlice(d1).Map(func(value float64, index int) float64 { 220 | return value * 3.14 221 | }).Same([]float64{0, 6.28, 9.42, 12.56, 15.7}) { 222 | t.Fail() 223 | } 224 | } 225 | 226 | func TestSlice_Unique(t *testing.T) { 227 | if !UseSlice([]int{1, 2, 2, 3}).Unique().Same([]int{1, 2, 3}) { 228 | t.Fail() 229 | } 230 | 231 | s1, s2 := []int{1, 2, 3}, []int{4, 5, 6} 232 | if !UseSlice([][]int{s1, s2, s1}).Unique().Same([][]int{s1, s2}) { 233 | t.Fail() 234 | } 235 | if !UseSlice([][]int{s1, nil, s2, nil, s1}).Unique().Same([][]int{s1, nil, s2}) { 236 | t.Fail() 237 | } 238 | if !UseSlice([]*[]int{&s1, &s2, &s1}).Unique().Same([]*[]int{&s1, &s2}) { 239 | t.Fail() 240 | } 241 | 242 | s3, s4 := &[]int{1, 2, 3}, &[]int{4, 5, 6} 243 | s5 := (*s4)[:2] 244 | if !UseSlice([]*[]int{s3, s4, &s5}).Unique().Same([]*[]int{s3, s4, &s5}) { 245 | t.Fail() 246 | } 247 | 248 | if !UseSlice([]any{s1, s1, s2, s3, s3, s4, s5, &s5}).Unique().Same([]any{s1, s2, s3, s4, s5, &s5}) { 249 | t.Fail() 250 | } 251 | } 252 | 253 | func TestSlice_Duplicates(t *testing.T) { 254 | if !UseSlice([]int{1, 2, 2, 3}).Duplicates().Same(map[int]int{2: 2}) { 255 | t.Fail() 256 | } 257 | if !UseSlice([]string{"a", "b", "a", "c"}).Duplicates().Same(map[int]string{2: "a"}) { 258 | t.Fail() 259 | } 260 | 261 | s1, s2 := []int{1, 2, 3}, []int{4, 5, 6} 262 | if !UseSlice([][]int{s1, s2, s1}).Duplicates().Same(map[int][]int{2: s1}) { 263 | t.Fail() 264 | } 265 | if !UseSlice([][]int{s1, nil, s2, nil, s1}).Duplicates().Same(map[int][]int{3: nil, 4: s1}) { 266 | t.Fail() 267 | } 268 | if !UseSlice([]*[]int{&s1, &s2, &s1}).Duplicates().Same(map[int]*[]int{2: &s1}) { 269 | t.Fail() 270 | } 271 | 272 | s3, s4 := &[]int{1, 2, 3}, &[]int{4, 5, 6} 273 | s5 := (*s4)[:2] 274 | if !UseSlice([]*[]int{s3, s4, &s5}).Duplicates().Same(map[int]*[]int{}) { 275 | t.Fail() 276 | } 277 | 278 | if !UseSlice([]any{s1, s1, s2, s3, s3, s4, s5, &s5}).Duplicates().Same(map[int]any{1: s1, 4: s3}) { 279 | t.Fail() 280 | } 281 | } 282 | 283 | func TestSlice_Merge(t *testing.T) { 284 | if !UseSlice([]int{1, 2}).Merge([]int{3, 4}).Same([]int{1, 2, 3, 4}) { 285 | t.Fail() 286 | } 287 | if !UseSlice([]int{1, 2}).Merge([]int{3, 4}, []int{5, 6}).Same([]int{1, 2, 3, 4, 5, 6}) { 288 | t.Fail() 289 | } 290 | } 291 | 292 | func TestSlice_Random(t *testing.T) { 293 | if v, ok := UseSlice([]int{}).Random(); ok || v != 0 { 294 | t.Fail() 295 | } 296 | if v, ok := UseSlice([]int{1}).Random(); !ok || v == 0 { 297 | t.Fail() 298 | } 299 | } 300 | 301 | func TestSlice_Reverse(t *testing.T) { 302 | if !UseSlice([]int{1, 2}).Reverse().Same([]int{2, 1}) { 303 | t.Fail() 304 | } 305 | if UseSlice([]any{}).Reverse().Len() != 0 { 306 | t.Fail() 307 | } 308 | } 309 | 310 | func TestSlice_Shuffle(t *testing.T) { 311 | if UseSlice([]any{}).Shuffle().Len() != 0 { 312 | t.Fail() 313 | } 314 | if v, ok := UseSlice([]int{1}).Shuffle().First(); !ok || v != 1 { 315 | t.Fail() 316 | } 317 | 318 | s1 := UseSlice([]int{1, 2}).Shuffle().All() 319 | if !UseSlice(s1).Same([]int{1, 2}) && !UseSlice(s1).Same([]int{2, 1}) { 320 | t.Fail() 321 | } 322 | } 323 | 324 | func TestSlice_Slice(t *testing.T) { 325 | d := []int{1, 2, 3, 4} 326 | 327 | // Normal 328 | if !UseSlice(d).Slice(0, 0).Same([]int{}) { 329 | t.Fail() 330 | } 331 | if !UseSlice(d).Slice(0, 2).Same([]int{1, 2}) { 332 | t.Fail() 333 | } 334 | if !UseSlice(d).Slice(2, 2).Same([]int{3, 4}) { 335 | t.Fail() 336 | } 337 | if !UseSlice(d).Slice(0, 4).Same([]int{1, 2, 3, 4}) { 338 | t.Fail() 339 | } 340 | 341 | // Offset out of range 342 | if !UseSlice(d).Slice(4, 0).Same([]int{}) { 343 | t.Fail() 344 | } 345 | if !UseSlice(d).Slice(4, 2).Same([]int{}) { 346 | t.Fail() 347 | } 348 | 349 | // (offset + length) out of range 350 | if !UseSlice(d).Slice(3, 2).Same([]int{4}) { 351 | t.Fail() 352 | } 353 | if !UseSlice(d).Slice(0, 5).Same([]int{1, 2, 3, 4}) { 354 | t.Fail() 355 | } 356 | if !UseSlice(d).Slice(0, 100).Same([]int{1, 2, 3, 4}) { 357 | t.Fail() 358 | } 359 | 360 | // Negative offset 361 | if !UseSlice(d).Slice(-2, 2).Same([]int{3, 4}) { 362 | t.Fail() 363 | } 364 | if !UseSlice(d).Slice(-4, 2).Same([]int{1, 2}) { 365 | t.Fail() 366 | } 367 | if !UseSlice(d).Slice(-4, 4).Same([]int{1, 2, 3, 4}) { 368 | t.Fail() 369 | } 370 | if !UseSlice(d).Slice(-4, 5).Same([]int{1, 2, 3, 4}) { 371 | t.Fail() 372 | } 373 | if !UseSlice(d).Slice(-5, 2).Same([]int{}) { 374 | t.Fail() 375 | } 376 | 377 | // Negative length 378 | if !UseSlice(d).Slice(0, -2).Same([]int{}) { 379 | t.Fail() 380 | } 381 | if !UseSlice(d).Slice(0, -10).Same([]int{}) { 382 | t.Fail() 383 | } 384 | if !UseSlice(d).Slice(1, -1).Same([]int{2}) { 385 | t.Fail() 386 | } 387 | if !UseSlice(d).Slice(1, -10).Same([]int{1, 2}) { 388 | t.Fail() 389 | } 390 | if !UseSlice(d).Slice(3, -4).Same([]int{1, 2, 3, 4}) { 391 | t.Fail() 392 | } 393 | if !UseSlice(d).Slice(3, -10).Same([]int{1, 2, 3, 4}) { 394 | t.Fail() 395 | } 396 | if !UseSlice(d).Slice(4, -10).Same([]int{}) { 397 | t.Fail() 398 | } 399 | 400 | // Negative offset and length 401 | if !UseSlice(d).Slice(-1, -1).Same([]int{4}) { 402 | t.Fail() 403 | } 404 | if !UseSlice(d).Slice(-1, -2).Same([]int{3, 4}) { 405 | t.Fail() 406 | } 407 | if !UseSlice(d).Slice(-1, -4).Same([]int{1, 2, 3, 4}) { 408 | t.Fail() 409 | } 410 | if !UseSlice(d).Slice(-1, -10).Same([]int{1, 2, 3, 4}) { 411 | t.Fail() 412 | } 413 | if !UseSlice(d).Slice(-3, -1).Same([]int{2}) { 414 | t.Fail() 415 | } 416 | if !UseSlice(d).Slice(-3, -10).Same([]int{1, 2}) { 417 | t.Fail() 418 | } 419 | if !UseSlice(d).Slice(-4, -1).Same([]int{1}) { 420 | t.Fail() 421 | } 422 | if !UseSlice(d).Slice(-4, -10).Same([]int{1}) { 423 | t.Fail() 424 | } 425 | if !UseSlice(d).Slice(-5, -1).Same([]int{}) { 426 | t.Fail() 427 | } 428 | if !UseSlice(d).Slice(-5, -10).Same([]int{}) { 429 | t.Fail() 430 | } 431 | 432 | // Pass only offset 433 | if !UseSlice(d).Slice(0).Same([]int{1, 2, 3, 4}) { 434 | t.Fail() 435 | } 436 | if !UseSlice(d).Slice(1).Same([]int{2, 3, 4}) { 437 | t.Fail() 438 | } 439 | if !UseSlice(d).Slice(3).Same([]int{4}) { 440 | t.Fail() 441 | } 442 | if !UseSlice(d).Slice(4).Same([]int{}) { 443 | t.Fail() 444 | } 445 | if !UseSlice(d).Slice(-1).Same([]int{4}) { 446 | t.Fail() 447 | } 448 | if !UseSlice(d).Slice(-3).Same([]int{2, 3, 4}) { 449 | t.Fail() 450 | } 451 | if !UseSlice(d).Slice(-4).Same([]int{1, 2, 3, 4}) { 452 | t.Fail() 453 | } 454 | if !UseSlice(d).Slice(-5).Same([]int{}) { 455 | t.Fail() 456 | } 457 | } 458 | 459 | func TestSlice_Split(t *testing.T) { 460 | d := []int{1, 2, 3, 4, 5} 461 | if !UseSlice(UseSlice(d).Split(2)).Same([][]int{{1, 2}, {3, 4}, {5}}) { 462 | t.Fail() 463 | } 464 | } 465 | 466 | func TestSlice_Splice(t *testing.T) { 467 | test := func(offset int, args ...any) *SliceCollection[[]int, int] { 468 | s := UseSlice([]int{1, 2, 3, 4}) 469 | chunk := s.Splice(offset, args...) 470 | 471 | s2 := []int{1, 2, 3, 4} 472 | var start, end int 473 | if len(args) >= 1 { 474 | start, end = OffsetToIndex(len(s2), offset, args[0].(int)) 475 | } else { 476 | start, end = OffsetToIndex(len(s2), offset) 477 | } 478 | 479 | if !s.Same(append(s2[:start], s2[end:]...)) { 480 | t.Fail() 481 | } 482 | 483 | return chunk 484 | } 485 | 486 | // Normal offset 487 | if !test(0).Same([]int{1, 2, 3, 4}) { 488 | t.Fail() 489 | } 490 | if !test(1).Same([]int{2, 3, 4}) { 491 | t.Fail() 492 | } 493 | if !test(3).Same([]int{4}) { 494 | t.Fail() 495 | } 496 | if !test(4).Same([]int{}) { 497 | t.Fail() 498 | } 499 | if !test(-1).Same([]int{4}) { 500 | t.Fail() 501 | } 502 | if !test(-3).Same([]int{2, 3, 4}) { 503 | t.Fail() 504 | } 505 | if !test(-4).Same([]int{1, 2, 3, 4}) { 506 | t.Fail() 507 | } 508 | 509 | // Offset out of range 510 | if !test(5).Same([]int{}) { 511 | t.Fail() 512 | } 513 | if !test(10).Same([]int{}) { 514 | t.Fail() 515 | } 516 | if !test(-5).Same([]int{}) { 517 | t.Fail() 518 | } 519 | 520 | // Normal length 521 | if !test(0, 1).Same([]int{1}) { 522 | t.Fail() 523 | } 524 | if !test(0, 3).Same([]int{1, 2, 3}) { 525 | t.Fail() 526 | } 527 | if !test(0, 4).Same([]int{1, 2, 3, 4}) { 528 | t.Fail() 529 | } 530 | if !test(3, -4).Same([]int{1, 2, 3, 4}) { 531 | t.Fail() 532 | } 533 | 534 | // Length out of range 535 | if !test(0, 5).Same([]int{1, 2, 3, 4}) { 536 | t.Fail() 537 | } 538 | if !test(0, 10).Same([]int{1, 2, 3, 4}) { 539 | t.Fail() 540 | } 541 | if !test(0, -1).Same([]int{}) { 542 | t.Fail() 543 | } 544 | if !test(4, -1).Same([]int{}) { 545 | t.Fail() 546 | } 547 | if !test(4, -4).Same([]int{}) { 548 | t.Fail() 549 | } 550 | 551 | // Replacement 552 | s := UseSlice([]int{1, 2, 3, 4}) 553 | if !s.Splice(1, 2, []int{22, 33}).Same([]int{2, 3}) || !s.Same([]int{1, 22, 33, 4}) { 554 | t.Fail() 555 | } 556 | s = UseSlice([]int{1, 2, 3, 4}) 557 | if !s.Splice(1, 0, []int{22, 33}).Same([]int{}) || !s.Same([]int{1, 22, 33, 2, 3, 4}) { 558 | t.Fail() 559 | } 560 | s = UseSlice([]int{1, 2, 3, 4}) 561 | if !s.Splice(1, 2, 22, 33).Same([]int{2, 3}) || !s.Same([]int{1, 22, 33, 4}) { 562 | t.Fail() 563 | } 564 | s = UseSlice([]int{1, 2, 3, 4}) 565 | if !s.Splice(-4, 4, 11, 22, 33, 44).Same([]int{1, 2, 3, 4}) || !s.Same([]int{11, 22, 33, 44}) { 566 | t.Fail() 567 | } 568 | } 569 | 570 | func TestSlice_Reduce(t *testing.T) { 571 | if UseSlice([]int{1, 2, 3}).Reduce(100, func(carry, value, key int) int { 572 | return carry + value 573 | }) != 106 { 574 | t.Fail() 575 | } 576 | } 577 | 578 | func TestSlice_Pop(t *testing.T) { 579 | c := UseSlice([]int{1, 2}) 580 | if v, ok := c.Pop(); !ok || v != 2 { 581 | t.Fail() 582 | } 583 | if v, ok := c.Pop(); !ok || v != 1 { 584 | t.Fail() 585 | } 586 | if !c.Empty() { 587 | t.Fail() 588 | } 589 | if v, ok := c.Pop(); ok || v != 0 { 590 | t.Fail() 591 | } 592 | 593 | // Functional test 594 | d := []int{1, 2} 595 | Pop(&d) 596 | if !Same(d, []int{1}) { 597 | t.Fail() 598 | } 599 | } 600 | 601 | func TestSlice_Push(t *testing.T) { 602 | c := UseSlice([]int{1, 2, 3}) 603 | c.Push(4) 604 | if !c.Same([]int{1, 2, 3, 4}) { 605 | t.Fail() 606 | } 607 | 608 | // Functional test 609 | d := []int{1, 2, 3} 610 | Push(&d, 4) 611 | if !Same(d, []int{1, 2, 3, 4}) { 612 | t.Fail() 613 | } 614 | } 615 | 616 | func TestSlice_Where(t *testing.T) { 617 | // Only target 618 | if !UseSlice([]int{1, 2, 3}).Where(2).Same([]int{2}) { 619 | t.Fail() 620 | } 621 | if !UseSlice([]int{1, 2, 3}).Where(4).Same([]int{}) { 622 | t.Fail() 623 | } 624 | 625 | // Operator and target 626 | if !UseSlice([]int{1, 2, 3}).Where("!=", 4).Same([]int{1, 2, 3}) { 627 | t.Fail() 628 | } 629 | if !UseSlice([]int{1, 2, 3}).Where("!=", 2).Same([]int{1, 3}) { 630 | t.Fail() 631 | } 632 | if !UseSlice([]int{1, 2, 3, 4}).Where(">", 2).Same([]int{3, 4}) { 633 | t.Fail() 634 | } 635 | if !UseSlice([]int{1, 2, 3, 4}).Where(">=", 2).Same([]int{2, 3, 4}) { 636 | t.Fail() 637 | } 638 | if !UseSlice([]int{1, 2, 3, 4}).Where("<", 3).Same([]int{1, 2}) { 639 | t.Fail() 640 | } 641 | if !UseSlice([]int{1, 2, 3, 4}).Where("<=", 3).Same([]int{1, 2, 3}) { 642 | t.Fail() 643 | } 644 | 645 | u1, u2, u3, u4 := User{1, "Hugo"}, User{2, "Lisa"}, User{3, "Iris"}, User{4, "Lisa"} 646 | if !UseSlice([]User{u1, u2, u3, u4}).Where("!=", u2).Same([]User{u1, u3, u4}) { 647 | t.Fail() 648 | } 649 | if !UseSlice([]*User{&u1, &u2, &u3, &u4}).Where("!=", &u2).Same([]*User{&u1, &u3, &u4}) { 650 | t.Fail() 651 | } 652 | if UseSlice([]*User{&u1, &u2, &u3, &u4}).Where("!=", u2).Same([]*User{&u1, &u3, &u4}) { 653 | t.Fail() 654 | } 655 | 656 | // Key and target 657 | d1 := []User{u1, u2, u3, u4} 658 | if !UseSlice(d1).Where("Name", "Lisa").Same([]User{{2, "Lisa"}, {4, "Lisa"}}) { 659 | t.Fail() 660 | } 661 | 662 | // Key, operator and target 663 | if !UseSlice(d1).Where("Name", "!=", "Lisa").Same([]User{{1, "Hugo"}, {3, "Iris"}}) { 664 | t.Fail() 665 | } 666 | } 667 | 668 | func TestSlice_WhereIn(t *testing.T) { 669 | // Only targets 670 | if !UseSlice([]int{1, 2, 3}).WhereIn([]int{1, 3}).Same([]int{1, 3}) { 671 | t.Fail() 672 | } 673 | if !UseSlice([]int{1, 2, 3}).WhereIn([]int{0, 2, -1}).Same([]int{2}) { 674 | t.Fail() 675 | } 676 | 677 | s1, s2 := []int{1, 2, 3}, []int{4, 5, 6} 678 | if !UseSlice([][]int{s1, s2}).WhereIn([][]int{s2}).Same([][]int{s2}) { 679 | t.Fail() 680 | } 681 | 682 | u1, u2, u3, u4 := User{1, "Hugo"}, User{2, "Lisa"}, User{3, "Iris"}, User{4, "Lisa"} 683 | if !UseSlice([]*User{&u1, &u2, &u3, &u4}).WhereIn([]*User{nil, &u2, &u1}).Same([]*User{&u1, &u2}) { 684 | t.Fail() 685 | } 686 | if !UseSlice([]User{u1, u2, u3, u4}).WhereIn([]User{{5, "Kite"}, u2, u1}).Same([]User{u1, u2}) { 687 | t.Fail() 688 | } 689 | 690 | // Key and targets 691 | d1 := []User{u1, u2, u3, u4} 692 | if !UseSlice(d1).WhereIn("Name", []string{"Iris", "Hugo"}).Same([]User{{1, "Hugo"}, {3, "Iris"}}) { 693 | t.Fail() 694 | } 695 | 696 | // Nil 697 | if !UseSlice([]*User{&u1, &u2, &u3, &u4}).WhereIn(nil).Same([]*User{}) { 698 | t.Fail() 699 | } 700 | if !UseSlice([]*User{&u1, &u2, &u3, &u4}).WhereIn([]*User{nil, &u3}).Same([]*User{&u3}) { 701 | t.Fail() 702 | } 703 | } 704 | 705 | func TestSlice_WhereNotIn(t *testing.T) { 706 | // Only targets 707 | if !UseSlice([]int{1, 2, 3}).WhereNotIn([]int{1, 3}).Same([]int{2}) { 708 | t.Fail() 709 | } 710 | if !UseSlice([]int{1, 2, 3}).WhereNotIn([]int{0, 2, -1}).Same([]int{1, 3}) { 711 | t.Fail() 712 | } 713 | if !UseSlice([]int{1, 2, 3}).WhereNotIn([]float64{1, 2, 3.14}).Same([]int{1, 2, 3}) { 714 | t.Fail() 715 | } 716 | if !UseSlice([]float64{1, 2, 3}).WhereNotIn([]int{1, 2, 3}).Same([]float64{1, 2, 3}) { 717 | t.Fail() 718 | } 719 | if !UseSlice([]int64{1, 2, 3}).WhereNotIn([]int8{1, 2, 3}).Same([]int64{}) { 720 | t.Fail() 721 | } 722 | 723 | u1, u2, u3, u4 := User{1, "Hugo"}, User{2, "Lisa"}, User{3, "Iris"}, User{4, "Lisa"} 724 | if !UseSlice([]*User{&u1, &u2, &u3, &u4}).WhereNotIn([]*User{nil, &u2, &u1}).Same([]*User{&u3, &u4}) { 725 | t.Fail() 726 | } 727 | 728 | // Key and targets 729 | d1 := []User{u1, u2, u3, u4} 730 | if !UseSlice(d1).WhereNotIn("Name", []string{"Lisa"}).Same([]User{{1, "Hugo"}, {3, "Iris"}}) { 731 | t.Fail() 732 | } 733 | 734 | // Nil 735 | if !UseSlice([]*User{&u1, &u2, &u3, &u4}).WhereNotIn(nil).Same([]*User{&u1, &u2, &u3, &u4}) { 736 | t.Fail() 737 | } 738 | } 739 | -------------------------------------------------------------------------------- /tests/types.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | type Foo struct { 4 | Bar string 5 | } 6 | 7 | type User struct { 8 | ID uint 9 | Name string 10 | } 11 | -------------------------------------------------------------------------------- /types/sortable.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "golang.org/x/exp/constraints" 5 | ) 6 | 7 | /** 8 | * SortableSlice 9 | */ 10 | 11 | type SortableSlice[T ~[]E, E constraints.Ordered] struct { 12 | Items T 13 | Desc bool 14 | } 15 | 16 | func (s SortableSlice[T, E]) Len() int { 17 | return len(s.Items) 18 | } 19 | 20 | func (s SortableSlice[T, E]) Less(i, j int) bool { 21 | if s.Desc { 22 | return s.Items[j] < s.Items[i] || (s.Items[j] != s.Items[j] && s.Items[i] == s.Items[i]) 23 | } else { 24 | return s.Items[i] < s.Items[j] || (s.Items[i] != s.Items[i] && s.Items[j] == s.Items[j]) 25 | } 26 | } 27 | 28 | func (s SortableSlice[T, E]) Swap(i, j int) { 29 | s.Items[i], s.Items[j] = s.Items[j], s.Items[i] 30 | } 31 | 32 | /** 33 | * SortableStruct 34 | */ 35 | 36 | type SortableStruct[E constraints.Ordered] struct { 37 | Value E 38 | Attached any 39 | } 40 | 41 | type SortableStructs[T ~[]E, E constraints.Ordered] struct { 42 | Items []*SortableStruct[E] 43 | Desc bool 44 | } 45 | 46 | func (s SortableStructs[T, E]) Len() int { 47 | return len(s.Items) 48 | } 49 | 50 | func (s SortableStructs[T, E]) Less(i, j int) bool { 51 | if s.Desc { 52 | return s.Items[j].Value < s.Items[i].Value || (s.Items[j].Value != s.Items[j].Value && s.Items[i].Value == s.Items[i].Value) 53 | } else { 54 | return s.Items[i].Value < s.Items[j].Value || (s.Items[i].Value != s.Items[i].Value && s.Items[j].Value == s.Items[j].Value) 55 | } 56 | } 57 | 58 | func (s SortableStructs[T, E]) Swap(i, j int) { 59 | s.Items[i], s.Items[j] = s.Items[j], s.Items[i] 60 | } 61 | --------------------------------------------------------------------------------