抢红包算法demo

网上那些乱七八糟的真是太多了 又不能运行 写的和描述的结果都不一致
决定自己写一个~
代码可以直接运行的
喜欢的话麻烦点个三连~~~

package com.superbrain.envelope.algorithm;

import lombok.Builder;
import lombok.Data;

import java.io.Serializable;
import java.math.BigDecimal;

/**
 * <p> Description:抢红包</p >
 * <p> CreationTime: 2021/4/10 16:18
 *
 * @author wangguowen
 * @since 1.0
 */
@Data
@Builder
public class RedEnvelope implements Serializable {

    private static final long serialVersionUID = -704116947856423131L;

    /**
     * 是否公平--固定金额/随机金额
     */
    private boolean fair;
    /**
     * 红包总金额(元)
     */
    private BigDecimal totalAccount;
    /**
     * 红包总数量
     */
    private int totalNum;

}

package com.superbrain.envelope.service;

import com.superbrain.envelope.algorithm.RedEnvelope;

import java.math.BigDecimal;
import java.util.List;

/**
 * <p> Description:RedEnvelopeService</p >
 * <p> CreationTime: 2021/4/11 12:03
 * <br>Copyright: &copy;2021 < a href=" ">Thunisoft</ a>
 * <br>Email: < a href="mailto:wangguowen@thunisoft.com">wangguowen@thunisoft.com</ a></p >
 *
 * @author wangguowen
 * @since 1.0
 */
public interface RedEnvelopeService {
    /**
     * 返回红包数组
     *
     * @param redEnvelope 红包对象
     * @param userId      用户id
     * @return 预分配好的红包集合
     */
    List<BigDecimal> grabRedEnvelope(RedEnvelope redEnvelope, int userId);
}
package com.superbrain.envelope.service.impl;

import com.superbrain.envelope.model.RedEnvelope;
import com.superbrain.envelope.service.RedEnvelopeService;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.util.Lists;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

/**
 * <p> Description:RedEnvelopeServiceImpl</p >
 * <p> CreationTime: 2021/4/11 12:04
 *
 * @author wangguowen
 * @since 1.0
 */
@Service
@Slf4j
public class RedEnvelopeServiceImpl implements RedEnvelopeService {

    /**
     * 最小分配的值
     */
    @Value("${red.envelope.account.min}")
    private String minAccount;
//
//    @Value("${red.envelope.account.max}")
//    private String maxAccount;

    private static Random random = new Random();

    static {
        random.setSeed(System.currentTimeMillis());
    }

    static double sqrt(double n) {
        // 对n开根号
        return Math.sqrt(n);
    }

    static double sqr(double n) {
        // 计算平方
        return n * n;
    }

    static double nextRandom(double n) {
        return random.nextDouble() * n;
    }

    /**
     * 生产min和max之间的随机数,但是概率不是平均的,从min到max方向概率逐渐加大。
     * 先平方,然后产生一个平方值范围内的随机数,再开方,这样就产生了一种“膨胀”再“收缩”的效果。
     *
     * @param min
     * @param max
     * @return
     */
    static double xRandom(double min, double max) {
        return sqrt(nextRandom(sqr(max - min)));
    }


    /**
     * 返回红包集合
     *
     * @param redEnvelope 红包对象
     * @return 预分配好的红包集合
     */
    @Override
    public List<BigDecimal> grabRedEnvelope(RedEnvelope redEnvelope) {
        if (accountValid(redEnvelope)) {
            return Lists.newArrayList();
        }
        List<BigDecimal> redEnvelopeList = redEnvelope.isFair() ?
                //金额固定
                fairAccountList(redEnvelope.getTotalAccount(), redEnvelope.getTotalNum()) :
                //金额随机
                randomAccountList(redEnvelope.getTotalAccount(), redEnvelope.getTotalNum());
        //打乱顺序
        Collections.shuffle(redEnvelopeList);
        return redEnvelopeList;
    }


    /**
     * 数额固定的红包分配机制
     *
     * @param redEnvelopeAccount 红包总金额
     * @param redEnvelopeNum 红包数量
     * @return 红包集合
     */
    private List<BigDecimal> fairAccountList(BigDecimal redEnvelopeAccount, int redEnvelopeNum) {
        List<BigDecimal> res = new ArrayList<>(redEnvelopeNum);
        //平均数
        BigDecimal avg = redEnvelopeAccount.divide(new BigDecimal(redEnvelopeNum), 2, RoundingMode.HALF_UP);
        while (redEnvelopeNum > 1) {
            //集合中添加固定金额
            //  BigDecimal money = avg;
            res.add(avg);
            redEnvelopeNum--;
            redEnvelopeAccount = redEnvelopeAccount.subtract(avg);
        }
        //最后一个红包为剩余余额
        res.add(redEnvelopeAccount);
        return res;
    }


