可指定概率的隨機(jī)對象生成工具-java

應(yīng)用場景:最近做一個推廣活動的小程序悄谐,用戶通過鏈接分享點(diǎn)擊數(shù)可以抽取獎勵介评,點(diǎn)擊數(shù)越高,抽取到大獎的概率越高爬舰,所以這里獎品的隨機(jī)概率是確定并且動態(tài)變化的们陆。

參考了HashMap的些許設(shè)計(jì)思路,代碼如下:

package com.rc.components.common.utils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;

import lombok.Data;

/**
 * 概率隨機(jī)工具
 * @author: rc
 * @date: 2018年2月5日 下午4:55:48
 * @version: V1.0
 * @review: rc/2018年2月5日 下午4:55:48
 */
public class ProbRandom {
    
    private final Integer randomBound; //隨機(jī)邊界范圍
    
    private final Random random; //隨機(jī)生成器
    
    private final HashMap<Object, Integer> originalMap; //隨機(jī)元素的原始數(shù)據(jù)容器
    
    private ArrayList<ProbDistribution> probDistributions; //元素分布概率的數(shù)組
    
    private boolean updateFlag; //原始數(shù)據(jù)容器是否有更新
    
    private Integer originalSum; //原始數(shù)據(jù)隨機(jī)值求和
    
    private Integer zeroOriginalCount; //原始數(shù)據(jù)隨機(jī)值為0(未設(shè)置隨機(jī)值)的元素個數(shù)
    
    public ProbRandom(Integer randomBound) {
        this.originalMap = new HashMap<>();
        this.updateFlag = true;
        this.randomBound = randomBound;
        this.originalSum = 0;
        this.zeroOriginalCount = 0;
        this.random = new Random();
    }
    
    /**
     * 生成隨機(jī)數(shù)
     * @return
     */
    public Object next(){
        distribute(); //計(jì)算元素分布的概率值
        if (null == probDistributions) {
            return null;
        }
        int nextInt = random.nextInt(randomBound);
        ProbDistribution prob = probDistributions.stream().filter(c->(c.floor<=nextInt&&c.ceil>nextInt)).findFirst().get();
        return prob.getKey();
    }
    
    /**
     * 添加隨機(jī)元素和概率值情屹,返回添加后的概率值
     * 如果prob=0 坪仇,概率值計(jì)算為總體概率值確定的默認(rèn)值
     * @param key
     * @param prob
     * @return
     */
    public void put(Object key, Integer prob){
        //校驗(yàn)
        if (null == key || prob == null ) {
            throw new NullPointerException("key或者prob不能為空");
        }
        if (prob < 0) {
            throw new RuntimeException("prob不能小于0");
        }
        //添加
        Integer previousProb = originalMap.put(key, prob);
        //更新
        if (prob == 0) {
            zeroOriginalCount ++ ;
        } else {
            originalSum += prob;
        }
        if (previousProb == null) {
            updateFlag = true;
        } else {
            if (previousProb == 0) {
                zeroOriginalCount -- ;
            } else {
                originalSum -= previousProb;
            }
            if (prob != previousProb) {
                updateFlag = true;
            }
        }
    }
    
    /**
     * 刪除已經(jīng)添加的元素,返回元素設(shè)置的隨機(jī)值
     * 如果元素不存在屁商,返回null
     * @param key
     * @return
     */
    public Integer remove(Object key){
        //校驗(yàn)
        if (null == key) {
            throw new NullPointerException("key或者prob不能為空");
        }
        //刪除
        Integer remove = originalMap.remove(key);
        if (null != remove) {
            updateFlag = true;
        }
        return remove;
    }
    
    /**
     * 等同于put(key,0)
     * @param key
     */
    public void put(Object key){
        put(key,0);
    }
    
    /**
     * 返回隨機(jī)值邊界randomBound
     * @return
     */
    public Integer getRandomBound(){
        return randomBound;
    }
    
