近期笔者在开发一个共享自习室的项目中,有这样一个需求:某个座位在某个时间段被预定,则其他人筛选时间段内查询座位预定情况时,如果筛选时间段与该座位时间段有冲突,则座位处于已售状态,此时不可被预定,如果筛选时间段与该座位时间段没有冲突,则显示空闲状态,可供其他人预定。
项目座位预定效果图如下:
座位状态检查是本项目中的核心功能之一,对于以上需求,我们提出了两套解决办法:
方案一:查询时间段均以1小时为区间,也就是订座均是整点订座,那么空闲时间段就是筛选查询时间段与已预订时间段的差集;
假设座位A已预订时间段为B,查询时间段为C,则其他用户可预订时间段就是C中时间段减去包含B的时间段全部或部分之后剩余的时间段;
例如座位A预定时间端B为:2020-03-28 15:00-18:00;
若用户查询时间段C1为:14:00-20:00,则用户可预定时间段为:14:00-15:00 、18:00-19:00 和 19:00-20:00 这三个时间段;
若用户查询时间段C2为:17:00-20:00,则用户可预定时间段为:18:00-19:00 和 19:00-20:00 这两个时间段;
若用户查询时间段C3为:19:00-21:00,则用户可预定时间段为:19:00-20:00 和 20:00-21:00 这两个时间段;
方案一可看出用户选择时间比较紧凑,适合用户量比较大的共享自习室,座位利用率比较高,但是不利于用户连续学习,时间段比较分散,另外这种情况的算法也比较复杂,笔者这里针对此种需求,分享部分代码供参考:
注:以下源码输入项目一部分,主要查看算法思路,无需关注具体功能:
/**
* 查询座位空闲状态
* @param storeid
* @param reservedate
* @param begintime
* @param endtime
* @return
*/
@CrossOrigin
@RequestMapping(value = "/seatStateCheckApi", method = RequestMethod.GET)
@ResponseBody
@ApiOperation(value = "查询空闲座位")
public ResultData<List<SeatArea>> seatStateCheckApi(@RequestParam(value = "storeid") Integer storeid,
@RequestParam(value = "reservedate") String reservedate,
@RequestParam(value = "begintime") String begintime, @RequestParam(value = "endtime") String endtime) {
List<SeatArea> seatAreas = storeService.seatAreaListByStoreID(storeid);
for (SeatArea seatArea : seatAreas) {
// TODO 空闲座位 算法
// 1 先查询店铺全部座位
// 2 根据开始结束时间生成备选时间区间段
// 3 获取已占用时间段,计算相同的时间段,即求交集时间段
// 4 交集时间段 < 备选时间区间段,算出不包含的时间段 即为可选的时间段
List<Seat> seats = storeService.seatListByStoreAndAreaID(storeid, seatArea.getAid());
for (Seat seat : seats) {
// 计算筛选时间区域,生成数组
int beginHour = Integer.valueOf(begintime.split(":")[0]);
String beginMinute = begintime.split(":")[1];
int endHour = Integer.valueOf(endtime.split(":")[0]);
int intervalHour = endHour - beginHour;
List<String> intervaltimeArr = new ArrayList<>();
for (int i = 0; i < intervalHour; i++) {
String time = Integer.toString(i + beginHour) + ":" + beginMinute;
intervaltimeArr.add(time);
System.out.println("筛选时间区域:" + time);
}
String[] intervaltimes = new String[intervaltimeArr.size()];
intervaltimeArr.toArray(intervaltimes);
// 查询对应的座位状态
SeatState seatState = storeService.querySeatStateBySeatIDAndReserveDate(seat.getSid(), reservedate);
if (seatState != null) {
// 已经占用时间数组
String[] spantimes = seatState.getSpantimes().split(",");
List<String> spantimeArr = Arrays.asList(spantimes);
for (String string : spantimeArr) {
System.out.println("全部已占用的时间段:" + string);
}
// 获取相同的字符串
List<String> sameElements = CommonUtility.getSameElements(spantimes, intervaltimes);
for (String string : sameElements) {
System.out.println("已占用时间段:" + string);
}
// 获取不相同的字符串
List<String> differentElements = CommonUtility.getDifferentElements(sameElements, intervaltimeArr);
for (String string : differentElements) {
System.out.println("未占用时间段:" + string);
}
if (differentElements.size() > 0) { // 还有空闲
seat.setState(0);
seat.setFreetimes(differentElements);
} else { // 全部出售了
seat.setState(1);
seat.setFreetimes(null);
}
seat.setSeatState(seatState);
} else {
seat.setState(0);
seat.setFreetimes(intervaltimeArr);
seat.setSeatState(null);
}
}
seatArea.setSeats(seats);
}
if (seatAreas != null) {
return new ResultData<List<SeatArea>>(0, "获取成功", seatAreas);
}
return new ResultData<>(1, "获取失败");
}
从以上流程可看出,用到两个方法,查询相同和查询不同元素方法,方法代码如下:
/**
* 找出两个数组中相同的元素
*
* @param one
* @param two
* @return
*/
public static List<String> getSameElements(String[] one, String[] two) {
Set<String> same = new HashSet<String>(); // 用来存放两个数组中相同的元素
Set<String> temp = new HashSet<String>(); // 用来存放数组a中的元素
for (int i = 0; i < one.length; i++) {
temp.add(one[i]); // 把数组a中的元素放到Set中,可以去除重复的元素
}
for (int j = 0; j < two.length; j++) {
// 把数组two中的元素添加到temp中
// 如果temp中已存在相同的元素,则temp.add(b[j])返回false
if (!temp.add(two[j])) {
same.add(two[j]);
}
}
List<String> sameElementArr = new ArrayList<String>();
for (String string : same) {
System.out.println("相同值:" + string);
sameElementArr.add(string);
}
return sameElementArr;
}
/**
* 找出两个数组中不相同的元素
*
* @param one
* @param two
* @return
*/
public static List<String> getDifferentElements(List<String> one, List<String> more) {
List<String> dlist = new ArrayList<String>();// 用来存放2个数组中不相同的元素
for (String string : more) {
if (!one.contains(string)) {
dlist.add(string);
}
}
List<String> differentElementArr = new ArrayList<String>();
for (String string : dlist) {
System.out.println("不同值:" + string);
differentElementArr.add(string);
}
return differentElementArr;
}
方案二:查询时间段以5分钟为间隔,空闲时间必须不包含已预订时间,即已预订时间和筛选查询时间必须没有任何交集,如果有,则显示已售,如果没有,才可以预订;
假设座位A已预订时间段为B,查询时间段为C,则其他用户可预订时间段只能是C和B没有任何交集的时间区段;
例如座位A预定时间端B为:2020-03-28 15:15-18:20;
若用户查询时间段C1为:14:10-20:35,则用户可预定时间段为:无;
若用户查询时间段C2为:19:40-20:10,则用户可预定时间段为:19:40-20:10 这一个时间区段;
方案二可看出用户选择时间随意性比较宽松,适合自由度比较大的用户,随到随学,可一次性预订一个时间区段,但是这种座位浪费度也高,不如方案一,笔者这里针对此种需求,分享部分代码供参考:
注:以下源码输入项目一部分,主要查看算法思路,无需关注具体功能:
/**
* 查询座位空闲状态
*
* @param storeid
* @param reservedate
* @param begintime
* @param endtime
* @return
*/
@CrossOrigin
@RequestMapping(value = "/seatStateCheckFreeApi", method = RequestMethod.GET)
@ResponseBody
@ApiOperation(value = "查询空闲座位NEW")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "query", name = "reservedate", value = "时间格式,如 2020-02-19", required = true),
@ApiImplicitParam(paramType = "query", name = "begintime", value = "时间格式,如 08:25", required = true) })
public ResultData<List<SeatArea>> seatStateCheckFreeApi(@RequestParam(value = "storeid") Integer storeid,
@RequestParam(value = "reservedate") String reservedate,
@RequestParam(value = "begintime") String begintime, @RequestParam(value = "endtime") String endtime) {
List<SeatArea> seatAreas = storeService.seatAreaListByStoreID(storeid);
for (SeatArea seatArea : seatAreas) {
// TODO 空闲座位 算法2
// 1 根据开始结束时间生成备选时间区间段 5 分钟一个区间
// 2 先查询店铺全部座位
// 3 获取已经占用的时间区间 08:25-09:15,11:20-15:45 ,生成临时区间块
// 4 查询是否有交集,如果有-占用,无-空闲
// 查询间隔,根据开始结束时间生成备选时间区间段 5 分钟一个区间
int intervaltime = 5;
long min = CommonUtility.getIntervalMin(begintime, endtime);
int numMin = (int) (min / intervaltime);
List<String> intervaltimeArr = new ArrayList<>();
for (int i = 0; i < numMin; i++) {
String time = CommonUtility.getDateNextMinute(begintime, intervaltime * i);
intervaltimeArr.add(time);
// System.out.println("筛选时间区域:" + time);
}
String[] intervaltimes = new String[intervaltimeArr.size()];
intervaltimeArr.toArray(intervaltimes);
System.out.println("筛选时间区域:" + intervaltimeArr);
// 全部座位
List<Seat> seats = storeService.seatListByStoreAndAreaID(storeid, seatArea.getAid());
for (Seat seat : seats) {
// 查询对应的座位状态
SeatState seatState = storeService.querySeatStateBySeatIDAndReserveDate(seat.getSid(), reservedate);
if (seatState != null && seatState.getSpantimes().length() > 0) {
// 已经占用时间数组 08:25-09:15,11:20-15:45
String[] spantimes = seatState.getSpantimes().split(",");
List<String> spantimeArr = Arrays.asList(spantimes);
System.out.println("全部已占用的时间区间:" + spantimeArr);
List<String> intervalSpantimeArr = new ArrayList<>();
for (String spantime : spantimeArr) {
// System.out.println("全部已占用的时间区间:" + spantime);
String[] st = spantime.split("-"); // 08:25-09:15
List<String> stArr = Arrays.asList(st);
String btime = stArr.get(0);
String etime = stArr.get(1);
long minspan = CommonUtility.getIntervalMin(btime, etime);
int numMinspan = (int) (minspan / intervaltime);
for (int i = 0; i < numMinspan; i++) {
String time = CommonUtility.getDateNextMinute(btime, intervaltime * i);
intervalSpantimeArr.add(time);
// System.out.println("某个已占用的时间段:" + time);
}
}
String[] intervalSpantimes = new String[intervalSpantimeArr.size()];
intervalSpantimeArr.toArray(intervalSpantimes);
System.out.println("全部已占用的时间段:" + intervalSpantimeArr);
// 获取相同的字符串
List<String> sameElements = CommonUtility.getSameElements(intervalSpantimes, intervaltimes);
for (String string : sameElements) {
System.out.println("已占用时间段:" + string);
}
if (sameElements.size() > 0) { // 有交集,不能选座
seat.setState(1);
} else { // 无交集,可选座
seat.setState(0);
}
seat.setSeatState(seatState);
} else {
seat.setState(0);
seat.setSeatState(null);
}
}
seatArea.setSeats(seats);
}
if (seatAreas != null) {
return new ResultData<List<SeatArea>>(0, "获取成功", seatAreas);
}
return new ResultData<>(1, "获取失败");
}
综上两种方案来说,各有利弊,对于商家来说,利润是首要选择,其次才是利用率,故此选择了方案二,其实笔者认为方案一更佳,怎奈何客户是上帝、是明灯、是金主,是我们前进路上的动力之源。
———————但是,我们开发者是有底线的———————