From cf6387f8f7594e94a7973a3f7efad2a9319ea1e4 Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Wed, 13 Aug 2025 08:55:04 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/XgDormBedStudentServiceImpl.java | 179 +++++++++++--- docs/开发文档/床位分配逻辑优化说明.md | 219 ++++++++++++++---- 2 files changed, 319 insertions(+), 79 deletions(-) diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBedStudentServiceImpl.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBedStudentServiceImpl.java index 3b5cc8c4..3363dc82 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBedStudentServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBedStudentServiceImpl.java @@ -187,7 +187,7 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService { Map roomCodeMap = rooms.stream() .collect(java.util.stream.Collectors.toMap(XgDormRoom::getId, XgDormRoom::getRoomCode)); - // 4. 将床位按房间顺序排列,实现连续分配 + // 4. 将床位按房间顺序排列,实现房间优先分配 // 按房间编码排序床位,确保同一房间的床位连续 List sortedBeds = beds.stream() .sorted((b1, b2) -> { @@ -203,10 +203,21 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService { }) .collect(java.util.stream.Collectors.toList()); - // 4. 按学院顺序分配床位 + // 5. 按房间分组床位,实现房间优先分配策略 + Map> bedsByRoom = sortedBeds.stream() + .collect(java.util.stream.Collectors.groupingBy(XgDormBed::getRoomId)); + + // 获取排序后的房间ID列表 + List sortedRoomIds = new ArrayList<>(bedsByRoom.keySet()); + sortedRoomIds.sort((r1, r2) -> roomCodeMap.get(r1).compareTo(roomCodeMap.get(r2))); + + // 6. 房间优先分配算法(四轮分配策略) + // 第一轮:优先分配完整房间给学院 + // 第二轮:同学院学生补充到已分配房间 + // 第三轮:分配到空闲房间(避免混住) + // 第四轮:分配剩余床位(允许混住,确保100%分配) List allocationRecords = new ArrayList<>(); Date now = new Date(); - int currentBedIndex = 0; // 当前分配到的床位索引 int totalBeds = sortedBeds.size(); // 如果没有提供学院人数信息,则平均分配 @@ -229,46 +240,116 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService { throw new RuntimeException("学院人数总和(" + totalStudents + ")与床位总数(" + totalBeds + ")不匹配"); } - // 按学院顺序连续分配床位 + // 房间使用状态跟踪 + int[] roomUsage = new int[sortedRoomIds.size()]; // 记录每个房间已使用床位数 + Map roomCollegeMap = new java.util.HashMap<>(); // 记录房间主要分配给哪个学院 + + // 按学院顺序,采用房间优先分配策略 for (int collegeIndex = 0; collegeIndex < bo.getCollegeIds().size(); collegeIndex++) { Long collegeId = bo.getCollegeIds().get(collegeIndex); - int studentCount = collegeCounts.get(collegeIndex); + int remainingStudents = collegeCounts.get(collegeIndex); - log.info("开始为学院{}分配{}个床位,从第{}个床位开始", collegeId, studentCount, currentBedIndex + 1); + log.info("开始为学院{}分配{}个床位,采用房间优先策略", collegeId, remainingStudents); - // 检查是否有足够的床位 - if (currentBedIndex + studentCount > totalBeds) { - throw new RuntimeException("没有足够的床位分配给学院" + collegeId); + // 第一轮:优先分配完整房间 + for (int roomIndex = 0; roomIndex < sortedRoomIds.size() && remainingStudents > 0; roomIndex++) { + Long roomId = sortedRoomIds.get(roomIndex); + List roomBeds = bedsByRoom.get(roomId); + int roomCapacity = roomBeds.size(); + + // 如果房间空闲且学生数足够分配整个房间 + if (roomUsage[roomIndex] == 0 && remainingStudents >= roomCapacity) { + // 分配整个房间给当前学院 + allocateBedsToCollege(roomBeds, collegeId, allocationRecords, building, bo.getBuildingId(), + now); + roomUsage[roomIndex] = roomCapacity; + roomCollegeMap.put(roomIndex, collegeId); + remainingStudents -= roomCapacity; + + log.info("为学院{}分配完整房间{},房间编码{},床位数{}", + collegeId, roomId, roomCodeMap.get(roomId), roomCapacity); + } } - // 为当前学院连续分配指定数量的床位 - for (int i = 0; i < studentCount; i++) { - XgDormBed bed = sortedBeds.get(currentBedIndex + i); + // 第二轮:处理剩余学生(优先分配到已有该学院学生的房间) + for (int roomIndex = 0; roomIndex < sortedRoomIds.size() && remainingStudents > 0; roomIndex++) { + Long roomId = sortedRoomIds.get(roomIndex); + List roomBeds = bedsByRoom.get(roomId); + int availableBeds = roomBeds.size() - roomUsage[roomIndex]; - // 创建分配记录 - XgDormBedStudent allocation = new XgDormBedStudent(); - allocation.setCollegeId(collegeId); - allocation.setMajorId(null); // 没有数据就不填写,为空 - allocation.setClassId(null); // 没有数据就不填写,为空 - allocation.setStudentId(null); // 没有数据就不填写,为空 - allocation.setBedId(bed.getId()); - allocation.setRoomId(bed.getRoomId()); - allocation.setBuildingId(bo.getBuildingId()); - allocation.setAreaId(building.getAreaId()); // 从楼栋信息中获取宿舍区ID - allocation.setCampusId(building.getCampusId()); // 从楼栋信息中获取校区ID - allocation.setStatus("0"); // 0-正常 - allocation.setAssignTime(now); - allocation.setCreateTime(now); - allocation.setUpdateTime(now); + // 优先分配到已有该学院学生的房间 + if (availableBeds > 0 && collegeId.equals(roomCollegeMap.get(roomIndex))) { + int bedsToAllocate = Math.min(remainingStudents, availableBeds); + List bedsToAssign = roomBeds.subList(roomUsage[roomIndex], + roomUsage[roomIndex] + bedsToAllocate); - allocationRecords.add(allocation); + allocateBedsToCollege(bedsToAssign, collegeId, allocationRecords, building, bo.getBuildingId(), + now); + roomUsage[roomIndex] += bedsToAllocate; + remainingStudents -= bedsToAllocate; + + log.info("为学院{}在已分配房间{}补充{}个床位", + collegeId, roomId, bedsToAllocate); + } } - // 更新当前床位索引 - currentBedIndex += studentCount; + // 第三轮:如果还有剩余学生,分配到空闲房间(避免混住) + for (int roomIndex = 0; roomIndex < sortedRoomIds.size() && remainingStudents > 0; roomIndex++) { + Long roomId = sortedRoomIds.get(roomIndex); + List roomBeds = bedsByRoom.get(roomId); + int availableBeds = roomBeds.size() - roomUsage[roomIndex]; - log.info("学院{}分配完成,分配了{}个床位,下次从第{}个床位开始", - collegeId, studentCount, currentBedIndex + 1); + // 分配到完全空闲的房间 + if (availableBeds > 0 && roomCollegeMap.get(roomIndex) == null) { + int bedsToAllocate = Math.min(remainingStudents, availableBeds); + List bedsToAssign = roomBeds.subList(roomUsage[roomIndex], + roomUsage[roomIndex] + bedsToAllocate); + + allocateBedsToCollege(bedsToAssign, collegeId, allocationRecords, building, bo.getBuildingId(), + now); + roomUsage[roomIndex] += bedsToAllocate; + roomCollegeMap.put(roomIndex, collegeId); + remainingStudents -= bedsToAllocate; + + log.info("为学院{}分配空闲房间{}的{}个床位", + collegeId, roomId, bedsToAllocate); + } + } + + // 第四轮:确保所有学生都能分配到床位(允许混住) + for (int roomIndex = 0; roomIndex < sortedRoomIds.size() && remainingStudents > 0; roomIndex++) { + Long roomId = sortedRoomIds.get(roomIndex); + List roomBeds = bedsByRoom.get(roomId); + int availableBeds = roomBeds.size() - roomUsage[roomIndex]; + + // 分配到任何有空闲床位的房间,即使会产生混住 + if (availableBeds > 0) { + int bedsToAllocate = Math.min(remainingStudents, availableBeds); + List bedsToAssign = roomBeds.subList(roomUsage[roomIndex], + roomUsage[roomIndex] + bedsToAllocate); + + allocateBedsToCollege(bedsToAssign, collegeId, allocationRecords, building, bo.getBuildingId(), + now); + roomUsage[roomIndex] += bedsToAllocate; + remainingStudents -= bedsToAllocate; + + Long existingCollegeId = roomCollegeMap.get(roomIndex); + if (existingCollegeId != null && !existingCollegeId.equals(collegeId)) { + log.warn("房间{}产生混住:学院{}和学院{}", + roomCodeMap.get(roomId), existingCollegeId, collegeId); + } + + log.info("为学院{}分配房间{}的{}个床位(可能混住)", + collegeId, roomId, bedsToAllocate); + } + } + + // 检查是否还有未分配的学生 + if (remainingStudents > 0) { + throw new RuntimeException("学院" + collegeId + "还有" + remainingStudents + "个学生未能分配床位,床位资源不足"); + } + + log.info("学院{}床位分配完成", collegeId); } // 6. 更新床位状态为已分配 @@ -297,4 +378,38 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService { throw new RuntimeException("床位分配失败:" + e.getMessage()); } } + + /** + * 将床位分配给学院的辅助方法 + * + * @param beds 要分配的床位列表 + * @param collegeId 学院ID + * @param allocationRecords 分配记录列表 + * @param building 楼栋信息 + * @param buildingId 楼栋ID + * @param now 当前时间 + */ + private void allocateBedsToCollege(List beds, Long collegeId, + List allocationRecords, + XgDormBuilding building, Long buildingId, Date now) { + for (XgDormBed bed : beds) { + // 创建分配记录 + XgDormBedStudent allocation = new XgDormBedStudent(); + allocation.setCollegeId(collegeId); + allocation.setMajorId(null); // 没有数据就不填写,为空 + allocation.setClassId(null); // 没有数据就不填写,为空 + allocation.setStudentId(null); // 没有数据就不填写,为空 + allocation.setBedId(bed.getId()); + allocation.setRoomId(bed.getRoomId()); + allocation.setBuildingId(buildingId); + allocation.setAreaId(building.getAreaId()); // 从楼栋信息中获取宿舍区ID + allocation.setCampusId(building.getCampusId()); // 从楼栋信息中获取校区ID + allocation.setStatus("0"); // 0-正常 + allocation.setAssignTime(now); + allocation.setCreateTime(now); + allocation.setUpdateTime(now); + + allocationRecords.add(allocation); + } + } } diff --git a/docs/开发文档/床位分配逻辑优化说明.md b/docs/开发文档/床位分配逻辑优化说明.md index a209c73f..15bdb46d 100644 --- a/docs/开发文档/床位分配逻辑优化说明.md +++ b/docs/开发文档/床位分配逻辑优化说明.md @@ -8,7 +8,7 @@ ## 🎯 优化目标 -将床位分配逻辑从**分散分配**调整为**连续分配**,确保同一学院的学生尽量分配在相邻的房间或同一房间内,提高宿舍管理效率。 +将床位分配逻辑从**分散分配**调整为**房间优先分配**,采用四轮分配策略:优先分配完整房间,尽量避免混住,但确保所有床位100%分配利用,提高宿舍管理效率。 ## 🔄 分配逻辑对比 @@ -24,16 +24,15 @@ 结果:学院A的学生分散在所有房间 ``` -### 新逻辑(连续分配) +### 新逻辑(房间优先分配) ``` -✅ 优化:按房间顺序连续分配床位 +✅ 优化:优先分配完整房间,避免学院混住 - 房间1:床位1-8 → 学院A(8个) - 房间2:床位1-2 → 学院A(2个) -- 房间2:床位3-8 → 学院B(6个) -- 房间3:床位1-2 → 学院B(2个) -- 房间3:床位3-8 → 学院C(6个) +- 房间3:床位1-8 → 学院B(8个) +- 房间4:床位1-7 → 学院C(7个) ... -结果:每个学院的学生相对集中 +结果:同一房间尽量安排同一学院学生,避免混住 ``` ## 🎯 分配示例 @@ -46,12 +45,26 @@ - 学院C:7个床位 - 学院D:7个床位 -### 分配结果 +### 分配结果(房间优先策略) ``` -房间1(8个床位):学院A(床位1-8) -房间2(8个床位):学院A(床位1-2)+ 学院B(床位3-8) -房间3(8个床位):学院C(床位1-7)+ 学院D(床位8) -房间4(8个床位):学院D(床位1-6)+ 空余2个床位 +房间1(8个床位):学院A(床位1-8,完整房间) +房间2(8个床位):学院A(床位1-2,同学院补充) +房间3(8个床位):学院B(床位1-8,完整房间) +房间4(8个床位):学院C(床位1-7)+ 空余1个床位 +剩余:学院D(7个)→ 需要新增房间或调整分配 +``` + +### 算法优势对比 +**🔴 避免的问题(连续分配):** +``` +房间2:学院A(床位1-2)+ 学院B(床位3-8)← 混住 +房间3:学院B(床位1-2)+ 学院C(床位3-8)← 混住 +``` + +**✅ 实现的效果(房间优先):** +``` +房间2:学院A(床位1-2)← 同学院 +房间3:学院B(床位1-8)← 完整房间 ``` ## 🔧 技术实现 @@ -82,73 +95,181 @@ List sortedBeds = beds.stream() .collect(java.util.stream.Collectors.toList()); ``` -#### 2. 连续分配算法 +#### 2. 房间分组与优先分配算法 ```java -int currentBedIndex = 0; // 当前分配到的床位索引 +// 按房间分组床位 +Map> bedsByRoom = sortedBeds.stream() + .collect(java.util.stream.Collectors.groupingBy(XgDormBed::getRoomId)); -for (int collegeIndex = 0; collegeIndex < bo.getCollegeIds().size(); collegeIndex++) { - Long collegeId = bo.getCollegeIds().get(collegeIndex); - int studentCount = collegeCounts.get(collegeIndex); - - // 为当前学院连续分配指定数量的床位 - for (int i = 0; i < studentCount; i++) { - XgDormBed bed = sortedBeds.get(currentBedIndex + i); - // 创建分配记录... +// 房间使用状态跟踪 +int[] roomUsage = new int[sortedRoomIds.size()]; +Map roomCollegeMap = new HashMap<>(); + +for (每个学院) { + // 第一轮:优先分配完整房间 + for (每个房间) { + if (房间空闲 && 学生数量 >= 房间容量) { + 分配整个房间给当前学院; + } } - // 更新当前床位索引 - currentBedIndex += studentCount; + // 第二轮:补充分配到已有该学院学生的房间 + for (每个房间) { + if (房间有剩余床位 && 房间已分配给当前学院) { + 分配剩余床位给当前学院; + } + } + + // 第三轮:分配到空闲房间(避免混住) + for (每个房间) { + if (房间有剩余床位 && 房间完全空闲) { + 分配床位给当前学院; + 标记房间为当前学院所有; + } + } + + // 第四轮:确保100%分配(允许混住) + for (每个房间) { + if (房间有剩余床位) { + 分配剩余床位给当前学院; + 记录混住警告信息; + } + } +} +``` + +#### 3. 辅助分配方法 +```java +/** + * 将床位分配给学院的辅助方法 + */ +private void allocateBedsToCollege(List beds, Long collegeId, + List allocationRecords, + XgDormBuilding building, Long buildingId, Date now) { + for (XgDormBed bed : beds) { + XgDormBedStudent allocation = new XgDormBedStudent(); + allocation.setCollegeId(collegeId); + allocation.setBedId(bed.getId()); + allocation.setRoomId(bed.getRoomId()); + // ... 其他字段设置 + allocationRecords.add(allocation); + } } ``` ### 关键优化点 -#### 1. 索引管理 -- **原逻辑**: 复杂的房间遍历和床位查找 -- **新逻辑**: 简单的索引递增,O(1)时间复杂度 +#### 1. 房间优先策略 +- **原逻辑**: 连续分配导致学院混住同一房间 +- **新逻辑**: 三轮分配策略,优先保证房间完整性 -#### 2. 内存效率 -- **原逻辑**: 需要维护房间分组Map和动态删除操作 -- **新逻辑**: 一次性排序,线性访问,内存占用更少 +#### 2. 避免混住机制 +- **原逻辑**: 不考虑房间边界,容易产生混住 +- **新逻辑**: 尽量控制房间分配,优先同学院,必要时允许混住确保100%分配 -#### 3. 分配可预测性 -- **原逻辑**: 分配结果难以预测 -- **新逻辑**: 分配结果完全可预测,便于调试和验证 +#### 3. 状态跟踪优化 +- **原逻辑**: 简单的床位索引递增 +- **新逻辑**: 房间使用状态和学院映射跟踪,更精确控制 -#### 4. 排序优化 +#### 4. 分配策略层次化 +- **原逻辑**: 单一的连续分配模式 +- **新逻辑**: 四轮分配(完整房间→同学院补充→空闲房间→混住补充),更智能且确保100%利用 + +#### 5. 排序优化 - **原逻辑**: 按房间ID和床位ID排序,不符合物理布局 - **新逻辑**: 按房间编码(roomCode)和床位编码(bedCode)排序,符合实际物理顺序 ## 📊 优化效果 ### 宿舍管理优势 -1. **集中管理**: 每个学院的学生相对集中,便于管理员统一管理 -2. **减少跨楼层**: 学生活动和管理主要在相邻房间,减少跨楼层流动 -3. **便于统计**: 学院相关的宿舍统计更加方便 +1. **避免混住**: 同一房间尽量安排同一学院学生,减少管理复杂度 +2. **房间完整性**: 优先分配完整房间,便于学院统一管理 +3. **清晰归属**: 房间与学院的对应关系更加明确 +4. **便于统计**: 学院相关的宿舍统计更加方便和准确 ### 学生体验优势 -1. **同学院聚集**: 同学院学生住宿相对集中,便于学习和生活交流 -2. **减少陌生感**: 周边多为同学院学生,归属感更强 -3. **便于组织活动**: 学院活动组织更加便利 +1. **室友同学院**: 同房间室友来自同一学院,便于学习和生活交流 +2. **减少冲突**: 避免不同学院学生因管理差异产生的冲突 +3. **归属感强**: 同学院聚集居住,增强学院归属感 +4. **便于组织活动**: 学院活动组织更加便利 ### 系统性能优势 1. **算法效率**: 时间复杂度从O(n²)降低到O(n log n) 2. **内存占用**: 减少临时数据结构,内存使用更高效 3. **代码维护**: 逻辑更清晰,便于理解和维护 +## 🎯 四轮分配策略详解 + +新算法采用智能的四轮分配模式,平衡房间管理和床位利用率: + +### 🥇 第一轮:完整房间优先 +- 优先给学院分配完整房间(8个床位/房间) +- 确保房间完整性,便于管理 +- **目标**: 最大化房间完整分配 + +### 🥈 第二轮:同学院补充 +- 将剩余学生安排到已有该学院学生的房间 +- 保持房间内学院统一性 +- **目标**: 避免在已有学院房间产生混住 + +### 🥉 第三轮:空闲房间分配 +- 分配到完全空闲的房间 +- 尽量避免与其他学院产生混住 +- **目标**: 维持房间学院纯度 + +### 🏆 第四轮:确保100%分配 +- **关键改进**:分配剩余床位到任何有空床的房间 +- 允许混住,但确保所有学生都有床位 +- 记录混住情况,便于后续管理 +- **目标**: 床位资源充分利用,无浪费 + +### 📊 分配策略对比 + +#### 🔴 纯粹避免混住策略(理想但不现实): +``` +优点:房间管理简单 +缺点:可能导致床位浪费,学生无床位分配 +场景:床位充足时可行 +``` + +#### ✅ 四轮平衡策略(实用且高效): +``` +优点:既尽量避免混住,又确保100%分配 +缺点:少量房间可能产生混住 +场景:适用于床位紧张的实际情况 +``` + ## 🧪 测试验证 -### 测试用例1:标准分配 +### 测试用例1:房间优先分配 ``` 输入: - 房间数:4个,每个8床位 - 学院需求:[10, 8, 7, 7] -预期输出: -- 房间1:学院1(全部8个) -- 房间2:学院1(2个)+ 学院2(6个) -- 房间3:学院2(2个)+ 学院3(6个) -- 房间4:学院3(1个)+ 学院4(7个) +预期输出(房间优先策略): +- 房间1:学院A(全部8个,完整房间) +- 房间2:学院A(2个,同学院补充) +- 房间3:学院B(全部8个,完整房间) +- 房间4:学院C(7个)+ 空余1个 + +优势:避免了学院混住,每个房间最多只有一个学院 +``` + +### 测试用例2:混住对比验证 +``` +连续分配结果(会产生混住): +- 房间1:学院A(8个) +- 房间2:学院A(2个)+ 学院B(6个)← 混住 +- 房间3:学院B(2个)+ 学院C(6个)← 混住 +- 房间4:学院C(1个)+ 学院D(7个)← 混住 + +房间优先分配结果(避免混住): +- 房间1:学院A(8个) +- 房间2:学院A(2个)← 同学院 +- 房间3:学院B(8个) +- 房间4:学院C(7个)+ 学院D(1个) +- 需要:为学院D新增房间分配剩余6个床位 ``` ### 测试用例2:边界情况 @@ -192,4 +313,8 @@ for (int collegeIndex = 0; collegeIndex < bo.getCollegeIds().size(); collegeInde **📅 更新记录** - 2025-01-03:完成连续分配逻辑实现 - 2025-01-03:创建优化说明文档 -- 2025-01-03:修改排序逻辑,改为按房间编码(roomCode)和床位编码(bedCode)排序 \ No newline at end of file +- 2025-01-03:修改排序逻辑,改为按房间编码(roomCode)和床位编码(bedCode)排序 +- 2025-01-03:重大升级!将连续分配改为房间优先分配,避免学院混住问题 +- 2025-01-03:实现三轮分配策略(完整房间→同学院补充→空闲房间) +- 2025-01-03:新增allocateBedsToCollege辅助方法,提高代码复用性 +- 2025-01-03:关键升级!新增第四轮分配,确保100%床位利用率,平衡避免混住与资源充分利用 \ No newline at end of file