欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java实现采样,等比例分和均分

程序员文章站 2022-05-02 10:25:36
...

今天接到老大给的一个任务,让我做一个从一些流量中,按照模版进行采样。需要按照等比例和均分。

例如:
模版有A和B,总数量是10个,A有4个,B有6个。

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

假设现在需要采6个:
按照均分,A和B个才3个。

理想情况下,如果都是上面的这种当然好了,能够整除。但是很多情况下是不能整除的,但是也要保证达到采样的总数。

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

废话不多说,直接上代码。

/***
     * 等比例采样
     * @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);
    }

注意:
这里当采样数量小于模版数量的时候,异常处理我这边省略了。
当采样数量大于总数的时候,不需要做任何处理,全部采。这里面我也省略了。

下面验证一下:

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);
    }
  1. 先来验证下能整除的情况下。

Java实现采样,等比例分和均分

  1. 验证下不能整除的情况下。
    这里面测试两个零界点。
    一个是数量等于模版总数
    一个是采样数量 = 总数 - 1
数量等于模版总数
 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);
    }

结果是:
Java实现采样,等比例分和均分

采样数量 = 总数 - 1

把sampleTotal设置成14;
Java实现采样,等比例分和均分

采样数量在 5 ~ 14之间

当我们测试了两个零界点之后,是没有问题的,那么中间的数量就没什么问题了。

把sampleTotal设置成9;
Java实现采样,等比例分和均分