    /**
     * 数额随机的红包分配机制
     *
     * @param redEnvelopeAccount 红包总金额
     * @param redEnvelopeNum 红包数量
     * @return 红包集合
     */
    private List<BigDecimal> randomAccountList(BigDecimal redEnvelopeAccount, int redEnvelopeNum) {
        List<BigDecimal> res = new ArrayList<>(redEnvelopeNum);
        while (redEnvelopeNum > 1) {
            //最大的红包为 (平均红包大小)*2
            BigDecimal avg = redEnvelopeAccount.divide(new BigDecimal(redEnvelopeNum), 2, RoundingMode.DOWN);
            BigDecimal avgMax = avg.multiply(new BigDecimal(2));
            //产生[0,1)之间的随机数
            double randomNum = random.nextDouble();
            //抢到的红包区间[0,avgMax)
            double doubleMoney = avgMax.doubleValue() * randomNum;
            //保留两位小数
            BigDecimal money = new BigDecimal(doubleMoney).setScale(2, RoundingMode.DOWN);
            //保证最小值是minAccount
            if (minAccount != null && money.compareTo(new BigDecimal(minAccount)) < 0) {
                money = new BigDecimal(minAccount);
            }
//            //如果超过了最大值
//            if (maxAccount != null && money.compareTo(new BigDecimal(maxAccount)) > 0) {
//                double xRandom = xRandom(new BigDecimal(minAccount).doubleValue(), new BigDecimal(maxAccount).doubleValue());
//                money = new BigDecimal(xRandom).add(avg);
//            }
            res.add(money);
            //减红包数量 减红包金额
            redEnvelopeNum--;
            redEnvelopeAccount = redEnvelopeAccount.subtract(money);
        }
        log.info("最后一个红包是:" + redEnvelopeAccount);
        //最后一个红包为剩余余额
        res.add(redEnvelopeAccount);
        return res;
    }


    private boolean accountValid(RedEnvelope redEnvelope) {
        if (redEnvelope.getTotalAccount().compareTo(new BigDecimal(0)) <= 0) {
            log.error("总金额必须大于0");
            return true;
        }
        if (redEnvelope.getTotalNum() <= 0) {
            log.error("总数量必须大于0");
            return true;
        }
//        BigDecimal bigDecimal = redEnvelope.getTotalAccount().divide(new BigDecimal(redEnvelope.getTotalNum()),
//                2, RoundingMode.DOWN);
        if (minAccount != null && (new BigDecimal(redEnvelope.getTotalNum()).multiply(new BigDecimal(minAccount))).compareTo(redEnvelope.getTotalAccount()) > 0) {
            log.error("您必须保证每个红包至少" + minAccount + "元");
            return true;
        }
//        if ((new BigDecimal(maxAccount).subtract(new BigDecimal(minAccount))).compareTo(new BigDecimal("1")) < 0) {
//            log.error("您必须保证最大值和最小值之间相差1元");
//            return true;
//        }
        return false;
    }


}

package com.superbrain.envelope;

import com.superbrain.envelope.algorithm.RedEnvelope;
import com.superbrain.envelope.service.RedEnvelopeService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;

@SpringBootTest
@Slf4j
class EnvelopeApplicationTests {

    @Autowired
    private RedEnvelopeService redEnvelopeService;

    @Test
    void grabRedEnvelopeRandom() {
        RedEnvelope redEnvelope = RedEnvelope.builder()
                .fair(false)
                .totalAccount(new BigDecimal(100))
                .totalNum(0)
                .build();
        List<BigDecimal> result = redEnvelopeService.grabRedEnvelope(redEnvelope, 1);
        if (CollectionUtils.isEmpty(result)) {
            return;
        }
        for (int i = 0; i < result.size(); i++) {
            log.info("第" + (i + 1) + "个人分配到的红包额度是:" + result.get(i));
        }
        BigDecimal reduce = result.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
        log.info("" + reduce);
    }


    @Test
    void grabRedEnvelopeFair() {
        RedEnvelope redEnvelope = RedEnvelope.builder()
                .fair(true)
                .totalAccount(new BigDecimal(100))
                .totalNum(0)
                .build();
        List<BigDecimal> result = redEnvelopeService.grabRedEnvelope(redEnvelope, 1);
        if (CollectionUtils.isEmpty(result)) {
            return;
        }
        for (int i = 0; i < result.size(); i++) {
            log.info("第" + (i + 1) + "个人分配到的红包额度是:" + result.get(i));
        }
        BigDecimal reduce = result.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
        log.info("" + reduce);
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值