    /**
     * 返回元素的隨機(jī)值prob
     * @param key
     * @return
     */
    public Integer getProb(Object key){
        return originalMap.get(key);
    }
    
    /**
     * 返回元素的集合
     * @return
     */
    public Set<Object> getKeySet(){
        return originalMap.keySet();
    }
        
    //計(jì)算概率元素的概率分布
    private void distribute() {
        if (randomBound < originalSum) {
            throw new RuntimeException("總隨機(jī)值不能大于隨機(jī)值邊界");
        }
        //如果原始數(shù)據(jù)容器有更新烟很,就從新計(jì)算分布概率數(shù)組
        if (updateFlag) {
            //創(chuàng)建分布概率數(shù)組
            probDistributions = new ArrayList<ProbRandom.ProbDistribution>();
            
            //重組分布概率數(shù)組
            Integer tempFloor = 0;
            if (zeroOriginalCount == 0) {
                for (Entry<Object, Integer> e : originalMap.entrySet()) {
                    Integer ceil = (int) (tempFloor + e.getValue()*1.00*randomBound/originalSum);
                    probDistributions.add(new ProbDistribution(e.getKey(), tempFloor, ceil)) ;
                    tempFloor = ceil; 
                }
            } else {
                Integer defProbSize = (int) ((randomBound - originalSum)*1.00/zeroOriginalCount);
                for (Entry<Object, Integer> e : originalMap.entrySet()) {
                    Integer ceil;
                    if (e.getValue() == 0) {
                        ceil = tempFloor + defProbSize;
                    } else {
                        ceil = (int) (tempFloor + e.getValue());
                        
                    }
                    probDistributions.add(new ProbDistribution(e.getKey(), tempFloor, ceil)) ;
                    tempFloor = ceil; 
                }
            }

            updateFlag = false;
        }
    }
    
    
    //描述單個元素分布概率情況
    @Data
    private class ProbDistribution{
        private Object key;
        private Integer floor;
        private Integer ceil;
        ProbDistribution(Object key, Integer floor, Integer ceil){
            this.key = key;
            this.floor = floor;
            this.ceil = ceil;
        }
    }
}

以上代碼大致思路:
1.使用一個集合originalMap作為隨機(jī)元素的容器,key是對應(yīng)隨機(jī)對象蜡镶,value是設(shè)定的隨機(jī)概率雾袱,這里的概率并不是百分?jǐn)?shù),而是以一個概率基數(shù)randomBound作為比較的官还。
2.在進(jìn)行隨機(jī)取樣時芹橡,是將originalMap中的所有概率值轉(zhuǎn)換計(jì)算distribute()成randomBound內(nèi)的數(shù)值范圍分布probDistributions,通過random生成的隨機(jī)數(shù)對應(yīng)的數(shù)值范圍分布next()來產(chǎn)生一定概率的隨機(jī)對象望伦。
3.其中distribute()是一個比較關(guān)鍵并且損耗性能的方法林说,所以在這里使用一個標(biāo)記updateFlag來標(biāo)識originalMap中概率值是否發(fā)生過變化,只有發(fā)生過變化的時候才需要重新distribute()進(jìn)行probDistributions的計(jì)算,否則使用原有的probDistributions來產(chǎn)生隨機(jī)對象即可菠红。
4.另外對外暴露了put,remove,get的方法來操作originalMap莽鸭,但是沒有直接獲取originalMap的方法,因?yàn)槿绻梢灾苯荧@取originalMap珠移,updateFlag就無法準(zhǔn)確的描述概率值是否發(fā)生了變化。

簡單的測試:

package com.rc.components.common.utils;

public class ProbRandoTest {
    
