想必大家在过年的时候玩微信抢红包非常高兴,今天项目中碰到一个问题:
总共有2000金币,需要随机掉落到N个怪物身上,每只怪掉率的金币不的少于 2000/(2 * N), N 可以被2000 整除。
遇到这个问题后,我想到了过年时大家都玩的很高兴的微信红包,当时我想到了微信红包这种随机算法是如何实现的,其中有没有顺序漏洞存在,这样通过领取的时机让自己利益最大化。因为很多微信红包在群里发的时候,都是整个群所有成员全体发,这样的话每个人都会都到一个红包,是不是我最后一个领,得到的最多?后来这个问题就搁浅了,没在去想,这两天项目中遇到了同样的问题,大概思考了一下,写出了游戏中怪随机掉落的算法。
def get_reward_arr(coin_sum, n):
det_coin = coin_sum / n
coin_arr = [det_coin for i in range(n)]
max_lose_coin = det_coin / 2 - 1
for i in range(n):
lose_coin = random.randint(0, max_lose_coin)
win_index = random.randint(0, n - 1)
coin_arr[i] -= lose_coin
coin_arr[win_index] += lose_coin
return coin_arr
程序首先将金币分成N等份,然后从每份中拿出最多一半的金币,在随机出一个获得金币的index,将此金币加到获得金币的index。
上面是我想到的一种方式,还有一种常见的方式:
def get_reward_arr(coin_sum, n):
rand_arr = [random.randint(n / 2, n) for i in range(n)]
rand_sum = reduce(lambda x, y: x + y, rand_arr)
coin_arr = [(rand_arr[i] * coin_sum) / rand_sum for i in range(n)]
return coin_arr
这种方式是先随机出一组数组,然后每次通过随机出占用比来得到获得的金币数。
结果有一天看到一篇博客里有谈到微信红包的实现方式,为了节省空间,他们利用了随机种子一定,则随机数就一定的原理,每次只记录总钱数,第几次获取,随机种子,总共分多少人。数据结构大概是:money, n, times, seed。这里不讨论具体微信如何处理浮点数,如何处理锁的问题,只探讨随机实现方式:
def get_reward_coin(coin_sum, n, seed, times):
random.seed(seed)
rand_num = 0
rand_sum = 0
for i in range(n):
ret = random.randint(n / 2, n)
rand_sum += ret
if i == times:
rand_num = ret
return (rand_num * coin_sum) / rand_sum
通过cpu的时间来节省内存空间,非常聪明的做法。
上面的算法并没有考虑浮点数的问题,如果项目中涉及到浮点数问题,对于不同的项目有不同的解决方案。
所有的测试代码可以通过访问这里。参考到的文章可以访问后端技术 by Tim Yang。