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;

posted @ 2020-10-03 17:05  刘翊扬  阅读(1433)  评论(0编辑  收藏  举报