最近在处理费率和保底费的优惠及标准区间。问题本质就是:标准合作区间是一个【时间段+标准值】,优惠合作区间是多段【时间段+标准值】,并且各个时间段的开始和结束日期可以随意指定,优惠区间和标准区间重合部分按照优惠值计算,非重合部分按照标准区间值计算。
上游对时间段的限制基本没有,唯一的限制就是多段优惠区间是连续的。

优惠期结束晚于合作期
优惠期开始早于合作期

优惠期大于合作期

优惠期和合作期完全不沾边

由于各种可能行都存在,用if else条件判断起来太麻烦,可能性太多了
当问题复杂的时候不妨把问题抽象看待:由于费率和保底费的计算都是以天为最小单位的,所以我们可以把每段时间都拆成一天一天的,放到一个集合里,然后以标准的时间值Map为基准,匹配优惠的,匹配到就修改该最小单元对应的值为优惠值,匹配不到则继续匹配,直到匹配结束。匹配完成后我们再依据值的连续性,将连续相同值的最小单元输出为时间段和值
第一步:分别将连续的优惠区间和标准区间进行最小单元化处理:
/**
* 将时间段和值拆分为天和值
*
* @param beginDate the begin date
* @param endDate the end date
* @param fee the fee
* @return the tree map
*/
public TreeMap<LocalDate, BigDecimal> Build(LocalDate beginDate, LocalDate endDate, BigDecimal fee) {
TreeMap<LocalDate, BigDecimal> feeMap = new TreeMap<>();
for (LocalDate date = beginDate; date.isBefore(endDate) || date.isEqual(endDate); date = date.plusDays(1)) {
date = DateUtil.stringToLocalDate(date.format(fmt), DateUtil.DATE_FORMAT);
feeMap.put(date, fee);
}
return feeMap;
}
第二步:对最小单元区间进行Merge处理:基于标准区间,对重合部分的优惠区间重新赋值
/**
* 以标准值时间段为基准,相同时间天的按优惠值
*
* @param standard the standard
* @param discount the discount
*/
public void Merge(TreeMap<LocalDate, BigDecimal> standard, TreeMap<LocalDate, BigDecimal> discount) {
Set<LocalDate> days = standard.keySet();
for (LocalDate day : days) {
if (discount.containsKey(day)) {
standard.put(day, discount.get(day));
}
}
}
第三步:将处理好的最小单元和值Map进行连续值时间区间合并
/**
* 将最小单元再转为时间段和值
*
* @param full the full
* @return the list
*/
public List<DateAndValueInfo> Output(TreeMap<LocalDate, BigDecimal> full) {
List<DateAndValueInfo> dateAndValueInfos = new ArrayList<>();
Set<LocalDate> days = full.keySet();
BigDecimal fee = new BigDecimal("0");
LocalDate start = null;
LocalDate end = null;
int count = 0;
for (LocalDate day : days) {
count++;
if (fee.toPlainString().equals(ZERO) && start == null) {
fee = full.get(day);
start = day;
end = day;
continue;
}
if (!fee.equals(full.get(day))) {
// 相同值连续阶段结束时,将本段连续值添加到时间段,最后一阶段也添加
Date beforeStartDate = DateUtil.localDateToDate(start);
Date beforeEndDate = DateUtil.localDateToDate(end);
DateAndValueInfo dateAndValueInfo = new DateAndValueInfo(beforeStartDate, beforeEndDate, fee);
dateAndValueInfos.add(dateAndValueInfo);
start = day;
fee = full.get(day);
}
end = day;
if (count == days.size()) {
// 最后一阶段也添加
Date beforeStartDate = DateUtil.localDateToDate(start);
Date beforeEndDate = DateUtil.localDateToDate(end);
DateAndValueInfo dateAndValueInfo = new DateAndValueInfo(beforeStartDate, beforeEndDate, fee);
dateAndValueInfos.add(dateAndValueInfo);
}
}
return dateAndValueInfos;
}
整体的代码调用如下:
/**
* 合并处理时间段和值
*
* @param disCountDateAndValueInfo the dis count date and value info
* @param standardDateAndValueInfo the standard date and value info
* @return the list
*/
public List<DateAndValueInfo> handleDateAndValues(List<DateAndValueInfo> disCountDateAndValueInfo, DateAndValueInfo standardDateAndValueInfo) {
TreeMap<LocalDate, BigDecimal> disCounts = new TreeMap<>();
for (DateAndValueInfo dateAndValueInfo : disCountDateAndValueInfo) {
TreeMap<LocalDate, BigDecimal> disCount = Build(DateUtil.dateToLocalDate(dateAndValueInfo.getFromDate()), DateUtil.dateToLocalDate(dateAndValueInfo.getToDate()), dateAndValueInfo.getValue());
disCounts.putAll(disCount);
}
TreeMap<LocalDate, BigDecimal> standard = Build(DateUtil.dateToLocalDate(standardDateAndValueInfo.getFromDate()), DateUtil.dateToLocalDate(standardDateAndValueInfo.getToDate()), standardDateAndValueInfo.getValue());
Merge(standard, disCounts);
return Output(standard);
}
用最小单元染色法解决甚至可以处理这样毫无规律的时间段merge

还可以解决这类问题:当不需要处理标准段而只需要用标准区间卡一个时间范围的话:

/**
* 获取优惠段和合作区间merge的时间
*
* @param disCountDateAndValueInfo the dis count date and value info
* @param standardDateAndValueInfo the standard date and value info
* @return the list
*/
private List<DateAndValueInfo> handleValidateInfo(List<DateAndValueInfo> disCountDateAndValueInfo, DateAndValueInfo standardDateAndValueInfo) {
// 所有优惠期统一划分为细粒度值-时间
TreeMap<LocalDate, BigDecimal> disCountInfos = new TreeMap<>();
for (DateAndValueInfo dateAndValueInfo : disCountDateAndValueInfo) {
TreeMap<LocalDate, BigDecimal> disCount = Build(DateUtil.dateToLocalDate(dateAndValueInfo.getFromDate()), DateUtil.dateToLocalDate(dateAndValueInfo.getToDate()), dateAndValueInfo.getValue());
disCountInfos.putAll(disCount);
}
LocalDate realStartDate = disCountInfos.firstKey().isBefore(DateUtil.dateToLocalDate(standardDateAndValueInfo.getFromDate())) ? DateUtil.dateToLocalDate(standardDateAndValueInfo.getFromDate()) : disCountInfos.firstKey();
LocalDate realEndDate = disCountInfos.lastKey().isBefore(DateUtil.dateToLocalDate(standardDateAndValueInfo.getToDate())) ? DateUtil.dateToLocalDate(standardDateAndValueInfo.getToDate()) : disCountInfos.lastKey();
// 不在卡死时间段内的值干掉
Set<LocalDate> days = disCountInfos.keySet();
for (LocalDate day : days) {
if (day.isBefore(realStartDate) || day.isAfter(realEndDate)) {
disCountInfos.remove(day);
}
}
return Output(disCountInfos);
}
大道至简,当问题复杂时,一定是因为思维陷入到了复杂的逻辑里,要学会思考如何抽象看待问题,透过现象看本质。