From 431cc120639c2e48656b75cd3afccdf1e24ea9ff Mon Sep 17 00:00:00 2001
From: dashjay
Date: Fri, 1 May 2026 23:21:15 +0800
Subject: [PATCH 01/12] fix chunk
---
pkg/xslice/xslice.go | 14 ++++----
pkg/xslice/xslice_benchmark_test.go | 50 ++++++++++++++++++-----------
2 files changed, 39 insertions(+), 25 deletions(-)
diff --git a/pkg/xslice/xslice.go b/pkg/xslice/xslice.go
index 910826c..53414f8 100644
--- a/pkg/xslice/xslice.go
+++ b/pkg/xslice/xslice.go
@@ -457,14 +457,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
}
diff --git a/pkg/xslice/xslice_benchmark_test.go b/pkg/xslice/xslice_benchmark_test.go
index ce85966..35f1dad 100644
--- a/pkg/xslice/xslice_benchmark_test.go
+++ b/pkg/xslice/xslice_benchmark_test.go
@@ -201,24 +201,38 @@ 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)
+ }
+ })
+ }
}
From a6a09082457194ecc1e8572fe475f01059cfe36c Mon Sep 17 00:00:00 2001
From: dashjay
Date: Fri, 1 May 2026 23:27:44 +0800
Subject: [PATCH 02/12] fix random element
---
pkg/xslice/xslice.go | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/pkg/xslice/xslice.go b/pkg/xslice/xslice.go
index 53414f8..1bc03c2 100644
--- a/pkg/xslice/xslice.go
+++ b/pkg/xslice/xslice.go
@@ -747,5 +747,8 @@ 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))])
}
From 6047faf3abedfab3fd518f56c499954a6df893cd Mon Sep 17 00:00:00 2001
From: dashjay
Date: Fri, 1 May 2026 23:36:36 +0800
Subject: [PATCH 03/12] perf: rewrite Seq-based
functions to direct for loops
---
pkg/xslice/xslice.go | 323 ++++++++++++++++++++++++++++++++++---------
1 file changed, 261 insertions(+), 62 deletions(-)
diff --git a/pkg/xslice/xslice.go b/pkg/xslice/xslice.go
index 1bc03c2..bc00f26 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.
@@ -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
+ out[i], out[j] = out[j], out[i]
+ }
+ return out[:n]
}
// RandomElement returns a random element from the slice as an optional.O[T].
From 658d2e49ef9700ec9005b50c24f425343998015b Mon Sep 17 00:00:00 2001
From: dashjay
Date: Sat, 2 May 2026 00:00:03 +0800
Subject: [PATCH 04/12] chore: switch from math/rand to math/rand/v2
---
pkg/xslice/xslice.go | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/pkg/xslice/xslice.go b/pkg/xslice/xslice.go
index bc00f26..8afe997 100644
--- a/pkg/xslice/xslice.go
+++ b/pkg/xslice/xslice.go
@@ -1,7 +1,7 @@
package xslice
import (
- "math/rand"
+ "math/rand/v2"
"github.com/dashjay/xiter/pkg/internal/constraints"
"github.com/dashjay/xiter/pkg/internal/xassert"
@@ -931,7 +931,7 @@ func Sample[T any, Slice ~[]T](in Slice, n int) Slice {
out := make(Slice, len(in))
copy(out, in)
for i := 0; i < n; i++ {
- j := rand.Intn(len(in)-i) + i
+ j := rand.IntN(len(in)-i) + i
out[i], out[j] = out[j], out[i]
}
return out[:n]
@@ -949,5 +949,5 @@ func RandomElement[T any, Slice ~[]T](in Slice) optional.O[T] {
if len(in) == 0 {
return optional.Empty[T]()
}
- return optional.FromValue(in[rand.Intn(len(in))])
+ return optional.FromValue(in[rand.IntN(len(in))])
}
From 9f00c64de38852de483464826e62eb1d98a26919 Mon Sep 17 00:00:00 2001
From: dashjay
Date: Sat, 2 May 2026 00:07:36 +0800
Subject: [PATCH 05/12] ci: add newer Go versions; revert
to math/rand for
compat
---
.github/workflows/ci.yml | 2 +-
pkg/xslice/xslice.go | 6 +++---
2 files changed, 4 insertions(+), 4 deletions(-)
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/xslice/xslice.go b/pkg/xslice/xslice.go
index 8afe997..bc00f26 100644
--- a/pkg/xslice/xslice.go
+++ b/pkg/xslice/xslice.go
@@ -1,7 +1,7 @@
package xslice
import (
- "math/rand/v2"
+ "math/rand"
"github.com/dashjay/xiter/pkg/internal/constraints"
"github.com/dashjay/xiter/pkg/internal/xassert"
@@ -931,7 +931,7 @@ func Sample[T any, Slice ~[]T](in Slice, n int) Slice {
out := make(Slice, len(in))
copy(out, in)
for i := 0; i < n; i++ {
- j := rand.IntN(len(in)-i) + i
+ j := rand.Intn(len(in)-i) + i
out[i], out[j] = out[j], out[i]
}
return out[:n]
@@ -949,5 +949,5 @@ func RandomElement[T any, Slice ~[]T](in Slice) optional.O[T] {
if len(in) == 0 {
return optional.Empty[T]()
}
- return optional.FromValue(in[rand.IntN(len(in))])
+ return optional.FromValue(in[rand.Intn(len(in))])
}
From f8dceaa00f5fb23a811bc6f549c67fbc7067445a Mon Sep 17 00:00:00 2001
From: dashjay
Date: Sat, 2 May 2026 00:18:21 +0800
Subject: [PATCH 06/12] update
---
pkg/xslice/README.md | 114 +++++++++++++++++++++----------------------
1 file changed, 57 insertions(+), 57 deletions(-)
diff --git a/pkg/xslice/README.md b/pkg/xslice/README.md
index 4778f98..b5d31e0 100644
--- a/pkg/xslice/README.md
+++ b/pkg/xslice/README.md
@@ -119,7 +119,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 +137,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 +153,7 @@ xslice.AvgN() 👉 float(0)
```
-## func [Chunk]()
+## func [Chunk]()
```go
func Chunk[T any, Slice ~[]T](in Slice, chunkSize int) []Slice
@@ -170,7 +170,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 +185,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 +200,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 +216,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 +231,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 +247,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 +263,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 +280,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 +297,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 +320,7 @@ xslice.ContainsBy([]string{"1", "2", "3"}, func(x string) bool {
```
-## func [Count]()
+## func [Count]()
```go
func Count[T any](in []T) int
@@ -336,7 +336,7 @@ xslice.Count([]int{}) 👉 0
```
-## func [Difference]()
+## func [Difference]()
```go
func Difference[T comparable, Slice ~[]T](left, right Slice) (onlyLeft, onlyRight Slice)
@@ -355,7 +355,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 +370,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 +386,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 +402,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 +416,7 @@ xslice.First([]int{}) 👉 0
```
-## func [FirstO]()
+## func [FirstO]()
```go
func FirstO[T any, Slice ~[]T](in Slice) optional.O[T]
@@ -430,7 +430,7 @@ xslice.FirstO([]int{}) 👉 0
```
-## func [Flatten]()
+## func [Flatten]()
```go
func Flatten[T any](in [][]T) []T
@@ -448,7 +448,7 @@ xslice.Flatten([][]int{{}, {}, {}}) 👉 []int{}
```
-## func [ForEach]()
+## func [ForEach]()
```go
func ForEach[T any](in []T, f func(T) bool)
@@ -470,7 +470,7 @@ Output:
```
-## func [ForEachIdx]()
+## func [ForEachIdx]()
```go
func ForEachIdx[T any](in []T, f func(idx int, v T) bool)
@@ -492,7 +492,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 +507,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 +522,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 +538,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 +554,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 +569,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
@@ -587,7 +587,7 @@ fmt.Println(intersect) // [4 5]
```
-## func [Join]()
+## func [Join]()
```go
func Join[T ~string](in []T, sep T) T
@@ -603,7 +603,7 @@ xslice.Join([]string{}, ".") 👉 ""
```
-## func [Last]()
+## func [Last]()
```go
func Last[T any, Slice ~[]T](in Slice) (T, bool)
@@ -617,7 +617,7 @@ xslice.Last([]int{}) 👉 0
```
-## func [LastO]()
+## func [LastO]()
```go
func LastO[T any, Slice ~[]T](in Slice) optional.O[T]
@@ -631,7 +631,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 +647,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 +663,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 +678,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 +693,7 @@ xslice.MaxN(1, 2, 3) 👉 3
```
-## func [Min]()
+## func [Min]()
```go
func Min[T constraints.Ordered](in []T) optional.O[T]
@@ -709,7 +709,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]
@@ -724,7 +724,7 @@ xslice.MinBy([]int{3, 2, 1} /*less = */, func(a, b int) bool { return a > b }).M
```
-## func [MinN]()
+## func [MinN]()
```go
func MinN[T constraints.Ordered](in ...T) optional.O[T]
@@ -739,7 +739,7 @@ xslice.MinN(1, 2, 3) 👉 1
```
-## func [RandomElement]()
+## func [RandomElement]()
```go
func RandomElement[T any, Slice ~[]T](in Slice) optional.O[T]
@@ -756,7 +756,7 @@ xslice.RandomElement([]int{}).Ok() 👉 false
```
-## func [Remove]()
+## func [Remove]()
```go
func Remove[T comparable, Slice ~[]T](in Slice, wantToRemove ...T) Slice
@@ -773,7 +773,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 +789,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 +805,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 +821,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 +837,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 +853,7 @@ xslice.Reverse([]int{}) 👉 []int{}
```
-## func [ReverseClone]()
+## func [ReverseClone]()
```go
func ReverseClone[T any, Slice ~[]T](in Slice) Slice
@@ -870,7 +870,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 +888,7 @@ xslice.Sample([]int{}, 3) 👉 []int{}
```
-## func [Shuffle]()
+## func [Shuffle]()
```go
func Shuffle[T any, Slice ~[]T](in Slice) Slice
@@ -904,7 +904,7 @@ xslice.Shuffle([]int{}) 👉 []int{}
```
-## func [ShuffleInPlace]()
+## func [ShuffleInPlace]()
```go
func ShuffleInPlace[T any, Slice ~[]T](in Slice)
@@ -920,7 +920,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 +936,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 +950,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 +966,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 +985,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 +1001,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 +1019,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 +1037,7 @@ fmt.Println(union) // [1 2 3 4 5 6]
```
-## func [Uniq]()
+## func [Uniq]()
```go
func Uniq[T comparable, Slice ~[]T](in Slice) Slice
From 05f9763d8ac8a2bf861cfb0ef5b507c1726217a1 Mon Sep 17 00:00:00 2001
From: dashjay
Date: Sat, 2 May 2026 00:33:40 +0800
Subject: [PATCH 07/12] chore: suppress gosec G404 nolint for math/rand usage
---
pkg/xslice/xslice.go | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/pkg/xslice/xslice.go b/pkg/xslice/xslice.go
index bc00f26..2adfb3b 100644
--- a/pkg/xslice/xslice.go
+++ b/pkg/xslice/xslice.go
@@ -931,7 +931,7 @@ func Sample[T any, Slice ~[]T](in Slice, n int) Slice {
out := make(Slice, len(in))
copy(out, in)
for i := 0; i < n; i++ {
- j := rand.Intn(len(in)-i) + i
+ j := rand.Intn(len(in)-i) + i //nolint:gosec
out[i], out[j] = out[j], out[i]
}
return out[:n]
@@ -949,5 +949,5 @@ func RandomElement[T any, Slice ~[]T](in Slice) optional.O[T] {
if len(in) == 0 {
return optional.Empty[T]()
}
- return optional.FromValue(in[rand.Intn(len(in))])
+ return optional.FromValue(in[rand.Intn(len(in))]) //nolint:gosec
}
From 121119aef4fa886b63fc94f7a8546ba405ef0bde Mon Sep 17 00:00:00 2001
From: dashjay
Date: Sat, 2 May 2026 00:51:03 +0800
Subject: [PATCH 08/12] xmap
---
pkg/xmap/xmap.go | 19 +++++--
pkg/xmap/xmap_benchmark_test.go | 89 ++++++++++++++++++++++++++++++
pkg/xmap/xmap_common.go | 95 +++++++++++++++++++++++++--------
pkg/xmap/xmap_old.go | 25 +++++++--
4 files changed, 196 insertions(+), 32 deletions(-)
create mode 100644 pkg/xmap/xmap_benchmark_test.go
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
}
From 66461695580839fbebb1f8b2e330a8d9c198d218 Mon Sep 17 00:00:00 2001
From: dashjay
Date: Sat, 2 May 2026 01:08:51 +0800
Subject: [PATCH 09/12] add some utils
---
pkg/xslice/xslice.go | 142 ++++++++++++++++++++++++++
pkg/xslice/xslice_benchmark_test.go | 68 +++++++++++++
pkg/xslice/xslice_test.go | 153 ++++++++++++++++++++++++++++
3 files changed, 363 insertions(+)
diff --git a/pkg/xslice/xslice.go b/pkg/xslice/xslice.go
index 2adfb3b..1375170 100644
--- a/pkg/xslice/xslice.go
+++ b/pkg/xslice/xslice.go
@@ -951,3 +951,145 @@ func RandomElement[T any, Slice ~[]T](in Slice) optional.O[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 35f1dad..676f211 100644
--- a/pkg/xslice/xslice_benchmark_test.go
+++ b/pkg/xslice/xslice_benchmark_test.go
@@ -236,3 +236,71 @@ func BenchmarkChunk(b *testing.B) {
})
}
}
+
+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())
+ })
+
}
From a23dbe12d886e6eb9fe7eb45b506bd9ebb874fc4 Mon Sep 17 00:00:00 2001
From: dashjay
Date: Sat, 2 May 2026 01:09:21 +0800
Subject: [PATCH 10/12] update doc
---
pkg/xmap/README.md | 48 +++++++--------
pkg/xslice/README.md | 136 +++++++++++++++++++++++++++++++++++++++++++
2 files changed, 160 insertions(+), 24 deletions(-)
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/xslice/README.md b/pkg/xslice/README.md
index b5d31e0..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]()
@@ -335,6 +359,22 @@ 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]()
@@ -429,6 +469,22 @@ 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]()
@@ -586,6 +642,22 @@ 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]()
@@ -602,6 +674,22 @@ 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]()
@@ -723,6 +811,22 @@ 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]()
@@ -738,6 +842,38 @@ 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]()
From e63360230b7f72b8401704e889d50de6af4a6c1e Mon Sep 17 00:00:00 2001
From: dashjay
Date: Sat, 2 May 2026 04:19:16 +0800
Subject: [PATCH 11/12] add more interface for xiter
---
pkg/xiter/xiter_benchmark_test.go | 54 +++++++++++++
pkg/xiter/xiter_common.go | 121 ++++++++++++++++++++++++++++++
pkg/xiter/xiter_common_test.go | 76 ++++++++++++++++++-
3 files changed, 250 insertions(+), 1 deletion(-)
create mode 100644 pkg/xiter/xiter_benchmark_test.go
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..5591487 100644
--- a/pkg/xiter/xiter_common.go
+++ b/pkg/xiter/xiter_common.go
@@ -238,3 +238,124 @@ 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 {
+ if !yield(fn()) {
+ break
+ }
+ }
+ }
+}
+
+// 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)
+ })
}
From 64f876a100ecc8756de1cb103efbe4bc4ecbff10 Mon Sep 17 00:00:00 2001
From: dashjay
Date: Sat, 2 May 2026 04:22:03 +0800
Subject: [PATCH 12/12] add doc
---
pkg/xiter/README.md | 98 +++++++++++++++++++++++++++++++++++++++
pkg/xiter/xiter_common.go | 5 +-
2 files changed, 99 insertions(+), 4 deletions(-)
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_common.go b/pkg/xiter/xiter_common.go
index 5591487..014b017 100644
--- a/pkg/xiter/xiter_common.go
+++ b/pkg/xiter/xiter_common.go
@@ -276,10 +276,7 @@ func Cycle[T any](seq Seq[T]) Seq[T] {
// // first5 contains 5 random numbers
func Generate[T any](fn func() T) Seq[T] {
return func(yield func(T) bool) {
- for {
- if !yield(fn()) {
- break
- }
+ for yield(fn()) {
}
}
}