今天接到老大给的一个任务,让我作一个从一些流量中,按照模版进行采样。须要按照等比例和均分。java
例如: 模版有A和B,总数量是10个,A有4个,B有6个。 假设如今须要采5个: 若是按照等比例分配:那么A要采2个,B要才3个。 假设如今须要采6个: 按照均分,A和B个才3个。
理想状况下,若是都是上面的这种固然好了,可以整除。可是不少状况下是不能整除的,可是也要保证达到采样的总数。测试
要求:
每一个模版都要采到。code
废话很少说,直接上代码。blog
/*** * 等比例采样 * @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); }
3.1. 先来验证下能整除的状况下。
get
3.2. 验证下不能整除的状况下。it
这里面测试两个零界点。
3.2.1 一个是数量等于模版总数
3.2.2 一个是采样数量 = 总数 - 1class
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); }
结果是:
map
把sampleTotal设置成14;
im
当咱们测试了两个零界点以后,是没有问题的,那么中间的数量就没什么问题了。
把sampleTotal设置成9;