今天接到老大给的一个任务,让我做一个从一些流量中,按照模版进行采样。需要按照等比例和均分。
例如:
模版有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
数量等于模版总数
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);
}
结果是:
采样数量 = 总数 - 1
把sampleTotal设置成14;
采样数量在 5 ~ 14之间
当我们测试了两个零界点之后,是没有问题的,那么中间的数量就没什么问题了。
把sampleTotal设置成9;