V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
zhuzhibin
V2EX  ›  问与答

[!] 讨论: 随机红包算法问题

  •  
  •   zhuzhibin · 2019-09-28 10:31:29 +08:00 · 3008 次点击
    这是一个创建于 1888 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我相信大家都有自己的看法,所以我也是抱着学习的心态问这个问题

    举个例子了呗:

    10 人群聊咯,我发了 100 元红包,然后 10 人随机瓜分 100 元红包

    方案应该很多,例如随机比例等等 ,嘿嘿 大家畅所欲言哈,想请教下各位

    17 条回复    2019-09-28 20:25:39 +08:00
    niknik
        1
    niknik  
       2019-09-28 10:37:25 +08:00
    一条绳子,九个结,每段长度,然后十段绳子随机分出去(自我猜测应该是这样的,嘻嘻)
    Baymaxbowen
        2
    Baymaxbowen  
       2019-09-28 10:38:48 +08:00
    memedahui
        3
    memedahui  
       2019-09-28 10:40:52 +08:00   ❤️ 3
    import java.util.LinkedList;
    import java.util.List;

    /**
    * Function: 模拟微信红包生成,以分为单位
    *
    * @author crossoverJie
    * Date: 03/01/2018 16:52
    * @since JDK 1.8
    */
    public class RedPacket {

    /**
    * 生成红包最小值 1 分
    */
    private static final int MIN_MONEY = 1;

    /**
    * 生成红包最大值 200 人民币
    */
    private static final int MAX_MONEY = 200 * 100;

    /**
    * 小于最小值
    */
    private static final int LESS = -1;
    /**
    * 大于最大值
    */
    private static final int MORE = -2;

    /**
    * 正常值
    */
    private static final int OK = 1;

    /**
    * 最大的红包是平均值的 TIMES 倍,防止某一次分配红包较大
    */
    private static final double TIMES = 2.1F;

    private int recursiveCount = 0;

    public List<Integer> splitRedPacket(int money, int count) {
    List<Integer> moneys = new LinkedList<>();

    //金额检查,如果最大红包 * 个数 < 总金额;则需要调大最小红包 MAX_MONEY
    if (MAX_MONEY * count <= money) {
    System.err.println("请调大最小红包金额 MAX_MONEY=[" + MAX_MONEY + "]");
    return moneys ;
    }


    //计算出最大红包
    int max = (int) ((money / count) * TIMES);
    max = max > MAX_MONEY ? MAX_MONEY : max;

    for (int i = 0; i < count; i++) {
    //随机获取红包
    int redPacket = randomRedPacket(money, MIN_MONEY, max, count - i);
    moneys.add(redPacket);
    //总金额每次减少
    money -= redPacket;
    }

    return moneys;
    }

    private int randomRedPacket(int totalMoney, int minMoney, int maxMoney, int count) {
    //只有一个红包直接返回
    if (count == 1) {
    return totalMoney;
    }

    if (minMoney == maxMoney) {
    return minMoney;
    }

    //如果最大金额大于了剩余金额 则用剩余金额 因为这个 money 每分配一次都会减小
    maxMoney = maxMoney > totalMoney ? totalMoney : maxMoney;

    //在 minMoney 到 maxMoney 生成一个随机红包
    int redPacket = (int) (Math.random() * (maxMoney - minMoney) + minMoney);

    int lastMoney = totalMoney - redPacket;

    int status = checkMoney(lastMoney, count - 1);

    //正常金额
    if (OK == status) {
    return redPacket;
    }

    //如果生成的金额不合法 则递归重新生成
    if (LESS == status) {
    recursiveCount++;
    System.out.println("recursiveCount==" + recursiveCount);
    return randomRedPacket(totalMoney, minMoney, redPacket, count);
    }

    if (MORE == status) {
    recursiveCount++;
    System.out.println("recursiveCount===" + recursiveCount);
    return randomRedPacket(totalMoney, redPacket, maxMoney, count);
    }

    return redPacket;
    }

    /**
    * 校验剩余的金额的平均值是否在 最小值和最大值这个范围内
    *
    * @param lastMoney
    * @param count
    * @return
    */
    private int checkMoney(int lastMoney, int count) {
    double avg = lastMoney / count;
    if (avg < MIN_MONEY) {
    return LESS;
    }

    if (avg > MAX_MONEY) {
    return MORE;
    }

    return OK;
    }


    public static void main(String[] args) {
    RedPacket redPacket = new RedPacket();
    List<Integer> redPackets = redPacket.splitRedPacket(20000, 100);
    System.out.println(redPackets);

    int sum = 0;
    for (Integer red : redPackets) {
    sum += red;
    }
    System.out.println(sum);
    }

    }






    注 引用至:github.com/crossoverJie
    lhx2008
        4
    lhx2008  
       2019-09-28 10:40:53 +08:00 via Android
    100 元,随机在 0-100 9 个数,加上 0 和 100,相邻的数距离有 10 个,就是 10 个红包
    Raymon111111
        5
    Raymon111111  
       2019-09-28 10:41:44 +08:00
    微信的逻辑是每次要抢的时候去随机生成一个合法的数字
    zhuzhibin
        6
    zhuzhibin  
    OP
       2019-09-28 10:43:39 +08:00 via iPhone
    @Baymaxbowen 老哥 我 ip 被 ban 了呜呜 解封后我倒回来看看
    zhuzhibin
        7
    zhuzhibin  
    OP
       2019-09-28 10:48:48 +08:00 via iPhone
    @lhx2008 如果刚好随机的情况是平均分配呢 ?每个人刚好 10 元
    lhx2008
        8
    lhx2008  
       2019-09-28 10:53:14 +08:00
    @zhuzhibin #7 没问题呀,随机到 0 10 20 30 40 50 60 70 80 90 100 就是平均分配了。要说 bug 就是随机数要去重,如果有重复要重新随机一个。
    Cbdy
        9
    Cbdy  
       2019-09-28 11:19:29 +08:00   ❤️ 1
    随手写了一个
    wysnylc
        10
    wysnylc  
       2019-09-28 11:57:26 +08:00
    红包算法都是预先生成,发红包的时候已经根据红包领取人数随机好了所有金额
    任何按照随机数来的都不可避免的可能会出现极大和极小值
    这不是算法问题,是业务问题
    x86
        11
    x86  
       2019-09-28 11:58:05 +08:00
    @Raymon111111 #5 我也感觉是这样每次点的时候拿剩余额度在随机一个
    snw
        12
    snw  
       2019-09-28 12:45:51 +08:00 via Android
    @wysnylc
    微信的红.包就不是这样。
    geelaw
        13
    geelaw  
       2019-09-28 15:05:18 +08:00 via iPhone   ❤️ 1
    https://geelaw.blog/entries/red-envelope/

    这个问题其实很无聊 - - 从我两个小时就能排版完毕就能看出来。
    w292614191
        14
    w292614191  
       2019-09-28 16:38:06 +08:00
    我记得,过年群里:80 元。
    我得了 79,另一个人 1 块钱。
    简直逆天。
    zhuzhibin
        15
    zhuzhibin  
    OP
       2019-09-28 19:02:16 +08:00
    @geelaw 老哥 👍 感谢回复
    DevRoss
        16
    DevRoss  
       2019-09-28 19:04:24 +08:00 via Android
    分层抽样减少方差呗
    zhuchaowe
        17
    zhuchaowe  
       2019-09-28 20:25:39 +08:00 via iPhone
    微信的逻辑: random(0.01,0.5*restMoney)
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1045 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 21:14 · PVG 05:14 · LAX 13:14 · JFK 16:14
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.