Java實現采樣,等比例分和均分


1. 需求分析

今天接到老大給的一個任務,讓我做一個從一些流量中,按照模版進行采樣。需要按照等比例和均分。

例如:
模版有A和B,總數量是10個,A有4個,B有6個。

假設現在需要采5個:
如果按照等比例分配:那么A要采2個,B要才3個。

假設現在需要采6個:
按照均分,A和B個才3個。

理想情況下,如果都是上面的這種當然好了,能夠整除。但是很多情況下是不能整除的,但是也要保證達到采樣的總數。

要求:
每個模版都要采到。

廢話不多說,直接上代碼。

2. 相關代碼

/***
     * 等比例采樣
     * @param map   存放數據,需要按照數量正序排
     * @param total  總數量
     * @param sampleTotal 需要采樣的數量
     */
    public static void allocateFlowByPercentage(Map<String,Integer> map, Integer total, Integer sampleTotal) {
        int newTotal = 0;
        int addCount = 0;
        int i = 0;
        double basePercentage = sampleTotal / total.doubleValue();

        Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, Integer> next = iterator.next();
            String key = next.getKey();
            if (sampleTotal == map.size()) {
                // 每個模版分1個
                map.put(key, 1);
                System.out.println("模版" + key + ":原來有流量:" + next.getValue() + "個,采樣:1個");
                newTotal++;
                continue;
            }
            double doubleCount = basePercentage * next.getValue();
            int newCount = (int) Math.round(doubleCount);
            if (newCount == 0) {
                newCount = 1;
                addCount++;
            } else if (newCount > doubleCount && addCount > 0 && newCount > 1) {
                addCount--;
                newCount--;
            }

            if (i == map.size() - 1) {
                // 最后一個不計算了,直接拿總數減去之前的總數。需要保證,map中存儲的數量,是按照正序從小到大排序的
                newCount = sampleTotal - newTotal;
            }
            System.out.println("模版" + key + ":原來有流量:" + next.getValue() + "個,采樣:" + newCount + "個");
            map.put(key, newCount);
            newTotal += newCount;
            i++;
        }
        System.out.println("實際采樣的總數:" + newTotal);
    }

/***
     * 均分采樣
     * @param map   存放數據,需要按照數量正序排
     * @param sampleTotal 需要采樣的數量
     */
    public static void allocateFlowByAverage(Map<String,Integer> map, Integer sampleTotal) {
        int newTotal = 0;
        int i = 0;
        double averageCount = sampleTotal.doubleValue() / map.size();

        Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, Integer> next = iterator.next();
            String key = next.getKey();
            if (sampleTotal == map.size()) {
                // 每個模版分1個
                map.put(key, 1);
                System.out.println("模版" + key + ":原來有流量:" + next.getValue() + "個,采樣:1個");
                newTotal++;
                continue;
            }
            int newCount = next.getValue();
            if (newCount > averageCount) {
                newCount = (int) Math.round(averageCount);
            }

            if (i == map.size() - 1) {
                // 最后一個不計算了,直接拿總數減去之前的總數。需要保證,map中存儲的數量,是按照正序從小到大排序的
                newCount = sampleTotal - newTotal;
            }
            System.out.println("模版" + key + ":原來有流量:" + next.getValue() + "個,采樣:" + newCount + "個");
            map.put(key, newCount);
            newTotal += newCount;
            i++;
        }
        System.out.println("實際采樣的總數:" + newTotal);
    }

注意:
這里當采樣數量小於模版數量的時候,異常處理我這邊省略了。
當采樣數量大於總數的時候,不需要做任何處理,全部采。這里面我也省略了。

3. 驗證

public static void main(String[] args) {
        // 保證添加的順序是從小到大
        Map<String,Integer> map = new LinkedHashMap<>();
        map.put("D", 4);
        map.put("E", 6);
        Integer total = 10;
        Integer sampleTotal = 5;
        System.out.println("========= 等比例采樣 ===========");
        allocateFlowByPercentage(map, total, sampleTotal);
        System.out.println();
        System.out.println("========= 均分采樣 ===========");
        
        map.put("D", 4);
        map.put("E", 6);
        sampleTotal = 6;
        allocateFlowByAverage(map, sampleTotal);
    }

3.1. 先來驗證下能整除的情況下。

3.2. 驗證下不能整除的情況下。

這里面測試兩個零界點。

3.2.1 一個是數量等於模版總數

3.2.2 一個是采樣數量 = 總數 - 1

3.3 數量等於模版總數

 public static void main(String[] args) {
        // 保證添加的順序是從小到大
        Map<String,Integer> map = new LinkedHashMap<>();
        map.put("A", 1);
        map.put("B", 1);
        map.put("C", 3);
        map.put("D", 4);
        map.put("E", 6);
        Integer total = 15;
        Integer sampleTotal = 5;
        System.out.println("========= 等比例采樣 ===========");
        allocateFlowByPercentage(map, total, sampleTotal);
        System.out.println();
        System.out.println("========= 均分采樣 ===========");

        map.put("A", 1);
        map.put("B", 1);
        map.put("C", 3);
        map.put("D", 4);
        map.put("E", 6);
        sampleTotal = 5;
        allocateFlowByAverage(map, sampleTotal);
    }

結果是:

3.4 采樣數量 = 總數 - 1

把sampleTotal設置成14;

3.5 采樣數量在 5 ~ 14之間

當我們測試了兩個零界點之后,是沒有問題的,那么中間的數量就沒什么問題了。
把sampleTotal設置成9;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM