用java代码实现简单的概率随机抽奖算法,看完相信你也会

工作需要,这两天写一个简单的java抽奖算法,因为逻辑简单不复杂,所以代码也很简洁,可以做到不同权重有不用的中奖概率(就类似于nginx集群一样,权重越大,概率越高),在这里将java概率随机抽奖代码抽离出来分享给大家。

具体需求:

给第三方推送数据,每个第三方根据预算会有不同的额度,考虑到服务器压力,所以采取了主动推送的方式,在每次推送的时候,需要根据第三方的配额计算出相应的概率,然后挑选一个第三方来推送。

思路分析:

从形式上看,跟随机抽奖几乎一模一样,都是在N中挑选1,而且还不是公平挑选,是带有概率性的。

由于只分享概率随机抽奖的算法,所以就暂不考虑上限的情况,简单的说一下算法部分的实现思路(重点在于计算概率和区间,我这里是数量固定,所以概率很好算,具体真实的抽奖业务中会很复杂)。

1、需要计算出每个第三方的配额在总配额中的占比情况:自身数量 / 总数量 = 各自占比

2、用100来做随机区间(1也可以),计算出每个区间的随机数值跨度:比例 * 100 = 区间跨度

3、根据跨度计算出每个区间的起始数值:从0开始按照每个跨度相加,最终将100拆分成N个区间

代码实现:

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

/**
 * Created by Felix on 2019/07/10.
 */
public class Lottery {

    /**
     * 计算自身相对于总体的比例
     *
     * @param self 自身
     * @param all  总体
     * @return 比例
     */
    private static float getPercent(Integer self, Integer all) {
        // 比例计算出来会有很多小数,为了看得清晰,要四舍五入一下
        return new BigDecimal(self / all.floatValue()).setScale(2, BigDecimal.ROUND_HALF_UP).floatValue();
    }

    /**
     * 根据比例,在100之间计算出区间跨度,进一步来计算中奖区间
     *
     * @param percent 比例
     * @return 跨度
     */
    private static int getRandom(float percent) {
        // 如果getPercent中没有四舍五入,那么就需要在这里四舍五入
        return new BigDecimal(percent * 100).intValue();
    }

    private void lottery() {

        Integer allAmount = 233;// 总配额(抽奖总次数)

        List<User> users = new ArrayList<>();
        users.add(new User(1, "张三", 11));
        users.add(new User(2, "李四", 22));
        users.add(new User(3, "王五", 5));
        users.add(new User(4, "赵六", 111));
        users.add(new User(5, "田七", 66));
        users.add(new User(6, "陈八", 18));

        Map<Integer, Offset> userOffsetMap = new HashMap<>();
        float percent;// 比例
        int span;// 跨度
        int start = 0;// 区间开始
        int end;// 区间结束
        for (User user : users) {
            percent = getPercent(user.getAmount(), allAmount);
            span = getRandom(percent);
            // 按照跨度计算offset
            end = start + span;
            userOffsetMap.put(user.getId(), new Offset(percent, span, start, end, user.getAmount()));
            // 因为区间不能超过100,所以每次都需要用新的数值+跨度
            start = end;
        }

        // log
        for (Integer userId : userOffsetMap.keySet()) {
            Offset offset = userOffsetMap.get(userId);
            System.out.println("用户: " + userId + ", 配额: " + offset.getAmount() + ", 占比: " + offset.getPercent() + ", 跨度: " + offset.getSpan() + ", 区间: [" + offset.getStart() + ", " + offset.getEnd() + ")");
        }

        Map<Integer, Integer> countMap = new HashMap<>();
        Integer userId;
        Integer num;
        for (int i = 0; i < allAmount; i++) {
            // 由于这里是递归处理,所以如果最后为null了,说明已经到达了所有人的配额上限(奖品或者抽奖次数用完了)
            userId = randomUser(userOffsetMap);
            if (userId == null) {
                break;
            }
            // 记录每个用户的中标次数,达到上限了就排除掉
            num = countMap.get(userId);
            if (num != null) {
                countMap.put(userId, num + 1);
            } else {
                countMap.put(userId, 1);
            }
            // 次数到了上限,移除用户(可看做:中奖之后移除用户)
            if (countMap.get(userId) >= userOffsetMap.get(userId).getAmount()) {
                userOffsetMap.remove(userId);
            }
        }

        // log
        for (Integer key : countMap.keySet()) {
            System.out.println(key + "中奖了" + countMap.get(key) + "次,概率: " + getPercent(countMap.get(key), allAmount));
        }
    }

