水果礼包拼手气(仿红包拼手气)含拼手气红包算法

现阶段有个需求:
现有一个水果礼包,礼包包含多品类、多规格、多数量的拼手气小红包生成。
转化为简单json 如: 礼包内有 {A={a1=3, a2=1}, B={b1=1}, C={c1=5}}
有A商品的a1规格3个,a2规格1个…
条件:有n个人按照拼手气抽取对应的水果。当然,抽取每种水果的几率是一样的,然后如果有多规格,再随机规格。


说一下我的思路吧,首先也是需要找到类似红包算法(因为是小白,算法这类的暂时是弱项了,网上大都说:截断正态分布或者叫做截尾正态分布)简单的说就是每次不能真正随机,要在平均数左右随机占大部分才是最符合红包的场景的,为此找了好久的算法,但是最后还是找到了类似的算法,这里因为计算精度的问题,又自己做了修改:

class LeftMoneyPackage {
        private int remainSize;
        private String remainMoney;

        public int getRemainSize() {
            return remainSize;
        }

        public void setRemainSize(int remainSize) {
            this.remainSize = remainSize;
        }

        public String getRemainMoney() {
            return remainMoney;
        }

        public void setRemainMoney(String remainMoney) {
            this.remainMoney = remainMoney;
        }

    }

    public static double getRandomMoney(LeftMoneyPackage _leftMoneyPackage) {
        // remainSize 剩余的红包数量
        // remainMoney 剩余的钱
        if (_leftMoneyPackage.remainSize == 1) {
            _leftMoneyPackage.remainSize--;
            return Double.parseDouble(_leftMoneyPackage.remainMoney);
        }
        Random r = new Random();
        String min = "0.01";
        String max = MoneyUtil
                .moneyMul(MoneyUtil.moneydiv(_leftMoneyPackage.remainMoney, (_leftMoneyPackage.remainSize) + ""), "2");
        String money = MoneyUtil.moneyMul(max, r.nextDouble() + "");
        money = MoneyUtil.moneyComp(min, money) ? "0.01" : money;
        _leftMoneyPackage.remainSize--;
        _leftMoneyPackage.remainMoney = MoneyUtil.moneySub(_leftMoneyPackage.remainMoney, money);
        return Double.parseDouble(money);
    }

    @Test
    public void testCount() {
        Map<Integer, Double> map = new HashMap<>();

        for (int a = 0; a < 10; a++) {//调整次数
            LeftMoneyPackage lp = new LeftMoneyPackage();
            lp.setRemainMoney("100");
            lp.setRemainSize(10);
            double totalMoney = 0;
            while (lp.getRemainSize() > 0) {
                double randomMoney = getRandomMoney(lp);
//              System.out.println("第 " + (10 - lp.getRemainSize()) + "次" + "   " + randomMoney);
                totalMoney = Double.parseDouble(MoneyUtil.moneyAdd(totalMoney + "", randomMoney + ""));
                int remainSize = lp.getRemainSize();
                if (map.get(remainSize) == null) {
                    map.put(remainSize, randomMoney);
                } else {
                    map.put(remainSize, map.get(remainSize) + randomMoney);
                }
            }
            System.out.println("总价:" + totalMoney);

        }

        Iterator<Entry<Integer, Double>> iterator = map.entrySet().iterator();

        while (iterator.hasNext()) {
            Entry<Integer, Double> next = iterator.next();
            System.out.println("第 " + next.getKey() + "个数平均值" + (next.getValue() / 10));//除以总次数
        }

    }

原理还没弄明白,但是有一点是需要注意的,这个算法不能保证每个人拿到的就是“0.01”,所以使用的时候应该把0.01改成0,先用总金额减去每个人的0.01拿出来再随机剩下的人可以拿到的金额。(先提出每人最少金额,再随机剩下的钱得到没人可以拿到的金额)
这个算法是修改后的,使用精度计算的java.math.BigDecimal;

其中有用到的MoneyUtil工具类:(如果审核没通过或者没有c币的童鞋可以联系我,或者百度上也有挺多的)
MoneyUtil.java

=-= 找这个花了好久的时间,还有验证,这个算法在随机次数越高的情况下,越接近我的理想值,所以我认为这是符合我的预期的算法。

接下来就是构造成为水果礼包的代码:这个还修改了上面算法有可能领取为0的情况(每人先取最少的份数)

