背景
需要做一个给每个人参加活动的人瓜分固定奖池的游戏币,有平均瓜分和随机分2种分配方案。
平均瓜分
平均分很简单,就是总money/总person取整数就行,无论整除不整除,不整除有结余也无所谓。
随机瓜分
需求详情
根据一个时间段的参与人数设置一个总游戏币数,所有人进行瓜分,每个人最少获得一个币,同时可以无限次重新随机计算,计算出来的列表进行分页展示,而且支持对单个人获得币进行修改,然后统一生成奖励。
思路
- 可以多次进行重新随机计算,所以不能直接将计算的结果持久化,防止大量操作删表新增操作,所以先保存数据到redis中做缓存
- 在缓存中需要做分页查询,类似查询榜单,list,zset类型可实现分页查询,不需要每次查询都将所有数据拉出来进行分页筛选,zset占用空间大,而且也不需要格外的value做实时排列。所以暂时选用list做存储类型
- 缓存中需要保存用户与随机得到的游戏币的对应,选用person+分隔符+币 的String拼接实现
- 需要针对一个用户获得币进行修改,list类型可根据下标索引进行修改,可支持
- 计算出来后,每个用户对应一个随机游戏币,需要按币的大小进行排序,存入redis,重写list,String排序实现
代码实现
涉及的redis操作(简化)
jedis.lpush(key,value); //存放key,value value是可变类型,LPUSH mylist a b c
jedis.lrange(key, startNumber, endNumber);//查询
jedis.lset(key,index,value); //根据index进行修改
jedis.lindex(key, index); //根据当前index获取list中的值
随机算法工具类
- 模拟微信红包算法(简单实现)
-
每个人最少得到1游戏币,所以先给每个人分配一个游戏币,保证在随机下每个人所得不为0
-
随机每个人所得范围在(0-money / people * 2)
-
同时利用Random产生的随机数固定种子下产生的随机序列数相同来排查记录。
-
当前利用微信抢红包不需要一次性将整个分布计算下来,可以在每个人领红包的时候再进行计算
-
利用excel工具直观查看随机数分布是否满足预想
-
废话不多说,贴代码:
-
@Test
public void randTestTest(){
randTest(1000,100);
}
public static void randTest(int sum,int count){
List<Integer> list = new ArrayList<>();
sum = sum-count*1;
Random random = new Random();
long factor = System.currentTimeMillis();
System.out.println("固定种子是:"+factor);
random.setSeed(factor);//当random设置相同的种子时,随机序列数相同
for(int i = 0;i<count;i++){
int rand = rand(sum, count-i, list,random);
sum = rand;
}
int summoneny = 0;
int min =list.get(0);
int max =-1;
for(Integer entry :list){
summoneny = summoneny+entry.intValue();
min = Math.min(min,entry.intValue());
max = Math.max(max,entry.intValue());
}
System.out.println(list.toString());
System.out.println(min);
System.out.println(max);
}
/**
*
* @param money 剩余的钱
* @param people 剩余的人数
* @param l 红包列表
* @return
*/
public static int rand(int money, int people, List<Integer> l ,Random random) {
if (people == 1) {
int red = money;
l.add(red+1);
return 0;
}
int min = 0;
int max = money / people * 2;
int red = random.nextInt(max);
red = red <= min ? min : red;
l.add(red+1);
int remain = money-red;
return remain;
}
- 以上思想是每个人口袋能得到多少游戏币,随机在奖池中取钱。现我们转变一下思想,利用游戏币找口袋,游戏币随机落入口袋。
-
分布图展示,看起来后者分布更加均匀
-
利用钱找人,有可能一个人没有被找过一次,当然可以先给每个人先分一个游戏币,然后剩下的币找人,上代码
-
@Test
public void rand1Test(){
Random random = new Random();
long factor = System.currentTimeMillis();
System.out.println("固定种子是:"+factor);
random.setSeed(factor);//当random设置相同的种子时,随机序列数相同
int[] ints = rand1(1000, 100, random);
int a = 0;
int min =ints[0];
int max =-1;
for (int integer : ints) {
a+=integer;
min = Math.min(min,integer);
max = Math.max(max,integer);
}
System.out.println(Arrays.toString(ints));
System.out.println(a);
System.out.println(min);
System.out.println(max);
}
/**
*
* @param money 总钱
* @param count 剩余的人数
* @return
*/
public int[] rand1(int money, int count ,Random random) {
int[] nums = new int[count];
for(int i= 0;i<money;i++){
int person = random.nextInt(count);
nums[person] +=1;
}
return nums;
}
重写排序工具类
public static void sortListString(List list){
System.out.println("排序前:"+list.toString());
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return Integer.valueOf(o1.substring(o1.indexOf(":")+1,o1.length()))
.compareTo(Integer.valueOf((o2.substring(o2.indexOf(":")+1,o2.length()))));
}
});
System.out.println("排序后:"+list.toString());
}
总结
- 整个过程也看过网上很多介绍正态分布的算法,还有网上各位大神的分析与实现,以上是一个简单的实现同时也很好的契合要求,故实现如此,作此记录。
- 在redis的操作文档中只发现如下的存储方式,RPUSH mylist a b c,所以选择String作为载体进行存储,当然可以选择list中保存可序列化对象,将对象序列化后再保存到redis中,可以添加更多的属性。由于时间问题并未依此方案实现。