2025-10-26 20:56:03 +08:00
|
|
|
|
package utils
|
|
|
|
|
|
|
2026-02-05 00:29:19 +08:00
|
|
|
|
import (
|
|
|
|
|
|
"errors"
|
|
|
|
|
|
|
|
|
|
|
|
"github.com/gogf/gf/v2/util/gconv"
|
|
|
|
|
|
"github.com/gogf/gf/v2/util/grand"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-10-26 20:56:03 +08:00
|
|
|
|
func FindWithIndex[T any](slice []T, predicate func(item T) bool) (int, *T, bool) {
|
|
|
|
|
|
for i := range slice {
|
|
|
|
|
|
if predicate(slice[i]) {
|
|
|
|
|
|
return i, &slice[i], true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return -1, nil, false
|
|
|
|
|
|
}
|
2025-11-23 23:38:03 +00:00
|
|
|
|
func LastFourElements[T any](s []T, n1 int) []T {
|
|
|
|
|
|
n := len(s)
|
|
|
|
|
|
if n <= n1 {
|
|
|
|
|
|
// 切片长度小于等于4时,返回整个切片
|
|
|
|
|
|
return s
|
|
|
|
|
|
}
|
|
|
|
|
|
// 切片长度大于4时,返回最后4个元素(从n-4索引到末尾)
|
|
|
|
|
|
return s[n-n1:]
|
|
|
|
|
|
}
|
2025-11-25 16:36:55 +08:00
|
|
|
|
func RemoveLast(s string) string {
|
|
|
|
|
|
if s == "" {
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}
|
|
|
|
|
|
runes := []rune(s)
|
|
|
|
|
|
return string(runes[:len(runes)-1])
|
|
|
|
|
|
}
|
2026-02-05 00:29:19 +08:00
|
|
|
|
|
|
|
|
|
|
// ************************** 函数1:核心函数(双数组+int型概率,泛型)**************************
|
|
|
|
|
|
// randomByIntProbs 核心概率计算函数:接收任意类型元素数组 + int型概率数组,实现权重随机
|
|
|
|
|
|
// T any:支持任意类型的元素切片,职责单一,只处理纯int概率的核心逻辑
|
|
|
|
|
|
func RandomByIntProbs[T any](natureSet []T, probs []int) (T, error) {
|
|
|
|
|
|
// 定义泛型零值,用于错误返回
|
|
|
|
|
|
var zeroT T
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 合法性校验:元素数组为空 或 概率数组与元素数组长度不匹配
|
|
|
|
|
|
if len(natureSet) == 0 {
|
|
|
|
|
|
return zeroT, errors.New("natureSet is empty")
|
|
|
|
|
|
}
|
|
|
|
|
|
if len(probs) == 0 || len(natureSet) != len(probs) {
|
|
|
|
|
|
// 长度不匹配,降级为等概率随机(兼容原有逻辑)
|
|
|
|
|
|
return natureSet[grand.Intn(len(natureSet))], nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 校验概率值非负,并计算总概率
|
|
|
|
|
|
totalProb := 0
|
|
|
|
|
|
for i, p := range probs {
|
|
|
|
|
|
if p < 0 {
|
|
|
|
|
|
return zeroT, errors.New("invalid prob value: index " + gconv.String(i) + " (must be non-negative integer)")
|
|
|
|
|
|
}
|
|
|
|
|
|
totalProb += p
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 总概率为0,降级为等概率随机
|
|
|
|
|
|
if totalProb == 0 {
|
|
|
|
|
|
return natureSet[grand.Intn(len(natureSet))], nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 计算前缀和(权重随机核心逻辑)
|
|
|
|
|
|
prefixSum := make([]int, len(probs))
|
|
|
|
|
|
prefixSum[0] = probs[0]
|
|
|
|
|
|
for i := 1; i < len(probs); i++ {
|
|
|
|
|
|
prefixSum[i] = prefixSum[i-1] + probs[i]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 生成随机数匹配前缀和,返回对应元素
|
|
|
|
|
|
randVal := grand.Intn(totalProb)
|
|
|
|
|
|
for i, sum := range prefixSum {
|
|
|
|
|
|
if randVal < sum {
|
|
|
|
|
|
return natureSet[i], nil
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 极端情况兜底,返回第一个元素
|
|
|
|
|
|
return natureSet[0], nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ************************** 函数2:封装函数(兼容string型概率,泛型)**************************
|
|
|
|
|
|
// randomByProbs 兼容原有入参格式:接收任意类型元素数组 + string型概率数组
|
|
|
|
|
|
// 内部完成 string[] -> int[] 的转换,然后调用核心函数 randomByIntProbs
|
|
|
|
|
|
func RandomByProbs[T any](natureSet []T, probs []string) (T, error) {
|
|
|
|
|
|
//var zeroT T
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 若string概率数组为空,直接调用核心函数(核心函数会处理降级逻辑)
|
|
|
|
|
|
if len(probs) == 0 {
|
|
|
|
|
|
return RandomByIntProbs(natureSet, []int{})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. string[] 转换为 int[](使用 gconv.Int 完成转换)
|
|
|
|
|
|
probInts := make([]int, len(probs))
|
|
|
|
|
|
for i, pStr := range probs {
|
|
|
|
|
|
// gconv.Int 灵活转换,失败返回0
|
|
|
|
|
|
probInts[i] = gconv.Int(pStr)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 调用核心函数,复用概率计算逻辑
|
|
|
|
|
|
return RandomByIntProbs(natureSet, probInts)
|
|
|
|
|
|
}
|