From c27059345afb2f840234ea697319ed89ae89ad52 Mon Sep 17 00:00:00 2001 From: dengbiao Date: Fri, 15 Nov 2024 14:54:52 +0800 Subject: [PATCH] add new_user_red_package --- .../new_user_red_package.go | 222 ++++++++++++++++++ rule/new_user_red_package/util_test.go | 67 ++++++ 2 files changed, 289 insertions(+) create mode 100644 rule/new_user_red_package/new_user_red_package.go create mode 100644 rule/new_user_red_package/util_test.go diff --git a/rule/new_user_red_package/new_user_red_package.go b/rule/new_user_red_package/new_user_red_package.go new file mode 100644 index 0000000..a5034b6 --- /dev/null +++ b/rule/new_user_red_package/new_user_red_package.go @@ -0,0 +1,222 @@ +package new_user_red_package + +import ( + "errors" + "fmt" + "math" + "math/rand" +) + +// RedPacketDistributor 红包分发器,负责管理和分发红包 +type RedPacketDistributor struct { + TotalAmount float64 // 总金额 + TotalDays int // 总天数 + RemainAmount float64 // 剩余金额 + RemainDays int // 剩余天数 + DailyAverage float64 // 每日平均金额 (TotalAmount / TotalDays) + DistributionHistory []float64 // 历史分发记录,用于调节分发策略 +} + +// DailyRedPacket 每日红包的详细信息 +type DailyRedPacket struct { + Day int // 当前天数 + BaseAmount float64 // 基础金额(未翻倍前) + IsMultiplied bool // 是否选择翻倍 + Multiplier float64 // 翻倍倍数 + FinalAmount float64 // 最终金额 + RemainAmount float64 // 剩余金额 + SafetyFactor float64 // 安全系数 +} + +// NewRedPacketDistributor 创建新的红包分发器 +func NewRedPacketDistributor(totalAmount, remainAmount float64, totalDays, remainDays int, distributionHistory []float64) *RedPacketDistributor { + return &RedPacketDistributor{ + TotalAmount: totalAmount, + TotalDays: totalDays, + RemainAmount: remainAmount, + RemainDays: remainDays, + DailyAverage: totalAmount / float64(totalDays), + DistributionHistory: distributionHistory, + } +} + +// NewRedPacketDistributorForTest 创建新的红包分发器(TODO::测试使用) +func NewRedPacketDistributorForTest(totalAmount float64, totalDays int) *RedPacketDistributor { + return &RedPacketDistributor{ + TotalAmount: totalAmount, + TotalDays: totalDays, + RemainAmount: totalAmount, + RemainDays: totalDays, + DailyAverage: totalAmount / float64(totalDays), + DistributionHistory: make([]float64, 0, totalDays), + } +} + +// calculateDynamicSafetyFactor 计算动态安全系数 +// 该系数用于控制红包金额的波动范围,确保资金分配的合理性 +func (r *RedPacketDistributor) calculateDynamicSafetyFactor(currentDay int) float64 { + // 基础安全系数 + baseSafety := 0.8 + + // 根据剩余金额与理想剩余金额的比率调整安全系数 + remainingRatio := r.RemainAmount / (r.DailyAverage * float64(r.RemainDays)) + if remainingRatio > 1.2 { // 剩余金额偏多,可以适当放宽限制 + baseSafety += 0.1 + } else if remainingRatio < 0.8 { // 剩余金额偏少,需要更谨慎 + baseSafety -= 0.1 + } + + // 根据历史分发情况调整安全系数 + if len(r.DistributionHistory) > 0 { + // 计算历史平均分发金额 + avgDistributed := 0.0 + for _, amount := range r.DistributionHistory { + avgDistributed += amount + } + avgDistributed /= float64(len(r.DistributionHistory)) + + // 如果历史平均值明显高于每日平均值,降低安全系数 + if avgDistributed > r.DailyAverage*1.2 { + baseSafety -= 0.05 + } + } + + // 在后期(进度>70%)逐步降低安全系数,允许更大的波动 + progress := float64(currentDay) / float64(r.TotalDays) + if progress > 0.7 { + baseSafety -= 0.1 * (progress - 0.7) + } + + // 确保安全系数在合理范围内 [0.5, 0.95] + return math.Max(0.5, math.Min(0.95, baseSafety)) +} + +// calculateMultiplier 计算翻倍倍率 +// 根据基础金额、每日平均值和剩余金额动态调整翻倍倍率 +func (r *RedPacketDistributor) calculateMultiplier(baseAmount float64) float64 { + // 基础金额与每日平均值的比率 + ratio := baseAmount / r.DailyAverage + // 基础倍率:金额越大,倍率越小 + baseMultiplier := 2.5 - (1.5 * math.Min(ratio, 1.0)) + + // 添加随机浮动,使金额更自然 [0.95, 1.05] + randomFactor := 0.95 + rand.Float64()*0.1 + + // 根据剩余金额状况调整最大倍率 + maxMultiplier := 3.0 + if r.RemainAmount < r.DailyAverage*float64(r.RemainDays) { + // 剩余金额不足时,降低最大倍率 + maxMultiplier = 2.0 + } + + // 确保最终倍率在合理范围内 [1.1, maxMultiplier] + return math.Max(1.1, math.Min(baseMultiplier*randomFactor, maxMultiplier)) +} + +// calculateSafeAmount 计算安全的基础金额 +// 通过多个维度的约束确保金额分配的合理性 +func (r *RedPacketDistributor) calculateSafeAmount(currentDay int) float64 { + // 最后一天直接返回剩余金额 + if r.RemainDays <= 1 { + return r.RemainAmount + } + + // 获取当前的安全系数 + safetyFactor := r.calculateDynamicSafetyFactor(currentDay) + // 计算剩余天数的平均金额 + avgRemaining := r.RemainAmount / float64(r.RemainDays) + + // 根据进度调整金额范围 + progress := float64(currentDay) / float64(r.TotalDays) + // 最小比率:随进度递减,确保后期有足够金额 + minRatio := 0.4 + (0.2 * (1.0 - progress)) + // 最大比率:随进度递增,允许更大波动 + maxRatio := 1.3 + (0.4 * progress) + + // 计算金额范围 + minAmount := avgRemaining * minRatio + maxAmount := avgRemaining * maxRatio + + // 预留安全金额,确保后续天数有基本保障 + safeReserve := r.RemainAmount * 0.3 / float64(r.RemainDays) + maxAllowed := r.RemainAmount - (safeReserve * float64(r.RemainDays-1)) + + // 在允许范围内随机生成金额 + randomRatio := rand.Float64() * safetyFactor + amount := minAmount + randomRatio*(maxAmount-minAmount) + + // 确保不超过最大允许金额 + return math.Min(amount, maxAllowed) +} + +// DistributeRedPacket 分发红包 +// 根据用户选择和各种约束条件计算最终的红包金额 +func (r *RedPacketDistributor) DistributeRedPacket(day int, userChooseMultiply bool) (DailyRedPacket, error) { + // 检查是否还有红包可分发 + if r.RemainDays <= 0 || r.RemainAmount <= 0 { + return DailyRedPacket{}, errors.New("当前红包已经发完!") + } + + // 初始化结果 + result := DailyRedPacket{ + Day: day, + IsMultiplied: false, + Multiplier: 1.0, + } + + // 计算安全系数 + safetyFactor := r.calculateDynamicSafetyFactor(day) + result.SafetyFactor = safetyFactor + + // 计算基础金额 + if r.RemainDays == 1 { + // 最后一天,分配所有剩余金额 + result.BaseAmount = r.RemainAmount + } else { + result.BaseAmount = r.calculateSafeAmount(day) + } + + // 处理翻倍逻辑 + if r.RemainDays > 1 && userChooseMultiply { + result.IsMultiplied = true + result.Multiplier = r.calculateMultiplier(result.BaseAmount) + result.FinalAmount = result.BaseAmount * result.Multiplier + } else { + result.FinalAmount = result.BaseAmount + } + + // 最终安全检查 + maxAllowed := r.RemainAmount + if r.RemainDays > 1 { + // 确保为后续天数预留足够金额 + minFutureNeed := (r.RemainAmount * 0.2) / float64(r.RemainDays) + maxAllowed = r.RemainAmount - (minFutureNeed * float64(r.RemainDays-1)) + } + result.FinalAmount = math.Min(result.FinalAmount, maxAllowed) + + // 更新分发器状态 + r.RemainAmount -= result.FinalAmount + r.RemainDays-- + result.RemainAmount = r.RemainAmount + + // 记录分发历史 + r.DistributionHistory = append(r.DistributionHistory, result.FinalAmount) + + return result, nil +} + +// formatRedPacketInfo 格式化红包信息输出 +func formatRedPacketInfo(packet DailyRedPacket) string { + multiplierInfo := "否" + if packet.IsMultiplied { + multiplierInfo = fmt.Sprintf("是 (%.2f倍)", packet.Multiplier) + } + + return fmt.Sprintf(`第 %d 天红包详情: + 基础金额: %.2f 元 + 是否翻倍: %s + 安全系数: %.2f + 最终金额: %.2f 元 + 剩余金额: %.2f 元 + `, packet.Day, packet.BaseAmount, multiplierInfo, packet.SafetyFactor, packet.FinalAmount, packet.RemainAmount) +} diff --git a/rule/new_user_red_package/util_test.go b/rule/new_user_red_package/util_test.go new file mode 100644 index 0000000..e72888e --- /dev/null +++ b/rule/new_user_red_package/util_test.go @@ -0,0 +1,67 @@ +package new_user_red_package + +import ( + "fmt" + "math/rand" + "testing" + "time" +) + +func TestDistributeRedPacket(t *testing.T) { + // 初始化随机数种子 + rand.Seed(time.Now().UnixNano()) + // 定义测试用例 + testCases := []struct { + amount float64 + days int + userChoices []bool // 每天用户是否选择翻倍 + }{ + { + amount: 10.0, + days: 5, + userChoices: []bool{true, false, true, false, false}, + }, + { + amount: 20.0, + days: 7, + userChoices: []bool{true, true, false, false, true, false, false}, + }, + } + + // 运行测试用例 + for _, tc := range testCases { + runTest(tc.amount, tc.days, tc.userChoices) + } +} + +// runTest 运行测试用例 +func runTest(totalAmount float64, totalDays int, userChoices []bool) { + fmt.Printf("\n测试: %.2f元 / %d天\n", totalAmount, totalDays) + fmt.Printf("每日平均金额: %.2f元\n", totalAmount/float64(totalDays)) + fmt.Println("----------------------------------------") + + distributor := NewRedPacketDistributorForTest(totalAmount, totalDays) + var totalDistributed float64 + + for day := 1; day <= totalDays; day++ { + // 获取用户选择 + userChoice := false + if day <= len(userChoices) { + userChoice = userChoices[day-1] + } + + // 分发红包并打印结果 + packet, err := distributor.DistributeRedPacket(day, userChoice) + if err != nil { + println("err:::", err.Error()) + break + } + totalDistributed += packet.FinalAmount + fmt.Println(formatRedPacketInfo(packet)) + } + + // 打印总结信息 + fmt.Println("----------------------------------------") + fmt.Printf("总计分发: %.2f 元\n", totalDistributed) + fmt.Printf("剩余金额: %.2f 元\n", distributor.RemainAmount) +}