關於共享自習室查詢座位空閒狀態的算法筆記

近期筆者在開發一個共享自習室的項目中,有這樣一個需求:某個座位在某個時間段被預定,則其他人篩選時間段內查詢座位預定情況時,如果篩選時間段與該座位時間段有衝突,則座位處於已售狀態,此時不可被預定,如果篩選時間段與該座位時間段沒有衝突,則顯示空閒狀態,可供其他人預定。

項目座位預定效果圖如下:

座位狀態檢查是本項目中的核心功能之一,對於以上需求,我們提出了兩套解決辦法:

方案一:查詢時間段均以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, "獲取失敗");
	}

綜上兩種方案來說,各有利弊,對於商家來說,利潤是首要選擇,其次纔是利用率,故此選擇了方案二,其實筆者認爲方案一更佳,怎奈何客戶是上帝、是明燈、是金主,是我們前進路上的動力之源。

 

———————但是,我們開發者是有底線的———————

 

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