关于共享自习室查询座位空闲状态的算法笔记

近期笔者在开发一个共享自习室的项目中,有这样一个需求:某个座位在某个时间段被预定,则其他人筛选时间段内查询座位预定情况时,如果筛选时间段与该座位时间段有冲突,则座位处于已售状态,此时不可被预定,如果筛选时间段与该座位时间段没有冲突,则显示空闲状态,可供其他人预定。

项目座位预定效果图如下:

座位状态检查是本项目中的核心功能之一,对于以上需求,我们提出了两套解决办法:

方案一:查询时间段均以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, "获取失败");
	}

综上两种方案来说,各有利弊,对于商家来说,利润是首要选择,其次才是利用率,故此选择了方案二,其实笔者认为方案一更佳,怎奈何客户是上帝、是明灯、是金主,是我们前进路上的动力之源。

 

———————但是,我们开发者是有底线的———————

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章