@Test
    public void test() {

        try {
            for(int a = 0 ;a<10;a++) {

                Map<String, Map<String, Integer>> fruit_specification = new HashMap<>();
                Map<String, Integer> a1 = new HashMap<>();
                a1.put("a1", 3);
                a1.put("a2", 1);
                fruit_specification.put("A", a1);

                Map<String, Integer> b1 = new HashMap<>();
                b1.put("b1", 1);
                fruit_specification.put("B", b1);

                Map<String, Integer> c1 = new HashMap<>();
                c1.put("c1", 5);
                fruit_specification.put("C", c1);

                List<Map<String, Map<String, Integer>>> randomGoods = getRandomGoods(fruit_specification, 3, 10);
                System.out.println(JSON.toJSON(randomGoods));

            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public List<Map<String, Map<String, Integer>>> getRandomGoods(Map<String, Map<String, Integer>> fruit_specification, Integer personCount,
            Integer goodsNum) throws Exception {
        List<Map<String, Map<String, Integer>>> userGetList = new ArrayList<>();//随机出来的水果列表
        // 得出当次应该随机几次 可随机分配的份数 = goodsNum -总人数(每人至少得到一件商品)
        goodsNum = goodsNum-personCount;
        for (; personCount > 0; personCount--) {
            Map<String, Map<String, Integer>> fruitMap = new HashMap<>();
            if(personCount == 1) {//如果剩下最后一个人,剩下的水果都是这个人的
                userGetList.add(fruit_specification);
                continue;
            }
            int getCount = getRandomCount(personCount, goodsNum);
            goodsNum = goodsNum - getCount;//减少随机的数量
            getCount= getCount+1;//每个人参与的人至少得到一件商品
            System.out.println("第"+personCount+"个人--数量:"+getCount );
            for (int j = 0; j < getCount; j++) {// 一份中需要取出getCount份水果

                // 随机选择水果
                List<String> fruitIds = new ArrayList<>(fruit_specification.keySet());// 水果列表,用于随机水果
                int size = fruitIds.size();
                if (size == 0) {
                    throw new Exception("礼包水果异常");
                }

                Random rd = new Random();
                int index = rd.nextInt(size);// 从水果列表随机选出一种水果

                Map<String, Integer> map = fruit_specification.get(fruitIds.get(index));// 规格列表
                List<String> specifications = new ArrayList<>(map.keySet());
                if (specifications.size() == 1) {
                    // 如果只有单个规格,则直接选取该规格一份
                    Integer specNum = map.get(specifications.get(0));
                    if(specNum == 1) {//剩下最后一份了,拿完要删除,避免后面的人也拿到
                        fruit_specification.remove(fruitIds.get(index));
                        //放入规格
                        putRandomGoodsList(fruitMap, fruitIds.get(index), specifications.get(0));
                    }else {
                        map.put(specifications.get(0), specNum-1);
                        //放入规格
                        putRandomGoodsList(fruitMap, fruitIds.get(index), specifications.get(0));
                    }
                } else {
                    //否则是多个规格
                    int size2 = specifications.size();
                    int index2 = rd.nextInt(size2);// 随机选取规格
                    Integer specNum = map.get(specifications.get(index2));//规格剩余数量
                    if(specNum == 1) {
                        map.remove(specifications.get(index2));
                        //放入规格
                        putRandomGoodsList(fruitMap, fruitIds.get(index), specifications.get(index2));
                    }else {
                        map.put(specifications.get(index2), specNum-1);
                        //放入规格
                        putRandomGoodsList(fruitMap, fruitIds.get(index), specifications.get(index2));
                    }
                }
            }
            //加入每个人的份数
            userGetList.add(fruitMap);
        }
        return userGetList;
    }

    /***
     * 放入一次水果随机记录
     * @param fruitMap
     * @param fruitId
     * @param specId
     */
    private void putRandomGoodsList(Map<String, Map<String, Integer>> fruitMap,String fruitId,String specId) {
        if(fruitMap.get(fruitId) == null) {
            Map<String, Integer> specMap = new HashMap<>();
            specMap.put(specId, 1);
            fruitMap.put(fruitId, specMap);
        }else {
            Map<String, Integer> specMap = fruitMap.get(fruitId);
            if(specMap.get(specId) == null) {//规格为空
                specMap.put(specId, 1);
                fruitMap.put(fruitId, specMap);
            }else {//两次抢到同一个商品
                specMap.put(specId, specMap.get(specId)+1);
            }
        }
    }

    /**
     * 根据抢礼包的人数和商品份数得到此次随机抽取次数
     * 
     * @param personCount
     * @param goodsNum
     * @return
     */
    public Integer getRandomCount(Integer personCount, Integer goodsNum) {
        if (personCount == 1) {
            return goodsNum;
        }
        Integer getNum = 0;
        Random r = new Random();
        if(goodsNum < personCount) {//既然份数都这么少,我就给他们随机了 =-=
            getNum = r.nextInt(goodsNum+1);
        }else {
            Integer min = 0;
            Integer max = getRoundNum(goodsNum.doubleValue() / personCount.doubleValue()) * 2;
            getNum = getRoundNum(r.nextDouble() * max);
            getNum = getNum < min ? min : getNum;
            getNum = getNum > goodsNum ? goodsNum : getNum;
        }
        return getNum;
    }

    /**
     * 四舍五入取整
     * 
     * @param num
     * @return
     */
    public int getRoundNum(double num) {
        double n = (double) ((int) num) + 0.5f;
        if (num < n) {
            return (int) (n - 0.5);
        } else {
            return (int) (n + 0.5f);
        }
    }

这边的思路是先随机得到抽取的次数,再每次从水果的map中选取水果,如果有多个水果规格,再随机获取规格。获取完减去/删除对应的数据即可。感觉最后水果礼包好像还没有像截尾正态分布那样,但是也尽量做到了平均了吧,=-=。
如果有更好的见解欢迎留言区留言 ╰( ̄▽ ̄)╮

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值