最近开始学go, 在慕课网上趁热打铁学了下抢红包的教程:
https://www.imooc.com/learn/345 go语言第一课
https://www.imooc.com/learn/1101 3小时极简春节抢红包之Go的实战
抢红包金额设计(2个公式):
最大可调度金额 = 总金额 - 最小金额*红包数量
平均可调度金额 = 最大可调度金额/红包数量
抢红包可以如下分为4个算法。
简单随机算法 | (1)算法:红包序列元素 = 随机数(0~最大可用金额) + 最小金额 (2)比较:缺点是金额先大后小 |
后洗牌算法 | (1)算法:2次随机,先随机再洗牌 红包序列元素 = 随机数(0~最大可用金额) + 最小金额 对生成的序列再洗牌打乱 (2)比较 缺点:需事先生成整个序列;只适用于发红包时生成 与简单随机比较:差不多,总体慢不到万分之一 |
先洗牌算法 | (1)算法:2次随机,先洗牌后随机 生成以一个一定长度的红包种子金额序列: a.种子金额序列长度控制在3~1/3 b.红包序列元素 = 最大可用剩余金额 / (n+1) c.最大可用剩余金额 = 剩余金额 - 最小金额*剩余数量 再随机一个数字,取模计算索引 按索引从种子金额序列拿出一个金额作为基数 红包序列元素 = 基数作为最大数随机 + 最小金额 (2)比较:随红包数量的变大,计算性能变差,红包分布出现先大后小 |
2次随机算法 (改进先洗牌算法) | (1)算法: |
2倍均值算法 | (1)算法: a.每次随机金额的平均值基本相等 b.剩余金额平均数的2倍作为随机最大数 问:为何是2倍,而不是3倍? 答:每次随机金额的平均值作为中位数,随机上下范围一致,(最大值+最小值)/2 = 平均值 |
简单随机
max = amount-count*min rand(max)+min | ||
后洗牌
remain = amount inds = make([]int64, 0) for(i=0 ~ <count) { x = SimpleRand(count-i, remain) remain -= x inds = append(inds, x) } | ||
先洗牌算法
seeds[ size = 3 ~ 1/3*count ] for(i=0 ~ <size) seeds = append(seeds, max/(i+1)) idx = rand(len(seeds)) x = rand(seeds[idx]) + min | ||
二次随机算法(改进先洗牌算法)
n = max/(rand(2*count)+1) + min x = rand(n) + min | ||
2倍均值算法
avg2 = 2*(max/count) + min x = avg2 + min |
1、简单随机算法
(1)算法:红包序列元素 = 随机数(0~最大可用金额) + 最小金额
(2)比较:缺点是金额先大后小
-----------------------------------------
package algo
import (
"math/rand"
)
const min = int64(1)
//简单随机算法;红包的数量,红包金额;金额单位为分,1元钱=100分
func SimpleRand(count, amount int64) int64 {
if count == 1 { return amount}
max := amount - min*count
x := rand.Int63n(max) + min
return x
}
-------------------------
package main
import "xxx/infra/algo"
func main() {
count,amount := int64(10),int64(100)
for i := int64(0); i < count; i++ {
x := algo.SimpleRand(count, amount*100)//单位分
fmt.Print(float64(x)/float64(100), ",")
}
fmt.Println()
}
-----------------------------------------
2、后洗牌算法
(1)算法:2次随机,先随机再洗牌
红包序列元素 = 随机数(0~最大可用金额) + 最小金额
对生成的序列再洗牌打乱
(2)比较
缺点:需事先生成整个序列;只适用于发红包时生成
与简单随机比较:差不多,总体慢不到万分之一
-----------------------------------------
package algo
import "math/rand"
func AfterShuffle(count, amount int64) []int64 {
inds := make([]int64, 0)
//计算最大可调度金额
max := amount - min*count
remain := max
//随机生成初级红包序列
for i := int64(0); i < count; i++ {
x := SimpleRand(count-i, remain)
remain -= x
inds = append(inds, x)
}
//洗牌,洗初级红包序列
rand.Shuffle(len(inds), func(i, j int) {
inds[i], inds[j] = inds[j], inds[i]
})
return inds
}
-------------------------
package main
import (
"fmt"
"imooc.com/resk/infra/algo"
)
func main() {
fmt.Println(algo.AfterShuffle(int64(10), int64(10000)))
}
-----------------------------------------
3、先洗牌算法
(1)2次随机,先洗牌后随机
生成以一个一定长度的红包种子金额序列:
a.种子金额序列长度控制在3~1/3
b.序列元素为最大可用剩余金额/(n+1)
c.最大可用剩余金额 = 剩余金额 - 最小金额*剩余数量
再随机一个数字,取模计算索引
按索引从种子金额序列拿出一个金额作为基数
基数作为最大数随机 + 最小金额作为红包序列元素
(2)比较:随红包数量的变大,计算性能变差,红包分布出现先大后小
-----------------------------------------
package algo
import (
"math/rand"
"time"
)
func BeforeShuffle(count, amount int64) int64 {
if count == 1 {return amount}
//计算出最大可调度金额
max := amount - min*count
//生成红包种子金额序列
seeds := make([]int64, 0)
//红包种子金额序列长度=3~1/2*count
size := count / 2
if size < 3 {
size = 3
}
for i := int64(0); i < size; i++ {
x := max / (i + 1)
seeds = append(seeds, x)
}
rand.Seed(time.Now().UnixNano())
//从红包种子金额序列中随机选择一个作为随机基数
idx := rand.Int63n(int64(len(seeds)))
//使用随机基数作为最大数,随机出一个数字作为红包金额序列元素
x := rand.Int63n(seeds[idx])
return x + min
}
-------------------------
package main
import (
"fmt"
"imooc.com/resk/infra/algo"
)
func main() {
count, amount := int64(10), int64(100)
for i := int64(0); i < count; i++ {
x := algo.BeforeShuffle(count, amount*100)
fmt.Print(x, ",")
}
fmt.Println()
}
-----------------------------------------
4、二次随机算法(改进先洗牌算法)
(1)算法
优化上述2个缺点,改为双随机:
a.把种子金额序列的生成改成随机生成
b.随机种子的数量,范围是0~2倍剩余数量
c.剩余金额除以随机的种子数量
(2)比较:缺点是当红包金额较大且数量较小时,最后一个大金额的概率增加
-----------------------------------------
package algo
import (
"math/rand"
"time"
)
func DoubleRandom(count, amount int64) int64 {
if count == 1 {return amount}
//计算最大可调度金额
max := amount - min*count
rand.Seed(time.Now().UnixNano())
//一次随机,计算出一个种子金额作为基数
seed := rand.Int63n(count*2) + 1
n := max/seed + min
//二次随机,计算出红包金额序列元素
x := rand.Int63n(n)
return x + min
}
-------------------------
package main
import (
"fmt"
"imooc.com/resk/infra/algo"
)
func main() {
count, amount := int64(10), int64(100)
for i := int64(0); i < count; i++ {
x := algo.DoubleRandom(count, amount*100)
fmt.Print(x, ",")
}
fmt.Println()
}
-----------------------------------------
4、2倍均值算法
(1)算法:
a.每次随机金额的平均值基本相等
b.剩余金额平均数的2倍作为随机最大数
问:为何是2倍,而不是3倍?
答:每次随机金额的平均值作为中位数,随机上下范围一致,(最大值+最小值)/2 = 平均值
(2)
-----------------------------------------
package algo
import (
"math/rand"
"time"
)
func DoubleAverage(count, amount int64) int64 {
if count == 1 {
return amount
}
//计算出最大可用金额
max := amount - min*count
//计算最大可用平均值
avg := max / count
//二倍均值基础在加上最小金额,防止出现0值
avg2 := 2*avg + min
//随机红包金额序列元素,把二倍均值作为随机的最大数
rand.Seed(time.Now().UnixNano())
x := rand.Int63n(avg2) + min
return x
}
-------------------------
package main
import (
"fmt"
"imooc.com/resk/infra/algo"
)
func main() {
count, amount := int64(10), int64(100)
for i := int64(0); i < count; i++ {
x := algo.DoubleAverage(count, amount*100)
fmt.Print(x, ",")
}
fmt.Println()
}
-----------------------------------------