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;