2025-10-26 20:56:03 +08:00
|
|
|
|
package utils
|
|
|
|
|
|
|
2026-02-05 00:29:19 +08:00
|
|
|
|
import (
|
|
|
|
|
|
"errors"
|
2026-02-09 01:29:33 +08:00
|
|
|
|
"fmt"
|
|
|
|
|
|
"math/rand/v2"
|
2026-02-25 19:05:50 +08:00
|
|
|
|
"time"
|
2026-02-05 00:29:19 +08:00
|
|
|
|
|
2026-03-13 12:04:35 +08:00
|
|
|
|
"github.com/gogf/gf/v2/os/gtime"
|
2026-02-05 00:29:19 +08:00
|
|
|
|
"github.com/gogf/gf/v2/util/gconv"
|
|
|
|
|
|
"github.com/gogf/gf/v2/util/grand"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-03-13 12:04:35 +08:00
|
|
|
|
// IsToday 判断给定时间是否是今天
|
|
|
|
|
|
func IsToday(t1 *gtime.Time) bool {
|
|
|
|
|
|
if t1 == nil {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
t := t1.Time
|
|
|
|
|
|
|
|
|
|
|
|
// 获取当前时间
|
|
|
|
|
|
now := time.Now()
|
|
|
|
|
|
|
|
|
|
|
|
// 比较年、月、日是否相同
|
|
|
|
|
|
return t.Year() == now.Year() &&
|
|
|
|
|
|
t.Month() == now.Month() &&
|
|
|
|
|
|
t.Day() == now.Day()
|
|
|
|
|
|
}
|
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
|
|
|
|
|
2026-02-09 01:29:33 +08:00
|
|
|
|
// RandomByWeight 根据整数权重随机选择元素(优化后的泛型版本)
|
|
|
|
|
|
// 入参:
|
|
|
|
|
|
// - elements: 待随机的元素集合(非空)
|
|
|
|
|
|
// - weights: 对应元素的权重(非负整数,长度需与elements一致;长度不匹配/总权重为0时降级为等概率随机)
|
|
|
|
|
|
//
|
|
|
|
|
|
// 返回:
|
|
|
|
|
|
// - 随机选中的元素
|
|
|
|
|
|
// - 错误(仅当elements为空/权重值为负时返回)
|
|
|
|
|
|
func RandomByWeight[Element any, Weight integer](elements []Element, weights []Weight) (Element, error) {
|
2026-02-05 00:29:19 +08:00
|
|
|
|
// 定义泛型零值,用于错误返回
|
2026-02-09 01:29:33 +08:00
|
|
|
|
var zero Element
|
2026-02-05 00:29:19 +08:00
|
|
|
|
|
2026-02-09 01:29:33 +08:00
|
|
|
|
// 1. 核心合法性校验:元素集合不能为空
|
|
|
|
|
|
if len(elements) == 0 {
|
|
|
|
|
|
return zero, errors.New("elements set is empty (cannot random from empty slice)")
|
2026-02-05 00:29:19 +08:00
|
|
|
|
}
|
2026-02-09 01:29:33 +08:00
|
|
|
|
|
|
|
|
|
|
// 2. 权重数组合法性校验:长度不匹配/为空时,降级为等概率随机(兼容原逻辑)
|
|
|
|
|
|
elemLen := len(elements)
|
|
|
|
|
|
if len(weights) == 0 || len(weights) != elemLen {
|
|
|
|
|
|
return elements[rand.IntN(elemLen)], nil
|
2026-02-05 00:29:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-09 01:29:33 +08:00
|
|
|
|
// 3. 校验权重非负,并计算总权重(统一转为int64避免溢出)
|
|
|
|
|
|
var totalWeight int64
|
|
|
|
|
|
// 预转换权重为int64,避免重复类型转换
|
|
|
|
|
|
intWeights := make([]int64, elemLen)
|
|
|
|
|
|
for i, w := range weights {
|
|
|
|
|
|
intW := int64(w)
|
|
|
|
|
|
if intW < 0 {
|
|
|
|
|
|
return zero, fmt.Errorf("invalid negative weight at index %d (value: %d)", i, w)
|
2026-02-05 00:29:19 +08:00
|
|
|
|
}
|
2026-02-09 01:29:33 +08:00
|
|
|
|
intWeights[i] = intW
|
|
|
|
|
|
totalWeight += intW
|
2026-02-05 00:29:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-09 01:29:33 +08:00
|
|
|
|
// 4. 总权重为0时,降级为等概率随机
|
|
|
|
|
|
if totalWeight == 0 {
|
|
|
|
|
|
return elements[rand.IntN(elemLen)], nil
|
2026-02-05 00:29:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-09 01:29:33 +08:00
|
|
|
|
// 5. 计算前缀和(权重随机核心逻辑,复用预转换的int64权重)
|
|
|
|
|
|
prefixWeights := make([]int64, elemLen)
|
|
|
|
|
|
prefixWeights[0] = intWeights[0]
|
|
|
|
|
|
for i := 1; i < elemLen; i++ {
|
|
|
|
|
|
prefixWeights[i] = prefixWeights[i-1] + intWeights[i]
|
2026-02-05 00:29:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-09 01:29:33 +08:00
|
|
|
|
// 6. 生成随机数并匹配前缀和(用int64避免溢出)
|
|
|
|
|
|
randomValue := grand.Intn(int(totalWeight))
|
|
|
|
|
|
for i, sum := range prefixWeights {
|
|
|
|
|
|
if randomValue < int(sum) {
|
|
|
|
|
|
return elements[i], nil
|
2026-02-05 00:29:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-09 01:29:33 +08:00
|
|
|
|
// 极端兜底:理论上不会走到这里(randomValue < totalWeight),返回第一个元素保证不panic
|
|
|
|
|
|
return elements[0], nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// integer 自定义泛型约束:匹配所有整数类型(扩展原~int的限制)
|
|
|
|
|
|
// 包含:int/int8/int16/int32/int64/uint/uint8/uint16/uint32/uint64/uintptr
|
|
|
|
|
|
type integer interface {
|
|
|
|
|
|
~int | ~int8 | ~int16 | ~int32 | ~int64 |
|
|
|
|
|
|
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
|
2026-02-05 00:29:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ************************** 函数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 {
|
2026-02-09 01:29:33 +08:00
|
|
|
|
return RandomByWeight(natureSet, []int{})
|
2026-02-05 00:29:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. string[] 转换为 int[](使用 gconv.Int 完成转换)
|
|
|
|
|
|
probInts := make([]int, len(probs))
|
|
|
|
|
|
for i, pStr := range probs {
|
|
|
|
|
|
// gconv.Int 灵活转换,失败返回0
|
|
|
|
|
|
probInts[i] = gconv.Int(pStr)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 调用核心函数,复用概率计算逻辑
|
2026-02-09 01:29:33 +08:00
|
|
|
|
return RandomByWeight(natureSet, probInts)
|
2026-02-05 00:29:19 +08:00
|
|
|
|
}
|
2026-02-25 19:05:50 +08:00
|
|
|
|
|
|
|
|
|
|
// IsCurrentTimeInRange 判断当前时间是否在 startStr 和 endStr 表示的时间区间内(格式:HH:MM)
|
|
|
|
|
|
// 返回值:true=在区间内,false=不在区间内,error=时间解析失败
|
|
|
|
|
|
func IsCurrentTimeInRange(startStr, endStr string) (bool, error) {
|
|
|
|
|
|
// 1. 解析开始和结束时间字符串为 time.Time 对象(日期用当前日期)
|
|
|
|
|
|
now := time.Now()
|
|
|
|
|
|
location := now.Location() // 使用当前时区,避免时区偏差
|
|
|
|
|
|
|
|
|
|
|
|
// 解析开始时间(HH:MM)
|
|
|
|
|
|
startTime, err := time.ParseInLocation("15:04", startStr, location)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return false, fmt.Errorf("解析开始时间 %s 失败:%w", startStr, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 解析结束时间(HH:MM)
|
|
|
|
|
|
endTime, err := time.ParseInLocation("15:04", endStr, location)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return false, fmt.Errorf("解析结束时间 %s 失败:%w", endStr, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 把开始/结束时间的日期替换为当前日期(只保留时分)
|
|
|
|
|
|
startToday := time.Date(now.Year(), now.Month(), now.Day(),
|
|
|
|
|
|
startTime.Hour(), startTime.Minute(), 0, 0, location)
|
|
|
|
|
|
endToday := time.Date(now.Year(), now.Month(), now.Day(),
|
|
|
|
|
|
endTime.Hour(), endTime.Minute(), 0, 0, location)
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 比较当前时间是否在 [startToday, endToday] 区间内
|
|
|
|
|
|
return now.After(startToday) && now.Before(endToday), nil
|
|
|
|
|
|
}
|