diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 72df710..4268e48 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -71,7 +71,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- go: [ '1.18.10', '1.19.13', '1.20.14', '1.21.13', '1.22.8', '1.23.2' ]
+ go: [ '1.18.10', '1.19.13', '1.20.14', '1.21.13', '1.22.8', '1.23.2', '1.24.2', '1.25.1', '1.26.2' ]
name: Go ${{ matrix.go }} test
steps:
- uses: actions/checkout@v4
diff --git a/pkg/xiter/README.md b/pkg/xiter/README.md
index b28bf97..b32822f 100644
--- a/pkg/xiter/README.md
+++ b/pkg/xiter/README.md
@@ -52,6 +52,7 @@ WARNING: golang 1.23 has higher performance on iterating Seq/Seq2 which boost by
- [func Reduce\[Sum, V any\]\(f func\(Sum, V\) Sum, sum Sum, seq Seq\[V\]\) Sum](<#Reduce>)
- [func Reduce2\[Sum, K, V any\]\(f func\(Sum, K, V\) Sum, sum Sum, seq Seq2\[K, V\]\) Sum](<#Reduce2>)
- [func Sum\[T constraints.Number\]\(seq Seq\[T\]\) T](<#Sum>)
+- [func ToChan\[T any\]\(seq Seq\[T\]\) \<\-chan T](<#ToChan>)
- [func ToMap\[K comparable, V any\]\(seq Seq2\[K, V\]\) \(out map\[K\]V\)](<#ToMap>)
- [func ToMapFromSeq\[K comparable, V any\]\(seq Seq\[K\], fn func\(k K\) V\) \(out map\[K\]V\)](<#ToMapFromSeq>)
- [func ToSlice\[T any\]\(seq Seq\[T\]\) \(out \[\]T\)](<#ToSlice>)
@@ -62,6 +63,7 @@ WARNING: golang 1.23 has higher performance on iterating Seq/Seq2 which boost by
- [func Chunk\[T any\]\(seq Seq\[T\], n int\) Seq\[\[\]T\]](<#Chunk>)
- [func Compact\[T comparable\]\(in Seq\[T\]\) Seq\[T\]](<#Compact>)
- [func Concat\[V any\]\(seqs ...Seq\[V\]\) Seq\[V\]](<#Concat>)
+ - [func Cycle\[T any\]\(seq Seq\[T\]\) Seq\[T\]](<#Cycle>)
- [func Filter\[V any\]\(f func\(V\) bool, seq Seq\[V\]\) Seq\[V\]](<#Filter>)
- [func FromChan\[T any\]\(in \<\-chan T\) Seq\[T\]](<#FromChan>)
- [func FromMapKeys\[K comparable, V any\]\(m map\[K\]V\) Seq\[K\]](<#FromMapKeys>)
@@ -69,11 +71,13 @@ WARNING: golang 1.23 has higher performance on iterating Seq/Seq2 which boost by
- [func FromSlice\[T any\]\(in \[\]T\) Seq\[T\]](<#FromSlice>)
- [func FromSliceReverse\[T any, Slice \~\[\]T\]\(in Slice\) Seq\[T\]](<#FromSliceReverse>)
- [func FromSliceShuffle\[T any\]\(in \[\]T\) Seq\[T\]](<#FromSliceShuffle>)
+ - [func Generate\[T any\]\(fn func\(\) T\) Seq\[T\]](<#Generate>)
- [func Intersect\[T comparable\]\(left Seq\[T\], right Seq\[T\]\) Seq\[T\]](<#Intersect>)
- [func Limit\[V any\]\(seq Seq\[V\], n int\) Seq\[V\]](<#Limit>)
- [func Map\[In, Out any\]\(f func\(In\) Out, seq Seq\[In\]\) Seq\[Out\]](<#Map>)
- [func Merge\[V xcmp.Ordered\]\(x, y Seq\[V\]\) Seq\[V\]](<#Merge>)
- [func MergeFunc\[V any\]\(x, y Seq\[V\], f func\(V, V\) int\) Seq\[V\]](<#MergeFunc>)
+ - [func Range\[T constraints.Integer\]\(start, end, step T\) Seq\[T\]](<#Range>)
- [func Repeat\[T any\]\(seq Seq\[T\], count int\) Seq\[T\]](<#Repeat>)
- [func Replace\[T comparable\]\(seq Seq\[T\], from, to T, n int\) Seq\[T\]](<#Replace>)
- [func ReplaceAll\[T comparable\]\(seq Seq\[T\], from, to T\) Seq\[T\]](<#ReplaceAll>)
@@ -97,6 +101,7 @@ WARNING: golang 1.23 has higher performance on iterating Seq/Seq2 which boost by
- [func MapToSeq2Value\[T any, K comparable, V any\]\(in Seq\[T\], mapFn func\(ele T\) \(K, V\)\) Seq2\[K, V\]](<#MapToSeq2Value>)
- [func Merge2\[K xcmp.Ordered, V any\]\(x, y Seq2\[K, V\]\) Seq2\[K, V\]](<#Merge2>)
- [func MergeFunc2\[K, V any\]\(x, y Seq2\[K, V\], f func\(K, K\) int\) Seq2\[K, V\]](<#MergeFunc2>)
+ - [func WithIndex\[T any\]\(seq Seq\[T\]\) Seq2\[int, T\]](<#WithIndex>)
- [type Zipped](<#Zipped>)
- [type Zipped2](<#Zipped2>)
@@ -730,6 +735,25 @@ sum := xiter.Sum(seq)
// sum is 15
```
+
+## func [ToChan]()
+
+```go
+func ToChan[T any](seq Seq[T]) <-chan T
+```
+
+ToChan sends all elements of seq to a returned channel and closes it when the seq is exhausted.
+
+EXAMPLE:
+
+```
+seq := xiter.FromSlice([]int{1, 2, 3})
+ch := xiter.ToChan(seq)
+for v := range ch {
+ fmt.Println(v)
+}
+```
+
## func [ToMap]()
@@ -894,6 +918,22 @@ func main() {
+
+### func [Cycle]()
+
+```go
+func Cycle[T any](seq Seq[T]) Seq[T]
+```
+
+Cycle returns a Seq that infinitely repeats the elements of seq. The input seq is materialized once, then cycled in memory.
+
+EXAMPLE:
+
+```
+seq := xiter.Cycle(xiter.FromSlice([]int{1, 2, 3}))
+// seq will yield: 1, 2, 3, 1, 2, 3, 1, 2, 3, ...
+```
+
### func [Filter]()
@@ -1017,6 +1057,23 @@ shuffledSeq := FromSliceShuffle(ToSlice(seq))
// shuffledSeq will yield a shuffled sequence of 1, 2, 3, 4, 5
```
+
+### func [Generate]()
+
+```go
+func Generate[T any](fn func() T) Seq[T]
+```
+
+Generate returns an infinite Seq where each element is produced by calling fn. The sequence is unbounded; use with Limit, TakeWhile, etc. to constrain.
+
+EXAMPLE:
+
+```
+seq := xiter.Generate(func() int { return rand.Intn(100) })
+first5 := xiter.ToSlice(xiter.Limit(seq, 5))
+// first5 contains 5 random numbers
+```
+
### func [Intersect]()
@@ -1184,6 +1241,25 @@ func MergeFunc[V any](x, y Seq[V], f func(V, V) int) Seq[V]
MergeFunc merges two sequences of values ordered by the function f. Values appear in the output once for each time they appear in x and once for each time they appear in y. When equal values appear in both sequences, the output contains the values from x before the values from y. If the two input sequences are not ordered by f, the output sequence will not be ordered by f, but it will still contain every value from x and y exactly once.
+
+### func [Range]()
+
+```go
+func Range[T constraints.Integer](start, end, step T) Seq[T]
+```
+
+Range returns a Seq of integers from start to end, stepping by step. If step \> 0, elements are yielded while i \< end. If step \< 0, elements are yielded while i \> end. If step == 0, an empty sequence is returned.
+
+EXAMPLE:
+
+```
+seq := xiter.Range(0, 10, 2)
+// seq will yield: 0, 2, 4, 6, 8
+
+seq = xiter.Range(10, 0, -3)
+// seq will yield: 10, 7, 4, 1
+```
+
### func [Repeat]()
@@ -1520,6 +1596,28 @@ func MergeFunc2[K, V any](x, y Seq2[K, V], f func(K, K) int) Seq2[K, V]
MergeFunc2 merges two sequences of key\-value pairs ordered by the function f. Pairs appear in the output once for each time they appear in x and once for each time they appear in y. When pairs with equal keys appear in both sequences, the output contains the pairs from x before the pairs from y. If the two input sequences are not ordered by f, the output sequence will not be ordered by f, but it will still contain every pair from x and y exactly once.
+
+### func [WithIndex]()
+
+```go
+func WithIndex[T any](seq Seq[T]) Seq2[int, T]
+```
+
+WithIndex returns a Seq2 that pairs each element from seq with its 0\-based index.
+
+EXAMPLE:
+
+```
+seq := xiter.FromSlice([]string{"a", "b", "c"})
+for idx, v := range xiter.WithIndex(seq) {
+ fmt.Println(idx, v)
+}
+// output:
+// 0 a
+// 1 b
+// 2 c
+```
+
## type [Zipped]()
diff --git a/pkg/xiter/xiter_benchmark_test.go b/pkg/xiter/xiter_benchmark_test.go
new file mode 100644
index 0000000..5e080bd
--- /dev/null
+++ b/pkg/xiter/xiter_benchmark_test.go
@@ -0,0 +1,54 @@
+package xiter_test
+
+import (
+ "testing"
+
+ "github.com/dashjay/xiter/pkg/xiter"
+)
+
+func BenchmarkCycle(b *testing.B) {
+ seq := xiter.Cycle(xiter.FromSlice(_range(0, 1000)))
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ xiter.ToSlice(xiter.Limit(seq, 10000))
+ }
+}
+
+func BenchmarkGenerate(b *testing.B) {
+ i := 0
+ gen := xiter.Generate(func() int {
+ i++
+ return i
+ })
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ xiter.ToSlice(xiter.Limit(gen, 10000))
+ }
+}
+
+func BenchmarkToChan(b *testing.B) {
+ seq := xiter.FromSlice(_range(0, 10000))
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ ch := xiter.ToChan(seq)
+ for range ch {
+ }
+ }
+}
+
+func BenchmarkRange(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ xiter.ToSlice(xiter.Range(0, 10000, 1))
+ }
+}
+
+func BenchmarkWithIndex(b *testing.B) {
+ seq := xiter.FromSlice(_range(0, 10000))
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ xiter.WithIndex(seq)(func(_ int, v int) bool {
+ _ = v
+ return true
+ })
+ }
+}
diff --git a/pkg/xiter/xiter_common.go b/pkg/xiter/xiter_common.go
index 2fd3c4f..014b017 100644
--- a/pkg/xiter/xiter_common.go
+++ b/pkg/xiter/xiter_common.go
@@ -238,3 +238,121 @@ func Moderate[T comparable](in Seq[T]) (T, bool) {
func ModerateO[T constraints.Number](in Seq[T]) optional.O[T] {
return optional.FromValue2(Moderate(in))
}
+
+// Cycle returns a Seq that infinitely repeats the elements of seq.
+// The input seq is materialized once, then cycled in memory.
+//
+// EXAMPLE:
+//
+// seq := xiter.Cycle(xiter.FromSlice([]int{1, 2, 3}))
+// // seq will yield: 1, 2, 3, 1, 2, 3, 1, 2, 3, ...
+func Cycle[T any](seq Seq[T]) Seq[T] {
+ var elems []T
+ seq(func(v T) bool {
+ elems = append(elems, v)
+ return true
+ })
+ if len(elems) == 0 {
+ return func(yield func(T) bool) {}
+ }
+ return func(yield func(T) bool) {
+ for {
+ for _, v := range elems {
+ if !yield(v) {
+ return
+ }
+ }
+ }
+ }
+}
+
+// Generate returns an infinite Seq where each element is produced by calling fn.
+// The sequence is unbounded; use with Limit, TakeWhile, etc. to constrain.
+//
+// EXAMPLE:
+//
+// seq := xiter.Generate(func() int { return rand.Intn(100) })
+// first5 := xiter.ToSlice(xiter.Limit(seq, 5))
+// // first5 contains 5 random numbers
+func Generate[T any](fn func() T) Seq[T] {
+ return func(yield func(T) bool) {
+ for yield(fn()) {
+ }
+ }
+}
+
+// ToChan sends all elements of seq to a returned channel and closes it when the seq is exhausted.
+//
+// EXAMPLE:
+//
+// seq := xiter.FromSlice([]int{1, 2, 3})
+// ch := xiter.ToChan(seq)
+// for v := range ch {
+// fmt.Println(v)
+// }
+func ToChan[T any](seq Seq[T]) <-chan T {
+ ch := make(chan T)
+ go func() {
+ seq(func(v T) bool {
+ ch <- v
+ return true
+ })
+ close(ch)
+ }()
+ return ch
+}
+
+// Range returns a Seq of integers from start to end, stepping by step.
+// If step > 0, elements are yielded while i < end.
+// If step < 0, elements are yielded while i > end.
+// If step == 0, an empty sequence is returned.
+//
+// EXAMPLE:
+//
+// seq := xiter.Range(0, 10, 2)
+// // seq will yield: 0, 2, 4, 6, 8
+//
+// seq = xiter.Range(10, 0, -3)
+// // seq will yield: 10, 7, 4, 1
+func Range[T constraints.Integer](start, end, step T) Seq[T] {
+ return func(yield func(T) bool) {
+ if step > 0 {
+ for i := start; i < end; i += step {
+ if !yield(i) {
+ return
+ }
+ }
+ } else if step < 0 {
+ for i := start; i > end; i += step {
+ if !yield(i) {
+ return
+ }
+ }
+ }
+ }
+}
+
+// WithIndex returns a Seq2 that pairs each element from seq with its 0-based index.
+//
+// EXAMPLE:
+//
+// seq := xiter.FromSlice([]string{"a", "b", "c"})
+// for idx, v := range xiter.WithIndex(seq) {
+// fmt.Println(idx, v)
+// }
+// // output:
+// // 0 a
+// // 1 b
+// // 2 c
+func WithIndex[T any](seq Seq[T]) Seq2[int, T] {
+ return func(yield func(int, T) bool) {
+ i := 0
+ seq(func(v T) bool {
+ if !yield(i, v) {
+ return false
+ }
+ i++
+ return true
+ })
+ }
+}
diff --git a/pkg/xiter/xiter_common_test.go b/pkg/xiter/xiter_common_test.go
index 6c367ff..df2ece4 100644
--- a/pkg/xiter/xiter_common_test.go
+++ b/pkg/xiter/xiter_common_test.go
@@ -127,5 +127,79 @@ func TestXIterCommon(t *testing.T) {
}
assert.Equal(t, xiter.ToMapFromSeq(xiter.Union(left, right), ut),
xiter.ToMapFromSeq(xiter.Union(right, left), ut))
- })
+ })
+
+ t.Run("cycle", func(t *testing.T) {
+ seq := xiter.Cycle(xiter.FromSlice([]int{1, 2, 3}))
+ first9 := xiter.ToSlice(xiter.Limit(seq, 9))
+ assert.Equal(t, []int{1, 2, 3, 1, 2, 3, 1, 2, 3}, first9)
+
+ emptyCycle := xiter.Cycle(xiter.FromSlice([]int{}))
+ assert.Len(t, xiter.ToSlice(emptyCycle), 0)
+
+ singleCycle := xiter.Cycle(xiter.FromSlice([]int{42}))
+ first5 := xiter.ToSlice(xiter.Limit(singleCycle, 5))
+ assert.Equal(t, []int{42, 42, 42, 42, 42}, first5)
+ })
+
+ t.Run("generate", func(t *testing.T) {
+ i := 0
+ gen := xiter.Generate(func() int {
+ i++
+ return i
+ })
+ first5 := xiter.ToSlice(xiter.Limit(gen, 5))
+ assert.Equal(t, []int{1, 2, 3, 4, 5}, first5)
+
+ assert.Len(t, xiter.ToSlice(xiter.Limit(gen, 0)), 0)
+ })
+
+ t.Run("to chan", func(t *testing.T) {
+ seq := xiter.FromSlice(_range(0, 10))
+ ch := xiter.ToChan(seq)
+ var result []int
+ for v := range ch {
+ result = append(result, v)
+ }
+ assert.Equal(t, _range(0, 10), result)
+
+ emptyCh := xiter.ToChan(xiter.FromSlice([]int{}))
+ var emptyResult []int
+ for v := range emptyCh {
+ emptyResult = append(emptyResult, v)
+ }
+ assert.Len(t, emptyResult, 0)
+ })
+
+ t.Run("range", func(t *testing.T) {
+ r := xiter.ToSlice(xiter.Range(0, 10, 2))
+ assert.Equal(t, []int{0, 2, 4, 6, 8}, r)
+
+ r2 := xiter.ToSlice(xiter.Range(10, 0, -3))
+ assert.Equal(t, []int{10, 7, 4, 1}, r2)
+
+ assert.Len(t, xiter.ToSlice(xiter.Range(0, 0, 1)), 0)
+ assert.Len(t, xiter.ToSlice(xiter.Range(5, 0, 1)), 0)
+ assert.Len(t, xiter.ToSlice(xiter.Range(0, 5, -1)), 0)
+ assert.Len(t, xiter.ToSlice(xiter.Range(0, 10, 0)), 0)
+
+ r3 := xiter.ToSlice(xiter.Range(0, 100, 1000))
+ assert.Equal(t, []int{0}, r3)
+
+ r4 := xiter.ToSlice(xiter.Range(0, 5, 1))
+ assert.Equal(t, []int{0, 1, 2, 3, 4}, r4)
+ })
+
+ t.Run("with index", func(t *testing.T) {
+ seq := xiter.FromSlice([]string{"a", "b", "c"})
+ idxSeq := xiter.WithIndex(seq)
+
+ collected := xiter.ToSliceSeq2Key(idxSeq)
+ assert.Equal(t, []int{0, 1, 2}, collected)
+ values := xiter.ToSliceSeq2Value(idxSeq)
+ assert.Equal(t, []string{"a", "b", "c"}, values)
+
+ emptyIdx := xiter.WithIndex(xiter.FromSlice([]int{}))
+ assert.Len(t, xiter.ToSliceSeq2Key(emptyIdx), 0)
+ })
}
diff --git a/pkg/xmap/README.md b/pkg/xmap/README.md
index 0388229..fce8245 100644
--- a/pkg/xmap/README.md
+++ b/pkg/xmap/README.md
@@ -35,7 +35,7 @@ import "github.com/dashjay/xiter/pkg/xmap"
-## func [Clone]()
+## func [Clone]()
```go
func Clone[M ~map[K]V, K comparable, V any](m M) M
@@ -44,7 +44,7 @@ func Clone[M ~map[K]V, K comparable, V any](m M) M
-## func [CoalesceMaps]()
+## func [CoalesceMaps]()
```go
func CoalesceMaps[M ~map[K]V, K comparable, V any](maps ...M) M
@@ -79,7 +79,7 @@ result := CoalesceMaps(map1, map2, map3)
```
-## func [Copy]()
+## func [Copy]()
```go
func Copy[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](dst M1, src M2)
@@ -88,7 +88,7 @@ func Copy[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](dst M1, src M2)
-## func [Delete]()
+## func [Delete]()
```go
func Delete[K comparable, V any](m map[K]V, k K) (old V, deleted bool)
@@ -107,7 +107,7 @@ old, deleted = xmap.Delete(m, "c")
```
-## func [DeleteIf]()
+## func [DeleteIf]()
```go
func DeleteIf[K comparable, V any](m map[K]V, k K, c func(K, V) bool) (old V, deleted bool)
@@ -128,7 +128,7 @@ old, deleted = xmap.DeleteIf(m, "c", func(k string, v int) bool { return v > 0 }
```
-## func [Equal]()
+## func [Equal]()
```go
func Equal[M1, M2 ~map[K]V, K, V comparable](m1 M1, m2 M2) bool
@@ -137,7 +137,7 @@ func Equal[M1, M2 ~map[K]V, K, V comparable](m1 M1, m2 M2) bool
-## func [EqualFunc]()
+## func [EqualFunc]()
```go
func EqualFunc[M1 ~map[K]V1, M2 ~map[K]V2, K comparable, V1, V2 any](m1 M1, m2 M2, eq func(V1, V2) bool) bool
@@ -146,7 +146,7 @@ func EqualFunc[M1 ~map[K]V1, M2 ~map[K]V2, K comparable, V1, V2 any](m1 M1, m2 M
-## func [Filter]()
+## func [Filter]()
```go
func Filter[M ~map[K]V, K comparable, V any](in M, fn func(K, V) bool) M
@@ -164,7 +164,7 @@ result := Filter(m, fn)
```
-## func [Find]()
+## func [Find]()
```go
func Find[K comparable, V any](in map[K]V, fn func(K, V) bool) (K, V, bool)
@@ -183,7 +183,7 @@ key, value, found = xmap.Find(m, func(k string, v int) bool { return v > 10 })
```
-## func [FindKey]()
+## func [FindKey]()
```go
func FindKey[K comparable, V any](in map[K]V, target K) (K, V, bool)
@@ -202,7 +202,7 @@ key, value, found = xmap.FindKey(m, "c")
```
-## func [FindKeyO]()
+## func [FindKeyO]()
```go
func FindKeyO[K comparable, V any](in map[K]V, target K) optional.O[union.U2[K, V]]
@@ -223,7 +223,7 @@ result2 := xmap.FindKeyO(m, "c")
```
-## func [FindO]()
+## func [FindO]()
```go
func FindO[K comparable, V any](in map[K]V, fn func(K, V) bool) optional.O[union.U2[K, V]]
@@ -244,7 +244,7 @@ result2 := xmap.FindO(m, func(k string, v int) bool { return v > 10 })
```
-## func [Keys]()
+## func [Keys]()
```go
func Keys[M ~map[K]V, K comparable, V any](m M) []K
@@ -253,7 +253,7 @@ func Keys[M ~map[K]V, K comparable, V any](m M) []K
-## func [MapKeys]()
+## func [MapKeys]()
```go
func MapKeys[K comparable, V1 any](in map[K]V1, fn func(K, V1) K) map[K]V1
@@ -286,7 +286,7 @@ result := MapKeys(m, fn)
```
-## func [MapValues]()
+## func [MapValues]()
```go
func MapValues[K comparable, V1, V2 any](in map[K]V1, fn func(K, V1) V2) map[K]V2
@@ -319,7 +319,7 @@ result := MapValues(m, fn)
```
-## func [MaxKey]()
+## func [MaxKey]()
```go
func MaxKey[K constraints.Ordered, V any](m map[K]V) optional.O[union.U2[K, V]]
@@ -340,7 +340,7 @@ result2 := xmap.MaxKey(map[string]int{})
```
-## func [MaxValue]()
+## func [MaxValue]()
```go
func MaxValue[K comparable, V constraints.Ordered](m map[K]V) optional.O[union.U2[K, V]]
@@ -361,7 +361,7 @@ result2 := xmap.MaxValue(map[string]int{})
```
-## func [MinKey]()
+## func [MinKey]()
```go
func MinKey[K constraints.Ordered, V any](m map[K]V) optional.O[union.U2[K, V]]
@@ -382,7 +382,7 @@ result2 := xmap.MinKey(map[string]int{})
```
-## func [MinValue]()
+## func [MinValue]()
```go
func MinValue[K comparable, V constraints.Ordered](m map[K]V) optional.O[union.U2[K, V]]
@@ -403,7 +403,7 @@ result2 := xmap.MinValue(map[string]int{})
```
-## func [ToUnionSlice]()
+## func [ToUnionSlice]()
```go
func ToUnionSlice[M ~map[K]V, K comparable, V any](m M) []union.U2[K, V]
@@ -412,7 +412,7 @@ func ToUnionSlice[M ~map[K]V, K comparable, V any](m M) []union.U2[K, V]
-## func [ToXSyncMap]()
+## func [ToXSyncMap]()
```go
func ToXSyncMap[K comparable, V any](in map[K]V) *xsync.SyncMap[K, V]
@@ -433,7 +433,7 @@ Returns:
```
-## func [Update]()
+## func [Update]()
```go
func Update[K comparable, V any](m map[K]V, k K, v V) (old V, replaced bool)
@@ -452,7 +452,7 @@ old, replaced = xmap.Update(m, "c", 3)
```
-## func [UpdateIf]()
+## func [UpdateIf]()
```go
func UpdateIf[K comparable, V any](m map[K]V, k K, v V, c func(K, V) bool) (old V, replaced bool)
@@ -473,7 +473,7 @@ old, replaced = xmap.UpdateIf(m, "c", 3, func(k string, v int) bool { return v >
```
-## func [Values]()
+## func [Values]()
```go
func Values[M ~map[K]V, K comparable, V any](m M) []V
diff --git a/pkg/xmap/xmap.go b/pkg/xmap/xmap.go
index 76dfadc..99120e7 100644
--- a/pkg/xmap/xmap.go
+++ b/pkg/xmap/xmap.go
@@ -7,7 +7,6 @@ import (
"maps"
"github.com/dashjay/xiter/pkg/union"
- "github.com/dashjay/xiter/pkg/xiter"
)
func Clone[M ~map[K]V, K comparable, V any](m M) M {
@@ -27,13 +26,25 @@ func Copy[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](dst M1, src M2) {
}
func Keys[M ~map[K]V, K comparable, V any](m M) []K {
- return xiter.ToSlice(xiter.FromMapKeys(m))
+ keys := make([]K, 0, len(m))
+ for k := range m {
+ keys = append(keys, k)
+ }
+ return keys
}
func Values[M ~map[K]V, K comparable, V any](m M) []V {
- return xiter.ToSlice(xiter.FromMapValues(m))
+ vals := make([]V, 0, len(m))
+ for _, v := range m {
+ vals = append(vals, v)
+ }
+ return vals
}
func ToUnionSlice[M ~map[K]V, K comparable, V any](m M) []union.U2[K, V] {
- return xiter.ToSlice(xiter.Seq2ToSeqUnion(xiter.FromMapKeyAndValues(m)))
+ result := make([]union.U2[K, V], 0, len(m))
+ for k, v := range m {
+ result = append(result, union.U2[K, V]{T1: k, T2: v})
+ }
+ return result
}
diff --git a/pkg/xmap/xmap_benchmark_test.go b/pkg/xmap/xmap_benchmark_test.go
new file mode 100644
index 0000000..f0e27cc
--- /dev/null
+++ b/pkg/xmap/xmap_benchmark_test.go
@@ -0,0 +1,89 @@
+package xmap_test
+
+import (
+ "testing"
+
+ "github.com/dashjay/xiter/pkg/xmap"
+)
+
+var _ = _map // suppress unused lint
+
+func BenchmarkXmap(b *testing.B) {
+ const size = 10_000
+
+ m := _map(0, size)
+
+ b.Run("keys", func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ _ = xmap.Keys(m)
+ }
+ })
+
+ b.Run("values", func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ _ = xmap.Values(m)
+ }
+ })
+
+ b.Run("to union slice", func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ _ = xmap.ToUnionSlice(m)
+ }
+ })
+
+ b.Run("filter", func(b *testing.B) {
+ fn := func(k int, v string) bool { return k%2 == 0 }
+ for i := 0; i < b.N; i++ {
+ _ = xmap.Filter(m, fn)
+ }
+ })
+
+ b.Run("map values", func(b *testing.B) {
+ fn := func(k int, v string) string { return v + "_x" }
+ for i := 0; i < b.N; i++ {
+ _ = xmap.MapValues(m, fn)
+ }
+ })
+
+ b.Run("map keys", func(b *testing.B) {
+ fn := func(k int, v string) int { return k * 2 }
+ for i := 0; i < b.N; i++ {
+ _ = xmap.MapKeys(m, fn)
+ }
+ })
+
+ b.Run("coalesce maps", func(b *testing.B) {
+ maps := make([]map[int]string, 10)
+ for i := 0; i < 10; i++ {
+ maps[i] = _map(i*1000, (i+1)*1000)
+ }
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _ = xmap.CoalesceMaps(maps...)
+ }
+ })
+
+ b.Run("max key", func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ _ = xmap.MaxKey(m)
+ }
+ })
+
+ b.Run("min key", func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ _ = xmap.MinKey(m)
+ }
+ })
+
+ b.Run("max value", func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ _ = xmap.MaxValue(m)
+ }
+ })
+
+ b.Run("min value", func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ _ = xmap.MinValue(m)
+ }
+ })
+}
diff --git a/pkg/xmap/xmap_common.go b/pkg/xmap/xmap_common.go
index e920d8a..48e2b8e 100644
--- a/pkg/xmap/xmap_common.go
+++ b/pkg/xmap/xmap_common.go
@@ -4,7 +4,6 @@ import (
"github.com/dashjay/xiter/pkg/internal/constraints"
"github.com/dashjay/xiter/pkg/optional"
"github.com/dashjay/xiter/pkg/union"
- "github.com/dashjay/xiter/pkg/xiter"
"github.com/dashjay/xiter/pkg/xsync"
)
@@ -33,11 +32,13 @@ import (
// result := CoalesceMaps(map1, map2, map3)
// // result will be map[string]int{"a": 1, "b": 3, "c": 4, "d": 5}
func CoalesceMaps[M ~map[K]V, K comparable, V any](maps ...M) M {
- seqs := make([]xiter.Seq2[K, V], 0, len(maps))
+ result := make(M)
for _, m := range maps {
- seqs = append(seqs, xiter.FromMapKeyAndValues(m))
+ for k, v := range m {
+ result[k] = v
+ }
}
- return xiter.ToMap(xiter.Concat2(seqs...))
+ return result
}
// Filter filters the map by the given function.
@@ -50,7 +51,13 @@ func CoalesceMaps[M ~map[K]V, K comparable, V any](maps ...M) M {
// result := Filter(m, fn)
// // result will be map[string]int{"b": 2, "c": 3}
func Filter[M ~map[K]V, K comparable, V any](in M, fn func(K, V) bool) M {
- return xiter.ToMap(xiter.Filter2(fn, xiter.FromMapKeyAndValues(in)))
+ result := make(M)
+ for k, v := range in {
+ if fn(k, v) {
+ result[k] = v
+ }
+ }
+ return result
}
// MapValues transforms the values of a map using the provided function while keeping keys unchanged.
@@ -74,7 +81,11 @@ func Filter[M ~map[K]V, K comparable, V any](in M, fn func(K, V) bool) M {
// result := MapValues(m, fn)
// // result will be map[string]string{"a": "value_1", "b": "value_2", "c": "value_3"}
func MapValues[K comparable, V1, V2 any](in map[K]V1, fn func(K, V1) V2) map[K]V2 {
- return xiter.ToMap(xiter.Map2(func(k K, v V1) (K, V2) { return k, fn(k, v) }, xiter.FromMapKeyAndValues(in)))
+ result := make(map[K]V2, len(in))
+ for k, v := range in {
+ result[k] = fn(k, v)
+ }
+ return result
}
// MapKeys transforms the keys of a map using the provided function while keeping values unchanged.
@@ -98,7 +109,11 @@ func MapValues[K comparable, V1, V2 any](in map[K]V1, fn func(K, V1) V2) map[K]V
// result := MapKeys(m, fn)
// // result will be map[string]int{"a_key": 1, "b_key": 2, "c_key": 3}
func MapKeys[K comparable, V1 any](in map[K]V1, fn func(K, V1) K) map[K]V1 {
- return xiter.ToMap(xiter.Map2(func(k K, v V1) (K, V1) { return fn(k, v), v }, xiter.FromMapKeyAndValues(in)))
+ result := make(map[K]V1, len(in))
+ for k, v := range in {
+ result[fn(k, v)] = v
+ }
+ return result
}
// ToXSyncMap converts a map to a xsync.SyncMap.
@@ -296,11 +311,19 @@ func DeleteIf[K comparable, V any](m map[K]V, k K, c func(K, V) bool) (old V, de
// result2 := xmap.MaxKey(map[string]int{})
// // result2.Ok() == false
func MaxKey[K constraints.Ordered, V any](m map[K]V) optional.O[union.U2[K, V]] {
- o := xiter.Max(xiter.FromMapKeys(m))
- if o.Ok() {
- return optional.FromValue(union.U2[K, V]{T1: o.Must(), T2: m[o.Must()]})
+ if len(m) == 0 {
+ return optional.Empty[union.U2[K, V]]()
}
- return optional.Empty[union.U2[K, V]]()
+ var maxK K
+ var maxV V
+ first := true
+ for k, v := range m {
+ if first || k > maxK {
+ maxK, maxV = k, v
+ first = false
+ }
+ }
+ return optional.FromValue(union.U2[K, V]{T1: maxK, T2: maxV})
}
// MinKey returns the minimum key in the map and its associated value as an optional.O[union.U2[K, V]].
@@ -317,11 +340,19 @@ func MaxKey[K constraints.Ordered, V any](m map[K]V) optional.O[union.U2[K, V]]
// result2 := xmap.MinKey(map[string]int{})
// // result2.Ok() == false
func MinKey[K constraints.Ordered, V any](m map[K]V) optional.O[union.U2[K, V]] {
- o := xiter.Min(xiter.FromMapKeys(m))
- if o.Ok() {
- return optional.FromValue(union.U2[K, V]{T1: o.Must(), T2: m[o.Must()]})
+ if len(m) == 0 {
+ return optional.Empty[union.U2[K, V]]()
}
- return optional.Empty[union.U2[K, V]]()
+ var minK K
+ var minV V
+ first := true
+ for k, v := range m {
+ if first || k < minK {
+ minK, minV = k, v
+ first = false
+ }
+ }
+ return optional.FromValue(union.U2[K, V]{T1: minK, T2: minV})
}
// MaxValue returns the maximum value in the map and its associated key as an optional.O[union.U2[K, V]].
@@ -338,10 +369,19 @@ func MinKey[K constraints.Ordered, V any](m map[K]V) optional.O[union.U2[K, V]]
// result2 := xmap.MaxValue(map[string]int{})
// // result2.Ok() == false
func MaxValue[K comparable, V constraints.Ordered](m map[K]V) optional.O[union.U2[K, V]] {
- o := xiter.MaxBy(xiter.Seq2ToSeqUnion(xiter.FromMapKeyAndValues(m)), func(a union.U2[K, V], b union.U2[K, V]) bool {
- return a.T2 < b.T2
- })
- return o
+ if len(m) == 0 {
+ return optional.Empty[union.U2[K, V]]()
+ }
+ var maxK K
+ var maxV V
+ first := true
+ for k, v := range m {
+ if first || v > maxV {
+ maxK, maxV = k, v
+ first = false
+ }
+ }
+ return optional.FromValue(union.U2[K, V]{T1: maxK, T2: maxV})
}
// MinValue returns the minimum value in the map and its associated key as an optional.O[union.U2[K, V]].
@@ -358,8 +398,17 @@ func MaxValue[K comparable, V constraints.Ordered](m map[K]V) optional.O[union.U
// result2 := xmap.MinValue(map[string]int{})
// // result2.Ok() == false
func MinValue[K comparable, V constraints.Ordered](m map[K]V) optional.O[union.U2[K, V]] {
- o := xiter.MinBy(xiter.Seq2ToSeqUnion(xiter.FromMapKeyAndValues(m)), func(a union.U2[K, V], b union.U2[K, V]) bool {
- return a.T2 < b.T2
- })
- return o
+ if len(m) == 0 {
+ return optional.Empty[union.U2[K, V]]()
+ }
+ var minK K
+ var minV V
+ first := true
+ for k, v := range m {
+ if first || v < minV {
+ minK, minV = k, v
+ first = false
+ }
+ }
+ return optional.FromValue(union.U2[K, V]{T1: minK, T2: minV})
}
diff --git a/pkg/xmap/xmap_old.go b/pkg/xmap/xmap_old.go
index 59eddef..a521494 100644
--- a/pkg/xmap/xmap_old.go
+++ b/pkg/xmap/xmap_old.go
@@ -5,11 +5,14 @@ package xmap
import (
"github.com/dashjay/xiter/pkg/union"
- "github.com/dashjay/xiter/pkg/xiter"
)
func Clone[M ~map[K]V, K comparable, V any](m M) M {
- return xiter.ToMap(xiter.FromMapKeyAndValues(m))
+ result := make(M, len(m))
+ for k, v := range m {
+ result[k] = v
+ }
+ return result
}
func Equal[M1, M2 ~map[K]V, K, V comparable](m1 M1, m2 M2) bool {
@@ -43,13 +46,25 @@ func Copy[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](dst M1, src M2) {
}
func Keys[M ~map[K]V, K comparable, V any](m M) []K {
- return xiter.ToSlice(xiter.FromMapKeys(m))
+ keys := make([]K, 0, len(m))
+ for k := range m {
+ keys = append(keys, k)
+ }
+ return keys
}
func Values[M ~map[K]V, K comparable, V any](m M) []V {
- return xiter.ToSlice(xiter.FromMapValues(m))
+ vals := make([]V, 0, len(m))
+ for _, v := range m {
+ vals = append(vals, v)
+ }
+ return vals
}
func ToUnionSlice[M ~map[K]V, K comparable, V any](m M) []union.U2[K, V] {
- return xiter.ToSlice(xiter.Seq2ToSeqUnion(xiter.FromMapKeyAndValues(m)))
+ result := make([]union.U2[K, V], 0, len(m))
+ for k, v := range m {
+ result = append(result, union.U2[K, V]{T1: k, T2: v})
+ }
+ return result
}
diff --git a/pkg/xslice/README.md b/pkg/xslice/README.md
index 4778f98..e97da29 100644
--- a/pkg/xslice/README.md
+++ b/pkg/xslice/README.md
@@ -9,6 +9,7 @@ import "github.com/dashjay/xiter/pkg/xslice"
## Index
- [func All\[T any\]\(in \[\]T, f func\(T\) bool\) bool](<#All>)
+- [func AllEqual\[T comparable\]\(in \[\]T\) bool](<#AllEqual>)
- [func Any\[T any\]\(in \[\]T, f func\(T\) bool\) bool](<#Any>)
- [func Avg\[T constraints.Number\]\(in \[\]T\) float64](<#Avg>)
- [func AvgBy\[V any, T constraints.Number\]\(in \[\]V, f func\(V\) T\) float64](<#AvgBy>)
@@ -24,12 +25,14 @@ import "github.com/dashjay/xiter/pkg/xslice"
- [func ContainsAny\[T comparable\]\(in \[\]T, v \[\]T\) bool](<#ContainsAny>)
- [func ContainsBy\[T any\]\(in \[\]T, f func\(T\) bool\) bool](<#ContainsBy>)
- [func Count\[T any\]\(in \[\]T\) int](<#Count>)
+- [func CountBy\[T any, K comparable\]\(in \[\]T, fn func\(T\) K\) map\[K\]int](<#CountBy>)
- [func Difference\[T comparable, Slice \~\[\]T\]\(left, right Slice\) \(onlyLeft, onlyRight Slice\)](<#Difference>)
- [func Filter\[T any, Slice \~\[\]T\]\(in Slice, f func\(T\) bool\) Slice](<#Filter>)
- [func Find\[T any\]\(in \[\]T, f func\(T\) bool\) \(val T, found bool\)](<#Find>)
- [func FindO\[T any\]\(in \[\]T, f func\(T\) bool\) optional.O\[T\]](<#FindO>)
- [func First\[T any, Slice \~\[\]T\]\(in Slice\) \(T, bool\)](<#First>)
- [func FirstO\[T any, Slice \~\[\]T\]\(in Slice\) optional.O\[T\]](<#FirstO>)
+- [func FlatMap\[T any, U any\]\(in \[\]T, fn func\(T\) \[\]U\) \[\]U](<#FlatMap>)
- [func Flatten\[T any\]\(in \[\]\[\]T\) \[\]T](<#Flatten>)
- [func ForEach\[T any\]\(in \[\]T, f func\(T\) bool\)](<#ForEach>)
- [func ForEachIdx\[T any\]\(in \[\]T, f func\(idx int, v T\) bool\)](<#ForEachIdx>)
@@ -39,7 +42,9 @@ import "github.com/dashjay/xiter/pkg/xslice"
- [func HeadO\[T any\]\(in \[\]T\) optional.O\[T\]](<#HeadO>)
- [func Index\[T comparable, Slice \~\[\]T\]\(in Slice, v T\) int](<#Index>)
- [func Intersect\[T comparable, Slice \~\[\]T\]\(left, right Slice\) Slice](<#Intersect>)
+- [func IsSorted\[T constraints.Ordered\]\(in \[\]T\) bool](<#IsSorted>)
- [func Join\[T \~string\]\(in \[\]T, sep T\) T](<#Join>)
+- [func KeyBy\[T any, K comparable\]\(in \[\]T, fn func\(T\) K\) map\[K\]T](<#KeyBy>)
- [func Last\[T any, Slice \~\[\]T\]\(in Slice\) \(T, bool\)](<#Last>)
- [func LastO\[T any, Slice \~\[\]T\]\(in Slice\) optional.O\[T\]](<#LastO>)
- [func Map\[T any, U any\]\(in \[\]T, f func\(T\) U\) \[\]U](<#Map>)
@@ -48,7 +53,10 @@ import "github.com/dashjay/xiter/pkg/xslice"
- [func MaxN\[T constraints.Ordered\]\(in ...T\) optional.O\[T\]](<#MaxN>)
- [func Min\[T constraints.Ordered\]\(in \[\]T\) optional.O\[T\]](<#Min>)
- [func MinBy\[T constraints.Ordered\]\(in \[\]T, f func\(T, T\) bool\) optional.O\[T\]](<#MinBy>)
+- [func MinMax\[T constraints.Ordered\]\(in \[\]T\) \(min T, max T, ok bool\)](<#MinMax>)
- [func MinN\[T constraints.Ordered\]\(in ...T\) optional.O\[T\]](<#MinN>)
+- [func Mode\[T comparable\]\(in \[\]T\) optional.O\[T\]](<#Mode>)
+- [func Partition\[T any, Slice \~\[\]T\]\(in Slice, fn func\(T\) bool\) \(yes, no Slice\)](<#Partition>)
- [func RandomElement\[T any, Slice \~\[\]T\]\(in Slice\) optional.O\[T\]](<#RandomElement>)
- [func Remove\[T comparable, Slice \~\[\]T\]\(in Slice, wantToRemove ...T\) Slice](<#Remove>)
- [func Repeat\[T any, Slice \~\[\]T\]\(in Slice, count int\) Slice](<#Repeat>)
@@ -86,6 +94,22 @@ xslice.All([]int{1, 2, 3}, func(x int) bool { return x > 0 }) 👉 true
xslice.All([]int{-1, 1, 2, 3}, func(x int) bool { return x > 0 }) 👉 false
```
+
+## func [AllEqual]()
+
+```go
+func AllEqual[T comparable](in []T) bool
+```
+
+AllEqual checks if all elements in the slice are equal. Empty and single\-element slices are considered to have all equal elements.
+
+EXAMPLE:
+
+```
+xslice.AllEqual([]int{1, 1, 1, 1}) 👉 true
+xslice.AllEqual([]int{1, 2, 1, 1}) 👉 false
+```
+
## func [Any]()
@@ -119,7 +143,7 @@ xslice.Avg([]int{}) 👉 float(0)
```
-## func [AvgBy]()
+## func [AvgBy]()
```go
func AvgBy[V any, T constraints.Number](in []V, f func(V) T) float64
@@ -137,7 +161,7 @@ xslice.AvgBy([]string{"1", "2", "3"}, func(x string) int {
```
-## func [AvgN]()
+## func [AvgN]()
```go
func AvgN[T constraints.Number](inputs ...T) float64
@@ -153,7 +177,7 @@ xslice.AvgN() 👉 float(0)
```
-## func [Chunk]()
+## func [Chunk]()
```go
func Chunk[T any, Slice ~[]T](in Slice, chunkSize int) []Slice
@@ -170,7 +194,7 @@ xslice.Chunk([]int{1, 2, 3, 4, 5}, 0) 👉 []int{}
```
-## func [ChunkInPlace]()
+## func [ChunkInPlace]()
```go
func ChunkInPlace[T any, Slice ~[]T](in Slice, chunkSize int) []Slice
@@ -185,7 +209,7 @@ xslice.Chunk([]int{1, 2, 3, 4, 5}, 0) 👉 []int{}
```
-## func [Clone]()
+## func [Clone]()
```go
func Clone[T any](in []T) []T
@@ -200,7 +224,7 @@ xslice.Clone([]int{1, 2, 3}) 👉 [1, 2, 3]
```
-## func [CloneBy]()
+## func [CloneBy]()
```go
func CloneBy[T any, U any](in []T, f func(T) U) []U
@@ -216,7 +240,7 @@ xslice.CloneBy([]int{1, 2, 3}, strconv.Itoa) 👉 ["1", "2", "3"]
```
-## func [Compact]()
+## func [Compact]()
```go
func Compact[T comparable, Slice ~[]T](in Slice) Slice
@@ -231,7 +255,7 @@ xslice.Compact([]int{0, 1, 2, 3, 4}) 👉 [1 2 3 4]
```
-## func [Concat]()
+## func [Concat]()
```go
func Concat[T any](vs ...[]T) []T
@@ -247,7 +271,7 @@ xslice.Concat([]int{1, 2, 3}, []int{}) 👉 [1, 2, 3]
```
-## func [Contains]()
+## func [Contains]()
```go
func Contains[T comparable](in []T, v T) bool
@@ -263,7 +287,7 @@ xslice.Contains([]int{-1, 2, 3}, 1) 👉 false
```
-## func [ContainsAll]()
+## func [ContainsAll]()
```go
func ContainsAll[T comparable](in []T, v []T) bool
@@ -280,7 +304,7 @@ xslice.ContainsAll([]string{"1", "2", "3"}, []string{}) 👉 true
```
-## func [ContainsAny]()
+## func [ContainsAny]()
```go
func ContainsAny[T comparable](in []T, v []T) bool
@@ -297,7 +321,7 @@ xslice.ContainsAny([]string{"1", "2", "3"}, []string{}) 👉 false
```
-## func [ContainsBy]()
+## func [ContainsBy]()
```go
func ContainsBy[T any](in []T, f func(T) bool) bool
@@ -320,7 +344,7 @@ xslice.ContainsBy([]string{"1", "2", "3"}, func(x string) bool {
```
-## func [Count]()
+## func [Count]()
```go
func Count[T any](in []T) int
@@ -335,8 +359,24 @@ xslice.Count([]int{1, 2, 3}) 👉 3
xslice.Count([]int{}) 👉 0
```
+
+## func [CountBy]()
+
+```go
+func CountBy[T any, K comparable](in []T, fn func(T) K) map[K]int
+```
+
+CountBy counts occurrences of each key in the slice, returning a map of keys to counts.
+
+EXAMPLE:
+
+```
+xslice.CountBy([]int{1, 2, 3, 2, 1, 2}, func(x int) int { return x })
+// 👉 map[int]int{1: 2, 2: 3, 3: 1}
+```
+
-## func [Difference]()
+## func [Difference]()
```go
func Difference[T comparable, Slice ~[]T](left, right Slice) (onlyLeft, onlyRight Slice)
@@ -355,7 +395,7 @@ fmt.Println(onlyRight) // [6 7 8]
```
-## func [Filter]()
+## func [Filter]()
```go
func Filter[T any, Slice ~[]T](in Slice, f func(T) bool) Slice
@@ -370,7 +410,7 @@ xslice.Filter([]int{1, 2, 3, 2, 4}, func(x int) bool { return x%2 == 0 }) 👉 [
```
-## func [Find]()
+## func [Find]()
```go
func Find[T any](in []T, f func(T) bool) (val T, found bool)
@@ -386,7 +426,7 @@ xslice.Find([]int{1, 2, 3}, func(x int) bool { return x == -1 }) 👉 0, false
```
-## func [FindO]()
+## func [FindO]()
```go
func FindO[T any](in []T, f func(T) bool) optional.O[T]
@@ -402,7 +442,7 @@ xslice.FindO(_range(0, 10), func(x int) bool { return x == -1 }).Ok() 👉 false
```
-## func [First]()
+## func [First]()
```go
func First[T any, Slice ~[]T](in Slice) (T, bool)
@@ -416,7 +456,7 @@ xslice.First([]int{}) 👉 0
```
-## func [FirstO]()
+## func [FirstO]()
```go
func FirstO[T any, Slice ~[]T](in Slice) optional.O[T]
@@ -429,8 +469,24 @@ xslice.FirstO([]int{1, 2, 3}) 👉 1
xslice.FirstO([]int{}) 👉 0
```
+
+## func [FlatMap]()
+
+```go
+func FlatMap[T any, U any](in []T, fn func(T) []U) []U
+```
+
+FlatMap maps each element to a slice and flattens the results into a single slice.
+
+EXAMPLE:
+
+```
+xslice.FlatMap([]int{1, 2, 3}, func(x int) []int { return []int{x, x * 10} })
+// 👉 []int{1, 10, 2, 20, 3, 30}
+```
+
-## func [Flatten]()
+## func [Flatten]()
```go
func Flatten[T any](in [][]T) []T
@@ -448,7 +504,7 @@ xslice.Flatten([][]int{{}, {}, {}}) 👉 []int{}
```
-## func [ForEach]()
+## func [ForEach]()
```go
func ForEach[T any](in []T, f func(T) bool)
@@ -470,7 +526,7 @@ Output:
```
-## func [ForEachIdx]()
+## func [ForEachIdx]()
```go
func ForEachIdx[T any](in []T, f func(idx int, v T) bool)
@@ -492,7 +548,7 @@ Output:
```
-## func [GroupBy]()
+## func [GroupBy]()
```go
func GroupBy[T any, K comparable, Slice ~[]T](in Slice, f func(T) K) map[K]Slice
@@ -507,7 +563,7 @@ xslice.GroupBy([]int{1, 2, 3, 2, 4}, func(x int) int { return x % 2 }) 👉 map[
```
-## func [GroupByMap]()
+## func [GroupByMap]()
```go
func GroupByMap[T any, Slice ~[]T, K comparable, V any](in Slice, f func(T) (K, V)) map[K][]V
@@ -522,7 +578,7 @@ xslice.GroupByMap([]int{1, 2, 3, 2, 4}, func(x int) (int, int) { return x % 2, x
```
-## func [Head]()
+## func [Head]()
```go
func Head[T any](in []T) (v T, hasOne bool)
@@ -538,7 +594,7 @@ optional.FromValue2(xslice.Head(_range(0, 0))).Ok() 👉 false
```
-## func [HeadO]()
+## func [HeadO]()
```go
func HeadO[T any](in []T) optional.O[T]
@@ -554,7 +610,7 @@ xslice.HeadO(_range(0, 0)).Ok() 👉 false
```
-## func [Index]()
+## func [Index]()
```go
func Index[T comparable, Slice ~[]T](in Slice, v T) int
@@ -569,7 +625,7 @@ xslice.Index([]int{1, 2, 3, 4, 5}, 666) 👉 -1
```
-## func [Intersect]()
+## func [Intersect]()
```go
func Intersect[T comparable, Slice ~[]T](left, right Slice) Slice
@@ -586,8 +642,24 @@ intersect := xslice.Intersect(left, right)
fmt.Println(intersect) // [4 5]
```
+
+## func [IsSorted]()
+
+```go
+func IsSorted[T constraints.Ordered](in []T) bool
+```
+
+IsSorted checks if the slice is sorted in ascending order. Empty and single\-element slices are considered sorted.
+
+EXAMPLE:
+
+```
+xslice.IsSorted([]int{1, 2, 3, 4}) 👉 true
+xslice.IsSorted([]int{1, 3, 2, 4}) 👉 false
+```
+
-## func [Join]()
+## func [Join]()
```go
func Join[T ~string](in []T, sep T) T
@@ -602,8 +674,24 @@ xslice.Join([]string{"1", "2", "3"}, ".") 👉 "1.2.3"
xslice.Join([]string{}, ".") 👉 ""
```
+
+## func [KeyBy]()
+
+```go
+func KeyBy[T any, K comparable](in []T, fn func(T) K) map[K]T
+```
+
+KeyBy creates a map from the slice using the key function. Later elements overwrite earlier ones for duplicate keys.
+
+EXAMPLE:
+
+```
+xslice.KeyBy([]int{1, 2, 3}, func(x int) int { return x * 10 })
+// 👉 map[int]int{10: 1, 20: 2, 30: 3}
+```
+
-## func [Last]()
+## func [Last]()
```go
func Last[T any, Slice ~[]T](in Slice) (T, bool)
@@ -617,7 +705,7 @@ xslice.Last([]int{}) 👉 0
```
-## func [LastO]()
+## func [LastO]()
```go
func LastO[T any, Slice ~[]T](in Slice) optional.O[T]
@@ -631,7 +719,7 @@ xslice.LastO([]int{}) 👉 0
```
-## func [Map]()
+## func [Map]()
```go
func Map[T any, U any](in []T, f func(T) U) []U
@@ -647,7 +735,7 @@ xslice.Map([]int{1, 2, 3}, strconv.Itoa) 👉 ["1", "2", "3"]
```
-## func [Max]()
+## func [Max]()
```go
func Max[T constraints.Ordered](in []T) optional.O[T]
@@ -663,7 +751,7 @@ xslice.Max([]int{}) 👉 0
```
-## func [MaxBy]()
+## func [MaxBy]()
```go
func MaxBy[T constraints.Ordered](in []T, f func(T, T) bool) optional.O[T]
@@ -678,7 +766,7 @@ xslice.MaxBy([]int{1, 2, 3} /*less = */, func(a, b int) bool { return a > b }).M
```
-## func [MaxN]()
+## func [MaxN]()
```go
func MaxN[T constraints.Ordered](in ...T) optional.O[T]
@@ -693,7 +781,7 @@ xslice.MaxN(1, 2, 3) 👉 3
```
-## func [Min]()
+## func [Min]()
```go
func Min[T constraints.Ordered](in []T) optional.O[T]
@@ -709,7 +797,7 @@ xslice.Min([]int{}) 👉 0
```
-## func [MinBy]()
+## func [MinBy]()
```go
func MinBy[T constraints.Ordered](in []T, f func(T, T) bool) optional.O[T]
@@ -723,8 +811,24 @@ EXAMPLE:
xslice.MinBy([]int{3, 2, 1} /*less = */, func(a, b int) bool { return a > b }).Must() 👉 3
```
+
+## func [MinMax]()
+
+```go
+func MinMax[T constraints.Ordered](in []T) (min T, max T, ok bool)
+```
+
+MinMax returns the minimum and maximum elements in the slice in a single pass.
+
+EXAMPLE:
+
+```
+xslice.MinMax([]int{3, 1, 4, 1, 5, 9}) 👉 (1, 9, true)
+xslice.MinMax([]int{}) 👉 (0, 0, false)
+```
+
-## func [MinN]()
+## func [MinN]()
```go
func MinN[T constraints.Ordered](in ...T) optional.O[T]
@@ -738,8 +842,40 @@ EXAMPLE:
xslice.MinN(1, 2, 3) 👉 1
```
+
+## func [Mode]()
+
+```go
+func Mode[T comparable](in []T) optional.O[T]
+```
+
+Mode returns the most frequently occurring element in the slice. If the slice is empty, it returns an empty optional. If there are multiple modes \(tie\), the first one to reach the maximum count is returned.
+
+EXAMPLE:
+
+```
+xslice.Mode([]int{1, 2, 3, 2, 1, 2}) 👉 2
+xslice.Mode([]int{}) 👉 optional.Empty[int]()
+```
+
+
+## func [Partition]()
+
+```go
+func Partition[T any, Slice ~[]T](in Slice, fn func(T) bool) (yes, no Slice)
+```
+
+Partition splits a slice into two slices based on a predicate. The first return slice contains elements where fn returns true.
+
+EXAMPLE:
+
+```
+yes, no := xslice.Partition([]int{1, 2, 3, 4, 5}, func(x int) bool { return x%2 == 0 })
+// yes 👉 []int{2, 4}, no 👉 []int{1, 3, 5}
+```
+
-## func [RandomElement]()
+## func [RandomElement]()
```go
func RandomElement[T any, Slice ~[]T](in Slice) optional.O[T]
@@ -756,7 +892,7 @@ xslice.RandomElement([]int{}).Ok() 👉 false
```
-## func [Remove]()
+## func [Remove]()
```go
func Remove[T comparable, Slice ~[]T](in Slice, wantToRemove ...T) Slice
@@ -773,7 +909,7 @@ fmt.Println(arr1) // [2, 3, 4]
```
-## func [Repeat]()
+## func [Repeat]()
```go
func Repeat[T any, Slice ~[]T](in Slice, count int) Slice
@@ -789,7 +925,7 @@ xslice.Repeat([]int{1, 2, 3}, 0) 👉 []int{}
```
-## func [RepeatBy]()
+## func [RepeatBy]()
```go
func RepeatBy[T any](n int, f func(i int) T) []T
@@ -805,7 +941,7 @@ xslice.RepeatBy(3, func(i int) string { return strconv.Itoa(i) }) 👉 []string{
```
-## func [Replace]()
+## func [Replace]()
```go
func Replace[T comparable, Slice ~[]T](in Slice, from, to T, count int) []T
@@ -821,7 +957,7 @@ xslice.Replace([]int{1, 2, 2}, 2, 4, -1) 👉 [1, 4, 4]
```
-## func [ReplaceAll]()
+## func [ReplaceAll]()
```go
func ReplaceAll[T comparable, Slice ~[]T](in Slice, from, to T) []T
@@ -837,7 +973,7 @@ xslice.ReplaceAll([]int{1, 2, 2}, 2, 4) 👉 [1, 4, 4]
```
-## func [Reverse]()
+## func [Reverse]()
```go
func Reverse[T any, Slice ~[]T](in Slice)
@@ -853,7 +989,7 @@ xslice.Reverse([]int{}) 👉 []int{}
```
-## func [ReverseClone]()
+## func [ReverseClone]()
```go
func ReverseClone[T any, Slice ~[]T](in Slice) Slice
@@ -870,7 +1006,7 @@ xslice.ReverseClone([]int{3, 2, 1}) 👉 [1, 2, 3]
```
-## func [Sample]()
+## func [Sample]()
```go
func Sample[T any, Slice ~[]T](in Slice, n int) Slice
@@ -888,7 +1024,7 @@ xslice.Sample([]int{}, 3) 👉 []int{}
```
-## func [Shuffle]()
+## func [Shuffle]()
```go
func Shuffle[T any, Slice ~[]T](in Slice) Slice
@@ -904,7 +1040,7 @@ xslice.Shuffle([]int{}) 👉 []int{}
```
-## func [ShuffleInPlace]()
+## func [ShuffleInPlace]()
```go
func ShuffleInPlace[T any, Slice ~[]T](in Slice)
@@ -920,7 +1056,7 @@ xslice.ShuffleInPlace(array) 👉 [2, 1, 3] (random)
```
-## func [Subset]()
+## func [Subset]()
```go
func Subset[T any, Slice ~[]T](in Slice, start, count int) Slice
@@ -936,7 +1072,7 @@ xslice.Subset([]int{1, 2, 3}, -1, 2) 👉 [2, 3]
```
-## func [SubsetInPlace]()
+## func [SubsetInPlace]()
```go
func SubsetInPlace[T any, Slice ~[]T](in Slice, start int, count int) Slice
@@ -950,7 +1086,7 @@ xslice.SubsetInPlace([]int{1, 2, 3}, -1, 2) 👉 [2, 3]
```
-## func [Sum]()
+## func [Sum]()
```go
func Sum[T constraints.Number, Slice ~[]T](in Slice) T
@@ -966,7 +1102,7 @@ xslice.Sum([]int{}) 👉 0
```
-## func [SumBy]()
+## func [SumBy]()
```go
func SumBy[T any, R constraints.Number, Slice ~[]T](in Slice, f func(T) R) R
@@ -985,7 +1121,7 @@ xslice.SumBy([]string{}, func(x string) int { return 0 }) 👉 0
```
-## func [SumN]()
+## func [SumN]()
```go
func SumN[T constraints.Number](in ...T) T
@@ -1001,7 +1137,7 @@ xslice.SumN() 👉 0
```
-## func [ToMap]()
+## func [ToMap]()
```go
func ToMap[T comparable, U any](in []T, f func(T) U) map[T]U
@@ -1019,7 +1155,7 @@ xslice.ToMap([]int{}, func(i int) string { return "" }) 👉 map[int]string{}
```
-## func [Union]()
+## func [Union]()
```go
func Union[T comparable, Slice ~[]T](left, right Slice) Slice
@@ -1037,7 +1173,7 @@ fmt.Println(union) // [1 2 3 4 5 6]
```
-## func [Uniq]()
+## func [Uniq]()
```go
func Uniq[T comparable, Slice ~[]T](in Slice) Slice
diff --git a/pkg/xslice/xslice.go b/pkg/xslice/xslice.go
index 910826c..1375170 100644
--- a/pkg/xslice/xslice.go
+++ b/pkg/xslice/xslice.go
@@ -38,7 +38,14 @@ func Any[T any](in []T, f func(T) bool) bool {
// xslice.Avg([]int{1, 2, 3}) 👉 float(2)
// xslice.Avg([]int{}) 👉 float(0)
func Avg[T constraints.Number](in []T) float64 {
- return xiter.AvgFromSeq(xiter.FromSlice(in))
+ if len(in) == 0 {
+ return 0
+ }
+ var sum T
+ for _, v := range in {
+ sum += v
+ }
+ return float64(sum) / float64(len(in))
}
// AvgN returns the average value of the items
@@ -48,7 +55,7 @@ func Avg[T constraints.Number](in []T) float64 {
// xslice.AvgN(1, 2, 3) 👉 float(2)
// xslice.AvgN() 👉 float(0)
func AvgN[T constraints.Number](inputs ...T) float64 {
- return xiter.AvgFromSeq(xiter.FromSlice(inputs))
+ return Avg(inputs)
}
// AvgBy returns the averaged of each item's value evaluated by f.
@@ -60,7 +67,14 @@ func AvgN[T constraints.Number](inputs ...T) float64 {
// return i
// }) 👉 float(2)
func AvgBy[V any, T constraints.Number](in []V, f func(V) T) float64 {
- return xiter.AvgByFromSeq(xiter.FromSlice(in), f)
+ if len(in) == 0 {
+ return 0
+ }
+ var sum T
+ for _, v := range in {
+ sum += f(v)
+ }
+ return float64(sum) / float64(len(in))
}
// Contains returns true if the slice contains the value v.
@@ -119,7 +133,7 @@ func ContainsAll[T comparable](in []T, v []T) bool {
// xslice.Count([]int{1, 2, 3}) 👉 3
// xslice.Count([]int{}) 👉 0
func Count[T any](in []T) int {
- return xiter.Count(xiter.FromSlice(in))
+ return len(in)
}
// Find returns the first item in the slice that satisfies the condition provided by f.
@@ -201,7 +215,23 @@ func Head[T any](in []T) (v T, hasOne bool) {
// xslice.Join([]string{"1", "2", "3"}, ".") 👉 "1.2.3"
// xslice.Join([]string{}, ".") 👉 ""
func Join[T ~string](in []T, sep T) T {
- return xiter.Join(xiter.FromSlice(in), sep)
+ if len(in) == 0 {
+ return ""
+ }
+ if len(in) == 1 {
+ return in[0]
+ }
+ n := len(sep) * (len(in) - 1)
+ for _, s := range in {
+ n += len(s)
+ }
+ b := make([]byte, n)
+ bp := copy(b, string(in[0]))
+ for _, s := range in[1:] {
+ bp += copy(b[bp:], string(sep))
+ bp += copy(b[bp:], string(s))
+ }
+ return T(string(b))
}
// Min returns the minimum value in the slice.
@@ -211,7 +241,16 @@ func Join[T ~string](in []T, sep T) T {
// xslice.Min([]int{1, 2, 3}) 👉 1
// xslice.Min([]int{}) 👉 0
func Min[T constraints.Ordered](in []T) optional.O[T] {
- return xiter.Min(xiter.FromSlice(in))
+ if len(in) == 0 {
+ return optional.Empty[T]()
+ }
+ m := in[0]
+ for _, v := range in[1:] {
+ if v < m {
+ m = v
+ }
+ }
+ return optional.FromValue(m)
}
// MinN returns the minimum value in the slice.
@@ -229,7 +268,16 @@ func MinN[T constraints.Ordered](in ...T) optional.O[T] {
//
// xslice.MinBy([]int{3, 2, 1} /*less = */, func(a, b int) bool { return a > b }).Must() 👉 3
func MinBy[T constraints.Ordered](in []T, f func(T, T) bool) optional.O[T] {
- return xiter.MinBy(xiter.FromSlice(in), f)
+ if len(in) == 0 {
+ return optional.Empty[T]()
+ }
+ m := in[0]
+ for _, v := range in[1:] {
+ if f(v, m) {
+ m = v
+ }
+ }
+ return optional.FromValue(m)
}
// Max returns the maximum value in the slice.
@@ -239,7 +287,16 @@ func MinBy[T constraints.Ordered](in []T, f func(T, T) bool) optional.O[T] {
// xslice.Max([]int{1, 2, 3}) 👉 3
// xslice.Max([]int{}) 👉 0
func Max[T constraints.Ordered](in []T) optional.O[T] {
- return xiter.Max(xiter.FromSlice(in))
+ if len(in) == 0 {
+ return optional.Empty[T]()
+ }
+ m := in[0]
+ for _, v := range in[1:] {
+ if v > m {
+ m = v
+ }
+ }
+ return optional.FromValue(m)
}
// MaxN returns the maximum value in the slice.
@@ -257,7 +314,16 @@ func MaxN[T constraints.Ordered](in ...T) optional.O[T] {
//
// xslice.MaxBy([]int{1, 2, 3} /*less = */, func(a, b int) bool { return a > b }).Must() 👉 1
func MaxBy[T constraints.Ordered](in []T, f func(T, T) bool) optional.O[T] {
- return xiter.MaxBy(xiter.FromSlice(in), f)
+ if len(in) == 0 {
+ return optional.Empty[T]()
+ }
+ m := in[0]
+ for _, v := range in[1:] {
+ if f(m, v) {
+ m = v
+ }
+ }
+ return optional.FromValue(m)
}
// Map returns a new slice with the results of applying the given function to every element in this slice.
@@ -267,7 +333,11 @@ func MaxBy[T constraints.Ordered](in []T, f func(T, T) bool) optional.O[T] {
// xslice.Map([]int{1, 2, 3}, func(x int) int { return x * 2 }) 👉 [2, 4, 6]
// xslice.Map([]int{1, 2, 3}, strconv.Itoa) 👉 ["1", "2", "3"]
func Map[T any, U any](in []T, f func(T) U) []U {
- return xiter.ToSlice(xiter.Map(f, xiter.FromSlice(in)))
+ out := make([]U, len(in))
+ for i, v := range in {
+ out[i] = f(v)
+ }
+ return out
}
// Clone returns a copy of the slice.
@@ -276,10 +346,7 @@ func Map[T any, U any](in []T, f func(T) U) []U {
//
// xslice.Clone([]int{1, 2, 3}) 👉 [1, 2, 3]
func Clone[T any](in []T) []T {
- if in == nil {
- return nil
- }
- return xiter.ToSlice(xiter.FromSlice(in))
+ return append([]T(nil), in...)
}
// CloneBy returns a copy of the slice with the results of applying the given function to every element in this slice.
@@ -302,11 +369,15 @@ func CloneBy[T any, U any](in []T, f func(T) U) []U {
// xslice.Concat([]int{1, 2, 3}, []int{4, 5, 6}) 👉 [1, 2, 3, 4, 5, 6]
// xslice.Concat([]int{1, 2, 3}, []int{}) 👉 [1, 2, 3]
func Concat[T any](vs ...[]T) []T {
- var seqs = make([]xiter.Seq[T], 0, len(vs))
+ n := 0
+ for _, v := range vs {
+ n += len(v)
+ }
+ out := make([]T, 0, n)
for _, v := range vs {
- seqs = append(seqs, xiter.FromSlice(v))
+ out = append(out, v...)
}
- return xiter.ToSlice(xiter.Concat(seqs...))
+ return out
}
// Subset returns a subset slice from the slice.
@@ -320,14 +391,21 @@ func Subset[T any, Slice ~[]T](in Slice, start, count int) Slice {
if count < 0 {
count = 0
}
- if start >= len(in) || -start > len(in) {
+ n := len(in)
+ if start >= n || -start > n {
return nil
}
- if start >= 0 {
- return xiter.ToSlice(xiter.Limit(xiter.Skip(xiter.FromSlice(in), start), count))
- } else {
- return xiter.ToSlice(xiter.Limit(xiter.Skip(xiter.FromSlice(in), len(in)+start), count))
+ if start < 0 {
+ start = n + start
+ }
+ if start+count > n {
+ count = n - start
+ }
+ out := make(Slice, count)
+ for i := 0; i < count; i++ {
+ out[i] = in[start+i]
}
+ return out
}
// SubsetInPlace returns a subset slice copied from the slice.
@@ -362,7 +440,21 @@ func SubsetInPlace[T any, Slice ~[]T](in Slice, start int, count int) Slice {
// xslice.Replace([]int{1, 2, 3}, 2, 4, 1) 👉 [1, 4, 3]
// xslice.Replace([]int{1, 2, 2}, 2, 4, -1) 👉 [1, 4, 4]
func Replace[T comparable, Slice ~[]T](in Slice, from, to T, count int) []T {
- return xiter.ToSlice(xiter.Replace(xiter.FromSlice(in), from, to, count))
+ if count == 0 {
+ return Clone(in)
+ }
+ out := Clone(in)
+ replaced := 0
+ for i, v := range out {
+ if v == from {
+ out[i] = to
+ replaced++
+ if count > 0 && replaced >= count {
+ break
+ }
+ }
+ }
+ return out
}
// ReplaceAll replaces all elements in the slice from 'from' to 'to'.
@@ -383,9 +475,11 @@ func ReplaceAll[T comparable, Slice ~[]T](in Slice, from, to T) []T {
// xslice.ReverseClone([]int{}) 👉 []int{}
// xslice.ReverseClone([]int{3, 2, 1}) 👉 [1, 2, 3]
func ReverseClone[T any, Slice ~[]T](in Slice) Slice {
- // why we do not use slices.Reverse() directly ?
- // because lower version golang may has not package "slices"
- return xiter.ToSlice(xiter.FromSliceReverse(in))
+ out := make(Slice, len(in))
+ for i, v := range in {
+ out[len(in)-1-i] = v
+ }
+ return out
}
// Reverse reverses the slice.
@@ -407,7 +501,14 @@ func Reverse[T any, Slice ~[]T](in Slice) {
// xslice.Repeat([]int{1, 2, 3}, 3) 👉 [1, 2, 3, 1, 2, 3, 1, 2, 3]
// xslice.Repeat([]int{1, 2, 3}, 0) 👉 []int{}
func Repeat[T any, Slice ~[]T](in Slice, count int) Slice {
- return xiter.ToSlice(xiter.Repeat(xiter.FromSlice(in), count))
+ if count <= 0 {
+ return nil
+ }
+ out := make(Slice, 0, len(in)*count)
+ for i := 0; i < count; i++ {
+ out = append(out, in...)
+ }
+ return out
}
// RepeatBy returns a new slice with the elements return by f repeated 'count' times.
@@ -431,7 +532,12 @@ func RepeatBy[T any](n int, f func(i int) T) []T {
// xslice.Shuffle([]int{1, 2, 3}) 👉 [2, 1, 3] (random)
// xslice.Shuffle([]int{}) 👉 []int{}
func Shuffle[T any, Slice ~[]T](in Slice) Slice {
- return xiter.ToSlice(xiter.FromSliceShuffle(in))
+ out := make(Slice, len(in))
+ copy(out, in)
+ rand.Shuffle(len(out), func(i, j int) {
+ out[i], out[j] = out[j], out[i]
+ })
+ return out
}
// ShuffleInPlace shuffles the slice.
@@ -457,14 +563,14 @@ func ShuffleInPlace[T any, Slice ~[]T](in Slice) {
// xslice.Chunk([]int{1, 2, 3, 4, 5}, 0) 👉 []int{}
func Chunk[T any, Slice ~[]T](in Slice, chunkSize int) []Slice {
xassert.MustBePositive(chunkSize)
- out := make([]Slice, 0, len(in)/chunkSize+1)
- seq := xiter.FromSlice(in)
- for {
- res := xiter.ToSlice(xiter.Limit(xiter.Skip(seq, len(out)*chunkSize), chunkSize))
- if len(res) == 0 {
- break
+ n := len(in)
+ out := make([]Slice, 0, n/chunkSize+1)
+ for i := 0; i < n; i += chunkSize {
+ end := i + chunkSize
+ if end > n {
+ end = n
}
- out = append(out, res)
+ out = append(out, append(Slice(nil), in[i:end]...))
}
return out
}
@@ -507,7 +613,11 @@ func Index[T comparable, Slice ~[]T](in Slice, v T) int {
// xslice.Sum([]int{1, 2, 3}) 👉 6
// xslice.Sum([]int{}) 👉 0
func Sum[T constraints.Number, Slice ~[]T](in Slice) T {
- return xiter.Sum(xiter.FromSlice(in))
+ var sum T
+ for _, v := range in {
+ sum += v
+ }
+ return sum
}
// SumN returns the sum of all input arguments.
@@ -517,7 +627,7 @@ func Sum[T constraints.Number, Slice ~[]T](in Slice) T {
// xslice.SumN(1, 2, 3) 👉 6
// xslice.SumN() 👉 0
func SumN[T constraints.Number](in ...T) T {
- return xiter.Sum(xiter.FromSlice(in))
+ return Sum(in)
}
// SumBy returns the sum of all elements in the slice after applying the given function f to each element.
@@ -530,7 +640,11 @@ func SumN[T constraints.Number](in ...T) T {
// }) 👉 6
// xslice.SumBy([]string{}, func(x string) int { return 0 }) 👉 0
func SumBy[T any, R constraints.Number, Slice ~[]T](in Slice, f func(T) R) R {
- return xiter.Sum(xiter.Map(f, xiter.FromSlice(in)))
+ var sum R
+ for _, v := range in {
+ sum += f(v)
+ }
+ return sum
}
// Uniq returns a new slice with the duplicate elements removed.
@@ -539,7 +653,15 @@ func SumBy[T any, R constraints.Number, Slice ~[]T](in Slice, f func(T) R) R {
//
// xslice.Uniq([]int{1, 2, 3, 2, 4}) 👉 [1, 2, 3, 4]
func Uniq[T comparable, Slice ~[]T](in Slice) Slice {
- return xiter.ToSlice(xiter.Uniq(xiter.FromSlice(in)))
+ seen := make(map[T]struct{}, len(in)/2)
+ out := make(Slice, 0, len(in))
+ for _, v := range in {
+ if _, ok := seen[v]; !ok {
+ seen[v] = struct{}{}
+ out = append(out, v)
+ }
+ }
+ return out
}
// GroupBy returns a map of the slice elements grouped by the given function f.
@@ -548,11 +670,12 @@ func Uniq[T comparable, Slice ~[]T](in Slice) Slice {
//
// xslice.GroupBy([]int{1, 2, 3, 2, 4}, func(x int) int { return x % 2 }) 👉 map[0:[2 4] 1:[1 3]]
func GroupBy[T any, K comparable, Slice ~[]T](in Slice, f func(T) K) map[K]Slice {
- seq2 := xiter.MapToSeq2(xiter.FromSlice(in), f)
- return xiter.Reduce2(func(sum map[K]Slice, k K, v T) map[K]Slice {
- sum[k] = append(sum[k], v)
- return sum
- }, map[K]Slice{}, seq2)
+ out := make(map[K]Slice, len(in)/2)
+ for _, v := range in {
+ k := f(v)
+ out[k] = append(out[k], v)
+ }
+ return out
}
// GroupByMap returns a map of the slice elements grouped by the given function f.
@@ -561,11 +684,12 @@ func GroupBy[T any, K comparable, Slice ~[]T](in Slice, f func(T) K) map[K]Slice
//
// xslice.GroupByMap([]int{1, 2, 3, 2, 4}, func(x int) (int, int) { return x % 2, x }) 👉 map[0:[2 4] 1:[1 3]]
func GroupByMap[T any, Slice ~[]T, K comparable, V any](in Slice, f func(T) (K, V)) map[K][]V {
- seq2 := xiter.MapToSeq2Value(xiter.FromSlice(in), f)
- return xiter.Reduce2(func(sum map[K][]V, k K, v V) map[K][]V {
- sum[k] = append(sum[k], v)
- return sum
- }, map[K][]V{}, seq2)
+ out := make(map[K][]V, len(in)/2)
+ for _, v := range in {
+ k, mv := f(v)
+ out[k] = append(out[k], mv)
+ }
+ return out
}
// Filter returns a new slice with the elements that satisfy the given function f.
@@ -574,7 +698,13 @@ func GroupByMap[T any, Slice ~[]T, K comparable, V any](in Slice, f func(T) (K,
//
// xslice.Filter([]int{1, 2, 3, 2, 4}, func(x int) bool { return x%2 == 0 }) 👉 [2 4]
func Filter[T any, Slice ~[]T](in Slice, f func(T) bool) Slice {
- return xiter.ToSlice(xiter.Filter(f, xiter.FromSlice(in)))
+ out := make(Slice, 0, len(in)/2)
+ for _, v := range in {
+ if f(v) {
+ out = append(out, v)
+ }
+ }
+ return out
}
// Compact returns a new slice with the zero elements removed.
@@ -583,7 +713,14 @@ func Filter[T any, Slice ~[]T](in Slice, f func(T) bool) Slice {
//
// xslice.Compact([]int{0, 1, 2, 3, 4}) 👉 [1 2 3 4]
func Compact[T comparable, Slice ~[]T](in Slice) Slice {
- return xiter.ToSlice(xiter.Compact(xiter.FromSlice(in)))
+ var zero T
+ out := make(Slice, 0, len(in))
+ for _, v := range in {
+ if v != zero {
+ out = append(out, v)
+ }
+ }
+ return out
}
// First returns the first element in the slice.
@@ -613,7 +750,11 @@ func FirstO[T any, Slice ~[]T](in Slice) optional.O[T] {
// xslice.Last([]int{1, 2, 3}) 👉 3
// xslice.Last([]int{}) 👉 0
func Last[T any, Slice ~[]T](in Slice) (T, bool) {
- return xiter.Last(xiter.FromSlice(in))
+ if len(in) == 0 {
+ var zero T
+ return zero, false
+ }
+ return in[len(in)-1], true
}
// LastO returns the last element in the slice as an optional.O[T].
@@ -637,8 +778,27 @@ func LastO[T any, Slice ~[]T](in Slice) optional.O[T] {
// fmt.Println(onlyLeft) // [1 2 3]
// fmt.Println(onlyRight) // [6 7 8]
func Difference[T comparable, Slice ~[]T](left, right Slice) (onlyLeft, onlyRight Slice) {
- onlyLeftSeq, onlyRightSeq := xiter.Difference(xiter.FromSlice(left), xiter.FromSlice(right))
- return xiter.ToSlice(onlyLeftSeq), xiter.ToSlice(onlyRightSeq)
+ rightSet := make(map[T]struct{}, len(right))
+ for _, v := range right {
+ rightSet[v] = struct{}{}
+ }
+ leftSet := make(map[T]struct{}, len(left))
+ for _, v := range left {
+ leftSet[v] = struct{}{}
+ }
+ onlyLeft = make(Slice, 0, len(left)/2)
+ for _, v := range left {
+ if _, ok := rightSet[v]; !ok {
+ onlyLeft = append(onlyLeft, v)
+ }
+ }
+ onlyRight = make(Slice, 0, len(right)/2)
+ for _, v := range right {
+ if _, ok := leftSet[v]; !ok {
+ onlyRight = append(onlyRight, v)
+ }
+ }
+ return
}
// Intersect returns a slice that contains the elements that are in both left and right slices.
@@ -656,7 +816,17 @@ func Intersect[T comparable, Slice ~[]T](left, right Slice) Slice {
} else {
smaller, larger = left, right
}
- return xiter.ToSlice(xiter.Intersect(xiter.FromSlice(smaller), xiter.FromSlice(larger)))
+ set := make(map[T]struct{}, len(smaller))
+ for _, v := range smaller {
+ set[v] = struct{}{}
+ }
+ out := make(Slice, 0, len(smaller))
+ for _, v := range larger {
+ if _, ok := set[v]; ok {
+ out = append(out, v)
+ }
+ }
+ return out
}
// Union returns a slice that contains all elements in left and right slices.
@@ -674,7 +844,18 @@ func Union[T comparable, Slice ~[]T](left, right Slice) Slice {
} else {
smaller, larger = right, left
}
- return xiter.ToSlice(xiter.Union(xiter.FromSlice(smaller), xiter.FromSlice(larger)))
+ set := make(map[T]struct{}, len(smaller))
+ for _, v := range smaller {
+ set[v] = struct{}{}
+ }
+ out := make(Slice, 0, len(smaller)+len(larger)/2)
+ out = append(out, smaller...)
+ for _, v := range larger {
+ if _, ok := set[v]; !ok {
+ out = append(out, v)
+ }
+ }
+ return out
}
// Remove returns a slice that remove all elements in wantToRemove
@@ -685,16 +866,20 @@ func Union[T comparable, Slice ~[]T](left, right Slice) Slice {
// arr1 := xslice.Remove(arr, 1)
// fmt.Println(arr1) // [2, 3, 4]
func Remove[T comparable, Slice ~[]T](in Slice, wantToRemove ...T) Slice {
- seq := xiter.FromSlice(wantToRemove)
- return xiter.ToSlice(xiter.Filter(func(v T) bool {
- if len(wantToRemove) == 0 {
- return true
- }
- if len(wantToRemove) == 1 {
- return wantToRemove[0] != v
+ if len(wantToRemove) == 0 {
+ return Clone(in)
+ }
+ removeSet := make(map[T]struct{}, len(wantToRemove))
+ for _, v := range wantToRemove {
+ removeSet[v] = struct{}{}
+ }
+ out := make(Slice, 0, len(in))
+ for _, v := range in {
+ if _, ok := removeSet[v]; !ok {
+ out = append(out, v)
}
- return !xiter.Contains(seq, v)
- }, xiter.FromSlice(in)))
+ }
+ return out
}
// Flatten returns a new slice with all nested slices flattened into a single slice.
@@ -719,9 +904,11 @@ func Flatten[T any](in [][]T) []T {
// xslice.ToMap([]int{1, 2, 1, 3}, func(i int) string { return fmt.Sprintf("val_%d", i) }) 👉 map[1:val_1 2:val_2 3:val_3] (note: key 1 has "val_1" from the last occurrence)
// xslice.ToMap([]int{}, func(i int) string { return "" }) 👉 map[int]string{}
func ToMap[T comparable, U any](in []T, f func(T) U) map[T]U {
- return xiter.ToMap(xiter.Map2(func(idx int, in T) (T, U) {
- return in, f(in)
- }, xiter.FromSliceIdx(in)))
+ out := make(map[T]U, len(in))
+ for _, v := range in {
+ out[v] = f(v)
+ }
+ return out
}
// Sample returns a new slice with n randomly selected elements from the input slice.
@@ -735,7 +922,19 @@ func ToMap[T comparable, U any](in []T, f func(T) U) map[T]U {
// xslice.Sample([]int{1, 2, 3}, 0) 👉 []int{}
// xslice.Sample([]int{}, 3) 👉 []int{}
func Sample[T any, Slice ~[]T](in Slice, n int) Slice {
- return xiter.ToSlice(xiter.Limit(xiter.FromSliceShuffle(in), n))
+ if n <= 0 || len(in) == 0 {
+ return nil
+ }
+ if n >= len(in) {
+ return Shuffle(in)
+ }
+ out := make(Slice, len(in))
+ copy(out, in)
+ for i := 0; i < n; i++ {
+ j := rand.Intn(len(in)-i) + i //nolint:gosec
+ out[i], out[j] = out[j], out[i]
+ }
+ return out[:n]
}
// RandomElement returns a random element from the slice as an optional.O[T].
@@ -747,5 +946,150 @@ func Sample[T any, Slice ~[]T](in Slice, n int) Slice {
// xslice.RandomElement([]int{42}) 👉 42 (always returns the only element)
// xslice.RandomElement([]int{}).Ok() 👉 false
func RandomElement[T any, Slice ~[]T](in Slice) optional.O[T] {
- return xiter.FirstO(xiter.FromSliceShuffle(in))
+ if len(in) == 0 {
+ return optional.Empty[T]()
+ }
+ return optional.FromValue(in[rand.Intn(len(in))]) //nolint:gosec
+}
+
+// CountBy counts occurrences of each key in the slice, returning a map of keys to counts.
+//
+// EXAMPLE:
+//
+// xslice.CountBy([]int{1, 2, 3, 2, 1, 2}, func(x int) int { return x })
+// // 👉 map[int]int{1: 2, 2: 3, 3: 1}
+func CountBy[T any, K comparable](in []T, fn func(T) K) map[K]int {
+ result := make(map[K]int)
+ for _, v := range in {
+ result[fn(v)]++
+ }
+ return result
+}
+
+// KeyBy creates a map from the slice using the key function.
+// Later elements overwrite earlier ones for duplicate keys.
+//
+// EXAMPLE:
+//
+// xslice.KeyBy([]int{1, 2, 3}, func(x int) int { return x * 10 })
+// // 👉 map[int]int{10: 1, 20: 2, 30: 3}
+func KeyBy[T any, K comparable](in []T, fn func(T) K) map[K]T {
+ result := make(map[K]T, len(in))
+ for _, v := range in {
+ result[fn(v)] = v
+ }
+ return result
+}
+
+// Partition splits a slice into two slices based on a predicate.
+// The first return slice contains elements where fn returns true.
+//
+// EXAMPLE:
+//
+// yes, no := xslice.Partition([]int{1, 2, 3, 4, 5}, func(x int) bool { return x%2 == 0 })
+// // yes 👉 []int{2, 4}, no 👉 []int{1, 3, 5}
+func Partition[T any, Slice ~[]T](in Slice, fn func(T) bool) (yes, no Slice) {
+ yes = make(Slice, 0)
+ no = make(Slice, 0)
+ for _, v := range in {
+ if fn(v) {
+ yes = append(yes, v)
+ } else {
+ no = append(no, v)
+ }
+ }
+ return
+}
+
+// FlatMap maps each element to a slice and flattens the results into a single slice.
+//
+// EXAMPLE:
+//
+// xslice.FlatMap([]int{1, 2, 3}, func(x int) []int { return []int{x, x * 10} })
+// // 👉 []int{1, 10, 2, 20, 3, 30}
+func FlatMap[T any, U any](in []T, fn func(T) []U) []U {
+ result := make([]U, 0)
+ for _, v := range in {
+ result = append(result, fn(v)...)
+ }
+ return result
+}
+
+// IsSorted checks if the slice is sorted in ascending order.
+// Empty and single-element slices are considered sorted.
+//
+// EXAMPLE:
+//
+// xslice.IsSorted([]int{1, 2, 3, 4}) 👉 true
+// xslice.IsSorted([]int{1, 3, 2, 4}) 👉 false
+func IsSorted[T constraints.Ordered](in []T) bool {
+ for i := 0; i < len(in)-1; i++ {
+ if in[i] > in[i+1] {
+ return false
+ }
+ }
+ return true
+}
+
+// AllEqual checks if all elements in the slice are equal.
+// Empty and single-element slices are considered to have all equal elements.
+//
+// EXAMPLE:
+//
+// xslice.AllEqual([]int{1, 1, 1, 1}) 👉 true
+// xslice.AllEqual([]int{1, 2, 1, 1}) 👉 false
+func AllEqual[T comparable](in []T) bool {
+ for i := 1; i < len(in); i++ {
+ if in[i] != in[0] {
+ return false
+ }
+ }
+ return true
+}
+
+// MinMax returns the minimum and maximum elements in the slice in a single pass.
+//
+// EXAMPLE:
+//
+// xslice.MinMax([]int{3, 1, 4, 1, 5, 9}) 👉 (1, 9, true)
+// xslice.MinMax([]int{}) 👉 (0, 0, false)
+func MinMax[T constraints.Ordered](in []T) (min T, max T, ok bool) {
+ if len(in) == 0 {
+ return
+ }
+ min, max = in[0], in[0]
+ for _, v := range in[1:] {
+ if v < min {
+ min = v
+ }
+ if v > max {
+ max = v
+ }
+ }
+ return min, max, true
+}
+
+// Mode returns the most frequently occurring element in the slice.
+// If the slice is empty, it returns an empty optional.
+// If there are multiple modes (tie), the first one to reach the maximum count is returned.
+//
+// EXAMPLE:
+//
+// xslice.Mode([]int{1, 2, 3, 2, 1, 2}) 👉 2
+// xslice.Mode([]int{}) 👉 optional.Empty[int]()
+func Mode[T comparable](in []T) optional.O[T] {
+ if len(in) == 0 {
+ return optional.Empty[T]()
+ }
+ counts := make(map[T]int)
+ maxCount := 0
+ var mode T
+ for _, v := range in {
+ counts[v]++
+ if counts[v] > maxCount {
+ maxCount = counts[v]
+ mode = v
+ }
+ }
+ return optional.FromValue(mode)
}
diff --git a/pkg/xslice/xslice_benchmark_test.go b/pkg/xslice/xslice_benchmark_test.go
index ce85966..676f211 100644
--- a/pkg/xslice/xslice_benchmark_test.go
+++ b/pkg/xslice/xslice_benchmark_test.go
@@ -201,24 +201,106 @@ func BenchmarkSlice(b *testing.B) {
}
func BenchmarkChunk(b *testing.B) {
- b.Run("xslice", func(b *testing.B) {
- arr := _range(0, 1000)
- for i := 1; i < b.N; i++ {
- xslice.Chunk(arr, i)
- }
- })
+ sizes := []struct {
+ name string
+ nElements int
+ chunkSize int
+ }{
+ {"small-chunks-10k-elems", 10_000, 10},
+ {"large-chunks-10k-elems", 10_000, 1000},
+ }
- b.Run("xslice-inplace", func(b *testing.B) {
- arr := _range(0, 1000)
- for i := 1; i < b.N; i++ {
- xslice.ChunkInPlace(arr, i)
- }
- })
+ for _, tc := range sizes {
+ b.Run("xslice-seq/"+tc.name, func(b *testing.B) {
+ arr := _range(0, tc.nElements)
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _ = xslice.Chunk(arr, tc.chunkSize)
+ }
+ })
- b.Run("lo", func(b *testing.B) {
- arr := _range(0, 1000)
- for i := 1; i < b.N; i++ {
- lo.Chunk(arr, i)
- }
- })
+ b.Run("xslice-inplace/"+tc.name, func(b *testing.B) {
+ arr := _range(0, tc.nElements)
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _ = xslice.ChunkInPlace(arr, tc.chunkSize)
+ }
+ })
+
+ b.Run("lo/"+tc.name, func(b *testing.B) {
+ arr := _range(0, tc.nElements)
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _ = lo.Chunk(arr, tc.chunkSize)
+ }
+ })
+ }
+}
+
+func BenchmarkCountBy(b *testing.B) {
+ data := _range(0, 10_000)
+ fn := func(x int) int { return x % 100 }
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _ = xslice.CountBy(data, fn)
+ }
+}
+
+func BenchmarkKeyBy(b *testing.B) {
+ data := _range(0, 10_000)
+ fn := func(x int) int { return x % 1000 }
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _ = xslice.KeyBy(data, fn)
+ }
+}
+
+func BenchmarkPartition(b *testing.B) {
+ data := _range(0, 10_000)
+ fn := func(x int) bool { return x%2 == 0 }
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _, _ = xslice.Partition(data, fn)
+ }
+}
+
+func BenchmarkFlatMap(b *testing.B) {
+ data := _range(0, 1000)
+ fn := func(x int) []int { return []int{x, x * 10} }
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _ = xslice.FlatMap(data, fn)
+ }
+}
+
+func BenchmarkIsSorted(b *testing.B) {
+ data := _range(0, 10_000)
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _ = xslice.IsSorted(data)
+ }
+}
+
+func BenchmarkAllEqual(b *testing.B) {
+ data := make([]int, 10_000)
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _ = xslice.AllEqual(data)
+ }
+}
+
+func BenchmarkMinMax(b *testing.B) {
+ data := _range(0, 10_000)
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _, _, _ = xslice.MinMax(data)
+ }
+}
+
+func BenchmarkMode(b *testing.B) {
+ data := _range(0, 1000)
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _ = xslice.Mode(data)
+ }
}
diff --git a/pkg/xslice/xslice_test.go b/pkg/xslice/xslice_test.go
index a58ce28..862442d 100644
--- a/pkg/xslice/xslice_test.go
+++ b/pkg/xslice/xslice_test.go
@@ -488,4 +488,157 @@ func TestSlices(t *testing.T) {
assert.Contains(t, strings, elem4.Must())
})
+ t.Run("count by", func(t *testing.T) {
+ result := xslice.CountBy([]int{1, 2, 3, 2, 1, 2}, func(x int) int { return x })
+ assert.Equal(t, map[int]int{1: 2, 2: 3, 3: 1}, result)
+
+ result2 := xslice.CountBy([]int{}, func(x int) int { return x })
+ assert.Equal(t, map[int]int{}, result2)
+
+ result3 := xslice.CountBy([]string{"a", "b", "a", "c", "b", "b"}, func(s string) string { return s })
+ assert.Equal(t, map[string]int{"a": 2, "b": 3, "c": 1}, result3)
+
+ result4 := xslice.CountBy([]int{1, 2, 3, 4, 5, 6}, func(x int) string {
+ if x%2 == 0 {
+ return "even"
+ }
+ return "odd"
+ })
+ assert.Equal(t, map[string]int{"even": 3, "odd": 3}, result4)
+ })
+
+ t.Run("key by", func(t *testing.T) {
+ result := xslice.KeyBy([]int{1, 2, 3}, func(x int) int { return x * 10 })
+ assert.Equal(t, map[int]int{10: 1, 20: 2, 30: 3}, result)
+
+ result2 := xslice.KeyBy([]int{1, 2, 3, 1}, func(x int) int { return x })
+ assert.Equal(t, map[int]int{1: 1, 2: 2, 3: 3}, result2)
+
+ result3 := xslice.KeyBy([]int{}, func(x int) int { return x })
+ assert.Equal(t, map[int]int{}, result3)
+
+ type person struct {
+ name string
+ age int
+ }
+ people := []person{{"alice", 30}, {"bob", 25}, {"charlie", 35}}
+ result4 := xslice.KeyBy(people, func(p person) string { return p.name })
+ assert.Equal(t, map[string]person{"alice": {"alice", 30}, "bob": {"bob", 25}, "charlie": {"charlie", 35}}, result4)
+ })
+
+ t.Run("partition", func(t *testing.T) {
+ yes, no := xslice.Partition([]int{1, 2, 3, 4, 5}, func(x int) bool { return x%2 == 0 })
+ assert.Equal(t, []int{2, 4}, yes)
+ assert.Equal(t, []int{1, 3, 5}, no)
+
+ yes2, no2 := xslice.Partition([]int{1, 2, 3}, func(x int) bool { return true })
+ assert.Equal(t, []int{1, 2, 3}, yes2)
+ assert.Equal(t, []int{}, no2)
+
+ yes3, no3 := xslice.Partition([]int{1, 2, 3}, func(x int) bool { return false })
+ assert.Equal(t, []int{}, yes3)
+ assert.Equal(t, []int{1, 2, 3}, no3)
+
+ yes4, no4 := xslice.Partition([]int{}, func(x int) bool { return true })
+ assert.Equal(t, []int{}, yes4)
+ assert.Equal(t, []int{}, no4)
+ })
+
+ t.Run("flat map", func(t *testing.T) {
+ result := xslice.FlatMap([]int{1, 2, 3}, func(x int) []int { return []int{x, x * 10} })
+ assert.Equal(t, []int{1, 10, 2, 20, 3, 30}, result)
+
+ result2 := xslice.FlatMap([]int{1, 2, 3}, func(x int) []int { return nil })
+ assert.Equal(t, []int{}, result2)
+
+ result3 := xslice.FlatMap([]int{}, func(x int) []int { return []int{x} })
+ assert.Equal(t, []int{}, result3)
+
+ result4 := xslice.FlatMap([]int{1, 2, 3}, func(x int) []int {
+ if x%2 == 0 {
+ return []int{x}
+ }
+ return nil
+ })
+ assert.Equal(t, []int{2}, result4)
+
+ result5 := xslice.FlatMap([]string{"ab", "cd"}, func(s string) []byte { return []byte(s) })
+ assert.Equal(t, []byte{'a', 'b', 'c', 'd'}, result5)
+ })
+
+ t.Run("is sorted", func(t *testing.T) {
+ assert.True(t, xslice.IsSorted([]int{1, 2, 3, 4}))
+ assert.True(t, xslice.IsSorted([]int{1, 1, 2, 3}))
+ assert.False(t, xslice.IsSorted([]int{1, 3, 2, 4}))
+ assert.False(t, xslice.IsSorted([]int{4, 3, 2, 1}))
+ assert.True(t, xslice.IsSorted([]int{}))
+ assert.True(t, xslice.IsSorted([]int{1}))
+ assert.True(t, xslice.IsSorted([]int{1, 1}))
+ })
+
+ t.Run("all equal", func(t *testing.T) {
+ assert.True(t, xslice.AllEqual([]int{1, 1, 1, 1}))
+ assert.False(t, xslice.AllEqual([]int{1, 2, 1, 1}))
+ assert.False(t, xslice.AllEqual([]int{1, 1, 1, 2}))
+ assert.True(t, xslice.AllEqual([]int{}))
+ assert.True(t, xslice.AllEqual([]int{42}))
+ assert.True(t, xslice.AllEqual([]int{1, 1}))
+ assert.True(t, xslice.AllEqual([]string{"a", "a", "a"}))
+ })
+
+ t.Run("min max", func(t *testing.T) {
+ min, max, ok := xslice.MinMax([]int{3, 1, 4, 1, 5, 9})
+ assert.Equal(t, 1, min)
+ assert.Equal(t, 9, max)
+ assert.True(t, ok)
+
+ min2, max2, ok2 := xslice.MinMax([]int{42})
+ assert.Equal(t, 42, min2)
+ assert.Equal(t, 42, max2)
+ assert.True(t, ok2)
+
+ min3, max3, ok3 := xslice.MinMax([]int{5, 5, 5})
+ assert.Equal(t, 5, min3)
+ assert.Equal(t, 5, max3)
+ assert.True(t, ok3)
+
+ _, _, ok4 := xslice.MinMax([]int{})
+ assert.False(t, ok4)
+
+ min5, max5, ok5 := xslice.MinMax([]int{-5, -2, -10, -1})
+ assert.Equal(t, -10, min5)
+ assert.Equal(t, -1, max5)
+ assert.True(t, ok5)
+
+ min6, max6, ok6 := xslice.MinMax([]string{"a", "z", "m", "b"})
+ assert.Equal(t, "a", min6)
+ assert.Equal(t, "z", max6)
+ assert.True(t, ok6)
+ })
+
+ t.Run("mode", func(t *testing.T) {
+ mode := xslice.Mode([]int{1, 2, 3, 2, 1, 2})
+ assert.True(t, mode.Ok())
+ assert.Equal(t, 2, mode.Must())
+
+ mode2 := xslice.Mode([]int{42})
+ assert.True(t, mode2.Ok())
+ assert.Equal(t, 42, mode2.Must())
+
+ mode3 := xslice.Mode([]int{1, 2, 3, 4})
+ assert.True(t, mode3.Ok())
+ assert.Equal(t, 1, mode3.Must())
+
+ mode4 := xslice.Mode([]int{})
+ assert.False(t, mode4.Ok())
+
+ mode5 := xslice.Mode([]string{"a", "b", "a", "c", "b", "b"})
+ assert.True(t, mode5.Ok())
+ assert.Equal(t, "b", mode5.Must())
+
+ mode6 := xslice.Mode([]int{1, 1, 2, 2})
+ assert.True(t, mode6.Ok())
+ assert.Equal(t, 1, mode6.Must())
+ })
+
}