最近工作中突然有个需求,是生成计划,根据不同的周期类型生成每天的计划,比如每周一,周三,周五,需要生成。或者是每月3号,19号,29号生成。
这样的需求其实就是获取符合要求的时间集合。一般情况下我们可以在循环中每次加一天再判断是否符合需求再进行加入任务时间集合中。
while (startTime.compareTo(endTime)<=0){
//周每日
if (weeks.contains(startTime.getDayOfWeek())){
localDates.add(startTime);
}
startTime = startTime.plusDays(1);
}
这样写代码虽然简单,但是在需要大量生成计划的时候效率并不高。而且显得一点都没有技术含量。所以左思右想之下,重新构建了一下算法。
其实可以看到如果在时间间隔大的时候是做了很多无效循环,就像如果我每个月1号2号生成计划,
然后我开始时间是1月1号到2月30号,那么这个循环就需要近60次。其中不停的startTime = startTime.plusDays(1);也是非常消耗效率。
这时候就需要设计一下算法。以上的方式主要是命中目标日期的概率较低。如果我们能每次迅速命中目标日期那么将大大提升效率。
首分析周每日
这里我们可以做一个循环数组的形式,因为每周的天数是固定的。
所以假设 这是需要 每周一,周三,周日的生成计划。我们可以这样转化成数组
[1,0,1,0,0,0,1] 下标索引加一就代表周一,数值为一代表需要生成计划,0则跳过。这样的话就可以快速命中。思路就是有一个指针在这个数组中不停前进,当位置是1的时候就把这个时间加入到符合要求的时间集合。下面上代码
private List<LocalDate> weekOfDay(PatrolSchedule patrolSchedule){
List<LocalDate> list = new ArrayList<>();
LocalDate scheduleStartTime = patrolSchedule.getScheduleStartTime();
String[] days=patrolSchedule.getCycleCount().split(",");
//获取计划开始第一天周几
int value = scheduleStartTime.getDayOfWeek().getValue()-1;
//定义一个数组队列 0-6对应周一到周日
int[] temp = new int[7];
//初始化数组
for (int i = 0; i < temp.length; i++) {
temp[i] = 0;
}
//因为1对应0 所以需要-1,需要生成的日期为1
for (int i = 0; i <days.length ; i++) {
temp[Integer.valueOf(days[i])-1] = 1;
}
//此时 temp 的格式为 0 0 1 0 0 1 1 其中1的代表周(index+1)为需要生成计划
System.out.println("数组"+Arrays.toString(temp));
if (temp[value]==1){
list.add(scheduleStartTime);
}
while (true){
int z=1;
//判断日期数在数组下标中是否为1,获取前进值
while (temp[(value+z)%temp.length] !=1){
z++;
}
//加上对应天数
scheduleStartTime = scheduleStartTime.plusDays(z);
if (scheduleStartTime.compareTo(patrolSchedule.getScheduleEndTime()) <=0){
list.add(scheduleStartTime);
}else {
return list;
}
//指针向前移
value = value+z;
}
}
可以看到前两个for循环先构建需要的循环数组。为了避免丢失起始天,所以先进行起始天判断是否加入集合中。循环中定义的z的作用就是获取到下一个符合条件的到当前的间隔,获取到后
scheduleStartTime.plusDays(z);就是下一个符合条件的时间,如果这个时间大于规定的结束时间跳出循环,每次在最后把当前指针前移到目标位置。value就是当前指针在的位置。
下面分析月每日
月每日跟周有所不同,因为每个月的天数不是固定的,所以不能用[10101]的方式来表示,而且数量也比较大。因为月每日,其中的日期我们是可以直接拿来用的,比如我需要每月的2,18,19,30,31 生成,这时候我们可把这个直接转化为数组。在写 逻辑时需要注意的是并不是每个月都有30,31。总体思路就是在得到[2,18,19,30,31]后获取起始时间的位置,比如从15号开始生成,那么第一个就是18号,数组中下标就为1,找到比起始日期大的最近的那个值。这里要注意如果到最后一个都没获取到比如数组是[1,2] 要从2月10号开始生成,那么就应该月份加一从下标为0继续开始。且还需要判断这个日期是否在当前月有。
下面上代码
private List<LocalDate> monthOfDay(PatrolSchedule patrolSchedule){
List<LocalDate> list = new ArrayList<>();
String[] dayss=patrolSchedule.getCycleCount().split(",");//获取日期段
LocalDate scheduleStartTime = patrolSchedule.getScheduleStartTime();
int dayOfMonth = scheduleStartTime.getDayOfMonth(); //获取开始时间的日期
int z = 0;
while (z <dayss.length && Integer.valueOf(dayss[z])< dayOfMonth){ //找到在日期段中,开始日期的位置,
z++;
}
while (true){
//判断当前月是否有这个日期以及是否到最后一个,满足则应该月份加一,指针回到起点
while (YearMonth.from(scheduleStartTime).atEndOfMonth().getDayOfMonth()<Integer.valueOf(dayss[z%dayss.length])|| z > (dayss.length-1)){
z=0;
scheduleStartTime = scheduleStartTime.plusMonths(1);
}
//将日期放入
scheduleStartTime= scheduleStartTime.withDayOfMonth(Integer.valueOf(dayss[z%dayss.length]));
if (scheduleStartTime.compareTo(patrolSchedule.getScheduleEndTime()) <=0){
list.add(scheduleStartTime);
}else {
return list;
}
z++;
}
如果需求是按次数生成的话,总体思路不变,变为for循环,再稍加修改即可
如果有效率更高的方法欢迎指教。