├── .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 |
--------------------------------------------------------------------------------