    public static void main(String[] args) {
        ProbRandom pr = new ProbRandom(1000);
        pr.put("A", 300);
        pr.put("B", 200);
        pr.put("C", 300);
        int countA = 0;
        int countB = 0;
        int countC = 0;
        long start = System.currentTimeMillis();
        System.out.println(start);
        for (int i = 0; i < 10000; i++) {
            Object next = pr.next();
            if ("A".equals(next)) {
                countA++;
            }else if ("B".equals(next)) {
                countB++;
            }else if ("C".equals(next)) {
                countC++;
            }
        }
        long end = System.currentTimeMillis();
        System.out.println(end-start);
        System.out.println("countA" + countA);
        System.out.println("countB" + countB);
        System.out.println("countC" + countC);
    }
}

測試結(jié)果
循環(huán)隨機(jī)1000000次耗時223毫秒
countA末融。钧惧。。375239
countB勾习。浓瞪。。250509
countC巧婶。乾颁。涂乌。374252
結(jié)果顯示概率準(zhǔn)確性和運(yùn)行速度都沒有太大問題。

當(dāng)然這只是一個初級的版本钮孵,沒有考慮多線程情況下的數(shù)據(jù)可靠性問題骂倘,如果需要考慮到線程安全,可以模擬ConcurrentHashMap的分段鎖機(jī)制巴席,實(shí)現(xiàn)性能和安全性調(diào)和历涝。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市漾唉,隨后出現(xiàn)的幾起案子荧库,更是在濱河造成了極大的恐慌,老刑警劉巖赵刑,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件分衫,死亡現(xiàn)場離奇詭異,居然都是意外死亡般此,警方通過查閱死者的電腦和手機(jī)蚪战,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來铐懊,“玉大人邀桑,你說我怎么就攤上這事】坪酰” “怎么了壁畸?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長茅茂。 經(jīng)常有香客問我捏萍,道長,這世上最難降的妖魔是什么空闲? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任令杈,我火速辦了婚禮,結(jié)果婚禮上碴倾,老公的妹妹穿的比我還像新娘这揣。我一直安慰自己,他們只是感情好影斑,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著机打,像睡著了一般矫户。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上残邀,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天皆辽,我揣著相機(jī)與錄音柑蛇,去河邊找鬼。 笑死驱闷,一個胖子當(dāng)著我的面吹牛耻台,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播空另,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼盆耽,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了扼菠?” 一聲冷哼從身側(cè)響起摄杂,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎循榆,沒想到半個月后析恢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡秧饮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年映挂,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盗尸。...
    茶點(diǎn)故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡柑船,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出振劳,到底是詐尸還是另有隱情椎组,我是刑警寧澤,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布历恐,位于F島的核電站寸癌,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏弱贼。R本人自食惡果不足惜蒸苇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吮旅。 院中可真熱鬧溪烤,春花似錦、人聲如沸庇勃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽责嚷。三九已至鸳兽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間罕拂,已是汗流浹背揍异。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工全陨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人衷掷。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓辱姨,卻偏偏與公主長得像,于是被迫代替她去往敵國和親戚嗅。 傳聞我的和親對象是個殘疾皇子雨涛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 國家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報(bào)批稿:20170802 前言: 排版 ...
    庭說閱讀 10,968評論 6 13
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法渡处,內(nèi)部類的語法镜悉,繼承相關(guān)的語法,異常的語法医瘫,線程的語...
    子非魚_t_閱讀 31,631評論 18 399
  • 轉(zhuǎn)自:http://blog.csdn.net/jackfrued/article/details/4492194...
    王帥199207閱讀 8,522評論 3 93
  • 一侣肄、進(jìn)程和線程 進(jìn)程 進(jìn)程就是一個執(zhí)行中的程序?qū)嵗總€進(jìn)程都有自己獨(dú)立的一塊內(nèi)存空間醇份,一個進(jìn)程中可以有多個線程稼锅。...
    阿敏其人閱讀 2,612評論 0 13
  • 我怎么肯認(rèn)自己的罪 畢竟世上比我更惡的人太多了 我隱藏其中 像個義人 像著 像著 我就是了 我怎么肯承認(rèn)自己是罪中...
    林以斯帖閱讀 193評論 0 0