Files
go-utils/slice/slice.go
2025-10-11 19:53:28 +04:00

93 lines
2.5 KiB
Go

package slice
import (
"math/rand/v2"
"time"
)
// Cycle returns a function that cycles through the provided items infinitely.
// Each call to the returned function returns the next item in sequence, wrapping
// back to the first item after the last one is returned.
//
// If no items are provided, the returned function will always return the zero
// value for type T.
//
// The returned function is not safe for concurrent use. If you need to call it
// from multiple goroutines, you must synchronize access with a mutex or similar.
//
// Example:
//
// next := Cycle(1, 2, 3)
// fmt.Println(next()) // 1
// fmt.Println(next()) // 2
// fmt.Println(next()) // 3
// fmt.Println(next()) // 1
func Cycle[T any](items ...T) func() T {
if len(items) == 0 {
var zero T
return func() T { return zero }
}
index := 0
return func() T {
item := items[index]
index = (index + 1) % len(items)
return item
}
}
// RandomCycle returns a function that cycles through the provided items with
// randomization. It cycles through all items sequentially, but when it completes
// a full cycle, it randomly picks a new starting point for the next cycle.
//
// The localRand parameter can be used to provide a custom random number generator.
// If nil, a new generator will be created using the current time as the seed.
//
// The returned function is not safe for concurrent use. If you need to call it
// from multiple goroutines, you must synchronize access with a mutex or similar.
//
// Special cases:
// - If no items are provided, the returned function always returns the zero value for type T.
// - If only one item is provided, the returned function always returns that item.
//
// Example:
//
// next := RandomCycle(nil, "a", "b", "c")
// // Might produce: "b", "c", "a", "c", "a", "b", ...
// // (cycles through all items, then starts from a random position)
func RandomCycle[T any](localRand *rand.Rand, items ...T) func() T {
switch sliceLen := len(items); sliceLen {
case 0:
var zero T
return func() T { return zero }
case 1:
return func() T { return items[0] }
default:
if localRand == nil {
//nolint:gosec
localRand = rand.New(
rand.NewPCG(
uint64(time.Now().UnixNano()),
uint64(time.Now().UnixNano()>>32),
),
)
}
currentIndex := localRand.IntN(sliceLen)
stopIndex := currentIndex
return func() T {
item := items[currentIndex]
currentIndex++
if currentIndex == sliceLen {
currentIndex = 0
}
if currentIndex == stopIndex {
currentIndex = localRand.IntN(sliceLen)
stopIndex = currentIndex
}
return item
}
}
}