    /**
     * 选中之后,会排除掉部分用户,递归调用,将机会让给别的人
     *
     * @param userOffsetMap 用户offset信息
     * @return 选中的用户
     */
    private Integer randomUser(Map<Integer, Offset> userOffsetMap) {
        Integer userId = select(userOffsetMap);
        if (!userOffsetMap.isEmpty() && userId == null) {
            return randomUser(userOffsetMap);
        }
        return userId;
    }

    /**
     * 按照计算好的区间,随机获取用户(即视为中标了)
     *
     * @param userOffsetMap 用户offset信息
     * @return 选中的用户
     */
    private Integer select(Map<Integer, Offset> userOffsetMap) {
        // 计算一个随机数值,看他散列在哪个用户的区间
        int rnum = new Random().nextInt(100);
        Offset offset;
        // 由于要兼顾每一个用户,所以需要循环(效率待定?)
        for (Integer userId : userOffsetMap.keySet()) {
            offset = userOffsetMap.get(userId);
            // 100以内随机,所以需要是左侧需要等于,右侧不需要
            if (offset.getStart() <= rnum && rnum < offset.getEnd()) {
                return userId;
            }
        }
        return null;
    }

    public static void main(String[] args) {
        new Lottery().lottery();
    }

    class User {

        private int id;
        private String name;
        private int amount;

        public User(int id, String name, int amount) {
            this.id = id;
            this.name = name;
            this.amount = amount;
        }

        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAmount() {
            return amount;
        }

        public void setAmount(int amount) {
            this.amount = amount;
        }
    }

    class Offset {

        private float percent;
        private int span;
        private int start;
        private int end;
        private int amount;

        public Offset(float percent, int span, int start, int end, int amount) {
            this.percent = percent;
            this.span = span;
            this.start = start;
            this.end = end;
            this.amount = amount;
        }

        public float getPercent() {
            return percent;
        }

        public void setPercent(float percent) {
            this.percent = percent;
        }

        public int getSpan() {
            return span;
        }

        public void setSpan(int span) {
            this.span = span;
        }

        public int getStart() {
            return start;
        }

        public void setStart(int start) {
            this.start = start;
        }

        public int getEnd() {
            return end;
        }

        public void setEnd(int end) {
            this.end = end;
        }

        public int getAmount() {
            return amount;
        }

        public void setAmount(int amount) {
            this.amount = amount;
        }
    }

}

实现效果:

用java实现简单的概率随机抽奖算法

跟代码中的参数做一下简单比对,总配额是233次(可视作为233次抽奖机会),6个用户按照不同比例分配不同的配额,全部抽奖完成之后看到的概率与配额的比例是一致的,说明按概率随机抽取成功!
最后给大家赠送一本书籍《数据结构与算法经典问题解析》豆瓣评分8.6 需要的朋友可以来简信我领取

用java代码实现简单的概率随机抽奖算法,看完相信你也会

本书的目的是使读者知晓数据结构和算法的设计原理和实现,而并非单纯地讲述定理及证明。为此,本书利用不同的复杂度来改善问题的解。对于许多问题,从穷举解法开始,逐步引入问题的最佳解,并给出算法所需的运行时间和空间。

全书包含4个部分,第一部分(第1〜2章)主要描述抽象数据类型,给出算法的基本概念和复杂度分析与评价方法,并讨论几乎每章都要用到的递归和回溯技术

第二部分(第3〜9章)介绍基本数据结构,包括链表、栈、队列、树、优先队列、堆、并査集和图,对于每一种数据结构分别采用多个实例进行具体的演示。

第三部分(第10-15章)介绍数据处理的技术,包括排序、査找、选择、符号表、散列和字符串算法。

第四部分(第16〜21章)重点介绍一些常用的算法设计技术及应用,包括贪婪算法、分治算法、动态规划算法、复杂度类型,并讨论对于面试和考试的一些有用话题。本书强调问题及分析,而不侧重于理论。可以作为从事计算机研究与开发的技术人员的参考书,特别是对正在准备面试、参加选拔性考试以及校园面试的读者尤为有用。

不想把篇幅拉的太长需要的朋友可以来私信领取

书籍免费获取方式:关注然后简信“资料”即可获得文档领取方式

同时希望大家领到之后不要做收藏党!而是能够花一些时间认真看完文档,让它真正发挥出价值来。

用java代码实现简单的概率随机抽奖算法,看完相信你也会
用java代码实现简单的概率随机抽奖算法,看完相信你也会
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容